@fractaal/pi-mcp 0.86.9 → 1.6.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 (91) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +60 -5
  3. package/dist/auth-provider.d.ts +4 -0
  4. package/dist/auth-provider.d.ts.map +1 -1
  5. package/dist/auth-provider.js.map +1 -1
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +9 -5
  8. package/dist/client.js.map +1 -1
  9. package/dist/config.d.ts +117 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +153 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/index.d.ts +6 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +5 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/input-schema.d.ts +7 -0
  18. package/dist/input-schema.d.ts.map +1 -0
  19. package/dist/input-schema.js +13 -0
  20. package/dist/input-schema.js.map +1 -0
  21. package/dist/naming.d.ts +18 -0
  22. package/dist/naming.d.ts.map +1 -0
  23. package/dist/naming.js +29 -0
  24. package/dist/naming.js.map +1 -0
  25. package/dist/oauth/auth-provider.d.ts +52 -0
  26. package/dist/oauth/auth-provider.d.ts.map +1 -0
  27. package/dist/oauth/auth-provider.js +97 -0
  28. package/dist/oauth/auth-provider.js.map +1 -0
  29. package/dist/oauth/callback.d.ts.map +1 -1
  30. package/dist/oauth/callback.js +5 -1
  31. package/dist/oauth/callback.js.map +1 -1
  32. package/dist/oauth/client-settings.d.ts +107 -0
  33. package/dist/oauth/client-settings.d.ts.map +1 -0
  34. package/dist/oauth/client-settings.js +114 -0
  35. package/dist/oauth/client-settings.js.map +1 -0
  36. package/dist/oauth/discovery.d.ts +3 -0
  37. package/dist/oauth/discovery.d.ts.map +1 -1
  38. package/dist/oauth/discovery.js +9 -6
  39. package/dist/oauth/discovery.js.map +1 -1
  40. package/dist/oauth/errors.d.ts +10 -2
  41. package/dist/oauth/errors.d.ts.map +1 -1
  42. package/dist/oauth/errors.js +13 -1
  43. package/dist/oauth/errors.js.map +1 -1
  44. package/dist/oauth/flow.d.ts +14 -0
  45. package/dist/oauth/flow.d.ts.map +1 -1
  46. package/dist/oauth/flow.js +52 -15
  47. package/dist/oauth/flow.js.map +1 -1
  48. package/dist/oauth/index.d.ts +5 -1
  49. package/dist/oauth/index.d.ts.map +1 -1
  50. package/dist/oauth/index.js +4 -1
  51. package/dist/oauth/index.js.map +1 -1
  52. package/dist/oauth/loopback.d.ts +51 -0
  53. package/dist/oauth/loopback.d.ts.map +1 -0
  54. package/dist/oauth/loopback.js +98 -0
  55. package/dist/oauth/loopback.js.map +1 -0
  56. package/dist/oauth/sign-in.d.ts +73 -0
  57. package/dist/oauth/sign-in.d.ts.map +1 -0
  58. package/dist/oauth/sign-in.js +182 -0
  59. package/dist/oauth/sign-in.js.map +1 -0
  60. package/dist/oauth/types.d.ts +1 -0
  61. package/dist/oauth/types.d.ts.map +1 -1
  62. package/dist/oauth/types.js +9 -5
  63. package/dist/oauth/types.js.map +1 -1
  64. package/dist/output-limit.d.ts +24 -0
  65. package/dist/output-limit.d.ts.map +1 -0
  66. package/dist/output-limit.js +58 -0
  67. package/dist/output-limit.js.map +1 -0
  68. package/dist/protocol/content.d.ts +2 -1
  69. package/dist/protocol/content.d.ts.map +1 -1
  70. package/dist/protocol/content.js.map +1 -1
  71. package/dist/transports/lines.d.ts +34 -0
  72. package/dist/transports/lines.d.ts.map +1 -0
  73. package/dist/transports/lines.js +85 -0
  74. package/dist/transports/lines.js.map +1 -0
  75. package/dist/transports/pipe.d.ts +49 -0
  76. package/dist/transports/pipe.d.ts.map +1 -0
  77. package/dist/transports/pipe.js +96 -0
  78. package/dist/transports/pipe.js.map +1 -0
  79. package/dist/transports/stdio.d.ts +1 -4
  80. package/dist/transports/stdio.d.ts.map +1 -1
  81. package/dist/transports/stdio.js +20 -51
  82. package/dist/transports/stdio.js.map +1 -1
  83. package/dist/transports/streamable-http.d.ts +9 -2
  84. package/dist/transports/streamable-http.d.ts.map +1 -1
  85. package/dist/transports/streamable-http.js +105 -30
  86. package/dist/transports/streamable-http.js.map +1 -1
  87. package/dist/transports/transport.d.ts +9 -1
  88. package/dist/transports/transport.d.ts.map +1 -1
  89. package/dist/transports/transport.js.map +1 -1
  90. package/package.json +4 -5
  91. /package/{LICENSES → THIRD_PARTY_NOTICES}/modelcontextprotocol-typescript-sdk.txt +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/oauth/callback.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type Server, type ServerResponse } from \"node:http\";\n\nexport interface OAuthCallback {\n\tcode: string;\n\tstate: string;\n\tiss?: string;\n}\n\n/** Outcome shown on the browser page after the redirect. */\nexport type OAuthCallbackPage = { ok: true } | { ok: false; message: string; details?: string };\n\nexport interface OAuthCallbackServerOptions {\n\t/** Address to listen on. Default: `127.0.0.1`. */\n\thost?: string;\n\t/**\n\t * Host name in `redirectUrl`, for example `localhost` for a client registered with it while\n\t * listening on `127.0.0.1`. Default: `host`.\n\t */\n\tredirectHost?: string;\n\tport?: number;\n\tpath?: string;\n\t/** More paths that receive the callback, for example a server-specific path of a redirect URI. */\n\textraPaths?: string[];\n\ttimeoutMs?: number;\n\t/** Render the browser page as HTML. Default: a plain-text message. */\n\trenderPage?: (page: OAuthCallbackPage) => string;\n}\n\nfunction plainText(page: OAuthCallbackPage): string {\n\tif (page.ok) return \"Authorization complete. You may close this window.\";\n\treturn page.details ? `${page.message}\\n\\n${page.details}` : page.message;\n}\n\nexport class OAuthCallbackServer {\n\treadonly redirectUrl: string;\n\tprivate server: Server;\n\tprivate paths: string[];\n\tprivate timeoutMs: number;\n\tprivate renderPage: ((page: OAuthCallbackPage) => string) | undefined;\n\tprivate pending = new Map<\n\t\tstring,\n\t\t{\n\t\t\tresolve: (callback: OAuthCallback) => void;\n\t\t\treject: (error: Error) => void;\n\t\t\ttimer: ReturnType<typeof setTimeout>;\n\t\t\tpath: string | undefined;\n\t\t}\n\t>();\n\n\tprivate constructor(\n\t\tserver: Server,\n\t\tredirectUrl: string,\n\t\tpaths: string[],\n\t\ttimeoutMs: number,\n\t\trenderPage: ((page: OAuthCallbackPage) => string) | undefined,\n\t) {\n\t\tthis.server = server;\n\t\tthis.redirectUrl = redirectUrl;\n\t\tthis.paths = paths;\n\t\tthis.timeoutMs = timeoutMs;\n\t\tthis.renderPage = renderPage;\n\t}\n\n\tstatic async listen(options: OAuthCallbackServerOptions = {}): Promise<OAuthCallbackServer> {\n\t\tconst host = options.host ?? \"127.0.0.1\";\n\t\tconst redirectHost = options.redirectHost ?? host;\n\t\tconst path = options.path ?? \"/callback\";\n\t\tlet instance: OAuthCallbackServer | undefined;\n\t\tconst server = createServer((request, response) => instance?.handle(request.url ?? \"/\", response));\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tserver.once(\"error\", reject);\n\t\t\tserver.listen(options.port ?? 0, host, () => {\n\t\t\t\tserver.off(\"error\", reject);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t\tconst address = server.address();\n\t\tif (!address || typeof address === \"string\") throw new Error(\"OAuth callback server did not bind to TCP\");\n\t\tinstance = new OAuthCallbackServer(\n\t\t\tserver,\n\t\t\t`http://${redirectHost.includes(\":\") ? `[${redirectHost}]` : redirectHost}:${address.port}${path}`,\n\t\t\t[path, ...(options.extraPaths ?? [])],\n\t\t\toptions.timeoutMs ?? 5 * 60_000,\n\t\t\toptions.renderPage,\n\t\t);\n\t\treturn instance;\n\t}\n\n\t/**\n\t * Wait for the authorization response with `state`. With `path`, a response on another path fails, so\n\t * a server-specific redirect URI can tell authorization servers apart (RFC 9700 section 4.4.2.2).\n\t */\n\twaitForCallback(state: string, path?: string): Promise<OAuthCallback> {\n\t\tif (this.pending.has(state)) throw new Error(\"OAuth state is already pending\");\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(state);\n\t\t\t\treject(new Error(\"OAuth callback timed out\"));\n\t\t\t}, this.timeoutMs);\n\t\t\tthis.pending.set(state, { resolve, reject, timer, path });\n\t\t});\n\t}\n\n\tasync close(): Promise<void> {\n\t\tfor (const pending of this.pending.values()) {\n\t\t\tclearTimeout(pending.timer);\n\t\t\tpending.reject(new Error(\"OAuth callback server closed\"));\n\t\t}\n\t\tthis.pending.clear();\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tthis.server.close((error) => (error ? reject(error) : resolve()));\n\t\t});\n\t}\n\n\tprivate reply(response: ServerResponse, status: number, page: OAuthCallbackPage): void {\n\t\tif (this.renderPage) {\n\t\t\tresponse.writeHead(status, { \"content-type\": \"text/html; charset=utf-8\", \"cache-control\": \"no-store\" });\n\t\t\tresponse.end(this.renderPage(page));\n\t\t} else {\n\t\t\tresponse.writeHead(status, { \"content-type\": \"text/plain; charset=utf-8\" }).end(plainText(page));\n\t\t}\n\t}\n\n\tprivate handle(rawUrl: string, response: ServerResponse): void {\n\t\tconst url = new URL(rawUrl, this.redirectUrl);\n\t\tif (!this.paths.includes(url.pathname)) {\n\t\t\tthis.reply(response, 404, { ok: false, message: \"Not found\" });\n\t\t\treturn;\n\t\t}\n\t\tconst state = url.searchParams.get(\"state\");\n\t\tconst pending = state ? this.pending.get(state) : undefined;\n\t\tif (!state || !pending) {\n\t\t\tthis.reply(response, 400, { ok: false, message: \"Invalid or expired OAuth state\" });\n\t\t\treturn;\n\t\t}\n\t\tclearTimeout(pending.timer);\n\t\tthis.pending.delete(state);\n\t\tif (pending.path !== undefined && url.pathname !== pending.path) {\n\t\t\tpending.reject(new Error(\"The authorization response arrived on another redirect URI\"));\n\t\t\tthis.reply(response, 400, { ok: false, message: \"Unexpected redirect URI\" });\n\t\t\treturn;\n\t\t}\n\t\tconst error = url.searchParams.get(\"error\");\n\t\tif (error) {\n\t\t\tconst description = url.searchParams.get(\"error_description\") ?? error;\n\t\t\tpending.reject(new Error(description));\n\t\t\tthis.reply(response, 200, {\n\t\t\t\tok: false,\n\t\t\t\tmessage: \"Authorization failed. You may close this window.\",\n\t\t\t\tdetails: description,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tconst code = url.searchParams.get(\"code\");\n\t\tif (!code) {\n\t\t\tpending.reject(new Error(\"OAuth callback did not include an authorization code\"));\n\t\t\tthis.reply(response, 400, { ok: false, message: \"Missing authorization code\" });\n\t\t\treturn;\n\t\t}\n\t\tconst iss = url.searchParams.get(\"iss\");\n\t\tpending.resolve({ code, state, ...(iss ? { iss } : {}) });\n\t\tthis.reply(response, 200, { ok: true });\n\t}\n}\n"]}
