@agent-native/core 0.104.0 → 0.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +64 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  5. package/corpus/core/src/client/AssistantChat.tsx +4 -0
  6. package/corpus/core/src/client/MultiTabAssistantChat.tsx +5 -1
  7. package/corpus/core/src/client/composer/PromptComposer.tsx +8 -0
  8. package/corpus/core/src/client/composer/TiptapComposer.tsx +38 -1
  9. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +107 -32
  10. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +33 -0
  11. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  12. package/corpus/core/src/client/use-chat-models.ts +10 -2
  13. package/corpus/core/src/connections/catalog.ts +107 -6
  14. package/corpus/core/src/deploy/build.ts +23 -0
  15. package/corpus/core/src/localization/default-messages.ts +2 -0
  16. package/corpus/core/src/mcp/actions/call-mcp-tool.ts +18 -0
  17. package/corpus/core/src/mcp/actions/list-mcp-tools.ts +18 -0
  18. package/corpus/core/src/mcp-client/app-api.ts +136 -0
  19. package/corpus/core/src/mcp-client/index.ts +21 -0
  20. package/corpus/core/src/mcp-client/oauth-client.ts +462 -0
  21. package/corpus/core/src/mcp-client/oauth-routes.ts +307 -0
  22. package/corpus/core/src/mcp-client/remote-store.ts +93 -118
  23. package/corpus/core/src/mcp-client/remote-url.ts +97 -0
  24. package/corpus/core/src/mcp-client/routes.ts +11 -0
  25. package/corpus/core/src/oauth-tokens/store.ts +10 -4
  26. package/corpus/core/src/provider-api/index.ts +690 -55
  27. package/corpus/core/src/secrets/register-framework-secrets.ts +30 -0
  28. package/corpus/core/src/server/action-discovery.ts +2 -0
  29. package/corpus/core/src/server/core-routes-plugin.ts +9 -1
  30. package/corpus/core/src/server/workspace-provider-oauth.ts +448 -61
  31. package/corpus/core/src/templates/default/AGENTS.md +9 -7
  32. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  33. package/corpus/templates/analytics/app/lib/data-source-status.ts +2 -0
  34. package/corpus/templates/analytics/app/pages/DataSources.tsx +61 -0
  35. package/corpus/templates/analytics/server/handlers/jira.ts +7 -0
  36. package/corpus/templates/analytics/server/lib/jira.ts +14 -42
  37. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +11 -0
  38. package/corpus/templates/clips/app/routes.ts +3 -1
  39. package/corpus/templates/dispatch/app/routes/integrations.tsx +57 -4
  40. package/dist/client/AgentPanel.js +1 -1
  41. package/dist/client/AgentPanel.js.map +1 -1
  42. package/dist/client/AssistantChat.d.ts +2 -0
  43. package/dist/client/AssistantChat.d.ts.map +1 -1
  44. package/dist/client/AssistantChat.js +2 -2
  45. package/dist/client/AssistantChat.js.map +1 -1
  46. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  47. package/dist/client/MultiTabAssistantChat.js +5 -2
  48. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  49. package/dist/client/composer/PromptComposer.d.ts +2 -0
  50. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  51. package/dist/client/composer/PromptComposer.js +5 -2
  52. package/dist/client/composer/PromptComposer.js.map +1 -1
  53. package/dist/client/composer/TiptapComposer.d.ts +4 -1
  54. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  55. package/dist/client/composer/TiptapComposer.js +13 -5
  56. package/dist/client/composer/TiptapComposer.js.map +1 -1
  57. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  58. package/dist/client/resources/McpIntegrationDialog.js +58 -14
  59. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  60. package/dist/client/resources/mcp-integration-catalog.d.ts +9 -0
  61. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  62. package/dist/client/resources/mcp-integration-catalog.js +13 -0
  63. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  64. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  65. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  66. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  67. package/dist/client/use-chat-models.d.ts +1 -0
  68. package/dist/client/use-chat-models.d.ts.map +1 -1
  69. package/dist/client/use-chat-models.js +8 -2
  70. package/dist/client/use-chat-models.js.map +1 -1
  71. package/dist/collab/awareness.d.ts +2 -2
  72. package/dist/collab/awareness.d.ts.map +1 -1
  73. package/dist/collab/routes.d.ts +1 -1
  74. package/dist/connections/catalog.d.ts +78 -7
  75. package/dist/connections/catalog.d.ts.map +1 -1
  76. package/dist/connections/catalog.js +97 -6
  77. package/dist/connections/catalog.js.map +1 -1
  78. package/dist/deploy/build.d.ts.map +1 -1
  79. package/dist/deploy/build.js +17 -0
  80. package/dist/deploy/build.js.map +1 -1
  81. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  82. package/dist/localization/default-messages.d.ts +2 -0
  83. package/dist/localization/default-messages.d.ts.map +1 -1
  84. package/dist/localization/default-messages.js +2 -0
  85. package/dist/localization/default-messages.js.map +1 -1
  86. package/dist/mcp/actions/call-mcp-tool.d.ts +7 -0
  87. package/dist/mcp/actions/call-mcp-tool.d.ts.map +1 -0
  88. package/dist/mcp/actions/call-mcp-tool.js +15 -0
  89. package/dist/mcp/actions/call-mcp-tool.js.map +1 -0
  90. package/dist/mcp/actions/list-mcp-tools.d.ts +5 -0
  91. package/dist/mcp/actions/list-mcp-tools.d.ts.map +1 -0
  92. package/dist/mcp/actions/list-mcp-tools.js +16 -0
  93. package/dist/mcp/actions/list-mcp-tools.js.map +1 -0
  94. package/dist/mcp-client/app-api.d.ts +34 -0
  95. package/dist/mcp-client/app-api.d.ts.map +1 -0
  96. package/dist/mcp-client/app-api.js +94 -0
  97. package/dist/mcp-client/app-api.js.map +1 -0
  98. package/dist/mcp-client/index.d.ts +3 -1
  99. package/dist/mcp-client/index.d.ts.map +1 -1
  100. package/dist/mcp-client/index.js +3 -1
  101. package/dist/mcp-client/index.js.map +1 -1
  102. package/dist/mcp-client/oauth-client.d.ts +113 -0
  103. package/dist/mcp-client/oauth-client.d.ts.map +1 -0
  104. package/dist/mcp-client/oauth-client.js +316 -0
  105. package/dist/mcp-client/oauth-client.js.map +1 -0
  106. package/dist/mcp-client/oauth-routes.d.ts +25 -0
  107. package/dist/mcp-client/oauth-routes.d.ts.map +1 -0
  108. package/dist/mcp-client/oauth-routes.js +218 -0
  109. package/dist/mcp-client/oauth-routes.js.map +1 -0
  110. package/dist/mcp-client/remote-store.d.ts +21 -6
  111. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  112. package/dist/mcp-client/remote-store.js +72 -124
  113. package/dist/mcp-client/remote-store.js.map +1 -1
  114. package/dist/mcp-client/remote-url.d.ts +8 -0
  115. package/dist/mcp-client/remote-url.d.ts.map +1 -0
  116. package/dist/mcp-client/remote-url.js +103 -0
  117. package/dist/mcp-client/remote-url.js.map +1 -0
  118. package/dist/mcp-client/routes.d.ts +1 -0
  119. package/dist/mcp-client/routes.d.ts.map +1 -1
  120. package/dist/mcp-client/routes.js +9 -0
  121. package/dist/mcp-client/routes.js.map +1 -1
  122. package/dist/notifications/routes.d.ts +3 -3
  123. package/dist/oauth-tokens/store.d.ts +2 -2
  124. package/dist/oauth-tokens/store.d.ts.map +1 -1
  125. package/dist/oauth-tokens/store.js +10 -6
  126. package/dist/oauth-tokens/store.js.map +1 -1
  127. package/dist/observability/routes.d.ts +5 -5
  128. package/dist/progress/routes.d.ts +1 -1
  129. package/dist/provider-api/index.d.ts +14 -0
  130. package/dist/provider-api/index.d.ts.map +1 -1
  131. package/dist/provider-api/index.js +499 -61
  132. package/dist/provider-api/index.js.map +1 -1
  133. package/dist/resources/handlers.d.ts +1 -1
  134. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  135. package/dist/secrets/register-framework-secrets.js +28 -0
  136. package/dist/secrets/register-framework-secrets.js.map +1 -1
  137. package/dist/secrets/routes.d.ts +9 -9
  138. package/dist/server/action-discovery.d.ts.map +1 -1
  139. package/dist/server/action-discovery.js +2 -0
  140. package/dist/server/action-discovery.js.map +1 -1
  141. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  142. package/dist/server/core-routes-plugin.js +9 -1
  143. package/dist/server/core-routes-plugin.js.map +1 -1
  144. package/dist/server/workspace-provider-oauth.d.ts +10 -3
  145. package/dist/server/workspace-provider-oauth.d.ts.map +1 -1
  146. package/dist/server/workspace-provider-oauth.js +325 -43
  147. package/dist/server/workspace-provider-oauth.js.map +1 -1
  148. package/dist/templates/default/AGENTS.md +9 -7
  149. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  150. package/package.json +2 -2
  151. package/src/templates/default/AGENTS.md +9 -7
  152. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client.js","sourceRoot":"","sources":["../../src/mcp-client/oauth-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth 2.1 client support for remote MCP servers.\n *\n * MCP servers advertise their OAuth endpoints through the standard protected\n * resource and authorization-server metadata documents. The SDK handles the\n * protocol details; this module owns the framework-specific encrypted storage\n * and refresh boundary.\n */\n\nimport {\n auth,\n refreshAuthorization,\n type OAuthClientProvider,\n} from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type {\n AuthorizationServerMetadata,\n OAuthClientInformationMixed,\n OAuthClientMetadata,\n OAuthProtectedResourceMetadata,\n OAuthTokens,\n} from \"@modelcontextprotocol/sdk/shared/auth.js\";\n\nimport {\n deleteOAuthTokens,\n getOAuthTokens,\n saveOAuthTokens,\n} from \"../oauth-tokens/store.js\";\nimport { validateRemoteUrl } from \"./remote-url.js\";\n\nconst TOKEN_EXPIRY_SKEW_MS = 60_000;\nconst MAX_OAUTH_REDIRECTS = 5;\n\ntype GuardedFetch = (\n url: string | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\nfunction checkedRemoteUrl(value: string | URL, label: string): URL {\n const result = validateRemoteUrl(String(value));\n if (!result.ok || !result.url) {\n throw new Error(\n `MCP OAuth ${label} is not allowed: ${result.error ?? \"invalid URL\"}`,\n );\n }\n return result.url;\n}\n\nfunction guardedOAuthFetch(): GuardedFetch {\n return async (url, init) => {\n let currentUrl = checkedRemoteUrl(url, \"request\");\n let currentInit: RequestInit = { ...init, redirect: \"manual\" };\n\n for (let redirectCount = 0; ; redirectCount += 1) {\n const response = await fetch(currentUrl, currentInit);\n if (![301, 302, 303, 307, 308].includes(response.status)) {\n return response;\n }\n if (redirectCount >= MAX_OAUTH_REDIRECTS) {\n throw new Error(\"MCP OAuth redirect limit exceeded.\");\n }\n const location = response.headers.get(\"location\");\n if (!location) throw new Error(\"MCP OAuth redirect is missing a target.\");\n\n let nextUrl: URL;\n try {\n nextUrl = checkedRemoteUrl(\n new URL(location, currentUrl),\n \"redirect target\",\n );\n } catch {\n throw new Error(\"MCP OAuth redirect target is not allowed.\");\n }\n await response.body?.cancel().catch(() => undefined);\n\n const nextHeaders = new Headers(currentInit.headers);\n if (nextUrl.origin !== currentUrl.origin) {\n nextHeaders.delete(\"authorization\");\n nextHeaders.delete(\"cookie\");\n nextHeaders.delete(\"proxy-authorization\");\n }\n const nextInit: RequestInit = {\n ...currentInit,\n headers: nextHeaders,\n };\n if (\n response.status === 303 ||\n ((response.status === 301 || response.status === 302) &&\n (currentInit.method ?? \"GET\").toUpperCase() === \"POST\")\n ) {\n nextInit.method = \"GET\";\n delete nextInit.body;\n nextHeaders.delete(\"content-length\");\n nextHeaders.delete(\"content-type\");\n }\n currentUrl = nextUrl;\n currentInit = nextInit;\n }\n };\n}\n\nfunction validateDiscoveryUrls(state: {\n authorizationServerUrl: string;\n authorizationServerMetadata?: AuthorizationServerMetadata;\n resourceMetadata?: OAuthProtectedResourceMetadata;\n resourceMetadataUrl?: string;\n}): void {\n checkedRemoteUrl(state.authorizationServerUrl, \"authorization server\");\n if (state.resourceMetadataUrl) {\n checkedRemoteUrl(state.resourceMetadataUrl, \"resource metadata\");\n }\n for (const url of state.resourceMetadata?.authorization_servers ?? []) {\n checkedRemoteUrl(url, \"discovered authorization server\");\n }\n for (const field of [\n \"authorization_endpoint\",\n \"token_endpoint\",\n \"registration_endpoint\",\n \"introspection_endpoint\",\n \"revocation_endpoint\",\n ] as const) {\n const url = (\n state.authorizationServerMetadata as Record<string, unknown> | undefined\n )?.[field];\n if (typeof url === \"string\") {\n checkedRemoteUrl(url, `OAuth ${field}`);\n }\n }\n}\n\nfunction credentialOwner(options: { scope: \"user\" | \"org\"; scopeId: string }) {\n return `${options.scope}:${options.scopeId}`;\n}\n\nexport interface McpOAuthDiscoveryState {\n authorizationServerUrl: string;\n authorizationServerMetadata?: AuthorizationServerMetadata;\n resourceMetadata?: OAuthProtectedResourceMetadata;\n resourceMetadataUrl?: string;\n}\n\nexport interface McpOAuthCredentialBundle {\n serverUrl: string;\n clientInformation: OAuthClientInformationMixed;\n discoveryState?: McpOAuthDiscoveryState;\n tokens: OAuthTokens;\n tokenExpiresAt?: number;\n}\n\nexport interface McpOAuthStartResult {\n authorizationUrl: URL;\n codeVerifier: string;\n state: string;\n clientInformation: OAuthClientInformationMixed;\n discoveryState?: McpOAuthDiscoveryState;\n}\n\nexport interface McpOAuthCallbackResult {\n credentials: McpOAuthCredentialBundle;\n}\n\nexport interface McpOAuthProviderOptions {\n serverUrl: string;\n redirectUrl: string;\n state: string;\n clientInformation?: OAuthClientInformationMixed;\n codeVerifier?: string;\n discoveryState?: McpOAuthDiscoveryState;\n}\n\n/**\n * A small adapter around the MCP SDK's OAuth provider interface. The route\n * stores the adapter's state in an encrypted, short-lived browser cookie; the\n * durable credential bundle is written only after the callback succeeds.\n */\nexport class McpOAuthClientProvider implements OAuthClientProvider {\n private readonly redirectUrlValue: string;\n private readonly stateValue: string;\n private readonly metadata: OAuthClientMetadata;\n private clientInfo?: OAuthClientInformationMixed;\n private savedTokens?: OAuthTokens;\n private savedCodeVerifier?: string;\n private savedDiscovery?: McpOAuthDiscoveryState;\n private authorizationUrl?: URL;\n\n constructor(options: McpOAuthProviderOptions) {\n this.redirectUrlValue = options.redirectUrl;\n this.stateValue = options.state;\n this.clientInfo = options.clientInformation;\n this.savedCodeVerifier = options.codeVerifier;\n this.savedDiscovery = options.discoveryState;\n this.metadata = {\n redirect_uris: [options.redirectUrl],\n token_endpoint_auth_method: \"none\",\n grant_types: [\"authorization_code\"],\n response_types: [\"code\"],\n client_name: \"Agent Native MCP connector\",\n };\n }\n\n get redirectUrl(): string {\n return this.redirectUrlValue;\n }\n\n get clientMetadata(): OAuthClientMetadata {\n return this.metadata;\n }\n\n state(): string {\n return this.stateValue;\n }\n\n clientInformation(): OAuthClientInformationMixed | undefined {\n return this.clientInfo;\n }\n\n saveClientInformation(info: OAuthClientInformationMixed): void {\n this.clientInfo = info;\n }\n\n tokens(): OAuthTokens | undefined {\n return this.savedTokens;\n }\n\n saveTokens(tokens: OAuthTokens): void {\n this.savedTokens = tokens;\n }\n\n redirectToAuthorization(authorizationUrl: URL): void {\n this.authorizationUrl = checkedRemoteUrl(\n authorizationUrl,\n \"authorization redirect\",\n );\n }\n\n saveCodeVerifier(codeVerifier: string): void {\n this.savedCodeVerifier = codeVerifier;\n }\n\n codeVerifier(): string {\n if (!this.savedCodeVerifier) {\n throw new Error(\"MCP OAuth code verifier is missing\");\n }\n return this.savedCodeVerifier;\n }\n\n saveDiscoveryState(state: {\n authorizationServerUrl: string;\n authorizationServerMetadata?: AuthorizationServerMetadata;\n resourceMetadata?: OAuthProtectedResourceMetadata;\n resourceMetadataUrl?: string;\n }): void {\n validateDiscoveryUrls(state);\n this.savedDiscovery = state;\n }\n\n discoveryState(): McpOAuthDiscoveryState | undefined {\n return this.savedDiscovery;\n }\n\n get authorizationRedirect(): URL | undefined {\n return this.authorizationUrl;\n }\n\n get savedCodeVerifierValue(): string | undefined {\n return this.savedCodeVerifier;\n }\n\n get savedTokensValue(): OAuthTokens | undefined {\n return this.savedTokens;\n }\n\n get savedClientInformation(): OAuthClientInformationMixed | undefined {\n return this.clientInfo;\n }\n}\n\nexport async function startMcpOAuthAuthorization(\n options: McpOAuthProviderOptions & { scope?: string },\n): Promise<McpOAuthStartResult> {\n checkedRemoteUrl(options.serverUrl, \"server\");\n const provider = new McpOAuthClientProvider(options);\n const result = await auth(provider, {\n serverUrl: options.serverUrl,\n scope: options.scope,\n fetchFn: guardedOAuthFetch(),\n });\n if (result !== \"REDIRECT\" || !provider.authorizationRedirect) {\n throw new Error(\"MCP server did not start an interactive OAuth flow\");\n }\n const clientInformation = provider.savedClientInformation;\n if (!clientInformation) {\n throw new Error(\"MCP OAuth client registration did not complete\");\n }\n return {\n authorizationUrl: provider.authorizationRedirect,\n codeVerifier: provider.savedCodeVerifierValue ?? provider.codeVerifier(),\n state: options.state,\n clientInformation,\n discoveryState: provider.discoveryState(),\n };\n}\n\nexport async function finishMcpOAuthAuthorization(\n options: McpOAuthProviderOptions & { authorizationCode: string },\n): Promise<McpOAuthCallbackResult> {\n checkedRemoteUrl(options.serverUrl, \"server\");\n const provider = new McpOAuthClientProvider(options);\n const result = await auth(provider, {\n serverUrl: options.serverUrl,\n authorizationCode: options.authorizationCode,\n fetchFn: guardedOAuthFetch(),\n });\n if (result !== \"AUTHORIZED\" || !provider.savedTokensValue) {\n throw new Error(\"MCP OAuth token exchange did not complete\");\n }\n const tokens = provider.savedTokensValue;\n return {\n credentials: {\n serverUrl: options.serverUrl,\n clientInformation: provider.savedClientInformation!,\n discoveryState: provider.discoveryState(),\n tokens,\n tokenExpiresAt: tokenExpiresAt(tokens),\n },\n };\n}\n\nexport function tokenExpiresAt(tokens: OAuthTokens): number | undefined {\n const expiresIn = Number(tokens.expires_in);\n if (!Number.isFinite(expiresIn) || expiresIn <= 0) return undefined;\n return Date.now() + expiresIn * 1_000;\n}\n\nexport async function saveMcpOAuthCredentials(options: {\n key: string;\n scope: \"user\" | \"org\";\n scopeId: string;\n credentials: McpOAuthCredentialBundle;\n}): Promise<void> {\n checkedRemoteUrl(options.credentials.serverUrl, \"server\");\n if (options.credentials.discoveryState) {\n validateDiscoveryUrls(options.credentials.discoveryState);\n }\n await saveOAuthTokens(\n \"mcp\",\n options.key,\n options.credentials as unknown as Record<string, unknown>,\n `${options.scope}:${options.scopeId}`,\n );\n}\n\nexport async function readMcpOAuthCredentials(options: {\n key: string;\n scope: \"user\" | \"org\";\n scopeId: string;\n}): Promise<McpOAuthCredentialBundle | null> {\n const stored = await getOAuthTokens(\n \"mcp\",\n options.key,\n credentialOwner(options),\n );\n if (!stored) return null;\n const parsed = stored as Partial<McpOAuthCredentialBundle>;\n if (\n typeof parsed.serverUrl !== \"string\" ||\n !parsed.clientInformation ||\n !parsed.tokens ||\n typeof parsed.tokens.access_token !== \"string\"\n ) {\n return null;\n }\n if (!validateRemoteUrl(parsed.serverUrl).ok) return null;\n if (parsed.discoveryState) {\n try {\n validateDiscoveryUrls(parsed.discoveryState);\n } catch {\n return null;\n }\n }\n return parsed as McpOAuthCredentialBundle;\n}\n\nexport async function deleteMcpOAuthCredentials(options: {\n key: string;\n scope: \"user\" | \"org\";\n scopeId: string;\n}): Promise<boolean> {\n return (\n (await deleteOAuthTokens(\"mcp\", options.key, credentialOwner(options))) > 0\n );\n}\n\n/**\n * Resolve an access token for the MCP manager. Refreshing happens only when a\n * token is near expiry, so ordinary manager reconfiguration does not perform\n * a network request for every connector.\n */\nexport async function getMcpOAuthAccessToken(options: {\n key: string;\n scope: \"user\" | \"org\";\n scopeId: string;\n serverUrl: string;\n}): Promise<string | null> {\n if (!validateRemoteUrl(options.serverUrl).ok) return null;\n const credentials = await readMcpOAuthCredentials(options);\n if (!credentials || credentials.serverUrl !== options.serverUrl) return null;\n\n const accessToken = credentials.tokens.access_token;\n if (\n typeof credentials.tokenExpiresAt !== \"number\" ||\n credentials.tokenExpiresAt - Date.now() > TOKEN_EXPIRY_SKEW_MS\n ) {\n return accessToken;\n }\n\n const refreshToken = credentials.tokens.refresh_token;\n const discovery = credentials.discoveryState;\n if (!refreshToken || !discovery?.authorizationServerUrl) {\n return accessToken;\n }\n\n try {\n const resource = discovery.resourceMetadata?.resource\n ? checkedRemoteUrl(discovery.resourceMetadata.resource, \"resource\")\n : undefined;\n const authorizationServerUrl = checkedRemoteUrl(\n discovery.authorizationServerUrl,\n \"authorization server\",\n );\n const refreshed = await refreshAuthorization(authorizationServerUrl, {\n metadata: discovery.authorizationServerMetadata,\n clientInformation: credentials.clientInformation,\n refreshToken,\n resource,\n fetchFn: guardedOAuthFetch(),\n });\n const nextTokens: OAuthTokens = {\n ...credentials.tokens,\n ...refreshed,\n ...(refreshed.refresh_token\n ? { refresh_token: refreshed.refresh_token }\n : credentials.tokens.refresh_token\n ? { refresh_token: credentials.tokens.refresh_token }\n : {}),\n };\n const next: McpOAuthCredentialBundle = {\n ...credentials,\n tokens: nextTokens,\n tokenExpiresAt: tokenExpiresAt(nextTokens),\n };\n await saveMcpOAuthCredentials({\n ...options,\n credentials: next,\n });\n return nextTokens.access_token;\n } catch {\n // Keep the old token so the MCP request can return the provider's normal\n // auth error. A single expired connector must not remove every server from\n // the process-wide manager during a config refresh.\n return accessToken;\n }\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import type { OAuthClientInformationMixed } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ import { type McpOAuthDiscoveryState } from "./oauth-client.js";
