-
Notifications
You must be signed in to change notification settings - Fork 1
use authorization request params #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -71,6 +71,11 @@ export type McpServerSource = 'platform' | 'external' | |||
| export type McpServerAuthMode = 'none' | 'oauth2' | ||||
| export type McpExternalOAuthDiscoverySource = 'prm' | 'issuer_override' | ||||
|
|
||||
| export interface McpExternalOAuthAuthorizationRequestParam { | ||||
| name: string | ||||
| value: string | ||||
| } | ||||
|
|
||||
| export interface McpExternalSecretField { | ||||
| name: string | ||||
| label: string | ||||
|
|
@@ -97,6 +102,7 @@ export interface McpExternalOAuthTemplate { | |||
| clientIdMetadataDocumentSupported?: boolean | ||||
| registrationEndpoint?: string | ||||
| tokenEndpointAuthMethodsSupported?: SupportedTokenEndpointAuthMethod[] | ||||
| authorizationRequestParams?: McpExternalOAuthAuthorizationRequestParam[] | ||||
| } | ||||
|
|
||||
| export interface DiscoverExternalAuthorizationInput { | ||||
|
|
@@ -422,6 +428,17 @@ const DISCOVERY_BASE_DELAY_MS = 500 | |||
| const DISCOVERY_TOTAL_BUDGET_MS = 20000 | ||||
| const REQUIRED_PKCE_CHALLENGE_METHOD = 'S256' | ||||
| const RESOURCE_URI_BACKFILL_FAILURE_COOLDOWN_MS = 15 * 60 * 1000 | ||||
| const OAUTH_AUTHORIZATION_REQUEST_PARAM_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,99}$/ | ||||
| const RESERVED_OAUTH_AUTHORIZATION_REQUEST_PARAM_NAMES = new Set([ | ||||
| 'response_type', | ||||
| 'client_id', | ||||
| 'redirect_uri', | ||||
| 'state', | ||||
| 'scope', | ||||
| 'resource', | ||||
| 'code_challenge', | ||||
| 'code_challenge_method' | ||||
| ]) | ||||
|
|
||||
| const isRecord = (value: unknown): value is Record<string, unknown> => | ||||
| value !== null && typeof value === 'object' && !Array.isArray(value) | ||||
|
|
@@ -440,6 +457,59 @@ const toOptionalStringArray = (value: unknown): string[] | undefined => { | |||
| const getOptionalString = (value: unknown): string | undefined => | ||||
| typeof value === 'string' ? value : undefined | ||||
|
|
||||
| const normalizeAuthorizationRequestParamEntries = ( | ||||
| value: unknown | ||||
| ): McpExternalOAuthAuthorizationRequestParam[] | undefined => { | ||||
| if (value === undefined) { | ||||
| return undefined | ||||
| } | ||||
| if (!Array.isArray(value)) { | ||||
| throw new McpValidationError('oauthTemplate.authorizationRequestParams must be an array when provided') | ||||
| } | ||||
|
|
||||
| const normalized = value.map((item, index) => { | ||||
| if (!isRecord(item)) { | ||||
| throw new McpValidationError( | ||||
| `oauthTemplate.authorizationRequestParams[${index}] must be an object with name and value` | ||||
| ) | ||||
| } | ||||
|
|
||||
| const name = getOptionalString(item.name)?.trim() | ||||
| const value = getOptionalString(item.value)?.trim() | ||||
|
|
||||
| if (!name) { | ||||
| throw new McpValidationError(`oauthTemplate.authorizationRequestParams[${index}].name is required`) | ||||
| } | ||||
| if (!OAUTH_AUTHORIZATION_REQUEST_PARAM_NAME_PATTERN.test(name)) { | ||||
| throw new McpValidationError( | ||||
| `oauthTemplate.authorizationRequestParams[${index}].name is invalid: ${name}` | ||||
| ) | ||||
| } | ||||
| if (RESERVED_OAUTH_AUTHORIZATION_REQUEST_PARAM_NAMES.has(name)) { | ||||
| throw new McpValidationError( | ||||
| `oauthTemplate.authorizationRequestParams[${index}].name must not override reserved OAuth parameter ${name}` | ||||
| ) | ||||
| } | ||||
| if (!value) { | ||||
| throw new McpValidationError(`oauthTemplate.authorizationRequestParams[${index}].value is required`) | ||||
| } | ||||
|
Comment on lines
+477
to
+495
|
||||
|
|
||||
| return { name, value } | ||||
| }) | ||||
|
|
||||
| const seenNames = new Set<string>() | ||||
| for (const entry of normalized) { | ||||
| if (seenNames.has(entry.name)) { | ||||
| throw new McpValidationError( | ||||
| `oauthTemplate.authorizationRequestParams contains duplicate parameter name ${entry.name}` | ||||
| ) | ||||
| } | ||||
| seenNames.add(entry.name) | ||||
| } | ||||
|
|
||||
| return normalized.length > 0 ? normalized : undefined | ||||
| } | ||||
|
Comment on lines
+460
to
+511
|
||||
|
|
||||
| export const canonicalizeExternalOAuthResourceUri = (input: string): string => { | ||||
| const parsed = new URL(input) | ||||
| parsed.search = '' | ||||
|
|
@@ -1723,6 +1793,7 @@ export class MCPService implements Resource { | |||
| if (!oauthTemplate.resourceUri) { | ||||
| throw new McpValidationError('oauthTemplate.resourceUri is required for external OAuth servers') | ||||
| } | ||||
| normalizeAuthorizationRequestParamEntries(oauthTemplate.authorizationRequestParams) | ||||
|
||||
| normalizeAuthorizationRequestParamEntries(oauthTemplate.authorizationRequestParams) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The local
const value = ...inside the map callback shadows thevalueparameter of normalizeAuthorizationRequestParamEntries, which makes the code harder to read and increases the chance of mistakes during future edits. Renaming the inner variable (e.g.,paramValue) would avoid the shadowing.