1
+ {"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/oauth/callback.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type Server, type ServerResponse } from \"node:http\";\n\nexport interface OAuthCallback {\n\tcode: string;\n\tstate: string;\n\tiss?: string;\n}\n\n/** Outcome shown on the browser page after the redirect. */\nexport type OAuthCallbackPage = { ok: true } | { ok: false; message: string; details?: string };\n\nexport interface OAuthCallbackServerOptions {\n\t/** Address to listen on. Default: `127.0.0.1`. */\n\thost?: string;\n\t/**\n\t * Host name in `redirectUrl`, for example `localhost` for a client registered with it while\n\t * listening on `127.0.0.1`. Default: `host`.\n\t */\n\tredirectHost?: string;\n\tport?: number;\n\tpath?: string;\n\t/** More paths that receive the callback, for example a server-specific path of a redirect URI. */\n\textraPaths?: string[];\n\ttimeoutMs?: number;\n\t/** Render the browser page as HTML. Default: a plain-text message. */\n\trenderPage?: (page: OAuthCallbackPage) => string;\n}\n\nfunction plainText(page: OAuthCallbackPage): string {\n\tif (page.ok) return \"Authorization complete. You may close this window.\";\n\treturn page.details ? `${page.message}\\n\\n${page.details}` : page.message;\n}\n\nexport class OAuthCallbackServer {\n\treadonly redirectUrl: string;\n\tprivate server: Server;\n\tprivate paths: string[];\n\tprivate timeoutMs: number;\n\tprivate renderPage: ((page: OAuthCallbackPage) => string) | undefined;\n\tprivate pending = new Map<\n\t\tstring,\n\t\t{\n\t\t\tresolve: (callback: OAuthCallback) => void;\n\t\t\treject: (error: Error) => void;\n\t\t\ttimer: ReturnType<typeof setTimeout>;\n\t\t\tpath: string | undefined;\n\t\t}\n\t>();\n\n\tprivate constructor(\n\t\tserver: Server,\n\t\tredirectUrl: string,\n\t\tpaths: string[],\n\t\ttimeoutMs: number,\n\t\trenderPage: ((page: OAuthCallbackPage) => string) | undefined,\n\t) {\n\t\tthis.server = server;\n\t\tthis.redirectUrl = redirectUrl;\n\t\tthis.paths = paths;\n\t\tthis.timeoutMs = timeoutMs;\n\t\tthis.renderPage = renderPage;\n\t}\n\n\tstatic async listen(options: OAuthCallbackServerOptions = {}): Promise<OAuthCallbackServer> {\n\t\tconst host = options.host ?? \"127.0.0.1\";\n\t\tconst redirectHost = options.redirectHost ?? host;\n\t\tconst path = options.path ?? \"/callback\";\n\t\tlet instance: OAuthCallbackServer | undefined;\n\t\tconst server = createServer((request, response) => instance?.handle(request.url ?? \"/\", response));\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tserver.once(\"error\", reject);\n\t\t\tserver.listen(options.port ?? 0, host, () => {\n\t\t\t\tserver.off(\"error\", reject);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t\tconst address = server.address();\n\t\tif (!address || typeof address === \"string\") throw new Error(\"OAuth callback server did not bind to TCP\");\n\t\tinstance = new OAuthCallbackServer(\n\t\t\tserver,\n\t\t\t`http://${redirectHost.includes(\":\") ? `[${redirectHost}]` : redirectHost}:${address.port}${path}`,\n\t\t\t[path, ...(options.extraPaths ?? [])],\n\t\t\toptions.timeoutMs ?? 5 * 60_000,\n\t\t\toptions.renderPage,\n\t\t);\n\t\treturn instance;\n\t}\n\n\t/**\n\t * Wait for the authorization response with `state`. With `path`, a response on another path fails, so\n\t * a server-specific redirect URI can tell authorization servers apart (RFC 9700 section 4.4.2.2).\n\t */\n\twaitForCallback(state: string, path?: string): Promise<OAuthCallback> {\n\t\tif (this.pending.has(state)) throw new Error(\"OAuth state is already pending\");\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(state);\n\t\t\t\treject(new Error(\"OAuth callback timed out\"));\n\t\t\t}, this.timeoutMs);\n\t\t\tthis.pending.set(state, { resolve, reject, timer, path });\n\t\t});\n\t}\n\n\tasync close(): Promise<void> {\n\t\tfor (const pending of this.pending.values()) {\n\t\t\tclearTimeout(pending.timer);\n\t\t\tpending.reject(new Error(\"OAuth callback server closed\"));\n\t\t}\n\t\tthis.pending.clear();\n\t\tconst closed = new Promise<void>((resolve, reject) => {\n\t\t\tthis.server.close((error) => (error ? reject(error) : resolve()));\n\t\t});\n\t\t// `close()` waits for every connection, and a browser may keep an unused one open (a preconnect or a\n\t\t// spare keep-alive socket) for minutes. Responses have been sent by now, so drop them.\n\t\tthis.server.closeAllConnections();\n\t\tawait closed;\n\t}\n\n\tprivate reply(response: ServerResponse, status: number, page: OAuthCallbackPage): void {\n\t\tif (this.renderPage) {\n\t\t\tresponse.writeHead(status, { \"content-type\": \"text/html; charset=utf-8\", \"cache-control\": \"no-store\" });\n\t\t\tresponse.end(this.renderPage(page));\n\t\t} else {\n\t\t\tresponse.writeHead(status, { \"content-type\": \"text/plain; charset=utf-8\" }).end(plainText(page));\n\t\t}\n\t}\n\n\tprivate handle(rawUrl: string, response: ServerResponse): void {\n\t\tconst url = new URL(rawUrl, this.redirectUrl);\n\t\tif (!this.paths.includes(url.pathname)) {\n\t\t\tthis.reply(response, 404, { ok: false, message: \"Not found\" });\n\t\t\treturn;\n\t\t}\n\t\tconst state = url.searchParams.get(\"state\");\n\t\tconst pending = state ? this.pending.get(state) : undefined;\n\t\tif (!state || !pending) {\n\t\t\tthis.reply(response, 400, { ok: false, message: \"Invalid or expired OAuth state\" });\n\t\t\treturn;\n\t\t}\n\t\tclearTimeout(pending.timer);\n\t\tthis.pending.delete(state);\n\t\tif (pending.path !== undefined && url.pathname !== pending.path) {\n\t\t\tpending.reject(new Error(\"The authorization response arrived on another redirect URI\"));\n\t\t\tthis.reply(response, 400, { ok: false, message: \"Unexpected redirect URI\" });\n\t\t\treturn;\n\t\t}\n\t\tconst error = url.searchParams.get(\"error\");\n\t\tif (error) {\n\t\t\tconst description = url.searchParams.get(\"error_description\") ?? error;\n\t\t\tpending.reject(new Error(description));\n\t\t\tthis.reply(response, 200, {\n\t\t\t\tok: false,\n\t\t\t\tmessage: \"Authorization failed. You may close this window.\",\n\t\t\t\tdetails: description,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tconst code = url.searchParams.get(\"code\");\n\t\tif (!code) {\n\t\t\tpending.reject(new Error(\"OAuth callback did not include an authorization code\"));\n\t\t\tthis.reply(response, 400, { ok: false, message: \"Missing authorization code\" });\n\t\t\treturn;\n\t\t}\n\t\tconst iss = url.searchParams.get(\"iss\");\n\t\tpending.resolve({ code, state, ...(iss ? { iss } : {}) });\n\t\tthis.reply(response, 200, { ok: true });\n\t}\n}\n"]}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The OAuth client that sign-in and refreshes use for one MCP server: its settings, how it identifies
3
+ * itself (a pre-registered client, a Client ID Metadata Document, or dynamic client registration), and
4
+ * the `McpOAuthProvider` built from them.
5
+ */
6
+ import type { McpFetch } from "../auth-provider.ts";
7
+ import { McpOAuthProvider, type McpOAuthStateStore } from "./provider.ts";
8
+ import type { OAuthClientInformationMixed } from "./types.ts";
9
+ /** Client settings of one MCP server, usually from the application's configuration. */
10
+ export interface McpOAuthSettings {
11
+ /** Pre-registered client id. Without it, sign-in uses a Client ID Metadata Document or registers a client. */
12
+ clientId?: string;
13
+ /** Already resolved. */
14
+ clientSecret?: string;
15
+ /**
16
+ * Port of the loopback callback server, for clients registered with a fixed redirect URI. Without
17
+ * `callbackUrl`, the redirect URI is `http://127.0.0.1:<port>/callback`. Only `listenForLoopbackSignIn`
18
+ * and the redirect URI of refreshes use it; `startMcpSignIn` and `completeMcpSignIn` take theirs from
19
+ * the caller.
20
+ */
21
+ callbackPort?: number;
22
+ /**
23
+ * Loopback redirect URI registered for `clientId`, for example `http://localhost:8080/oauth/callback`.
24
+ * Without a port, the callback server listens on `callbackPort` or a free port, which is added to the
25
+ * URI (RFC 8252). Default: `http://127.0.0.1/callback`. Like `callbackPort`, only `listenForLoopbackSignIn`
26
+ * and refreshes use it.
27
+ */
28
+ callbackUrl?: string;
29
+ /** Scopes to request, separated by spaces. Default: the scopes the server advertises. */
30
+ scope?: string;
31
+ /** `client_name` for dynamic client registration. Default: the application's `clientName`. */
32
+ clientName?: string;
33
+ /**
34
+ * How to identify without `clientId`. `auto` (default): a Client ID Metadata Document when the
35
+ * authorization server supports them for public clients and the documents list the redirect URI,
36
+ * otherwise dynamic client registration; with `clientName`, always dynamic client registration.
37
+ * `cimd`: always the document, and sign-in fails without support. `dcr`: always dynamic client
38
+ * registration.
39
+ */
40
+ clientRegistration?: "auto" | "dcr" | "cimd";
41
+ /**
42
+ * Authorization server metadata document to use instead of discovery through the server, for servers
43
+ * that advertise a wrong authorization server or none. It is trusted as configured.
44
+ */
45
+ authServerMetadataUrl?: URL;
46
+ }
47
+ /**
48
+ * The Client ID Metadata Documents an application publishes: `<baseUrl>/client.json`, and for each MCP
49
+ * server `<baseUrl>/<callback ID>/client.json`, whose redirect URIs carry the path `<path>/<callback ID>`.
50
+ */
51
+ export interface McpClientMetadataDocuments {
52
+ /** For example `https://pi.dev/oauth`. */
53
+ baseUrl: string;
54
+ /** Whether the documents list `redirectUrl`. Loopback redirect URIs match on any port (RFC 8252). */
55
+ listsRedirectUrl(redirectUrl: string): boolean;
56
+ }
57
+ /** State store of one MCP server, with a lock that keeps other processes from refreshing at the same time. */
58
+ export interface McpOAuthServerStore extends McpOAuthStateStore {
59
+ /**
60
+ * Run `fn` while no other process refreshes the server's tokens. Not reentrant: `startMcpSignIn` and
61
+ * `createMcpAuthProvider` take it themselves, so never call them from `fn`. Throw
62
+ * `McpRefreshLockBusyError` when another process holds the lock for the whole wait: `startMcpSignIn`
63
+ * then goes to the browser step. Any other error fails the sign-in or refresh.
64
+ */
65
+ withRefreshLock<T>(fn: () => Promise<T>): Promise<T>;
66
+ }
67
+ /** The OAuth client of one MCP server: where its state lives, its settings, and the application's identity. */
68
+ export interface McpOAuthClientOptions {
69
+ serverUrl: string;
70
+ store: McpOAuthServerStore;
71
+ settings: McpOAuthSettings;
72
+ /** `client_name` for dynamic client registration unless `settings.clientName` names another: the application. */
73
+ clientName: string;
74
+ /** Omitted: dynamic client registration only. */
75
+ clientMetadataDocuments?: McpClientMetadataDocuments;
76
+ }
77
+ /** An `McpOAuthProvider` for the client, redirecting to `redirectUrl` or the document's variant of it. */
78
+ export declare function createOAuthClientProvider(options: McpOAuthClientOptions, redirectUrl: string, onRedirect?: (url: URL) => void): McpOAuthProvider;
79
+ /**
80
+ * Whether the client may identify with a Client ID Metadata Document instead of registering. `auto`
81
+ * registers when the user asked for a client name or the documents do not list the redirect URI.
82
+ */
83
+ export declare function offersClientMetadataDocument(settings: McpOAuthSettings, documents: McpClientMetadataDocuments | undefined, redirectUrl: string): boolean;
84
+ /** The path of the redirect URI of a server-specific Client ID Metadata Document: `<path>/<callback ID>`. */
85
+ export declare function serverSpecificPath(path: string, serverUrl: string): string;
86
+ /** Redirect URIs a stored client was registered with. */
87
+ export declare function registeredRedirectUrls(client: OAuthClientInformationMixed | undefined): string[];
88
+ /** `fetch` with `OAUTH_REQUEST_TIMEOUT_MS` per request, on top of the request's own signal. */
89
+ export declare function timedFetch(fetch?: McpFetch): McpFetch;
90
+ /** Redirect URI when the settings name none and no client is stored. */
91
+ export declare const DEFAULT_REDIRECT_URL = "http://127.0.0.1/callback";
92
+ /** Where the loopback callback server listens and the redirect URI it serves, from `callbackUrl` and `callbackPort`. */
93
+ export interface LoopbackSettings {
94
+ /** The redirect URI, without the port unless one is configured. */
95
+ url: string;
96
+ /** Address to listen on. */
97
+ host: string;
98
+ /** Host name in the redirect URI. */
99
+ redirectHost: string;
100
+ port: number | undefined;
101
+ path: string;
102
+ /** The exact redirect URI, when the port is fixed. */
103
+ fixedRedirectUrl: string | undefined;
104
+ }
105
+ /** The loopback callback settings of `settings`. */
106
+ export declare function loopbackSettings(settings: McpOAuthSettings): LoopbackSettings;
107
+ //# sourceMappingURL=client-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-settings.d.ts","sourceRoot":"","sources":["../../src/oauth/client-settings.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,KAAK,EAA+B,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAE3F,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAChC,8GAA8G;IAC9G,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7C;;;OAGG;IACH,qBAAqB,CAAC,EAAE,GAAG,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IAC1C,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,qGAAqG;IACrG,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/C;AAED,8GAA8G;AAC9G,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC9D;;;;;OAKG;IACH,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACrD;AAED,+GAA+G;AAC/G,MAAM,WAAW,qBAAqB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,iHAAiH;IACjH,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,uBAAuB,CAAC,EAAE,0BAA0B,CAAC;CACrD;AAED,0GAA0G;AAC1G,wBAAgB,yBAAyB,CACxC,OAAO,EAAE,qBAAqB,EAC9B,WAAW,EAAE,MAAM,EACnB,UAAU,GAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAe,GACvC,gBAAgB,CA2BlB;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC3C,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,0BAA0B,GAAG,SAAS,EACjD,WAAW,EAAE,MAAM,GACjB,OAAO,CAIT;AA0BD,6GAA6G;AAC7G,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1E;AASD,yDAAyD;AACzD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,2BAA2B,GAAG,SAAS,GAAG,MAAM,EAAE,CAEhG;AAQD,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,KAAK,GAAE,QAA2B,GAAG,QAAQ,CAKvE;AAKD,wEAAwE;AACxE,eAAO,MAAM,oBAAoB,8BAA4C,CAAC;AAE9E,wHAAwH;AACxH,MAAM,WAAW,gBAAgB;IAChC,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,CAqB7E"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The OAuth client that sign-in and refreshes use for one MCP server: its settings, how it identifies
3
+ * itself (a pre-registered client, a Client ID Metadata Document, or dynamic client registration), and
4
+ * the `McpOAuthProvider` built from them.
5
+ */
6
+ import { createHash } from "node:crypto";
7
+ import { McpOAuthProvider } from "./provider.js";
8
+ /** An `McpOAuthProvider` for the client, redirecting to `redirectUrl` or the document's variant of it. */
9
+ export function createOAuthClientProvider(options, redirectUrl, onRedirect = () => { }) {
10
+ const { serverUrl, settings, clientMetadataDocuments: documents } = options;
11
+ return new McpOAuthProvider({
12
+ serverUrl,
13
+ redirectUrl,
14
+ clientMetadata: { client_name: settings.clientName ?? options.clientName },
15
+ clientMetadataDocument: offersClientMetadataDocument(settings, documents, redirectUrl)
16
+ ? (metadata) => {
17
+ if (!documents?.listsRedirectUrl(redirectUrl)) {
18
+ throw new Error(`oauth.clientRegistration "cimd" needs Client ID Metadata Documents that list the redirect URI ${redirectUrl}`);
19
+ }
20
+ const document = clientMetadataDocument(serverUrl, redirectUrl, metadata, documents.baseUrl);
21
+ if (!document && settings.clientRegistration === "cimd") {
22
+ throw new Error('The authorization server does not support Client ID Metadata Documents for public clients; remove oauth.clientRegistration "cimd"');
23
+ }
24
+ return document;
25
+ }
26
+ : undefined,
27
+ clientId: settings.clientId,
28
+ clientSecret: settings.clientSecret,
29
+ store: options.store,
30
+ onRedirect,
31
+ });
32
+ }
33
+ /**
34
+ * Whether the client may identify with a Client ID Metadata Document instead of registering. `auto`
35
+ * registers when the user asked for a client name or the documents do not list the redirect URI.
36
+ */
37
+ export function offersClientMetadataDocument(settings, documents, redirectUrl) {
38
+ const registration = settings.clientRegistration ?? "auto";
39
+ if (registration !== "auto")
40
+ return registration === "cimd";
41
+ return !settings.clientId && !settings.clientName && documents?.listsRedirectUrl(redirectUrl) === true;
42
+ }
43
+ /**
44
+ * The Client ID Metadata Document to use, chosen like Codex chooses its own, or `undefined` when the
45
+ * authorization server does not support documents for public clients. Without the `iss` parameter in
46
+ * authorization responses (RFC 9207), the redirect URI and the document are specific to the MCP server,
47
+ * so a response cannot be mixed up with one from another authorization server (RFC 9700 section 4.4.2.2).
48
+ */
49
+ function clientMetadataDocument(serverUrl, redirectUrl, metadata, baseUrl) {
50
+ if (!metadata?.client_id_metadata_document_supported ||
51
+ !metadata.token_endpoint_auth_methods_supported?.includes("none")) {
52
+ return undefined;
53
+ }
54
+ if (metadata.authorization_response_iss_parameter_supported)
55
+ return { url: `${baseUrl}/client.json`, redirectUrl };
56
+ const redirect = new URL(redirectUrl);
57
+ redirect.pathname = serverSpecificPath(redirect.pathname, serverUrl);
58
+ return { url: `${baseUrl}/${callbackId(serverUrl)}/client.json`, redirectUrl: redirect.href };
59
+ }
60
+ /** The path of the redirect URI of a server-specific Client ID Metadata Document: `<path>/<callback ID>`. */
61
+ export function serverSpecificPath(path, serverUrl) {
62
+ return `${path.replace(/\/$/, "")}/${callbackId(serverUrl)}`;
63
+ }
64
+ /** 12 characters identifying an MCP server URL in callback paths, computed like Codex does. */
65
+ function callbackId(serverUrl) {
66
+ const url = new URL(serverUrl);
67
+ url.hash = "";
68
+ return createHash("sha256").update(url.href).digest().subarray(0, 9).toString("base64url");
69
+ }
70
+ /** Redirect URIs a stored client was registered with. */
71
+ export function registeredRedirectUrls(client) {
72
+ return client && "redirect_uris" in client ? client.redirect_uris : [];
73
+ }
74
+ /**
75
+ * Bounds each request to the authorization server, so an unresponsive one cannot hold the refresh lock,
76
+ * delay shutdown, or keep a sign-in waiting for long.
77
+ */
78
+ const OAUTH_REQUEST_TIMEOUT_MS = 15_000;
79
+ /** `fetch` with `OAUTH_REQUEST_TIMEOUT_MS` per request, on top of the request's own signal. */
80
+ export function timedFetch(fetch = globalThis.fetch) {
81
+ return (input, init) => {
82
+ const timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
83
+ return fetch(input, { ...init, signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout });
84
+ };
85
+ }
86
+ const CALLBACK_HOST = "127.0.0.1";
87
+ const CALLBACK_PATH = "/callback";
88
+ /** Redirect URI when the settings name none and no client is stored. */
89
+ export const DEFAULT_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
90
+ /** The loopback callback settings of `settings`. */
91
+ export function loopbackSettings(settings) {
92
+ const configured = settings.callbackUrl ?? DEFAULT_REDIRECT_URL;
93
+ const url = new URL(configured);
94
+ const address = url.hostname.replace(/^\[|\]$/g, "");
95
+ const port = url.port ? Number(url.port) : settings.callbackPort;
96
+ let fixedRedirectUrl;
97
+ // A configured URI with a port is sent exactly as written, since servers compare it as a string.
98
+ if (url.port)
99
+ fixedRedirectUrl = settings.callbackUrl;
100
+ else if (port !== undefined) {
101
+ url.port = String(port);
102
+ fixedRedirectUrl = url.href;
103
+ }
104
+ return {
105
+ url: configured,
106
+ // `localhost` is served on 127.0.0.1; browsers fall back to it when ::1 refuses.
107
+ host: address === "localhost" ? CALLBACK_HOST : address,
108
+ redirectHost: address,
109
+ port,
110
+ path: url.pathname,
111
+ fixedRedirectUrl,
112
+ };
113
+ }
114
+ //# sourceMappingURL=client-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-settings.js","sourceRoot":"","sources":["../../src/oauth/client-settings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The OAuth client that sign-in and refreshes use for one MCP server: its settings, how it identifies\n * itself (a pre-registered client, a Client ID Metadata Document, or dynamic client registration), and\n * the `McpOAuthProvider` built from them.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { McpFetch } from \"../auth-provider.ts\";\nimport type { OAuthClientMetadataDocument } from \"./flow.ts\";\nimport { McpOAuthProvider, type McpOAuthStateStore } from \"./provider.ts\";\nimport type { AuthorizationServerMetadata, OAuthClientInformationMixed } from \"./types.ts\";\n\n/** Client settings of one MCP server, usually from the application's configuration. */\nexport interface McpOAuthSettings {\n\t/** Pre-registered client id. Without it, sign-in uses a Client ID Metadata Document or registers a client. */\n\tclientId?: string;\n\t/** Already resolved. */\n\tclientSecret?: string;\n\t/**\n\t * Port of the loopback callback server, for clients registered with a fixed redirect URI. Without\n\t * `callbackUrl`, the redirect URI is `http://127.0.0.1:<port>/callback`. Only `listenForLoopbackSignIn`\n\t * and the redirect URI of refreshes use it; `startMcpSignIn` and `completeMcpSignIn` take theirs from\n\t * the caller.\n\t */\n\tcallbackPort?: number;\n\t/**\n\t * Loopback redirect URI registered for `clientId`, for example `http://localhost:8080/oauth/callback`.\n\t * Without a port, the callback server listens on `callbackPort` or a free port, which is added to the\n\t * URI (RFC 8252). Default: `http://127.0.0.1/callback`. Like `callbackPort`, only `listenForLoopbackSignIn`\n\t * and refreshes use it.\n\t */\n\tcallbackUrl?: string;\n\t/** Scopes to request, separated by spaces. Default: the scopes the server advertises. */\n\tscope?: string;\n\t/** `client_name` for dynamic client registration. Default: the application's `clientName`. */\n\tclientName?: string;\n\t/**\n\t * How to identify without `clientId`. `auto` (default): a Client ID Metadata Document when the\n\t * authorization server supports them for public clients and the documents list the redirect URI,\n\t * otherwise dynamic client registration; with `clientName`, always dynamic client registration.\n\t * `cimd`: always the document, and sign-in fails without support. `dcr`: always dynamic client\n\t * registration.\n\t */\n\tclientRegistration?: \"auto\" | \"dcr\" | \"cimd\";\n\t/**\n\t * Authorization server metadata document to use instead of discovery through the server, for servers\n\t * that advertise a wrong authorization server or none. It is trusted as configured.\n\t */\n\tauthServerMetadataUrl?: URL;\n}\n\n/**\n * The Client ID Metadata Documents an application publishes: `<baseUrl>/client.json`, and for each MCP\n * server `<baseUrl>/<callback ID>/client.json`, whose redirect URIs carry the path `<path>/<callback ID>`.\n */\nexport interface McpClientMetadataDocuments {\n\t/** For example `https://pi.dev/oauth`. */\n\tbaseUrl: string;\n\t/** Whether the documents list `redirectUrl`. Loopback redirect URIs match on any port (RFC 8252). */\n\tlistsRedirectUrl(redirectUrl: string): boolean;\n}\n\n/** State store of one MCP server, with a lock that keeps other processes from refreshing at the same time. */\nexport interface McpOAuthServerStore extends McpOAuthStateStore {\n\t/**\n\t * Run `fn` while no other process refreshes the server's tokens. Not reentrant: `startMcpSignIn` and\n\t * `createMcpAuthProvider` take it themselves, so never call them from `fn`. Throw\n\t * `McpRefreshLockBusyError` when another process holds the lock for the whole wait: `startMcpSignIn`\n\t * then goes to the browser step. Any other error fails the sign-in or refresh.\n\t */\n\twithRefreshLock<T>(fn: () => Promise<T>): Promise<T>;\n}\n\n/** The OAuth client of one MCP server: where its state lives, its settings, and the application's identity. */\nexport interface McpOAuthClientOptions {\n\tserverUrl: string;\n\tstore: McpOAuthServerStore;\n\tsettings: McpOAuthSettings;\n\t/** `client_name` for dynamic client registration unless `settings.clientName` names another: the application. */\n\tclientName: string;\n\t/** Omitted: dynamic client registration only. */\n\tclientMetadataDocuments?: McpClientMetadataDocuments;\n}\n\n/** An `McpOAuthProvider` for the client, redirecting to `redirectUrl` or the document's variant of it. */\nexport function createOAuthClientProvider(\n\toptions: McpOAuthClientOptions,\n\tredirectUrl: string,\n\tonRedirect: (url: URL) => void = () => {},\n): McpOAuthProvider {\n\tconst { serverUrl, settings, clientMetadataDocuments: documents } = options;\n\treturn new McpOAuthProvider({\n\t\tserverUrl,\n\t\tredirectUrl,\n\t\tclientMetadata: { client_name: settings.clientName ?? options.clientName },\n\t\tclientMetadataDocument: offersClientMetadataDocument(settings, documents, redirectUrl)\n\t\t\t? (metadata) => {\n\t\t\t\t\tif (!documents?.listsRedirectUrl(redirectUrl)) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`oauth.clientRegistration \"cimd\" needs Client ID Metadata Documents that list the redirect URI ${redirectUrl}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tconst document = clientMetadataDocument(serverUrl, redirectUrl, metadata, documents.baseUrl);\n\t\t\t\t\tif (!document && settings.clientRegistration === \"cimd\") {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'The authorization server does not support Client ID Metadata Documents for public clients; remove oauth.clientRegistration \"cimd\"',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn document;\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tclientId: settings.clientId,\n\t\tclientSecret: settings.clientSecret,\n\t\tstore: options.store,\n\t\tonRedirect,\n\t});\n}\n\n/**\n * Whether the client may identify with a Client ID Metadata Document instead of registering. `auto`\n * registers when the user asked for a client name or the documents do not list the redirect URI.\n */\nexport function offersClientMetadataDocument(\n\tsettings: McpOAuthSettings,\n\tdocuments: McpClientMetadataDocuments | undefined,\n\tredirectUrl: string,\n): boolean {\n\tconst registration = settings.clientRegistration ?? \"auto\";\n\tif (registration !== \"auto\") return registration === \"cimd\";\n\treturn !settings.clientId && !settings.clientName && documents?.listsRedirectUrl(redirectUrl) === true;\n}\n\n/**\n * The Client ID Metadata Document to use, chosen like Codex chooses its own, or `undefined` when the\n * authorization server does not support documents for public clients. Without the `iss` parameter in\n * authorization responses (RFC 9207), the redirect URI and the document are specific to the MCP server,\n * so a response cannot be mixed up with one from another authorization server (RFC 9700 section 4.4.2.2).\n */\nfunction clientMetadataDocument(\n\tserverUrl: string,\n\tredirectUrl: string,\n\tmetadata: AuthorizationServerMetadata | undefined,\n\tbaseUrl: string,\n): OAuthClientMetadataDocument | undefined {\n\tif (\n\t\t!metadata?.client_id_metadata_document_supported ||\n\t\t!metadata.token_endpoint_auth_methods_supported?.includes(\"none\")\n\t) {\n\t\treturn undefined;\n\t}\n\tif (metadata.authorization_response_iss_parameter_supported) return { url: `${baseUrl}/client.json`, redirectUrl };\n\tconst redirect = new URL(redirectUrl);\n\tredirect.pathname = serverSpecificPath(redirect.pathname, serverUrl);\n\treturn { url: `${baseUrl}/${callbackId(serverUrl)}/client.json`, redirectUrl: redirect.href };\n}\n\n/** The path of the redirect URI of a server-specific Client ID Metadata Document: `<path>/<callback ID>`. */\nexport function serverSpecificPath(path: string, serverUrl: string): string {\n\treturn `${path.replace(/\\/$/, \"\")}/${callbackId(serverUrl)}`;\n}\n\n/** 12 characters identifying an MCP server URL in callback paths, computed like Codex does. */\nfunction callbackId(serverUrl: string): string {\n\tconst url = new URL(serverUrl);\n\turl.hash = \"\";\n\treturn createHash(\"sha256\").update(url.href).digest().subarray(0, 9).toString(\"base64url\");\n}\n\n/** Redirect URIs a stored client was registered with. */\nexport function registeredRedirectUrls(client: OAuthClientInformationMixed | undefined): string[] {\n\treturn client && \"redirect_uris\" in client ? client.redirect_uris : [];\n}\n\n/**\n * Bounds each request to the authorization server, so an unresponsive one cannot hold the refresh lock,\n * delay shutdown, or keep a sign-in waiting for long.\n */\nconst OAUTH_REQUEST_TIMEOUT_MS = 15_000;\n\n/** `fetch` with `OAUTH_REQUEST_TIMEOUT_MS` per request, on top of the request's own signal. */\nexport function timedFetch(fetch: McpFetch = globalThis.fetch): McpFetch {\n\treturn (input, init) => {\n\t\tconst timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);\n\t\treturn fetch(input, { ...init, signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout });\n\t};\n}\n\nconst CALLBACK_HOST = \"127.0.0.1\";\nconst CALLBACK_PATH = \"/callback\";\n\n/** Redirect URI when the settings name none and no client is stored. */\nexport const DEFAULT_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;\n\n/** Where the loopback callback server listens and the redirect URI it serves, from `callbackUrl` and `callbackPort`. */\nexport interface LoopbackSettings {\n\t/** The redirect URI, without the port unless one is configured. */\n\turl: string;\n\t/** Address to listen on. */\n\thost: string;\n\t/** Host name in the redirect URI. */\n\tredirectHost: string;\n\tport: number | undefined;\n\tpath: string;\n\t/** The exact redirect URI, when the port is fixed. */\n\tfixedRedirectUrl: string | undefined;\n}\n\n/** The loopback callback settings of `settings`. */\nexport function loopbackSettings(settings: McpOAuthSettings): LoopbackSettings {\n\tconst configured = settings.callbackUrl ?? DEFAULT_REDIRECT_URL;\n\tconst url = new URL(configured);\n\tconst address = url.hostname.replace(/^\\[|\\]$/g, \"\");\n\tconst port = url.port ? Number(url.port) : settings.callbackPort;\n\tlet fixedRedirectUrl: string | undefined;\n\t// A configured URI with a port is sent exactly as written, since servers compare it as a string.\n\tif (url.port) fixedRedirectUrl = settings.callbackUrl;\n\telse if (port !== undefined) {\n\t\turl.port = String(port);\n\t\tfixedRedirectUrl = url.href;\n\t}\n\treturn {\n\t\turl: configured,\n\t\t// `localhost` is served on 127.0.0.1; browsers fall back to it when ::1 refuses.\n\t\thost: address === \"localhost\" ? CALLBACK_HOST : address,\n\t\tredirectHost: address,\n\t\tport,\n\t\tpath: url.pathname,\n\t\tfixedRedirectUrl,\n\t};\n}\n"]}
@@ -5,6 +5,7 @@ export declare function discoverProtectedResourceMetadata(serverUrl: string | UR
5
5
  resourceMetadataUrl?: string | URL;
6
6
  protocolVersion?: string;
7
7
  fetch?: McpFetch;
8
+ signal?: AbortSignal;
8
9
  }): Promise<OAuthProtectedResourceMetadata>;