3
+ import { type RemoteMcpScope } from "./remote-store.js";
4
+ export interface McpOAuthFlow {
5
+ name: string;
6
+ url: string;
7
+ description?: string;
8
+ scope: RemoteMcpScope;
9
+ scopeId: string;
10
+ owner: string;
11
+ orgId?: string;
12
+ redirectUri: string;
13
+ state: string;
14
+ codeVerifier: string;
15
+ clientInformation: OAuthClientInformationMixed;
16
+ discoveryState?: McpOAuthDiscoveryState;
17
+ returnUrl?: string;
18
+ expiresAt: number;
19
+ }
20
+ export interface McpOAuthRoutesOptions {
21
+ reconfigure: () => Promise<void>;
22
+ }
23
+ export declare function mountMcpOAuthRoutes(nitroApp: any, options: McpOAuthRoutesOptions): void;
24
+ export declare function isValidMcpOAuthFlow(flow: McpOAuthFlow, email: string, orgId: string | undefined, state: string): boolean;
25
+ //# sourceMappingURL=oauth-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-routes.d.ts","sourceRoot":"","sources":["../../src/mcp-client/oauth-routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AAkB5F,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAK3B,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,2BAA2B,CAAC;IAC/C,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,GAAG,EACb,OAAO,EAAE,qBAAqB,GAC7B,IAAI,CA6BN;AA0LD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,KAAK,EAAE,MAAM,GACZ,OAAO,CAcT"}
@@ -0,0 +1,218 @@
1
+ import crypto from "node:crypto";
2
+ import { deleteCookie, defineEventHandler, getCookie, getMethod, getQuery, setCookie, setResponseStatus, } from "h3";
3
+ import { getOrgContext } from "../org/context.js";
4
+ import { decryptSecretValue, encryptSecretValue } from "../secrets/crypto.js";
5
+ import { getSession, safeReturnPath } from "../server/auth.js";
6
+ import { getH3App } from "../server/framework-request-handler.js";
7
+ import { getAppUrl, resolveOAuthRedirectUri } from "../server/google-oauth.js";
8
+ import { runWithRequestContext } from "../server/request-context.js";
9
+ import { finishMcpOAuthAuthorization, startMcpOAuthAuthorization, } from "./oauth-client.js";
10
+ import { addOAuthRemoteServer, normalizeServerName, validateRemoteUrl, } from "./remote-store.js";
11
+ const FLOW_COOKIE = "an_mcp_oauth_flow";
12
+ const FLOW_TTL_SECONDS = 10 * 60;
13
+ export function mountMcpOAuthRoutes(nitroApp, options) {
14
+ const mountedApps = (globalThis.__agentNativeMcpOAuthMountedApps ??= new WeakSet());
15
+ if (mountedApps.has(nitroApp))
16
+ return;
17
+ mountedApps.add(nitroApp);
18
+ getH3App(nitroApp).use("/_agent-native/mcp/servers/oauth", defineEventHandler(async (event) => {
19
+ const method = getMethod(event);
20
+ const pathname = (event.url?.pathname || "")
21
+ .replace(/^\/+/, "")
22
+ .replace(/\/+$/, "");
23
+ const parts = pathname ? pathname.split("/") : [];
24
+ if (method !== "GET") {
25
+ setResponseStatus(event, 405);
26
+ return { error: "Method not allowed" };
27
+ }
28
+ if (parts.length === 1 && parts[0] === "start") {
29
+ return handleMcpOAuthStart(event);
30
+ }
31
+ if (parts.length === 1 && parts[0] === "callback") {
32
+ return handleMcpOAuthCallback(event, options);
33
+ }
34
+ setResponseStatus(event, 404);
35
+ return { error: "Not found" };
36
+ }));
37
+ }
38
+ async function handleMcpOAuthStart(event) {
39
+ const session = await getSession(event).catch(() => null);
40
+ if (!session?.email)
41
+ return unauthorized(event);
42
+ const query = getQuery(event);
43
+ const rawUrl = text(query.url);
44
+ const rawName = text(query.name);
45
+ const returnUrl = text(query.return);
46
+ if (!rawUrl || !rawName) {
47
+ setResponseStatus(event, 400);
48
+ return { error: "MCP OAuth requires a server name and URL." };
49
+ }
50
+ const urlCheck = validateRemoteUrl(rawUrl);
51
+ if (!urlCheck.ok) {
52
+ setResponseStatus(event, 400);
53
+ return { error: urlCheck.error ?? "MCP server URL is not allowed." };
54
+ }
55
+ const name = normalizeServerName(rawName);
56
+ if (!name) {
57
+ setResponseStatus(event, 400);
58
+ return { error: "MCP server name is invalid." };
59
+ }
60
+ const requestedScope = query.scope === "org" ? "org" : "user";
61
+ const requestedOrgId = text(query.orgId);
62
+ const org = requestedScope === "org"
63
+ ? await getOrgContext(event).catch(() => null)
64
+ : null;
65
+ const scope = requestedScope;
66
+ const scopeId = scope === "user" ? session.email : (org?.orgId ?? "");
67
+ if (scope === "org" && requestedOrgId && requestedOrgId !== scopeId) {
68
+ setResponseStatus(event, 403);
69
+ return {
70
+ error: "The selected organization is not the active organization.",
71
+ };
72
+ }
73
+ if (scope === "org" && (!scopeId || !isOrgAdmin(org?.role))) {
74
+ setResponseStatus(event, scopeId ? 403 : 400);
75
+ return {
76
+ error: scopeId
77
+ ? "Only organization owners and admins can connect an org MCP server."
78
+ : "Join an organization before connecting an org MCP server.",
79
+ };
80
+ }
81
+ const redirectUri = resolveOAuthRedirectUri(event, "/_agent-native/mcp/servers/oauth/callback");
82
+ if (!redirectUri) {
83
+ setResponseStatus(event, 400);
84
+ return { error: "Invalid MCP OAuth redirect URI." };
85
+ }
86
+ const state = crypto.randomUUID();
87
+ const safeReturnUrl = returnUrl ? safeReturnPath(returnUrl) : undefined;
88
+ try {
89
+ const started = await runWithRequestContext({ userEmail: session.email, orgId: org?.orgId ?? undefined }, () => startMcpOAuthAuthorization({
90
+ serverUrl: urlCheck.url.toString(),
91
+ redirectUrl: redirectUri,
92
+ state,
93
+ }));
94
+ const flow = {
95
+ name,
96
+ url: urlCheck.url.toString(),
97
+ description: text(query.description),
98
+ scope,
99
+ scopeId,
100
+ owner: session.email,
101
+ ...(org?.orgId ? { orgId: org.orgId } : {}),
102
+ redirectUri,
103
+ state: started.state,
104
+ codeVerifier: started.codeVerifier,
105
+ clientInformation: started.clientInformation,
106
+ ...(started.discoveryState
107
+ ? { discoveryState: started.discoveryState }
108
+ : {}),
109
+ ...(safeReturnUrl ? { returnUrl: safeReturnUrl } : {}),
110
+ expiresAt: Date.now() + FLOW_TTL_SECONDS * 1_000,
111
+ };
112
+ setCookie(event, FLOW_COOKIE, encryptSecretValue(JSON.stringify(flow)), {
113
+ httpOnly: true,
114
+ secure: redirectUri.startsWith("https://"),
115
+ sameSite: "lax",
116
+ path: "/",
117
+ maxAge: FLOW_TTL_SECONDS,
118
+ });
119
+ return Response.redirect(started.authorizationUrl.href, 302);
120
+ }
121
+ catch {
122
+ setResponseStatus(event, 400);
123
+ return {
124
+ error: "This MCP server could not start OAuth. It may not support standard MCP OAuth discovery or dynamic client registration.",
125
+ };
126
+ }
127
+ }
128
+ async function handleMcpOAuthCallback(event, options) {
129
+ const session = await getSession(event).catch(() => null);
130
+ if (!session?.email)
131
+ return unauthorized(event);
132
+ const query = getQuery(event);
133
+ const code = text(query.code);
134
+ const state = text(query.state);
135
+ const providerError = text(query.error);
136
+ const flow = readFlow(event);
137
+ deleteCookie(event, FLOW_COOKIE, { path: "/" });
138
+ if (providerError || !code || !state) {
139
+ setResponseStatus(event, 400);
140
+ return { error: "MCP OAuth authorization was not completed." };
141
+ }
142
+ const org = flow?.scope === "org" ? await getOrgContext(event).catch(() => null) : null;
143
+ if (!flow ||
144
+ !isValidMcpOAuthFlow(flow, session.email, org?.orgId ?? undefined, state)) {
145
+ setResponseStatus(event, 400);
146
+ return { error: "MCP OAuth state is invalid or expired." };
147
+ }
148
+ if (flow.scope === "org" && !isOrgAdmin(org?.role)) {
149
+ setResponseStatus(event, 403);
150
+ return {
151
+ error: "Only organization owners and admins can connect an org MCP server.",
152
+ };
153
+ }
154
+ try {
155
+ const finished = await runWithRequestContext({ userEmail: session.email, orgId: org?.orgId ?? undefined }, () => finishMcpOAuthAuthorization({
156
+ serverUrl: flow.url,
157
+ redirectUrl: flow.redirectUri,
158
+ state: flow.state,
159
+ clientInformation: flow.clientInformation,
160
+ codeVerifier: flow.codeVerifier,
161
+ discoveryState: flow.discoveryState,
162
+ authorizationCode: code,
163
+ }));
164
+ const result = await addOAuthRemoteServer(flow.scope, flow.scopeId, {
165
+ name: flow.name,
166
+ url: flow.url,
167
+ description: flow.description,
168
+ credentials: finished.credentials,
169
+ });
170
+ if (!result.ok) {
171
+ setResponseStatus(event, 400);
172
+ return { error: result.error };
173
+ }
174
+ await options.reconfigure();
175
+ const returnPath = flow.returnUrl ??
176
+ `/settings/connections?connected=mcp-${encodeURIComponent(flow.name)}`;
177
+ return Response.redirect(getAppUrl(event, returnPath), 302);
178
+ }
179
+ catch {
180
+ setResponseStatus(event, 400);
181
+ return { error: "MCP OAuth authorization could not be completed." };
182
+ }
183
+ }
184
+ function readFlow(event) {
185
+ const encrypted = getCookie(event, FLOW_COOKIE);
186
+ if (!encrypted)
187
+ return null;
188
+ try {
189
+ const parsed = JSON.parse(decryptSecretValue(encrypted));
190
+ return parsed && typeof parsed === "object" ? parsed : null;
191
+ }
192
+ catch {
193
+ return null;
194
+ }
195
+ }
196
+ export function isValidMcpOAuthFlow(flow, email, orgId, state) {
197
+ const scopeMatches = flow.scope === "user"
198
+ ? flow.scopeId === email && !flow.orgId
199
+ : flow.scope === "org" && flow.scopeId === orgId && flow.orgId === orgId;
200
+ return (flow.expiresAt >= Date.now() &&
201
+ flow.owner === email &&
202
+ flow.state === state &&
203
+ scopeMatches &&
204
+ typeof flow.scopeId === "string" &&
205
+ typeof flow.redirectUri === "string" &&
206
+ flow.redirectUri.includes("/_agent-native/mcp/servers/oauth/callback"));
207
+ }
208
+ function isOrgAdmin(role) {
209
+ return role === "owner" || role === "admin";
210
+ }
211
+ function text(value) {
212
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
213
+ }
214
+ function unauthorized(event) {
215
+ setResponseStatus(event, 401);
216
+ return { error: "Authentication required" };
217
+ }
218
+ //# sourceMappingURL=oauth-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-routes.js","sourceRoot":"","sources":["../../src/mcp-client/oauth-routes.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"node:crypto\";\n\nimport type { OAuthClientInformationMixed } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport {\n deleteCookie,\n defineEventHandler,\n getCookie,\n getMethod,\n getQuery,\n setCookie,\n setResponseStatus,\n type H3Event,\n} from \"h3\";\n\nimport { getOrgContext } from \"../org/context.js\";\nimport { decryptSecretValue, encryptSecretValue } from \"../secrets/crypto.js\";\nimport { getSession, safeReturnPath } from \"../server/auth.js\";\nimport { getH3App } from \"../server/framework-request-handler.js\";\nimport { getAppUrl, resolveOAuthRedirectUri } from \"../server/google-oauth.js\";\nimport { runWithRequestContext } from \"../server/request-context.js\";\nimport {\n finishMcpOAuthAuthorization,\n startMcpOAuthAuthorization,\n type McpOAuthDiscoveryState,\n} from \"./oauth-client.js\";\nimport {\n addOAuthRemoteServer,\n normalizeServerName,\n validateRemoteUrl,\n type RemoteMcpScope,\n} from \"./remote-store.js\";\n\nconst FLOW_COOKIE = \"an_mcp_oauth_flow\";\nconst FLOW_TTL_SECONDS = 10 * 60;\n\nexport interface McpOAuthFlow {\n name: string;\n url: string;\n description?: string;\n scope: RemoteMcpScope;\n scopeId: string;\n owner: string;\n orgId?: string;\n redirectUri: string;\n state: string;\n codeVerifier: string;\n clientInformation: OAuthClientInformationMixed;\n discoveryState?: McpOAuthDiscoveryState;\n returnUrl?: string;\n expiresAt: number;\n}\n\nexport interface McpOAuthRoutesOptions {\n reconfigure: () => Promise<void>;\n}\n\nexport function mountMcpOAuthRoutes(\n nitroApp: any,\n options: McpOAuthRoutesOptions,\n): void {\n const mountedApps: WeakSet<object> = ((\n globalThis as any\n ).__agentNativeMcpOAuthMountedApps ??= new WeakSet<object>());\n if (mountedApps.has(nitroApp)) return;\n mountedApps.add(nitroApp);\n\n getH3App(nitroApp).use(\n \"/_agent-native/mcp/servers/oauth\",\n defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n const pathname = (event.url?.pathname || \"\")\n .replace(/^\\/+/, \"\")\n .replace(/\\/+$/, \"\");\n const parts = pathname ? pathname.split(\"/\") : [];\n if (method !== \"GET\") {\n setResponseStatus(event, 405);\n return { error: \"Method not allowed\" };\n }\n if (parts.length === 1 && parts[0] === \"start\") {\n return handleMcpOAuthStart(event);\n }\n if (parts.length === 1 && parts[0] === \"callback\") {\n return handleMcpOAuthCallback(event, options);\n }\n setResponseStatus(event, 404);\n return { error: \"Not found\" };\n }),\n );\n}\n\nasync function handleMcpOAuthStart(\n event: H3Event,\n): Promise<Response | Record<string, unknown>> {\n const session = await getSession(event).catch(() => null);\n if (!session?.email) return unauthorized(event);\n\n const query = getQuery(event);\n const rawUrl = text(query.url);\n const rawName = text(query.name);\n const returnUrl = text(query.return);\n if (!rawUrl || !rawName) {\n setResponseStatus(event, 400);\n return { error: \"MCP OAuth requires a server name and URL.\" };\n }\n const urlCheck = validateRemoteUrl(rawUrl);\n if (!urlCheck.ok) {\n setResponseStatus(event, 400);\n return { error: urlCheck.error ?? \"MCP server URL is not allowed.\" };\n }\n const name = normalizeServerName(rawName);\n if (!name) {\n setResponseStatus(event, 400);\n return { error: \"MCP server name is invalid.\" };\n }\n\n const requestedScope = query.scope === \"org\" ? \"org\" : \"user\";\n const requestedOrgId = text(query.orgId);\n const org =\n requestedScope === \"org\"\n ? await getOrgContext(event).catch(() => null)\n : null;\n const scope: RemoteMcpScope = requestedScope;\n const scopeId = scope === \"user\" ? session.email : (org?.orgId ?? \"\");\n if (scope === \"org\" && requestedOrgId && requestedOrgId !== scopeId) {\n setResponseStatus(event, 403);\n return {\n error: \"The selected organization is not the active organization.\",\n };\n }\n if (scope === \"org\" && (!scopeId || !isOrgAdmin(org?.role))) {\n setResponseStatus(event, scopeId ? 403 : 400);\n return {\n error: scopeId\n ? \"Only organization owners and admins can connect an org MCP server.\"\n : \"Join an organization before connecting an org MCP server.\",\n };\n }\n\n const redirectUri = resolveOAuthRedirectUri(\n event,\n \"/_agent-native/mcp/servers/oauth/callback\",\n );\n if (!redirectUri) {\n setResponseStatus(event, 400);\n return { error: \"Invalid MCP OAuth redirect URI.\" };\n }\n\n const state = crypto.randomUUID();\n const safeReturnUrl = returnUrl ? safeReturnPath(returnUrl) : undefined;\n try {\n const started = await runWithRequestContext(\n { userEmail: session.email, orgId: org?.orgId ?? undefined },\n () =>\n startMcpOAuthAuthorization({\n serverUrl: urlCheck.url!.toString(),\n redirectUrl: redirectUri,\n state,\n }),\n );\n const flow: McpOAuthFlow = {\n name,\n url: urlCheck.url!.toString(),\n description: text(query.description),\n scope,\n scopeId,\n owner: session.email,\n ...(org?.orgId ? { orgId: org.orgId } : {}),\n redirectUri,\n state: started.state,\n codeVerifier: started.codeVerifier,\n clientInformation: started.clientInformation,\n ...(started.discoveryState\n ? { discoveryState: started.discoveryState }\n : {}),\n ...(safeReturnUrl ? { returnUrl: safeReturnUrl } : {}),\n expiresAt: Date.now() + FLOW_TTL_SECONDS * 1_000,\n };\n setCookie(event, FLOW_COOKIE, encryptSecretValue(JSON.stringify(flow)), {\n httpOnly: true,\n secure: redirectUri.startsWith(\"https://\"),\n sameSite: \"lax\",\n path: \"/\",\n maxAge: FLOW_TTL_SECONDS,\n });\n return Response.redirect(started.authorizationUrl.href, 302);\n } catch {\n setResponseStatus(event, 400);\n return {\n error:\n \"This MCP server could not start OAuth. It may not support standard MCP OAuth discovery or dynamic client registration.\",\n };\n }\n}\n\nasync function handleMcpOAuthCallback(\n event: H3Event,\n options: McpOAuthRoutesOptions,\n): Promise<Response | Record<string, unknown>> {\n const session = await getSession(event).catch(() => null);\n if (!session?.email) return unauthorized(event);\n\n const query = getQuery(event);\n const code = text(query.code);\n const state = text(query.state);\n const providerError = text(query.error);\n const flow = readFlow(event);\n deleteCookie(event, FLOW_COOKIE, { path: \"/\" });\n if (providerError || !code || !state) {\n setResponseStatus(event, 400);\n return { error: \"MCP OAuth authorization was not completed.\" };\n }\n const org =\n flow?.scope === \"org\" ? await getOrgContext(event).catch(() => null) : null;\n if (\n !flow ||\n !isValidMcpOAuthFlow(flow, session.email, org?.orgId ?? undefined, state)\n ) {\n setResponseStatus(event, 400);\n return { error: \"MCP OAuth state is invalid or expired.\" };\n }\n if (flow.scope === \"org\" && !isOrgAdmin(org?.role)) {\n setResponseStatus(event, 403);\n return {\n error:\n \"Only organization owners and admins can connect an org MCP server.\",\n };\n }\n\n try {\n const finished = await runWithRequestContext(\n { userEmail: session.email, orgId: org?.orgId ?? undefined },\n () =>\n finishMcpOAuthAuthorization({\n serverUrl: flow.url,\n redirectUrl: flow.redirectUri,\n state: flow.state,\n clientInformation: flow.clientInformation,\n codeVerifier: flow.codeVerifier,\n discoveryState: flow.discoveryState,\n authorizationCode: code,\n }),\n );\n const result = await addOAuthRemoteServer(flow.scope, flow.scopeId, {\n name: flow.name,\n url: flow.url,\n description: flow.description,\n credentials: finished.credentials,\n });\n if (!result.ok) {\n setResponseStatus(event, 400);\n return { error: result.error };\n }\n await options.reconfigure();\n const returnPath =\n flow.returnUrl ??\n `/settings/connections?connected=mcp-${encodeURIComponent(flow.name)}`;\n return Response.redirect(getAppUrl(event, returnPath), 302);\n } catch {\n setResponseStatus(event, 400);\n return { error: \"MCP OAuth authorization could not be completed.\" };\n }\n}\n\nfunction readFlow(event: H3Event): McpOAuthFlow | null {\n const encrypted = getCookie(event, FLOW_COOKIE);\n if (!encrypted) return null;\n try {\n const parsed = JSON.parse(decryptSecretValue(encrypted)) as McpOAuthFlow;\n return parsed && typeof parsed === \"object\" ? parsed : null;\n } catch {\n return null;\n }\n}\n\nexport function isValidMcpOAuthFlow(\n flow: McpOAuthFlow,\n email: string,\n orgId: string | undefined,\n state: string,\n): boolean {\n const scopeMatches =\n flow.scope === \"user\"\n ? flow.scopeId === email && !flow.orgId\n : flow.scope === \"org\" && flow.scopeId === orgId && flow.orgId === orgId;\n return (\n flow.expiresAt >= Date.now() &&\n flow.owner === email &&\n flow.state === state &&\n scopeMatches &&\n typeof flow.scopeId === \"string\" &&\n typeof flow.redirectUri === \"string\" &&\n flow.redirectUri.includes(\"/_agent-native/mcp/servers/oauth/callback\")\n );\n}\n\nfunction isOrgAdmin(role: unknown): boolean {\n return role === \"owner\" || role === \"admin\";\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction unauthorized(event: H3Event) {\n setResponseStatus(event, 401);\n return { error: \"Authentication required\" };\n}\n"]}
@@ -20,6 +20,8 @@
20
20
  * re-saved to migrate.
