@humain/terminal 0.1.1 → 0.2.1

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 (193) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
  3. package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
  4. package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
  5. package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
  6. package/dist/bundle/chunks/chunk-EY4DWHQU.js +2 -0
  7. package/dist/bundle/chunks/{chunk-LYAMSPR2.js → chunk-I47ZWR55.js} +483 -506
  8. package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
  10. package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
  11. package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
  12. package/dist/bundle/chunks/compat-QCV26HNS.js +2 -0
  13. package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
  14. package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
  15. package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
  16. package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
  17. package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
  18. package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
  19. package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
  20. package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
  21. package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
  22. package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
  23. package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
  24. package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
  25. package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
  26. package/dist/bundle/cli.js +1 -1
  27. package/dist/bundle/index.js +1 -1
  28. package/dist/bundle/rpc-entry.js +1 -1
  29. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  30. package/dist/core/tools/file-mutation-queue.js +21 -8
  31. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  32. package/dist/core/tools/subagent-env.d.ts +18 -0
  33. package/dist/core/tools/subagent-env.d.ts.map +1 -0
  34. package/dist/core/tools/subagent-env.js +43 -0
  35. package/dist/core/tools/subagent-env.js.map +1 -0
  36. package/dist/core/tools/subagent-scheduler.d.ts +31 -0
  37. package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
  38. package/dist/core/tools/subagent-scheduler.js +203 -0
  39. package/dist/core/tools/subagent-scheduler.js.map +1 -0
  40. package/dist/core/tools/subagent.d.ts +83 -3
  41. package/dist/core/tools/subagent.d.ts.map +1 -1
  42. package/dist/core/tools/subagent.js +214 -76
  43. package/dist/core/tools/subagent.js.map +1 -1
  44. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  45. package/dist/humain/mcp-adapter/vendor/README.md +125 -19
  46. package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
  47. package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
  48. package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
  49. package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
  50. package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
  51. package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
  52. package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
  53. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
  54. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
  55. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
  56. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
  57. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
  58. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
  59. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
  60. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
  61. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
  62. package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
  63. package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
  64. package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
  65. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
  66. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
  67. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
  68. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
  69. package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
  70. package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
  71. package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
  72. package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
  73. package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
  74. package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
  75. package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
  76. package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
  77. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
  78. package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
  79. package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
  80. package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
  81. package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
  82. package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
  83. package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
  84. package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
  85. package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
  86. package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
  87. package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
  88. package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
  89. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
  90. package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
  91. package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
  92. package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
  93. package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
  94. package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
  95. package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
  96. package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
  97. package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
  98. package/dist/humain/mcp-adapter/vendor/package.json +13 -3
  99. package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
  100. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
  101. package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
  102. package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
  103. package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
  104. package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
  105. package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
  106. package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
  107. package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
  108. package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
  109. package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
  110. package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
  111. package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
  112. package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
  113. package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
  114. package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
  115. package/dist/humain/web-access/vendor/abortable.js +22 -0
  116. package/dist/humain/web-access/vendor/abortable.js.map +1 -0
  117. package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
  118. package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
  119. package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
  120. package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
  121. package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
  122. package/dist/humain/web-access/vendor/credential-source.js +1 -0
  123. package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
  124. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  125. package/dist/humain/web-access/vendor/curator-page.js +20 -2
  126. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  127. package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
  128. package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
  129. package/dist/humain/web-access/vendor/curator-run.js +38 -0
  130. package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
  131. package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
  132. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  133. package/dist/humain/web-access/vendor/curator-server.js +2 -0
  134. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  135. package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
  136. package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
  137. package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
  138. package/dist/humain/web-access/vendor/extract.d.ts +1 -1
  139. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  140. package/dist/humain/web-access/vendor/extract.js +58 -11
  141. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  142. package/dist/humain/web-access/vendor/fetch-params.js +1 -1
  143. package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
  144. package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
  145. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  146. package/dist/humain/web-access/vendor/gemini-search.js +12 -5
  147. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  148. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  149. package/dist/humain/web-access/vendor/index.js +23 -19
  150. package/dist/humain/web-access/vendor/index.js.map +1 -1
  151. package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
  152. package/dist/humain/web-access/vendor/openai-search.js +40 -14
  153. package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
  154. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  155. package/dist/humain/web-access/vendor/page-query.js +6 -2
  156. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  157. package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
  158. package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
  159. package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
  160. package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
  161. package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
  162. package/dist/humain/web-access/vendor/serpapi.js +215 -0
  163. package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
  164. package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
  165. package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
  166. package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
  167. package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
  168. package/dist/humain/web-access/vendor/storage.js +9 -0
  169. package/dist/humain/web-access/vendor/storage.js.map +1 -1
  170. package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
  171. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  172. package/dist/humain/web-access/vendor/summary-review.js +35 -24
  173. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  174. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  175. package/dist/humain/web-access/vendor/utils.js +5 -3
  176. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  177. package/dist/modes/json-event.d.ts +8 -0
  178. package/dist/modes/json-event.d.ts.map +1 -1
  179. package/dist/modes/json-event.js +17 -0
  180. package/dist/modes/json-event.js.map +1 -1
  181. package/dist/modes/print-mode.d.ts.map +1 -1
  182. package/dist/modes/print-mode.js +3 -2
  183. package/dist/modes/print-mode.js.map +1 -1
  184. package/docs/environment-variables.md +8 -0
  185. package/docs/json.md +16 -0
  186. package/npm-shrinkwrap.json +6 -6
  187. package/package.json +2 -2
  188. package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
  189. package/src/humain/mcp-adapter/upstream.json +6 -4
  190. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  191. package/src/humain/web-access/UPSTREAM.md +18 -8
  192. package/src/humain/web-access/upstream.json +6 -5
  193. package/dist/bundle/chunks/chunk-2LC3S3VV.js +0 -105