9
10
  export declare function buildAuthorizationServerDiscoveryUrls(authorizationServerUrl: string | URL): {
10
11
  url: URL;
@@ -14,6 +15,7 @@ export declare function discoverAuthorizationServerMetadata(authorizationServerU
14
15
  fetch?: McpFetch;
15
16
  protocolVersion?: string;
16
17
  skipIssuerValidation?: boolean;
18
+ signal?: AbortSignal;
17
19
  }): Promise<AuthorizationServerMetadata | undefined>;
18
20
  export declare function discoverOAuthServerInfo(serverUrl: string | URL, options?: {
19
21
  resourceMetadataUrl?: URL;
@@ -21,6 +23,7 @@ export declare function discoverOAuthServerInfo(serverUrl: string | URL, options
21
23
  authorizationServerMetadataUrl?: URL;
22
24
  fetch?: McpFetch;
23
25
  skipIssuerValidation?: boolean;
26
+ signal?: AbortSignal;
24
27
  }): Promise<OAuthServerInfo>;
25
28
  export declare function resourceUrlFromServerUrl(value: string | URL): URL;
26
29
  export declare function selectResource(serverUrl: string | URL, metadata?: OAuthProtectedResourceMetadata): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/oauth/discovery.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EACN,KAAK,2BAA2B,EAChC,KAAK,cAAc,EACnB,KAAK,8BAA8B,EACnC,KAAK,eAAe,EAGpB,MAAM,YAAY,CAAC;AAsBpB,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAiB1E;AAQD,wBAAsB,iCAAiC,CACtD,SAAS,EAAE,MAAM,GAAG,GAAG,EACvB,OAAO,GAAE;IAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,QAAQ,CAAA;CAAO,GAC9F,OAAO,CAAC,8BAA8B,CAAC,CAoBzC;AAED,wBAAgB,qCAAqC,CACpD,sBAAsB,EAAE,MAAM,GAAG,GAAG,GAClC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAA;CAAE,EAAE,CASxC;AAED,wBAAsB,mCAAmC,CACxD,sBAAsB,EAAE,MAAM,GAAG,GAAG,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1F,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC,CAmBlD;AAED,wBAAsB,uBAAuB,CAC5C,SAAS,EAAE,MAAM,GAAG,GAAG,EACvB,OAAO,GAAE;IACR,mBAAmB,CAAC,EAAE,GAAG,CAAC;IAC1B,gHAAgH;IAChH,8BAA8B,CAAC,EAAE,GAAG,CAAC;IACrC,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAC1B,GACJ,OAAO,CAAC,eAAe,CAAC,CA6B1B;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG,CAIjE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE,QAAQ,CAAC,EAAE,8BAA8B,GAAG,MAAM,GAAG,SAAS,CAarH"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/oauth/discovery.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EACN,KAAK,2BAA2B,EAChC,KAAK,cAAc,EACnB,KAAK,8BAA8B,EACnC,KAAK,eAAe,EAGpB,MAAM,YAAY,CAAC;AAsBpB,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAiB1E;AAcD,wBAAsB,iCAAiC,CACtD,SAAS,EAAE,MAAM,GAAG,GAAG,EACvB,OAAO,GAAE;IACR,mBAAmB,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CAChB,GACJ,OAAO,CAAC,8BAA8B,CAAC,CA0BzC;AAED,wBAAgB,qCAAqC,CACpD,sBAAsB,EAAE,MAAM,GAAG,GAAG,GAClC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAA;CAAE,EAAE,CASxC;AAED,wBAAsB,mCAAmC,CACxD,sBAAsB,EAAE,MAAM,GAAG,GAAG,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAChH,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC,CAwBlD;AAED,wBAAsB,uBAAuB,CAC5C,SAAS,EAAE,MAAM,GAAG,GAAG,EACvB,OAAO,GAAE;IACR,mBAAmB,CAAC,EAAE,GAAG,CAAC;IAC1B,gHAAgH;IAChH,8BAA8B,CAAC,EAAE,GAAG,CAAC;IACrC,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;CAChB,GACJ,OAAO,CAAC,eAAe,CAAC,CAoC1B;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG,CAIjE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE,QAAQ,CAAC,EAAE,8BAA8B,GAAG,MAAM,GAAG,SAAS,CAarH"}
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Adapted from modelcontextprotocol/typescript-sdk v1.29.0 src/client/auth.ts.
3
- * Copyright (c) 2024 Anthropic, PBC. Licensed under MIT; see LICENSES/.
3
+ * Copyright (c) 2024 Anthropic, PBC. Licensed under MIT; see THIRD_PARTY_NOTICES/.
4
4
  * Modified to remove Zod/CORS shims and enforce authorization-server issuer validation.