21
21
  */
22
22
  import type { McpHttpServerConfig } from "./config.js";
23
+ import { type McpOAuthCredentialBundle } from "./oauth-client.js";
24
+ export { validateRemoteUrl } from "./remote-url.js";
23
25
  export type RemoteMcpScope = "user" | "org";
24
26
  export interface StoredRemoteMcpServer {
25
27
  /** Stable unique id — used for removal / URLs. */
@@ -43,6 +45,8 @@ export interface StoredRemoteMcpServer {
43
45
  * supplied (or for legacy cleartext rows).
44
46
  */
45
47
  headerSecretKey?: string;
48
+ /** Reference to the encrypted OAuth credential bundle for this server. */
49
+ oauthSecretKey?: string;
46
50
  /**
47
51
  * Trusted first-party Agent-Native app. Only framework-controlled
48
52
  * registrations should set this; management routes intentionally do not
@@ -72,12 +76,6 @@ export declare function normalizeServerName(input: string): string;
72
76
  * name don't collide in the global MCP manager.
73
77
  */
74
78
  export declare function hashEmail(email: string): string;
75
- /** Reject obviously-wrong URLs. Allows http only for localhost. */
76
- export declare function validateRemoteUrl(raw: string): {
77
- ok: boolean;
78
- url?: URL;
79
- error?: string;
80
- };
81
79
  export declare function listRemoteServers(scope: RemoteMcpScope, scopeId: string): Promise<StoredRemoteMcpServer[]>;
82
80
  export declare function addRemoteServer(scope: RemoteMcpScope, scopeId: string, input: {
83
81
  name: string;
@@ -92,6 +90,23 @@ export declare function addRemoteServer(scope: RemoteMcpScope, scopeId: string,
92
90
  ok: false;
93
91
  error: string;
94
92
  }>;
93
+ /**
94
+ * Persist a remote MCP server whose authorization is managed by the MCP
95
+ * server's OAuth 2.1 metadata and token endpoints. OAuth credentials are
96
+ * stored separately from the settings row and never returned to clients.
97
+ */
98
+ export declare function addOAuthRemoteServer(scope: RemoteMcpScope, scopeId: string, input: {
99
+ name: string;
100
+ url: string;
101
+ description?: string;
102
+ credentials: McpOAuthCredentialBundle;
103
+ }): Promise<{
104
+ ok: true;
105
+ server: StoredRemoteMcpServer;
106
+ } | {
107
+ ok: false;
108
+ error: string;
109
+ }>;
95
110
  export declare function addFirstPartyRemoteServer(orgId: string, input: {
96
111
  appId: string;
97
112
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"remote-store.d.ts","sourceRoot":"","sources":["../../src/mcp-client/remote-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"remote-store.d.ts","sourceRoot":"","sources":["../../src/mcp-client/remote-store.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,9 @@ import { createHash } from "node:crypto";
23
23
  import { writeAppSecret, readAppSecret, deleteAppSecret, } from "../secrets/storage.js";
24
24
  import { getOrgSetting, putOrgSetting, deleteOrgSetting, } from "../settings/org-settings.js";
25
25
  import { getUserSetting, putUserSetting, deleteUserSetting, } from "../settings/user-settings.js";
26
+ import { deleteMcpOAuthCredentials, getMcpOAuthAccessToken, saveMcpOAuthCredentials, } from "./oauth-client.js";
27
+ import { validateRemoteUrl } from "./remote-url.js";
28
+ export { validateRemoteUrl } from "./remote-url.js";
26
29
  const SETTINGS_KEY = "mcp-servers-remote";
27
30
  function toSecretScope(scope) {
28
31
  return scope === "user" ? "user" : "workspace";
@@ -103,129 +106,6 @@ export function hashEmail(email) {
103
106
  function sanitiseOrgId(orgId) {
104
107
  return orgId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
105
108
  }
106
- /**
107
- * Hostname patterns that are always rejected to prevent SSRF via DNS rebinding
108
- * or well-known internal service names.
109
- */
110
- const BLOCKED_HOSTNAME_PATTERNS = [
111
- /^localhost$/i,
112
- /\.localhost$/i,
113
- /\.local$/i,
114
- /\.internal$/i,
115
- // DNS rebind services
116
- /\.nip\.io$/i,
117
- /\.sslip\.io$/i,
118
- /\.xip\.io$/i,
119
- /\.localtest\.me$/i,
120
- /\.lvh\.me$/i,
121
- // Cloud metadata hosts
122
- /^metadata\.google\.internal$/i,
123
- /^instance-data$/i,
124
- ];
125
- /** Literal IPs that are always rejected (cloud metadata endpoints). */
126
- const BLOCKED_IPS = new Set([
127
- "169.254.169.254", // AWS/GCP/Azure IMDS
128
- "100.100.100.200", // Alibaba Cloud metadata
129
- "::1",
130
- ]);
131
- /** Parse a dotted-decimal IPv4 string to a 32-bit integer for range checks. */
132
- function ipv4ToInt(ip) {
133
- const parts = ip.split(".");
134
- if (parts.length !== 4)
135
- return null;
136
- let n = 0;
137
- for (const part of parts) {
138
- const byte = parseInt(part, 10);
139
- if (isNaN(byte) || byte < 0 || byte > 255)
140
- return null;
141
- n = (n << 8) | byte;
142
- }
143
- return n >>> 0;
144
- }
145
- function isPrivateIpv4(hostname) {
146
- const n = ipv4ToInt(hostname);
147
- if (n === null)
148
- return false;
149
- // 10.0.0.0/8
150
- if ((n & 0xff000000) >>> 0 === 0x0a000000)
151
- return true;
152
- // 172.16.0.0/12
153
- if ((n & 0xfff00000) >>> 0 === 0xac100000)
154
- return true;
155
- // 192.168.0.0/16
156
- if ((n & 0xffff0000) >>> 0 === 0xc0a80000)
157
- return true;
158
- // 169.254.0.0/16 (link-local, includes AWS/GCP metadata)
159
- if ((n & 0xffff0000) >>> 0 === 0xa9fe0000)
160
- return true;
161
- // 127.0.0.0/8 (loopback)
162
- if ((n & 0xff000000) >>> 0 === 0x7f000000)
163
- return true;
164
- return false;
165
- }
166
- function isBlockedHostname(hostname) {
167
- const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
168
- if (BLOCKED_IPS.has(normalized))
169
- return true;
170
- if (isPrivateIpv4(normalized))
171
- return true;
172
- // IPv6 ULA (fc00::/7) and link-local (fe80::/10)
173
- if (normalized === "::1")
174
- return true;
175
- if (normalized.startsWith("fc") || normalized.startsWith("fd"))
176
- return true;
177
- if (normalized.startsWith("fe80"))
178
- return true;
179
- if (normalized.startsWith("::ffff:")) {
180
- const mapped = normalized.slice("::ffff:".length);
181
- if (isPrivateIpv4(mapped))
182
- return true;
183
- const hexMatch = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(mapped);
184
- if (hexMatch) {
185
- const value = (parseInt(hexMatch[1], 16) << 16) | parseInt(hexMatch[2], 16);
186
- const dotted = [
187
- (value >>> 24) & 0xff,
188
- (value >>> 16) & 0xff,
189
- (value >>> 8) & 0xff,
190
- value & 0xff,
191
- ].join(".");
192
- if (isPrivateIpv4(dotted))
193
- return true;
194
- }
195
- }
196
- for (const pattern of BLOCKED_HOSTNAME_PATTERNS) {
197
- if (pattern.test(normalized))
198
- return true;
199
- }
200
- return false;
201
- }
202
- /** Reject obviously-wrong URLs. Allows http only for localhost. */
203
- export function validateRemoteUrl(raw) {
204
- let url;
205
- try {
206
- url = new URL(raw);
207
- }
208
- catch {
209
- return { ok: false, error: "Not a valid URL" };
210
- }
211
- if (url.protocol === "https:") {
212
- if (isBlockedHostname(url.hostname)) {
213
- return {
214
- ok: false,
215
- error: `Host "${url.hostname}" is not allowed (private/internal address)`,
216
- };
217
- }
218
- return { ok: true, url };
219
- }
220
- if (url.protocol === "http:") {
221
- const host = url.hostname;
222
- if (host === "localhost" || host === "127.0.0.1") {
223
- return { ok: true, url };
224
- }
225
- return { ok: false, error: "Plain http is only allowed for localhost" };
226
- }
227
- return { ok: false, error: `Unsupported protocol: ${url.protocol}` };
228
- }
229
109
  async function readList(scope, scopeId) {
230
110
  const raw = scope === "user"
231
111
  ? await getUserSetting(scopeId, SETTINGS_KEY)
@@ -254,6 +134,47 @@ export async function addRemoteServer(scope, scopeId, input) {
254
134
  }
255
135
  return addRemoteServerInternal(scope, scopeId, input);
256
136
  }
137
+ /**
138
+ * Persist a remote MCP server whose authorization is managed by the MCP
139
+ * server's OAuth 2.1 metadata and token endpoints. OAuth credentials are
140
+ * stored separately from the settings row and never returned to clients.
141
+ */
142
+ export async function addOAuthRemoteServer(scope, scopeId, input) {
143
+ const oauthSecretKey = `mcp_oauth:${shortId()}`;
144
+ try {
145
+ await saveMcpOAuthCredentials({
146
+ key: oauthSecretKey,
147
+ scope,
148
+ scopeId,
149
+ credentials: input.credentials,
150
+ });
151
+ const result = await addRemoteServerInternal(scope, scopeId, {
152
+ name: input.name,
153
+ url: input.url,
154
+ description: input.description,
155
+ oauthSecretKey,
156
+ });
157
+ if (!result.ok) {
158
+ await deleteMcpOAuthCredentials({
159
+ key: oauthSecretKey,
160
+ scope,
161
+ scopeId,
162
+ });
163
+ }
164
+ return result;
165
+ }
166
+ catch (err) {
167
+ await deleteMcpOAuthCredentials({
168
+ key: oauthSecretKey,
169
+ scope,
170
+ scopeId,
171
+ }).catch(() => { });
172
+ return {
173
+ ok: false,
174
+ error: `Failed to save MCP OAuth credentials: ${err?.message ?? err}`,
175
+ };
176
+ }
177
+ }
257
178
  export async function addFirstPartyRemoteServer(orgId, input) {
258
179
  const trust = await isFirstPartyRemoteEndpointTrusted(orgId, input.appId, input.url);
259
180
  if (!trust.ok)
@@ -313,6 +234,7 @@ async function addRemoteServerInternal(scope, scopeId, input) {
313
234
  ...(input.firstPartyAppId
314
235
  ? { firstPartyAppId: input.firstPartyAppId }
315
236
  : {}),
237
+ ...(input.oauthSecretKey ? { oauthSecretKey: input.oauthSecretKey } : {}),
316
238
  description: input.description?.trim() || undefined,
317
239
  createdAt: Date.now(),
318
240
  };
@@ -416,6 +338,19 @@ export async function removeRemoteServer(scope, scopeId, id) {
416
338
  console.warn(`[mcp-client] Failed to delete MCP header secret ${removed.headerSecretKey}: ${err?.message ?? err}`);
417
339
  }
418
340
  }
341
+ if (removed?.oauthSecretKey) {
342
+ try {
343
+ await deleteMcpOAuthCredentials({
344
+ key: removed.oauthSecretKey,
345
+ scope,
346
+ scopeId,
347
+ });
348
+ }
349
+ catch (err) {
350
+ // eslint-disable-next-line no-console
351
+ console.warn(`[mcp-client] Failed to delete MCP OAuth credentials ${removed.oauthSecretKey}: ${err?.message ?? err}`);
352
+ }
353
+ }
419
354
  return true;
420
355
  }
421
356
  /**
@@ -485,10 +420,23 @@ export function toHttpServerConfig(stored) {
485
420
  * secrets).
486
421
  */
487
422
  export async function toHttpServerConfigAsync(scope, scopeId, stored) {
423
+ let headers = await materializeHeaders(scope, scopeId, stored);
424
+ if (stored.oauthSecretKey) {
425
+ const accessToken = await getMcpOAuthAccessToken({
426
+ key: stored.oauthSecretKey,
427
+ scope,
428
+ scopeId,
429
+ serverUrl: stored.url,
430
+ });
431
+ if (accessToken) {
432
+ headers ??= {};
433
+ headers.Authorization = `Bearer ${accessToken}`;
434
+ }
435
+ }
488
436
  return {
489
437
  type: "http",
490
438
  url: stored.url,
491
- headers: await materializeHeaders(scope, scopeId, stored),
439
+ headers,
492
440
  ...(stored.firstParty ? { firstParty: true } : {}),
493
441
  ...(stored.firstPartyAppId
494
442
  ? { firstPartyAppId: stored.firstPartyAppId }