@@ -9,10 +9,15 @@ import {
9
9
  extractWWWAuthenticateParams,
10
10
  LATEST_PROTOCOL_VERSION,
11
11
  UnauthorizedError,
12
+ validateClientMetadataUrl,
12
13
  type AuthOptions,
13
14
  } from "@modelcontextprotocol/client"
14
15
  import open from "open"
15
- import { McpOAuthProvider, type McpOAuthConfig } from "./mcp-oauth-provider.ts"
16
+ import {
17
+ getOAuthCallbackPort,
18
+ McpOAuthProvider,
19
+ type McpOAuthConfig,
20
+ } from "./mcp-oauth-provider.ts"
16
21
  import {
17
22
  ensureCallbackServer,
18
23
  waitForCallback,
@@ -23,21 +28,24 @@ import {
23
28
  } from "./mcp-callback-server.ts"
24
29
  import {
25
30
  getAuthForUrl,
26
- isTokenExpired,
27
- hasStoredTokens,
31
+ getAuthEntry,
28
32
  clearAllCredentials,
29
33
  clearClientInfo,
30
- clearTokens,
31
34
  clearCodeVerifier,
32
35
  getOAuthState,
33
36
  clearOAuthState,
34
37
  getAuthBaseDir,
38
+ beginOAuthRevocation,
39
+ captureOAuthAuthority,
35
40
  OAuthCredentialStoreError,
36
41
  type AuthStorageOptions,
42
+ type OAuthAuthority,
37
43
  type StoredTokens,
38
44
  } from "./mcp-auth.ts"
39
45
  import { isServerDisabled, type ServerEntry } from "./types.ts"
40
- import { formatTerminalError, interpolateEnvRecord, interpolateEnvVars } from "./utils.ts"
46
+ import { formatTerminalError, interpolateEnvVars } from "./utils.ts"
47
+ import { createOAuthFetch, oauthHeaderResolver, resolveOAuthHeaders } from "./mcp-auth-fetch.ts"
48
+ import { isBuiltInAgentPlugin } from "./agent-plugin-provenance.ts"
41
49
  import { abortable, throwIfAborted } from "./abort.ts"
42
50
  import { combineAbortSignals, isAbortError } from "./runtime-owner.ts"
43
51
 
@@ -50,6 +58,7 @@ export interface McpOAuthRuntime {
50
58
 
51
59
  export interface AuthenticateOptions {
52
60
  onAuthorizationUrl?: (authorizationUrl: string) => void | Promise<void>
61
+ openAuthorizationUrl?: (authorizationUrl: string) => void | Promise<void>
53
62
  onAuthorizationInput?: (
54
63
  authorizationUrl: string,
55
64
  signal: AbortSignal,
@@ -58,10 +67,17 @@ export interface AuthenticateOptions {
58
67
  signal?: AbortSignal
59
68
  runtime?: McpOAuthRuntime
60
69
  skipIssuerMetadataValidation?: boolean
70
+ definition?: Pick<ServerEntry, "headers" | "oauth">
61
71
  }
62
72
 
63
73
  type AuthDiscovery = Pick<AuthOptions, "resourceMetadataUrl" | "scope" | "skipIssuerMetadataValidation">
64
74
 
75
+ function pluginAwareOAuthHeaders(definition?: ServerEntry): () => Headers {
76
+ return oauthHeaderResolver(definition?.headers, {
77
+ literal: definition ? isBuiltInAgentPlugin(definition, "headers") : false,
78
+ })
79
+ }
80
+
65
81
  function applyOAuthConfig(discovery: AuthDiscovery, config: McpOAuthConfig): AuthDiscovery {
66
82
  return {
67
83
  ...discovery,
@@ -78,16 +94,20 @@ type PendingAuth = {
78
94
  manualRedirect: boolean
79
95
  manualCompletionController?: AbortController
80
96
  discovery: AuthDiscovery
97
+ getHeaders: () => Headers
81
98
  authStorageOptions: AuthStorageOptions
99
+ authority: OAuthAuthority
82
100
  }
83
101
 
102
+ type PendingAuthentication = { promise: Promise<AuthStatus>; authority: OAuthAuthority }
103
+
84
104
  type RuntimeState = {
85
105
  controller: AbortController
86
106
  generation: number
87
107
  pendingAuths: Map<string, PendingAuth>
88
108
  pendingAuthStates: Map<string, string>
89
109
  pendingAuthCleanupTimers: Map<string, ReturnType<typeof setTimeout>>
90
- pendingAuthentications: Map<string, Promise<AuthStatus>>
110
+ pendingAuthentications: Map<string, PendingAuthentication>
91
111
  }
92
112
 
93
113
  const runtimeStates = new WeakMap<McpOAuthRuntime, RuntimeState>()
@@ -128,8 +148,23 @@ function getRuntimeState(runtime: McpOAuthRuntime): RuntimeState {
128
148
  return state
129
149
  }
130
150
 
151
+ function getAuthStorageIdentity(options: AuthStorageOptions): ["encrypted-file"] | ["os", string] {
152
+ return options.credentialStore === "encrypted-file"
153
+ ? ["encrypted-file"]
154
+ : ["os", getAuthBaseDir(options)]
155
+ }
156
+
131
157
  function getPendingAuthKey(serverName: string, options: AuthStorageOptions): string {
132
- return `${serverName}|${getAuthBaseDir(options)}`
158
+ return JSON.stringify([serverName, ...getAuthStorageIdentity(options)])
159
+ }
160
+
161
+ function hasOAuthAuthority(authority: OAuthAuthority): boolean {
162
+ try {
163
+ authority()
164
+ return true
165
+ } catch {
166
+ return false
167
+ }
133
168
  }
134
169
 
135
170
  export function hasPendingAuth(serverName: string, options?: AuthStorageOptions, runtime?: McpOAuthRuntime): boolean {
@@ -173,6 +208,20 @@ export function extractOAuthConfig(definition: ServerEntry): McpOAuthConfig {
173
208
  ? definition.oauth.clientSecret
174
209
  : interpolateEnvVars(definition.oauth.clientSecret)
175
210
  }
211
+ if (definition.oauth?.clientMetadataUrl !== undefined) {
212
+ if (typeof definition.oauth.clientMetadataUrl !== "string") {
213
+ throw new Error("OAuth clientMetadataUrl must be a string")
214
+ }
215
+ const clientMetadataUrl = interpolateEnvVars(definition.oauth.clientMetadataUrl).trim()
216
+ if (!clientMetadataUrl) {
217
+ throw new Error("OAuth clientMetadataUrl must not be empty")
218
+ }
219
+ validateClientMetadataUrl(clientMetadataUrl)
220
+ config.clientMetadataUrl = clientMetadataUrl
221
+ }
222
+ if (config.clientMetadataUrl !== undefined && config.clientSecret !== undefined && !config.clientId) {
223
+ throw new Error("OAuth clientSecret requires an explicit clientId when clientMetadataUrl is configured")
224
+ }
176
225
  if (definition.oauth?.scope !== undefined) {
177
226
  if (typeof definition.oauth.scope !== "string") throw new Error("OAuth scope must be a string")
178
227
  config.scope = interpolateEnvVars(definition.oauth.scope)
@@ -269,12 +318,13 @@ export function extractOAuthConfig(definition: ServerEntry): McpOAuthConfig {
269
318
  }
270
319
 
271
320
  async function probeAuthDiscovery(serverUrl: string, definition?: ServerEntry, signal?: AbortSignal): Promise<AuthDiscovery> {
272
- // Discovery must not execute config commands or send their source text.
273
- const discoveryHeaders = definition?.headers
274
- ? Object.fromEntries(Object.entries(definition.headers).filter(([, value]) => !value.startsWith("!") || value.startsWith("!!")))
275
- : undefined
276
- const headers = new Headers(interpolateEnvRecord(discoveryHeaders))
277
- headers.set("content-type", "application/json")
321
+ // The preliminary probe is command-free; real SDK discovery resolves commands.
322
+ const serviceHeaders = resolveOAuthHeaders(definition?.headers, {
323
+ commands: false,
324
+ literal: definition ? isBuiltInAgentPlugin(definition, "headers") : false,
325
+ })
326
+ const probeFetch = createOAuthFetch(serverUrl, () => serviceHeaders, signal, { timeout: false })
327
+ const headers = new Headers({ "content-type": "application/json" })
278
328
 
279
329
  const controller = new AbortController()
280
330
  const discoverySignal = combineAbortSignals(signal, controller.signal)
@@ -283,7 +333,7 @@ async function probeAuthDiscovery(serverUrl: string, definition?: ServerEntry, s
283
333
  try {
284
334
  headers.set("accept", "application/json, text/event-stream")
285
335
 
286
- const response = await fetch(new URL(serverUrl), {
336
+ const response = await probeFetch(new URL(serverUrl), {
287
337
  method: "POST",
288
338
  headers,
289
339
  body: JSON.stringify({
@@ -310,13 +360,41 @@ async function probeAuthDiscovery(serverUrl: string, definition?: ServerEntry, s
310
360
  }
311
361
 
312
362
  type OAuthRedirectTarget =
313
- | { mode: "local"; port: number; callbackHost: string; callbackPath: string }
363
+ | {
364
+ mode: "local"
365
+ port?: number
366
+ callbackHost: string
367
+ callbackPath: string
368
+ dynamicPort: boolean
369
+ resolveRedirectUri: (port: number) => string
370
+ }
314
371
  | { mode: "manual" }
315
372
 
316
373
  function parseOAuthRedirectUri(redirectUri: string): OAuthRedirectTarget {
374
+ const dynamicPortPlaceholder = "{port}"
375
+ const placeholderCount = redirectUri.split(dynamicPortPlaceholder).length - 1
376
+ if (placeholderCount > 1) {
377
+ throw new Error("OAuth redirectUri may contain at most one {port} placeholder")
378
+ }
379
+
380
+ let parsedRedirectUri = redirectUri
381
+ const dynamicPort = placeholderCount === 1
382
+ if (dynamicPort) {
383
+ const authorityStart = redirectUri.indexOf("://") + 3
384
+ const pathStart = redirectUri.slice(authorityStart).search(/[/?#]/)
385
+ const authorityEnd = pathStart === -1 ? redirectUri.length : authorityStart + pathStart
386
+ const authority = redirectUri.slice(authorityStart, authorityEnd)
387
+ if (authorityStart < 3 || !authority.endsWith(`:${dynamicPortPlaceholder}`)) {
388
+ throw new Error("OAuth redirectUri {port} placeholder must be the loopback URI port")
389
+ }
390
+ // Parse with a real port, then replace the placeholder only after the OS
391
+ // assigns the callback listener's port.
392
+ parsedRedirectUri = redirectUri.replace(dynamicPortPlaceholder, "1")
393
+ }
394
+
317
395
  let url: URL
318
396
  try {
319
- url = new URL(redirectUri)
397
+ url = new URL(parsedRedirectUri)
320
398
  } catch (error) {
321
399
  throw new Error(`Invalid OAuth redirectUri: ${redirectUri}`, { cause: error })
322
400
  }
@@ -331,6 +409,9 @@ function parseOAuthRedirectUri(redirectUri: string): OAuthRedirectTarget {
331
409
 
332
410
  const hostname = url.hostname.toLowerCase()
333
411
  const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1"
412
+ if (dynamicPort && (url.protocol !== "http:" || !isLocalhost)) {
413
+ throw new Error("OAuth redirectUri {port} placeholder is allowed only for an http:// localhost or loopback URI")
414
+ }
334
415
  if (url.port) {
335
416
  const parsedPort = Number.parseInt(url.port, 10)
336
417
  if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
@@ -349,12 +430,17 @@ function parseOAuthRedirectUri(redirectUri: string): OAuthRedirectTarget {
349
430
  }
350
431
 
351
432
  const port = Number.parseInt(url.port, 10)
352
- if (!Number.isInteger(port) || port <= 0 || port > 65535) {
353
- throw new Error("OAuth localhost redirectUri must include an explicit numeric port")
354
- }
355
-
356
433
  const callbackHost = hostname === "[::1]" ? "::1" : hostname
357
- return { mode: "local", port, callbackHost, callbackPath: url.pathname }
434
+ return {
435
+ mode: "local",
436
+ ...(dynamicPort ? {} : { port }),
437
+ callbackHost,
438
+ callbackPath: url.pathname,
439
+ dynamicPort,
440
+ resolveRedirectUri: assignedPort => dynamicPort
441
+ ? redirectUri.replace(dynamicPortPlaceholder, String(assignedPort))
442
+ : redirectUri,
443
+ }
358
444
  }
359
445
 
360
446
  /**
@@ -366,8 +452,11 @@ export async function startAuth(
366
452
  serverUrl: string,
367
453
  definition?: ServerEntry,
368
454
  options: AuthenticateOptions = {},
455
+ operationAuthority?: OAuthAuthority,
369
456
  ): Promise<{ authorizationUrl: string }> {
370
457
  if (isServerDisabled(definition)) throw new Error(`MCP server "${serverName}" is disabled`)
458
+ const authority = operationAuthority ?? captureOAuthAuthority(serverName)
459
+ authority()
371
460
  const runtime = getRuntime(options)
372
461
  const runtimeState = getRuntimeState(runtime)
373
462
  const config = definition ? extractOAuthConfig(definition) : {}
@@ -378,21 +467,27 @@ export async function startAuth(
378
467
 
379
468
  if (config.grantType === "client_credentials") {
380
469
  const storedAuth = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
470
+ authority()
381
471
  if (storedAuth?.clientInfo && !storedAuth.tokens && !config.clientId) {
382
472
  clearClientInfo(serverName, authStorageOptions)
383
473
  clearCodeVerifier(serverName, authStorageOptions)
384
- await clearOAuthState(serverName, authStorageOptions)
474
+ clearOAuthState(serverName, authStorageOptions)
385
475
  }
386
476
 
477
+ authority()
387
478
  const authProvider = new McpOAuthProvider(serverName, serverUrl, config, {
388
479
  onRedirect: async () => {
389
480
  throw new Error("Browser redirect is not used for client_credentials flow")
390
481
  },
391
- }, authStorageOptions, runtime.signal)
482
+ }, authStorageOptions, runtime.signal, undefined, authority)
392
483
  try {
484
+ const fetchFn = createOAuthFetch(serverUrl, pluginAwareOAuthHeaders(definition), signal)
485
+ authProvider.setAuthFetch(fetchFn)
393
486
  const discovery = applyOAuthConfig(await probeAuthDiscovery(serverUrl, definition, signal), config)
487
+ authority()
394
488
  throwIfAborted(signal)
395
- const result = await abortable(runSdkAuth(authProvider, { serverUrl, ...discovery }), signal)
489
+ const result = await abortable(runSdkAuth(authProvider, { serverUrl, ...discovery, fetchFn }), signal)
490
+ authority()
396
491
  throwIfAborted(signal)
397
492
  if (result !== "AUTHORIZED") {
398
493
  throw new UnauthorizedError("Failed to authorize")
@@ -405,6 +500,7 @@ export async function startAuth(
405
500
 
406
501
  const existingPendingAuth = runtimeState.pendingAuths.get(getPendingAuthKey(serverName, authStorageOptions))
407
502
  if (existingPendingAuth?.serverUrl === serverUrl) {
503
+ existingPendingAuth.authority()
408
504
  return { authorizationUrl: existingPendingAuth.authorizationUrl }
409
505
  }
410
506
 
@@ -415,18 +511,30 @@ export async function startAuth(
415
511
  if (!manualRedirect) {
416
512
  try {
417
513
  await ensureCallbackServer({
418
- strictPort: Boolean(config.clientId) || config.redirectUri !== undefined,
514
+ strictPort: redirectTarget?.mode === "local"
515
+ ? !redirectTarget.dynamicPort
516
+ : Boolean(config.clientId),
419
517
  oauthState,
420
518
  reserveState: true,
421
519
  ...(redirectTarget?.mode === "local"
422
- ? { port: redirectTarget.port, callbackHost: redirectTarget.callbackHost, callbackPath: redirectTarget.callbackPath }
520
+ ? {
521
+ ...(redirectTarget.port !== undefined ? { port: redirectTarget.port } : {}),
522
+ callbackHost: redirectTarget.callbackHost,
523
+ callbackPath: redirectTarget.callbackPath,
524
+ }
423
525
  : {}),
424
526
  })
527
+ authority()
425
528
  throwIfAborted(signal)
529
+ if (redirectTarget?.mode === "local" && redirectTarget.dynamicPort) {
530
+ config.redirectUri = redirectTarget.resolveRedirectUri(getOAuthCallbackPort())
531
+ }
426
532
  } catch (error) {
427
533
  releaseCallbackServer(oauthState)
428
534
  try {
429
- await cleanupAndReleaseCallbackServerIfIdle(() => clearOAuthState(serverName, authStorageOptions))
535
+ await cleanupAndReleaseCallbackServerIfIdle(() => {
536
+ if (hasOAuthAuthority(authority)) clearOAuthState(serverName, authStorageOptions)
537
+ })
430
538
  } catch (cleanupError) {
431
539
  throw new AggregateError([error, cleanupError], "OAuth startup cleanup failed")
432
540
  }
@@ -435,41 +543,52 @@ export async function startAuth(
435
543
  }
436
544
 
437
545
  let capturedUrl: URL | undefined
546
+ authority()
438
547
  const authProvider = new McpOAuthProvider(serverName, serverUrl, config, {
439
548
  onRedirect: async (url) => {
440
549
  capturedUrl = url
441
550
  },
442
- }, authStorageOptions, runtime.signal, oauthState)
551
+ }, authStorageOptions, runtime.signal, oauthState, authority)
443
552
 
444
553
  try {
445
554
  const storedAuth = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
555
+ authority()
446
556
  if (storedAuth?.clientInfo && !config.clientId) {
447
557
  if (!storedAuth.tokens) {
448
558
  clearClientInfo(serverName, authStorageOptions)
449
559
  clearCodeVerifier(serverName, authStorageOptions)
450
- await clearOAuthState(serverName, authStorageOptions)
560
+ clearOAuthState(serverName, authStorageOptions)
451
561
  } else {
452
562
  const redirectUris = storedAuth.clientInfo.redirectUris
453
- if (!Array.isArray(redirectUris) || !redirectUris.includes(authProvider.redirectUrl ?? "")) {
563
+ const redirectUriMatches = Array.isArray(redirectUris)
564
+ && redirectUris.includes(authProvider.redirectUrl ?? "")
565
+ if (!redirectUriMatches && !storedAuth.tokens.refreshToken) {
566
+ // A stale redirect URI only blocks the interactive leg; refresh does
567
+ // not send redirect_uri, so keep refresh-capable credentials intact.
454
568
  clearClientInfo(serverName, authStorageOptions)
455
- clearTokens(serverName, authStorageOptions)
456
569
  clearCodeVerifier(serverName, authStorageOptions)
457
- await clearOAuthState(serverName, authStorageOptions)
570
+ clearOAuthState(serverName, authStorageOptions)
458
571
  }
459
572
  }
460
573
  }
461
574
 
462
575
  throwIfAborted(signal)
463
576
 
577
+ const getHeaders = pluginAwareOAuthHeaders(definition)
578
+ const fetchFn = createOAuthFetch(serverUrl, getHeaders, signal)
579
+ authProvider.setAuthFetch(fetchFn)
464
580
  const discovery = applyOAuthConfig(await probeAuthDiscovery(serverUrl, definition, signal), config)
581
+ authority()
465
582
  throwIfAborted(signal)
466
- const result = await abortable(runSdkAuth(authProvider, { serverUrl, ...discovery }), signal)
583
+ const result = await abortable(runSdkAuth(authProvider, { serverUrl, ...discovery, fetchFn }), signal)
584
+ authority()
467
585
  throwIfAborted(signal)
468
586
  if (result === "AUTHORIZED") {
469
587
  authProvider.deactivate()
470
588
  releaseCallbackServer(oauthState)
471
- await clearOAuthState(serverName, authStorageOptions)
589
+ clearOAuthState(serverName, authStorageOptions)
472
590
  await stopCallbackServerIfIdle()
591
+ authority()
473
592
  return { authorizationUrl: "" }
474
593
  }
475
594
  if (!capturedUrl) {
@@ -483,8 +602,11 @@ export async function startAuth(
483
602
  manualRedirect,
484
603
  ...(manualRedirect ? { manualCompletionController: new AbortController() } : {}),
485
604
  discovery,
605
+ getHeaders,
486
606
  authStorageOptions,
607
+ authority,
487
608
  }, oauthState, signal, generation)
609
+ authority()
488
610
  return { authorizationUrl: capturedUrl.toString() }
489
611
  } catch (error) {
490
612
  authProvider.deactivate()
@@ -507,7 +629,9 @@ async function setPendingAuth(
507
629
  ): Promise<void> {
508
630
  const state = getRuntimeState(runtime)
509
631
  const key = getPendingAuthKey(serverName, pendingAuth.authStorageOptions)
632
+ pendingAuth.authority()
510
633
  await clearPendingAuth(runtime, serverName, undefined, pendingAuth.authStorageOptions)
634
+ pendingAuth.authority()
511
635
  throwIfAborted(signal)
512
636
  if (generation !== state.generation) throw new Error("OAuth runtime stopped")
513
637
  state.pendingAuths.set(key, pendingAuth)
@@ -536,31 +660,38 @@ async function clearPendingAuth(
536
660
  ): Promise<void> {
537
661
  const state = getRuntimeState(runtime)
538
662
  const key = getPendingAuthKey(serverName, fallbackStorageOptions)
539
- const pendingAuth = state.pendingAuths.get(key)
540
- const authStorageOptions = pendingAuth?.authStorageOptions ?? fallbackStorageOptions
541
663
  const pendingState = state.pendingAuthStates.get(key)
542
- if (oauthState && pendingState && pendingState !== oauthState) return
543
-
544
- const timer = state.pendingAuthCleanupTimers.get(key)
545
- if (timer) {
546
- clearTimeout(timer)
547
- state.pendingAuthCleanupTimers.delete(key)
664
+ if (oauthState && pendingState !== oauthState) {
665
+ cancelPendingCallback(oauthState)
666
+ return
548
667
  }
549
668
 
550
- pendingAuth?.manualCompletionController?.abort(reason)
551
- pendingAuth?.authProvider.deactivate()
552
- state.pendingAuths.delete(key)
553
- state.pendingAuthStates.delete(key)
669
+ const { pendingAuth } = detachPending(state, key, reason)
670
+ const authStorageOptions = pendingAuth?.authStorageOptions ?? fallbackStorageOptions
554
671
  const stateToRelease = pendingState ?? oauthState
555
672
  if (stateToRelease) {
556
- cancelPendingCallback(stateToRelease)
557
- const storedState = await getOAuthState(serverName, authStorageOptions)
558
- if (storedState === stateToRelease) {
559
- await clearOAuthState(serverName, authStorageOptions)
673
+ if (!pendingState) cancelPendingCallback(stateToRelease)
674
+ if (pendingAuth && hasOAuthAuthority(pendingAuth.authority)) {
675
+ const storedState = getOAuthState(serverName, authStorageOptions)
676
+ if (storedState === stateToRelease) clearOAuthState(serverName, authStorageOptions)
560
677
  }
561
678
  }
562
679
  }
563
680
 
681
+ function detachPending(state: RuntimeState, key: string, reason: Error) {
682
+ const pendingAuth = state.pendingAuths.get(key)
683
+ const oauthState = state.pendingAuthStates.get(key)
684
+ const timer = state.pendingAuthCleanupTimers.get(key)
685
+ if (timer) clearTimeout(timer)
686
+ state.pendingAuthCleanupTimers.delete(key)
687
+ state.pendingAuths.delete(key)
688
+ state.pendingAuthStates.delete(key)
689
+ pendingAuth?.manualCompletionController?.abort(reason)
690
+ pendingAuth?.authProvider.deactivate()
691
+ if (oauthState) cancelPendingCallback(oauthState)
692
+ return { pendingAuth, oauthState }
693
+ }
694
+
564
695
  async function clearPendingAuthAndReleaseIfIdle(
565
696
  runtime: McpOAuthRuntime,
566
697
  serverName: string,
@@ -593,6 +724,17 @@ async function cleanupAndReleaseCallbackServerIfIdle(cleanup: () => void | Promi
593
724
  if (cleanupFailure) throw cleanupFailure.error
594
725
  }
595
726
 
727
+ function detachPendingAuthsForServer(serverName: string, reason: Error): void {
728
+ for (const runtime of activeRuntimes) {
729
+ const state = getRuntimeState(runtime)
730
+ for (const [key, pendingAuth] of state.pendingAuths) {
731
+ if (pendingAuth.serverName !== serverName) continue
732
+ if (state.pendingAuths.get(key) !== pendingAuth) continue
733
+ detachPending(state, key, reason)
734
+ }
735
+ }
736
+ }
737
+
596
738
  function getSearchParamsFromInput(input: string): URLSearchParams | undefined {
597
739
  try {
598
740
  const url = new URL(input)
@@ -773,6 +915,7 @@ export async function completeAuth(
773
915
  if (!pendingAuth) {
774
916
  throw new Error(`No pending OAuth flow for server: ${serverName}`)
775
917
  }
918
+ pendingAuth.authority()
776
919
 
777
920
  const oauthState = runtimeState.pendingAuthStates.get(key)
778
921
  throwIfAborted(signal)
@@ -780,7 +923,10 @@ export async function completeAuth(
780
923
  let keepPendingForRetry = false
781
924
  let caughtError: unknown
782
925
  try {
926
+ const fetchFn = createOAuthFetch(pendingAuth.serverUrl, pendingAuth.getHeaders, signal)
927
+ pendingAuth.authProvider.setAuthFetch(fetchFn)
783
928
  const discoveryState = await pendingAuth.authProvider.discoveryState()
929
+ pendingAuth.authority()
784
930
  const metadata = discoveryState?.authorizationServerMetadata
785
931
  const expectedIssuer = metadata?.issuer ?? discoveryState?.authorizationServerUrl
786
932
  const requiresIssuer = (metadata as { authorization_response_iss_parameter_supported?: unknown } | undefined)
@@ -801,7 +947,9 @@ export async function completeAuth(
801
947
  authorizationCode: code,
802
948
  ...(iss !== undefined ? { iss } : {}),
803
949
  ...pendingAuth.discovery,
950
+ fetchFn,
804
951
  }), signal)
952
+ pendingAuth.authority()
805
953
  throwIfAborted(signal)
806
954
  if (result !== "AUTHORIZED") {
807
955
  throw new UnauthorizedError("Failed to authorize")
@@ -820,6 +968,7 @@ export async function completeAuth(
820
968
  }
821
969
  throw cleanupError
822
970
  }
971
+ if (caughtError === undefined) pendingAuth.authority()
823
972
  }
824
973
  }
825
974
  }
@@ -839,15 +988,23 @@ export async function authenticate(
839
988
  options: AuthenticateOptions = {},
840
989
  ): Promise<AuthStatus> {
841
990
  if (isServerDisabled(definition)) throw new Error(`MCP server "${serverName}" is disabled`)
991
+ const authority = captureOAuthAuthority(serverName)
842
992
  const runtime = getRuntime(options)
843
993
  const runtimeState = getRuntimeState(runtime)
844
994
  const authStorageOptions = options.authStorageOptions ?? {}
845
995
  const signal = combineAbortSignals(runtime.signal, options.signal)
846
996
  throwIfAborted(signal)
847
- const authKey = `${serverName}|${serverUrl}|${getAuthBaseDir(authStorageOptions)}`
997
+ const authKey = JSON.stringify([serverName, serverUrl, ...getAuthStorageIdentity(authStorageOptions)])
848
998
  const inFlight = runtimeState.pendingAuthentications.get(authKey)
849
999
  if (inFlight) {
850
- return inFlight
1000
+ try {
1001
+ inFlight.authority()
1002
+ return inFlight.promise
1003
+ } catch {
1004
+ if (runtimeState.pendingAuthentications.get(authKey) === inFlight) {
1005
+ runtimeState.pendingAuthentications.delete(authKey)
1006
+ }
1007
+ }
851
1008
  }
852
1009
 
853
1010
  const operation = (async (): Promise<AuthStatus> => {
@@ -855,7 +1012,8 @@ export async function authenticate(
855
1012
  ...options,
856
1013
  ...(signal ? { signal } : {}),
857
1014
  runtime,
858
- })
1015
+ }, authority)
1016
+ authority()
859
1017
 
860
1018
  if (!authorizationUrl) {
861
1019
  return "authenticated"
@@ -897,7 +1055,11 @@ export async function authenticate(
897
1055
  console.log(`MCP Auth: Open this URL to authenticate ${serverName}:\n${authorizationUrl}`)
898
1056
  }
899
1057
  try {
900
- await abortable(open(authorizationUrl), signal)
1058
+ await abortable(Promise.resolve(
1059
+ options.openAuthorizationUrl
1060
+ ? options.openAuthorizationUrl(authorizationUrl)
1061
+ : open(authorizationUrl),
1062
+ ), signal)
901
1063
  } catch (error) {
902
1064
  if (isAbortError(error, signal)) throw error
903
1065
  console.warn(`MCP Auth: Failed to open browser for ${serverName}; waiting for manual callback`, { error })
@@ -934,12 +1096,15 @@ export async function authenticate(
934
1096
  }
935
1097
  })()
936
1098
 
937
- runtimeState.pendingAuthentications.set(authKey, operation)
1099
+ const pendingAuthentication = { promise: operation, authority }
1100
+ runtimeState.pendingAuthentications.set(authKey, pendingAuthentication)
938
1101
 
939
1102
  try {
940
- return await operation
1103
+ const result = await operation
1104
+ authority()
1105
+ return result
941
1106
  } finally {
942
- if (runtimeState.pendingAuthentications.get(authKey) === operation) {
1107
+ if (runtimeState.pendingAuthentications.get(authKey) === pendingAuthentication) {
943
1108
  runtimeState.pendingAuthentications.delete(authKey)
944
1109
  }
945
1110
  }
@@ -957,18 +1122,23 @@ export async function getValidToken(
957
1122
  serverUrl: string,
958
1123
  options: AuthenticateOptions = {},
959
1124
  ): Promise<StoredTokens | null> {
1125
+ const authority = captureOAuthAuthority(serverName)
960
1126
  const runtime = getRuntime(options)
961
1127
  const authStorageOptions = options.authStorageOptions ?? {}
962
1128
  const signal = combineAbortSignals(runtime.signal, options.signal)
963
1129
  throwIfAborted(signal)
964
1130
  const entry = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
1131
+ if (!hasOAuthAuthority(authority)) return null
965
1132
  throwIfAborted(signal)
966
1133
  if (!entry?.tokens) {
967
1134
  return null
968
1135
  }
969
1136
 
970
- const expired = await isTokenExpired(serverName, authStorageOptions)
1137
+ const expired = entry.tokens.expiresAt
1138
+ ? entry.tokens.expiresAt < Date.now() / 1000
1139
+ : false
971
1140
  if (expired === false) {
1141
+ authority()
972
1142
  return entry.tokens
973
1143
  }
974
1144
 
@@ -976,30 +1146,39 @@ export async function getValidToken(
976
1146
  console.log(`MCP Auth: Token expired for ${serverName}, attempting refresh`)
977
1147
 
978
1148
  try {
979
- const authProvider = new McpOAuthProvider(serverName, serverUrl, {}, {
1149
+ const config = options.definition ? extractOAuthConfig(options.definition) : {}
1150
+ const fetchFn = createOAuthFetch(serverUrl, pluginAwareOAuthHeaders(options.definition), signal)
1151
+ authority()
1152
+ const authProvider = new McpOAuthProvider(serverName, serverUrl, config, {
980
1153
  onRedirect: async () => {},
981
- }, authStorageOptions, runtime.signal)
1154
+ }, authStorageOptions, runtime.signal, undefined, authority)
982
1155
 
983
1156
  try {
1157
+ authProvider.setAuthFetch(fetchFn)
984
1158
  const clientInfo = await authProvider.clientInformation()
1159
+ authority()
985
1160
  throwIfAborted(signal)
986
1161
  if (!clientInfo) {
987
1162
  console.log(`MCP Auth: No client info for refresh for ${serverName}`)
988
1163
  return null
989
1164
  }
990
1165
 
991
- const discovery = await probeAuthDiscovery(serverUrl, undefined, signal)
1166
+ const discovery = applyOAuthConfig(await probeAuthDiscovery(serverUrl, options.definition, signal), config)
1167
+ authority()
992
1168
  throwIfAborted(signal)
993
1169
  const result = await abortable(runSdkAuth(authProvider, {
994
1170
  serverUrl,
995
1171
  ...discovery,
996
1172
  ...(options.skipIssuerMetadataValidation === true ? { skipIssuerMetadataValidation: true } : {}),
1173
+ fetchFn,
997
1174
  }), signal)
1175
+ authority()
998
1176
  throwIfAborted(signal)
999
1177
  if (result !== "AUTHORIZED") {
1000
1178
  return null
1001
1179
  }
1002
1180
  const refreshed = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
1181
+ authority()
1003
1182
  throwIfAborted(signal)
1004
1183
  return refreshed?.tokens ?? null
1005
1184
  } finally {
@@ -1007,12 +1186,13 @@ export async function getValidToken(
1007
1186
  }
1008
1187
  } catch (error) {
1009
1188
  if (isAbortError(error, signal) || error instanceof OAuthCredentialStoreError) throw error
1010
- console.error(`MCP Auth: Token refresh failed for ${serverName}`, { error })
1189
+ console.error(`MCP Auth: Token refresh failed for ${serverName}`)
1011
1190
  return null
1012
1191
  }
1013
1192
  }
1014
1193
 
1015
1194
  // No expiration info or no refresh token, assume valid
1195
+ authority()
1016
1196
  return entry.tokens
1017
1197
  }
1018
1198
 
@@ -1025,11 +1205,9 @@ export async function getValidToken(
1025
1205
  export async function getAuthStatus(serverName: string, options: AuthenticateOptions = {}): Promise<AuthStatus> {
1026
1206
  getRuntime(options)
1027
1207
  const authStorageOptions = options.authStorageOptions ?? {}
1028
- const hasTokens = await hasStoredTokens(serverName, authStorageOptions)
1029
- if (!hasTokens) return "not_authenticated"
1030
-
1031
- const expired = await isTokenExpired(serverName, authStorageOptions)
1032
- return expired ? "expired" : "authenticated"
1208
+ const entry = getAuthEntry(serverName, authStorageOptions)
1209
+ if (!entry?.tokens) return "not_authenticated"
1210
+ return entry.tokens.expiresAt && entry.tokens.expiresAt < Date.now() / 1000 ? "expired" : "authenticated"
1033
1211
  }
1034
1212
 
1035
1213
  /**
@@ -1042,17 +1220,18 @@ export async function removeAuth(serverName: string, options: AuthenticateOption
1042
1220
  const signal = combineAbortSignals(runtime.signal, options.signal)
1043
1221
  throwIfAborted(signal)
1044
1222
  const authStorageOptions = options.authStorageOptions ?? {}
1045
- const oauthState = await getOAuthState(serverName, authStorageOptions)
1046
- throwIfAborted(signal)
1047
- if (oauthState) {
1048
- cancelPendingCallback(oauthState)
1223
+ const releaseRevocation = beginOAuthRevocation(serverName)
1224
+ try {
1225
+ detachPendingAuthsForServer(serverName, new Error("Authorization cancelled by logout"))
1226
+ const storedOAuthState = getOAuthState(serverName, authStorageOptions)
1227
+ if (storedOAuthState) cancelPendingCallback(storedOAuthState)
1228
+ await stopCallbackServerIfIdle()
1229
+ throwIfAborted(signal)
1230
+ clearAllCredentials(serverName, authStorageOptions)
1231
+ console.log(`MCP Auth: Removed credentials for ${serverName}`)
1232
+ } finally {
1233
+ releaseRevocation()
1049
1234
  }
1050
- await clearPendingAuthAndReleaseIfIdle(runtime, serverName, oauthState, authStorageOptions)
1051
- throwIfAborted(signal)
1052
- clearAllCredentials(serverName, authStorageOptions)
1053
- await clearOAuthState(serverName, authStorageOptions)
1054
- throwIfAborted(signal)
1055
- console.log(`MCP Auth: Removed credentials for ${serverName}`)
1056
1235
  }
1057
1236
 
1058
1237
  /**