5
5
  */
6
6
  import { LATEST_PROTOCOL_VERSION } from "../protocol/types.js";
@@ -43,9 +43,10 @@ export function parseWwwAuthenticate(header) {
43
43
  errorDescription: field(header, "error_description"),
44
44
  };
45
45
  }
46
- async function fetchMetadata(url, fetch, protocolVersion) {
46
+ async function fetchMetadata(url, fetch, protocolVersion, signal) {
47
47
  return fetch(url, {
48
48
  headers: { Accept: "application/json", "MCP-Protocol-Version": protocolVersion },
49
+ signal,
49
50
  });
50
51
  }
51
52
  export async function discoverProtectedResourceMetadata(serverUrl, options = {}) {
@@ -54,10 +55,10 @@ export async function discoverProtectedResourceMetadata(serverUrl, options = {})
54
55
  const version = options.protocolVersion ?? LATEST_PROTOCOL_VERSION;
55
56
  let response = await fetchMetadata(options.resourceMetadataUrl
56
57
  ? new URL(options.resourceMetadataUrl)
57
- : new URL(`/.well-known/oauth-protected-resource${pathSuffix(server.pathname)}`, server.origin), fetch, version);
58
+ : new URL(`/.well-known/oauth-protected-resource${pathSuffix(server.pathname)}`, server.origin), fetch, version, options.signal);
58
59
  if (!options.resourceMetadataUrl && server.pathname !== "/" && isDiscoveryMiss(response.status)) {
59
60
  discard(response);
60
- response = await fetchMetadata(new URL("/.well-known/oauth-protected-resource", server.origin), fetch, version);
61
+ response = await fetchMetadata(new URL("/.well-known/oauth-protected-resource", server.origin), fetch, version, options.signal);
61
62
  }
62
63
  if (!response.ok) {
63
64
  discard(response);
@@ -79,7 +80,7 @@ export function buildAuthorizationServerDiscoveryUrls(authorizationServerUrl) {
79
80
  export async function discoverAuthorizationServerMetadata(authorizationServerUrl, options = {}) {
80
81
  const fetch = options.fetch ?? globalThis.fetch;
81
82
  for (const { url } of buildAuthorizationServerDiscoveryUrls(authorizationServerUrl)) {
82
- const response = await fetchMetadata(url, fetch, options.protocolVersion ?? LATEST_PROTOCOL_VERSION);
83
+ const response = await fetchMetadata(url, fetch, options.protocolVersion ?? LATEST_PROTOCOL_VERSION, options.signal);
83
84
  if (!response.ok) {
84
85
  discard(response);
85
86
  if (isDiscoveryMiss(response.status))
@@ -104,6 +105,7 @@ export async function discoverOAuthServerInfo(serverUrl, options = {}) {
104
105
  resourceMetadata = await discoverProtectedResourceMetadata(serverUrl, {
105
106
  resourceMetadataUrl: options.resourceMetadataUrl,
106
107
  fetch: options.fetch,
108
+ signal: options.signal,
107
109
  });
108
110
  }
109
111
  catch (error) {
@@ -112,7 +114,7 @@ export async function discoverOAuthServerInfo(serverUrl, options = {}) {
112
114
  }
113
115
  if (options.authorizationServerMetadataUrl) {
114
116
  const url = options.authorizationServerMetadataUrl;
115
- const response = await fetchMetadata(url, options.fetch ?? globalThis.fetch, LATEST_PROTOCOL_VERSION);
117
+ const response = await fetchMetadata(url, options.fetch ?? globalThis.fetch, LATEST_PROTOCOL_VERSION, options.signal);
116
118
  if (!response.ok) {
117
119
  discard(response);
118
120
  throw new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);
@@ -126,6 +128,7 @@ export async function discoverOAuthServerInfo(serverUrl, options = {}) {
126
128
  authorizationServerMetadata: await discoverAuthorizationServerMetadata(authorizationServerUrl, {
127
129
  fetch: options.fetch,
128
130
  skipIssuerValidation: options.skipIssuerValidation,
131
+ signal: options.signal,
129
132
  }),
130
133
  resourceMetadata,
131
134
  };
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/oauth/discovery.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Adapted from modelcontextprotocol/typescript-sdk v1.29.0 src/client/auth.ts.\n * Copyright (c) 2024 Anthropic, PBC. Licensed under MIT; see LICENSES/.\n * Modified to remove Zod/CORS shims and enforce authorization-server issuer validation.\n */\n\nimport type { McpFetch } from \"../auth-provider.ts\";\nimport { LATEST_PROTOCOL_VERSION } from \"../protocol/types.ts\";\nimport { OAuthIssuerMismatchError } from \"./errors.ts\";\nimport {\n\ttype AuthorizationServerMetadata,\n\ttype OAuthChallenge,\n\ttype OAuthProtectedResourceMetadata,\n\ttype OAuthServerInfo,\n\tparseAuthorizationServerMetadata,\n\tparseProtectedResourceMetadata,\n} from \"./types.ts\";\n\nfunction discard(response: Response | undefined): void {\n\tvoid response?.body?.cancel().catch(() => {});\n}\n\n/** 4xx and 502 mean \"not here\", so discovery tries the next candidate URL. */\nfunction isDiscoveryMiss(status: number): boolean {\n\treturn (status >= 400 && status < 500) || status === 502;\n}\n\n/** Path suffix for `/.well-known/<kind><path>`; empty for the root path. */\nfunction pathSuffix(pathname: string): string {\n\treturn pathname.endsWith(\"/\") ? pathname.slice(0, -1) : pathname;\n}\n\nfunction field(header: string, name: string): string | undefined {\n\tconst match = header.match(new RegExp(String.raw`(?:^|[,\\s])${name}=(?:\"([^\"]*)\"|([^\\s,]+))`, \"i\"));\n\t// An empty value (`scope=\"\"`) carries no information, so it counts as absent.\n\treturn match?.[1] || match?.[2] || undefined;\n}\n\nexport function parseWwwAuthenticate(header: string | null): OAuthChallenge {\n\tif (!header) return {};\n\tconst scheme = header.trimStart().split(/\\s+/, 1)[0]?.toLowerCase();\n\tif (scheme !== \"bearer\" && scheme !== \"dpop\") return {};\n\tconst resourceMetadata = field(header, \"resource_metadata\");\n\tlet resourceMetadataUrl: URL | undefined;\n\tif (resourceMetadata) {\n\t\ttry {\n\t\t\tresourceMetadataUrl = new URL(resourceMetadata);\n\t\t} catch {}\n\t}\n\treturn {\n\t\tresourceMetadataUrl,\n\t\tscope: field(header, \"scope\"),\n\t\terror: field(header, \"error\"),\n\t\terrorDescription: field(header, \"error_description\"),\n\t};\n}\n\nasync function fetchMetadata(url: URL, fetch: McpFetch, protocolVersion: string): Promise<Response> {\n\treturn fetch(url, {\n\t\theaders: { Accept: \"application/json\", \"MCP-Protocol-Version\": protocolVersion },\n\t});\n}\n\nexport async function discoverProtectedResourceMetadata(\n\tserverUrl: string | URL,\n\toptions: { resourceMetadataUrl?: string | URL; protocolVersion?: string; fetch?: McpFetch } = {},\n): Promise<OAuthProtectedResourceMetadata> {\n\tconst server = new URL(serverUrl);\n\tconst fetch = options.fetch ?? globalThis.fetch;\n\tconst version = options.protocolVersion ?? LATEST_PROTOCOL_VERSION;\n\tlet response = await fetchMetadata(\n\t\toptions.resourceMetadataUrl\n\t\t\t? new URL(options.resourceMetadataUrl)\n\t\t\t: new URL(`/.well-known/oauth-protected-resource${pathSuffix(server.pathname)}`, server.origin),\n\t\tfetch,\n\t\tversion,\n\t);\n\tif (!options.resourceMetadataUrl && server.pathname !== \"/\" && isDiscoveryMiss(response.status)) {\n\t\tdiscard(response);\n\t\tresponse = await fetchMetadata(new URL(\"/.well-known/oauth-protected-resource\", server.origin), fetch, version);\n\t}\n\tif (!response.ok) {\n\t\tdiscard(response);\n\t\tthrow new Error(`HTTP ${response.status} loading OAuth protected resource metadata`);\n\t}\n\treturn parseProtectedResourceMetadata(await response.json());\n}\n\nexport function buildAuthorizationServerDiscoveryUrls(\n\tauthorizationServerUrl: string | URL,\n): { url: URL; type: \"oauth\" | \"oidc\" }[] {\n\tconst issuer = new URL(authorizationServerUrl);\n\tconst path = pathSuffix(issuer.pathname);\n\tconst urls: { url: URL; type: \"oauth\" | \"oidc\" }[] = [\n\t\t{ url: new URL(`/.well-known/oauth-authorization-server${path}`, issuer.origin), type: \"oauth\" },\n\t\t{ url: new URL(`/.well-known/openid-configuration${path}`, issuer.origin), type: \"oidc\" },\n\t];\n\tif (path) urls.push({ url: new URL(`${path}/.well-known/openid-configuration`, issuer.origin), type: \"oidc\" });\n\treturn urls;\n}\n\nexport async function discoverAuthorizationServerMetadata(\n\tauthorizationServerUrl: string | URL,\n\toptions: { fetch?: McpFetch; protocolVersion?: string; skipIssuerValidation?: boolean } = {},\n): Promise<AuthorizationServerMetadata | undefined> {\n\tconst fetch = options.fetch ?? globalThis.fetch;\n\tfor (const { url } of buildAuthorizationServerDiscoveryUrls(authorizationServerUrl)) {\n\t\tconst response = await fetchMetadata(url, fetch, options.protocolVersion ?? LATEST_PROTOCOL_VERSION);\n\t\tif (!response.ok) {\n\t\t\tdiscard(response);\n\t\t\tif (isDiscoveryMiss(response.status)) continue;\n\t\t\tthrow new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);\n\t\t}\n\t\tconst metadata = parseAuthorizationServerMetadata(await response.json());\n\t\tif (!options.skipIssuerValidation) {\n\t\t\tconst expected = String(authorizationServerUrl);\n\t\t\t// URL parsing adds a trailing slash to bare origins, so compare without one on either side.\n\t\t\tconst trim = (value: string) => (value.endsWith(\"/\") ? value.slice(0, -1) : value);\n\t\t\tif (trim(metadata.issuer) !== trim(expected)) throw new OAuthIssuerMismatchError(expected, metadata.issuer);\n\t\t}\n\t\treturn metadata;\n\t}\n\treturn undefined;\n}\n\nexport async function discoverOAuthServerInfo(\n\tserverUrl: string | URL,\n\toptions: {\n\t\tresourceMetadataUrl?: URL;\n\t\t/** Metadata document to use instead of discovery. It is trusted as configured, so its issuer is not checked. */\n\t\tauthorizationServerMetadataUrl?: URL;\n\t\tfetch?: McpFetch;\n\t\tskipIssuerValidation?: boolean;\n\t} = {},\n): Promise<OAuthServerInfo> {\n\tlet resourceMetadata: OAuthProtectedResourceMetadata | undefined;\n\ttry {\n\t\tresourceMetadata = await discoverProtectedResourceMetadata(serverUrl, {\n\t\t\tresourceMetadataUrl: options.resourceMetadataUrl,\n\t\t\tfetch: options.fetch,\n\t\t});\n\t} catch (error) {\n\t\tif (error instanceof TypeError) throw error;\n\t}\n\tif (options.authorizationServerMetadataUrl) {\n\t\tconst url = options.authorizationServerMetadataUrl;\n\t\tconst response = await fetchMetadata(url, options.fetch ?? globalThis.fetch, LATEST_PROTOCOL_VERSION);\n\t\tif (!response.ok) {\n\t\t\tdiscard(response);\n\t\t\tthrow new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);\n\t\t}\n\t\tconst metadata = parseAuthorizationServerMetadata(await response.json());\n\t\treturn { authorizationServerUrl: metadata.issuer, authorizationServerMetadata: metadata, resourceMetadata };\n\t}\n\tconst authorizationServerUrl = resourceMetadata?.authorization_servers?.[0] ?? String(new URL(\"/\", serverUrl));\n\treturn {\n\t\tauthorizationServerUrl,\n\t\tauthorizationServerMetadata: await discoverAuthorizationServerMetadata(authorizationServerUrl, {\n\t\t\tfetch: options.fetch,\n\t\t\tskipIssuerValidation: options.skipIssuerValidation,\n\t\t}),\n\t\tresourceMetadata,\n\t};\n}\n\nexport function resourceUrlFromServerUrl(value: string | URL): URL {\n\tconst url = new URL(value);\n\turl.hash = \"\";\n\treturn url;\n}\n\nexport function selectResource(serverUrl: string | URL, metadata?: OAuthProtectedResourceMetadata): string | undefined {\n\tif (!metadata) return undefined;\n\tconst requested = resourceUrlFromServerUrl(serverUrl);\n\tconst configured = new URL(metadata.resource);\n\tif (requested.origin !== configured.origin) {\n\t\tthrow new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);\n\t}\n\tconst requestedPath = requested.pathname.endsWith(\"/\") ? requested.pathname : `${requested.pathname}/`;\n\tconst configuredPath = configured.pathname.endsWith(\"/\") ? configured.pathname : `${configured.pathname}/`;\n\tif (!requestedPath.startsWith(configuredPath)) {\n\t\tthrow new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);\n\t}\n\treturn metadata.resource;\n}\n"]}
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/oauth/discovery.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Adapted from modelcontextprotocol/typescript-sdk v1.29.0 src/client/auth.ts.\n * Copyright (c) 2024 Anthropic, PBC. Licensed under MIT; see THIRD_PARTY_NOTICES/.\n * Modified to remove Zod/CORS shims and enforce authorization-server issuer validation.\n */\n\nimport type { McpFetch } from \"../auth-provider.ts\";\nimport { LATEST_PROTOCOL_VERSION } from \"../protocol/types.ts\";\nimport { OAuthIssuerMismatchError } from \"./errors.ts\";\nimport {\n\ttype AuthorizationServerMetadata,\n\ttype OAuthChallenge,\n\ttype OAuthProtectedResourceMetadata,\n\ttype OAuthServerInfo,\n\tparseAuthorizationServerMetadata,\n\tparseProtectedResourceMetadata,\n} from \"./types.ts\";\n\nfunction discard(response: Response | undefined): void {\n\tvoid response?.body?.cancel().catch(() => {});\n}\n\n/** 4xx and 502 mean \"not here\", so discovery tries the next candidate URL. */\nfunction isDiscoveryMiss(status: number): boolean {\n\treturn (status >= 400 && status < 500) || status === 502;\n}\n\n/** Path suffix for `/.well-known/<kind><path>`; empty for the root path. */\nfunction pathSuffix(pathname: string): string {\n\treturn pathname.endsWith(\"/\") ? pathname.slice(0, -1) : pathname;\n}\n\nfunction field(header: string, name: string): string | undefined {\n\tconst match = header.match(new RegExp(String.raw`(?:^|[,\\s])${name}=(?:\"([^\"]*)\"|([^\\s,]+))`, \"i\"));\n\t// An empty value (`scope=\"\"`) carries no information, so it counts as absent.\n\treturn match?.[1] || match?.[2] || undefined;\n}\n\nexport function parseWwwAuthenticate(header: string | null): OAuthChallenge {\n\tif (!header) return {};\n\tconst scheme = header.trimStart().split(/\\s+/, 1)[0]?.toLowerCase();\n\tif (scheme !== \"bearer\" && scheme !== \"dpop\") return {};\n\tconst resourceMetadata = field(header, \"resource_metadata\");\n\tlet resourceMetadataUrl: URL | undefined;\n\tif (resourceMetadata) {\n\t\ttry {\n\t\t\tresourceMetadataUrl = new URL(resourceMetadata);\n\t\t} catch {}\n\t}\n\treturn {\n\t\tresourceMetadataUrl,\n\t\tscope: field(header, \"scope\"),\n\t\terror: field(header, \"error\"),\n\t\terrorDescription: field(header, \"error_description\"),\n\t};\n}\n\nasync function fetchMetadata(\n\turl: URL,\n\tfetch: McpFetch,\n\tprotocolVersion: string,\n\tsignal: AbortSignal | undefined,\n): Promise<Response> {\n\treturn fetch(url, {\n\t\theaders: { Accept: \"application/json\", \"MCP-Protocol-Version\": protocolVersion },\n\t\tsignal,\n\t});\n}\n\nexport async function discoverProtectedResourceMetadata(\n\tserverUrl: string | URL,\n\toptions: {\n\t\tresourceMetadataUrl?: string | URL;\n\t\tprotocolVersion?: string;\n\t\tfetch?: McpFetch;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<OAuthProtectedResourceMetadata> {\n\tconst server = new URL(serverUrl);\n\tconst fetch = options.fetch ?? globalThis.fetch;\n\tconst version = options.protocolVersion ?? LATEST_PROTOCOL_VERSION;\n\tlet response = await fetchMetadata(\n\t\toptions.resourceMetadataUrl\n\t\t\t? new URL(options.resourceMetadataUrl)\n\t\t\t: new URL(`/.well-known/oauth-protected-resource${pathSuffix(server.pathname)}`, server.origin),\n\t\tfetch,\n\t\tversion,\n\t\toptions.signal,\n\t);\n\tif (!options.resourceMetadataUrl && server.pathname !== \"/\" && isDiscoveryMiss(response.status)) {\n\t\tdiscard(response);\n\t\tresponse = await fetchMetadata(\n\t\t\tnew URL(\"/.well-known/oauth-protected-resource\", server.origin),\n\t\t\tfetch,\n\t\t\tversion,\n\t\t\toptions.signal,\n\t\t);\n\t}\n\tif (!response.ok) {\n\t\tdiscard(response);\n\t\tthrow new Error(`HTTP ${response.status} loading OAuth protected resource metadata`);\n\t}\n\treturn parseProtectedResourceMetadata(await response.json());\n}\n\nexport function buildAuthorizationServerDiscoveryUrls(\n\tauthorizationServerUrl: string | URL,\n): { url: URL; type: \"oauth\" | \"oidc\" }[] {\n\tconst issuer = new URL(authorizationServerUrl);\n\tconst path = pathSuffix(issuer.pathname);\n\tconst urls: { url: URL; type: \"oauth\" | \"oidc\" }[] = [\n\t\t{ url: new URL(`/.well-known/oauth-authorization-server${path}`, issuer.origin), type: \"oauth\" },\n\t\t{ url: new URL(`/.well-known/openid-configuration${path}`, issuer.origin), type: \"oidc\" },\n\t];\n\tif (path) urls.push({ url: new URL(`${path}/.well-known/openid-configuration`, issuer.origin), type: \"oidc\" });\n\treturn urls;\n}\n\nexport async function discoverAuthorizationServerMetadata(\n\tauthorizationServerUrl: string | URL,\n\toptions: { fetch?: McpFetch; protocolVersion?: string; skipIssuerValidation?: boolean; signal?: AbortSignal } = {},\n): Promise<AuthorizationServerMetadata | undefined> {\n\tconst fetch = options.fetch ?? globalThis.fetch;\n\tfor (const { url } of buildAuthorizationServerDiscoveryUrls(authorizationServerUrl)) {\n\t\tconst response = await fetchMetadata(\n\t\t\turl,\n\t\t\tfetch,\n\t\t\toptions.protocolVersion ?? LATEST_PROTOCOL_VERSION,\n\t\t\toptions.signal,\n\t\t);\n\t\tif (!response.ok) {\n\t\t\tdiscard(response);\n\t\t\tif (isDiscoveryMiss(response.status)) continue;\n\t\t\tthrow new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);\n\t\t}\n\t\tconst metadata = parseAuthorizationServerMetadata(await response.json());\n\t\tif (!options.skipIssuerValidation) {\n\t\t\tconst expected = String(authorizationServerUrl);\n\t\t\t// URL parsing adds a trailing slash to bare origins, so compare without one on either side.\n\t\t\tconst trim = (value: string) => (value.endsWith(\"/\") ? value.slice(0, -1) : value);\n\t\t\tif (trim(metadata.issuer) !== trim(expected)) throw new OAuthIssuerMismatchError(expected, metadata.issuer);\n\t\t}\n\t\treturn metadata;\n\t}\n\treturn undefined;\n}\n\nexport async function discoverOAuthServerInfo(\n\tserverUrl: string | URL,\n\toptions: {\n\t\tresourceMetadataUrl?: URL;\n\t\t/** Metadata document to use instead of discovery. It is trusted as configured, so its issuer is not checked. */\n\t\tauthorizationServerMetadataUrl?: URL;\n\t\tfetch?: McpFetch;\n\t\tskipIssuerValidation?: boolean;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<OAuthServerInfo> {\n\tlet resourceMetadata: OAuthProtectedResourceMetadata | undefined;\n\ttry {\n\t\tresourceMetadata = await discoverProtectedResourceMetadata(serverUrl, {\n\t\t\tresourceMetadataUrl: options.resourceMetadataUrl,\n\t\t\tfetch: options.fetch,\n\t\t\tsignal: options.signal,\n\t\t});\n\t} catch (error) {\n\t\tif (error instanceof TypeError) throw error;\n\t}\n\tif (options.authorizationServerMetadataUrl) {\n\t\tconst url = options.authorizationServerMetadataUrl;\n\t\tconst response = await fetchMetadata(\n\t\t\turl,\n\t\t\toptions.fetch ?? globalThis.fetch,\n\t\t\tLATEST_PROTOCOL_VERSION,\n\t\t\toptions.signal,\n\t\t);\n\t\tif (!response.ok) {\n\t\t\tdiscard(response);\n\t\t\tthrow new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);\n\t\t}\n\t\tconst metadata = parseAuthorizationServerMetadata(await response.json());\n\t\treturn { authorizationServerUrl: metadata.issuer, authorizationServerMetadata: metadata, resourceMetadata };\n\t}\n\tconst authorizationServerUrl = resourceMetadata?.authorization_servers?.[0] ?? String(new URL(\"/\", serverUrl));\n\treturn {\n\t\tauthorizationServerUrl,\n\t\tauthorizationServerMetadata: await discoverAuthorizationServerMetadata(authorizationServerUrl, {\n\t\t\tfetch: options.fetch,\n\t\t\tskipIssuerValidation: options.skipIssuerValidation,\n\t\t\tsignal: options.signal,\n\t\t}),\n\t\tresourceMetadata,\n\t};\n}\n\nexport function resourceUrlFromServerUrl(value: string | URL): URL {\n\tconst url = new URL(value);\n\turl.hash = \"\";\n\treturn url;\n}\n\nexport function selectResource(serverUrl: string | URL, metadata?: OAuthProtectedResourceMetadata): string | undefined {\n\tif (!metadata) return undefined;\n\tconst requested = resourceUrlFromServerUrl(serverUrl);\n\tconst configured = new URL(metadata.resource);\n\tif (requested.origin !== configured.origin) {\n\t\tthrow new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);\n\t}\n\tconst requestedPath = requested.pathname.endsWith(\"/\") ? requested.pathname : `${requested.pathname}/`;\n\tconst configuredPath = configured.pathname.endsWith(\"/\") ? configured.pathname : `${configured.pathname}/`;\n\tif (!requestedPath.startsWith(configuredPath)) {\n\t\tthrow new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);\n\t}\n\treturn metadata.resource;\n}\n"]}
@@ -4,10 +4,11 @@ export declare class OAuthError extends Error {
4
4
  constructor(code: string, message: string, errorUri?: string);
5
5
  }
6
6
  export declare class OAuthIssuerMismatchError extends Error {
7
- readonly expected: string;
7
+ /** `undefined` when no authorization server metadata was discovered. */
8
+ readonly expected: string | undefined;
8
9
  /** `undefined` when an authorization response lacks the `iss` parameter its server promised (RFC 9207). */
9
10
  readonly received: string | undefined;
10
- constructor(expected: string, received: string | undefined);
11
+ constructor(expected: string | undefined, received: string | undefined);
11
12
  }
12
13
  export declare class OAuthInsecureEndpointError extends Error {
13
14
  readonly endpoint: string;
@@ -21,4 +22,11 @@ export declare class OAuthRegistrationError extends Error {
21
22
  export declare class McpOAuthAuthorizationRequiredError extends Error {
22
23
  constructor();
23
24
  }
25
+ /**
26
+ * Thrown by `McpOAuthServerStore.withRefreshLock()` when another process holds the refresh lock for the
27
+ * whole wait. `startMcpSignIn()` then skips the refresh and goes to the browser step.
28
+ */
29
+ export declare class McpRefreshLockBusyError extends Error {
30
+ constructor();
31
+ }
24
32
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/oauth/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAW,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAEtC,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAK3D;CACD;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2GAA2G;IAC3G,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAEtC,YAAY,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAOzD;CACD;AAED,qBAAa,0BAA2B,SAAQ,KAAK;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,YAAY,QAAQ,EAAE,MAAM,EAI3B;CACD;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAKvC;CACD;AAED,qBAAa,kCAAmC,SAAQ,KAAK;IAC5D,cAGC;CACD"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/oauth/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAW,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAEtC,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAK3D;CACD;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAClD,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,2GAA2G;IAC3G,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAEtC,YAAY,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAMrE;CACD;AAED,qBAAa,0BAA2B,SAAQ,KAAK;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,YAAY,QAAQ,EAAE,MAAM,EAI3B;CACD;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAKvC;CACD;AAED,qBAAa,kCAAmC,SAAQ,KAAK;IAC5D,cAGC;CACD;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IACjD,cAGC;CACD"}
@@ -9,11 +9,13 @@ export class OAuthError extends Error {
9
9
  }
10
10
  }
11
11
  export class OAuthIssuerMismatchError extends Error {
12
+ /** `undefined` when no authorization server metadata was discovered. */
12
13
  expected;
13
14
  /** `undefined` when an authorization response lacks the `iss` parameter its server promised (RFC 9207). */
14
15
  received;
15
16
  constructor(expected, received) {
16
- super(`OAuth issuer mismatch: expected ${JSON.stringify(expected)}, received ${received === undefined ? "none" : JSON.stringify(received)}`);
17
+ const format = (issuer) => (issuer === undefined ? "none" : JSON.stringify(issuer));
18
+ super(`OAuth issuer mismatch: expected ${format(expected)}, received ${format(received)}`);
17
19
  this.name = "OAuthIssuerMismatchError";
18
20
  this.expected = expected;
19
21
  this.received = received;
@@ -43,4 +45,14 @@ export class McpOAuthAuthorizationRequiredError extends Error {
43
45
  this.name = "McpOAuthAuthorizationRequiredError";
44
46
  }
45
47
  }
48
+ /**
49
+ * Thrown by `McpOAuthServerStore.withRefreshLock()` when another process holds the refresh lock for the
50
+ * whole wait. `startMcpSignIn()` then skips the refresh and goes to the browser step.
51
+ */
52
+ export class McpRefreshLockBusyError extends Error {
53
+ constructor() {
54
+ super("Another process is refreshing the MCP server's tokens");
55
+ this.name = "McpRefreshLockBusyError";
56
+ }
57
+ }
46
58
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/oauth/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,UAAW,SAAQ,KAAK;IAC3B,IAAI,CAAS;IACb,QAAQ,CAAqB;IAEtC,YAAY,IAAY,EAAE,OAAe,EAAE,QAAiB;QAC3D,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACzC,QAAQ,CAAS;IAC1B,2GAA2G;IAClG,QAAQ,CAAqB;IAEtC,YAAY,QAAgB,EAAE,QAA4B;QACzD,KAAK,CACJ,mCAAmC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,cAAc,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CACrI,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IAC3C,QAAQ,CAAS;IAE1B,YAAY,QAAgB;QAC3B,KAAK,CAAC,4DAA4D,QAAQ,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACvC,MAAM,CAAS;IACf,IAAI,CAAS;IAEtB,YAAY,MAAc,EAAE,IAAY;QACvC,KAAK,CAAC,wDAAwD,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AAED,MAAM,OAAO,kCAAmC,SAAQ,KAAK;IAC5D;QACC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,oCAAoC,CAAC;IAClD,CAAC;CACD","sourcesContent":["export class OAuthError extends Error {\n\treadonly code: string;\n\treadonly errorUri: string | undefined;\n\n\tconstructor(code: string, message: string, errorUri?: string) {\n\t\tsuper(message || code);\n\t\tthis.name = \"OAuthError\";\n\t\tthis.code = code;\n\t\tthis.errorUri = errorUri;\n\t}\n}\n\nexport class OAuthIssuerMismatchError extends Error {\n\treadonly expected: string;\n\t/** `undefined` when an authorization response lacks the `iss` parameter its server promised (RFC 9207). */\n\treadonly received: string | undefined;\n\n\tconstructor(expected: string, received: string | undefined) {\n\t\tsuper(\n\t\t\t`OAuth issuer mismatch: expected ${JSON.stringify(expected)}, received ${received === undefined ? \"none\" : JSON.stringify(received)}`,\n\t\t);\n\t\tthis.name = \"OAuthIssuerMismatchError\";\n\t\tthis.expected = expected;\n\t\tthis.received = received;\n\t}\n}\n\nexport class OAuthInsecureEndpointError extends Error {\n\treadonly endpoint: string;\n\n\tconstructor(endpoint: string) {\n\t\tsuper(`Refusing to send OAuth credentials to non-HTTPS endpoint ${endpoint}`);\n\t\tthis.name = \"OAuthInsecureEndpointError\";\n\t\tthis.endpoint = endpoint;\n\t}\n}\n\nexport class OAuthRegistrationError extends Error {\n\treadonly status: number;\n\treadonly body: string;\n\n\tconstructor(status: number, body: string) {\n\t\tsuper(`OAuth dynamic client registration failed with status ${status}: ${body}`);\n\t\tthis.name = \"OAuthRegistrationError\";\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nexport class McpOAuthAuthorizationRequiredError extends Error {\n\tconstructor() {\n\t\tsuper(\"MCP OAuth authorization requires user interaction\");\n\t\tthis.name = \"McpOAuthAuthorizationRequiredError\";\n\t}\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/oauth/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,UAAW,SAAQ,KAAK;IAC3B,IAAI,CAAS;IACb,QAAQ,CAAqB;IAEtC,YAAY,IAAY,EAAE,OAAe,EAAE,QAAiB;QAC3D,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IAClD,wEAAwE;IAC/D,QAAQ,CAAqB;IACtC,2GAA2G;IAClG,QAAQ,CAAqB;IAEtC,YAAY,QAA4B,EAAE,QAA4B;QACrE,MAAM,MAAM,GAAG,CAAC,MAA0B,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QACxG,KAAK,CAAC,mCAAmC,MAAM,CAAC,QAAQ,CAAC,cAAc,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC3F,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IAC3C,QAAQ,CAAS;IAE1B,YAAY,QAAgB;QAC3B,KAAK,CAAC,4DAA4D,QAAQ,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACvC,MAAM,CAAS;IACf,IAAI,CAAS;IAEtB,YAAY,MAAc,EAAE,IAAY;QACvC,KAAK,CAAC,wDAAwD,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AAED,MAAM,OAAO,kCAAmC,SAAQ,KAAK;IAC5D;QACC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,oCAAoC,CAAC;IAClD,CAAC;CACD;AAED;;;GAGG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACjD;QACC,KAAK,CAAC,uDAAuD,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACvC,CAAC;CACD","sourcesContent":["export class OAuthError extends Error {\n\treadonly code: string;\n\treadonly errorUri: string | undefined;\n\n\tconstructor(code: string, message: string, errorUri?: string) {\n\t\tsuper(message || code);\n\t\tthis.name = \"OAuthError\";\n\t\tthis.code = code;\n\t\tthis.errorUri = errorUri;\n\t}\n}\n\nexport class OAuthIssuerMismatchError extends Error {\n\t/** `undefined` when no authorization server metadata was discovered. */\n\treadonly expected: string | undefined;\n\t/** `undefined` when an authorization response lacks the `iss` parameter its server promised (RFC 9207). */\n\treadonly received: string | undefined;\n\n\tconstructor(expected: string | undefined, received: string | undefined) {\n\t\tconst format = (issuer: string | undefined) => (issuer === undefined ? \"none\" : JSON.stringify(issuer));\n\t\tsuper(`OAuth issuer mismatch: expected ${format(expected)}, received ${format(received)}`);\n\t\tthis.name = \"OAuthIssuerMismatchError\";\n\t\tthis.expected = expected;\n\t\tthis.received = received;\n\t}\n}\n\nexport class OAuthInsecureEndpointError extends Error {\n\treadonly endpoint: string;\n\n\tconstructor(endpoint: string) {\n\t\tsuper(`Refusing to send OAuth credentials to non-HTTPS endpoint ${endpoint}`);\n\t\tthis.name = \"OAuthInsecureEndpointError\";\n\t\tthis.endpoint = endpoint;\n\t}\n}\n\nexport class OAuthRegistrationError extends Error {\n\treadonly status: number;\n\treadonly body: string;\n\n\tconstructor(status: number, body: string) {\n\t\tsuper(`OAuth dynamic client registration failed with status ${status}: ${body}`);\n\t\tthis.name = \"OAuthRegistrationError\";\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nexport class McpOAuthAuthorizationRequiredError extends Error {\n\tconstructor() {\n\t\tsuper(\"MCP OAuth authorization requires user interaction\");\n\t\tthis.name = \"McpOAuthAuthorizationRequiredError\";\n\t}\n}\n\n/**\n * Thrown by `McpOAuthServerStore.withRefreshLock()` when another process holds the refresh lock for the\n * whole wait. `startMcpSignIn()` then skips the refresh and goes to the browser step.\n */\nexport class McpRefreshLockBusyError extends Error {\n\tconstructor() {\n\t\tsuper(\"Another process is refreshing the MCP server's tokens\");\n\t\tthis.name = \"McpRefreshLockBusyError\";\n\t}\n}\n"]}
@@ -41,12 +41,24 @@ export interface OAuthFlowOptions {
41
41
  */
42
42
  authorizationServerMetadataUrl?: URL;
43
43
  fetch?: McpFetch;
44
+ /** Aborts every request of the flow. Requests have no time limit of their own; combine with a timeout as needed. */
45
+ signal?: AbortSignal;
44
46
  skipIssuerValidation?: boolean;
45
47
  /**
46
48
  * Go straight to the authorization redirect instead of refreshing stored tokens, for example when the
47
49
  * server asks for scopes the current grant lacks (a refresh keeps the old scope).
48
50
  */
49
51
  skipRefresh?: boolean;
52
+ /**
53
+ * Only refresh the stored tokens: return `REDIRECT` instead of registering a client or preparing an
54
+ * authorization request, so a pending authorization (its `state` and PKCE verifier) stays usable.
55
+ */
56
+ refreshOnly?: boolean;
57
+ /**
58
+ * With `authorizationCode`: the redirect URI the authorization request used. The exchange fails before
59
+ * sending the code when the flow would send another one (RFC 6749 section 4.1.3 requires the same).
60
+ */
61
+ authorizationRedirectUrl?: string;
50
62
  }
51
63
  export type OAuthFlowResult = "AUTHORIZED" | "REDIRECT";
52
64
  export interface TokenRequestOptions {
@@ -55,6 +67,7 @@ export interface TokenRequestOptions {
55
67
  resource?: string;
56
68
  addClientAuthentication?: AddClientAuthentication;
57
69
  fetch?: McpFetch;
70
+ signal?: AbortSignal;
58
71
  }
59
72
  export declare function startAuthorization(authorizationServerUrl: string | URL, options: {
60
73
  metadata?: AuthorizationServerMetadata;
@@ -72,6 +85,7 @@ export declare function registerClient(authorizationServerUrl: string | URL, opt
72
85
  clientMetadata: OAuthClientMetadata;
73
86
  scope?: string;
74
87
  fetch?: McpFetch;
88
+ signal?: AbortSignal;
75
89
  }): Promise<OAuthClientInformationFull>;
76
90
  export declare function exchangeAuthorizationCode(authorizationServerUrl: string | URL, options: TokenRequestOptions & {
77
91
  code: string;