@humain/terminal 0.1.1 → 0.2.2
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.
- package/CHANGELOG.md +22 -0
- package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
- package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
- package/dist/bundle/chunks/{chunk-LYAMSPR2.js → chunk-ADQYVZ6A.js} +483 -506
- package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
- package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
- package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
- package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
- package/dist/bundle/chunks/chunk-NMPIM7MN.js +2 -0
- package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
- package/dist/bundle/chunks/compat-CN7WN6DA.js +2 -0
- package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
- package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
- package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
- package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
- package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
- package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
- package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
- package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
- package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/model-runtime.d.ts +21 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +34 -4
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +21 -8
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/subagent-env.d.ts +20 -0
- package/dist/core/tools/subagent-env.d.ts.map +1 -0
- package/dist/core/tools/subagent-env.js +47 -0
- package/dist/core/tools/subagent-env.js.map +1 -0
- package/dist/core/tools/subagent-scheduler.d.ts +36 -0
- package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
- package/dist/core/tools/subagent-scheduler.js +213 -0
- package/dist/core/tools/subagent-scheduler.js.map +1 -0
- package/dist/core/tools/subagent.d.ts +83 -3
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +215 -76
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
- package/dist/humain/mcp-adapter/vendor/README.md +125 -19
- package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
- package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
- package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
- package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
- package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
- package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
- package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
- package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
- package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
- package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
- package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
- package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
- package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
- package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
- package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
- package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
- package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
- package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
- package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
- package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
- package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
- package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
- package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
- package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
- package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
- package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
- package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
- package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
- package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
- package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
- package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
- package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
- package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
- package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
- package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
- package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
- package/dist/humain/mcp-adapter/vendor/package.json +13 -3
- package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
- package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
- package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
- package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
- package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
- package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
- package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
- package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
- package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
- package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
- package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
- package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
- package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
- package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
- package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/abortable.js +22 -0
- package/dist/humain/web-access/vendor/abortable.js.map +1 -0
- package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
- package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
- package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
- package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/credential-source.js +1 -0
- package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.js +20 -2
- package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
- package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/curator-run.js +38 -0
- package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.js +2 -0
- package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/extract.js +58 -11
- package/dist/humain/web-access/vendor/extract.js.map +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
- package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.js +12 -5
- package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
- package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/index.js +23 -19
- package/dist/humain/web-access/vendor/index.js.map +1 -1
- package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/openai-search.js +40 -14
- package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
- package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/page-query.js +6 -2
- package/dist/humain/web-access/vendor/page-query.js.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
- package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
- package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
- package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/serpapi.js +215 -0
- package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
- package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
- package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
- package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/storage.js +9 -0
- package/dist/humain/web-access/vendor/storage.js.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.js +35 -24
- package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
- package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/utils.js +5 -3
- package/dist/humain/web-access/vendor/utils.js.map +1 -1
- package/dist/modes/json-event.d.ts +8 -0
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +17 -0
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +3 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/environment-variables.md +9 -0
- package/docs/json.md +16 -0
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
- package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
- package/src/humain/mcp-adapter/upstream.json +6 -4
- package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
- package/src/humain/web-access/UPSTREAM.md +18 -8
- package/src/humain/web-access/upstream.json +6 -5
- 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 {
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
:
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
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
|
-
| {
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
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
|
-
? {
|
|
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(() =>
|
|
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
|
-
|
|
560
|
+
clearOAuthState(serverName, authStorageOptions)
|
|
451
561
|
} else {
|
|
452
562
|
const redirectUris = storedAuth.clientInfo.redirectUris
|
|
453
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
543
|
-
|
|
544
|
-
|
|
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
|
|
551
|
-
pendingAuth?.
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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 =
|
|
997
|
+
const authKey = JSON.stringify([serverName, serverUrl, ...getAuthStorageIdentity(authStorageOptions)])
|
|
848
998
|
const inFlight = runtimeState.pendingAuthentications.get(authKey)
|
|
849
999
|
if (inFlight) {
|
|
850
|
-
|
|
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(
|
|
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
|
-
|
|
1099
|
+
const pendingAuthentication = { promise: operation, authority }
|
|
1100
|
+
runtimeState.pendingAuthentications.set(authKey, pendingAuthentication)
|
|
938
1101
|
|
|
939
1102
|
try {
|
|
940
|
-
|
|
1103
|
+
const result = await operation
|
|
1104
|
+
authority()
|
|
1105
|
+
return result
|
|
941
1106
|
} finally {
|
|
942
|
-
if (runtimeState.pendingAuthentications.get(authKey) ===
|
|
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 =
|
|
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
|
|
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,
|
|
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}
|
|
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
|
|
1029
|
-
if (!
|
|
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
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
/**
|