@duckmind/dm-windows-x64 0.63.6 → 0.63.9

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/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +174 -11
  3. package/extensions/dm-mcp-adapter/OAUTH.md +3 -0
  4. package/extensions/dm-mcp-adapter/THIRD_PARTY_NOTICES.md +4 -0
  5. package/extensions/dm-mcp-adapter/abort.js +35 -0
  6. package/extensions/dm-mcp-adapter/agent-dir.js +45 -0
  7. package/extensions/dm-mcp-adapter/agent-plugin-loader.js +349 -0
  8. package/extensions/dm-mcp-adapter/app-bridge.bundle.js +5843 -0
  9. package/extensions/dm-mcp-adapter/cli.js +354 -0
  10. package/extensions/dm-mcp-adapter/commands.js +619 -0
  11. package/extensions/dm-mcp-adapter/config.js +1060 -0
  12. package/extensions/dm-mcp-adapter/consent-manager.js +60 -0
  13. package/extensions/dm-mcp-adapter/direct-tools.js +544 -0
  14. package/extensions/dm-mcp-adapter/dist/agent-dir.js +45 -0
  15. package/extensions/dm-mcp-adapter/dist/agent-plugin-loader.js +349 -0
  16. package/extensions/dm-mcp-adapter/dist/config.js +1060 -0
  17. package/extensions/dm-mcp-adapter/dist/mcp-bearer-store.js +311 -0
  18. package/extensions/dm-mcp-adapter/dist/metadata-cache.js +285 -0
  19. package/extensions/dm-mcp-adapter/dist/package-mcp-loader.js +144 -0
  20. package/extensions/dm-mcp-adapter/dist/resource-tools.js +7 -0
  21. package/extensions/dm-mcp-adapter/dist/types.js +247 -0
  22. package/extensions/dm-mcp-adapter/dist/ui-app-bridge-helpers.js +36 -0
  23. package/extensions/dm-mcp-adapter/dist/ui-stream-types.js +58 -0
  24. package/extensions/dm-mcp-adapter/dist/ui-tool-visibility.js +26 -0
  25. package/extensions/dm-mcp-adapter/dist/utils.js +307 -0
  26. package/extensions/dm-mcp-adapter/elicitation-handler.js +306 -0
  27. package/extensions/dm-mcp-adapter/error-signal.js +16 -0
  28. package/extensions/dm-mcp-adapter/errors.js +225 -0
  29. package/extensions/dm-mcp-adapter/failure-backoff.js +19 -0
  30. package/extensions/dm-mcp-adapter/glimpse-ui.js +61 -0
  31. package/extensions/dm-mcp-adapter/host-html-template.js +464 -0
  32. package/extensions/dm-mcp-adapter/index.js +1079 -0
  33. package/extensions/dm-mcp-adapter/init.js +624 -0
  34. package/extensions/dm-mcp-adapter/json-schema-validator.js +53 -0
  35. package/extensions/dm-mcp-adapter/lifecycle.js +385 -0
  36. package/extensions/dm-mcp-adapter/logger.js +115 -0
  37. package/extensions/dm-mcp-adapter/mcp-auth-flow.js +824 -0
  38. package/extensions/dm-mcp-adapter/mcp-auth.js +732 -0
  39. package/extensions/dm-mcp-adapter/mcp-bearer-store.js +311 -0
  40. package/extensions/dm-mcp-adapter/mcp-callback-server.js +388 -0
  41. package/extensions/dm-mcp-adapter/mcp-code.js +291 -0
  42. package/extensions/dm-mcp-adapter/mcp-keyring-helper.cjs +88 -0
  43. package/extensions/dm-mcp-adapter/mcp-oauth-provider.js +490 -0
  44. package/extensions/dm-mcp-adapter/mcp-output-guard.js +386 -0
  45. package/extensions/dm-mcp-adapter/mcp-panel.js +951 -0
  46. package/extensions/dm-mcp-adapter/mcp-probe.js +151 -0
  47. package/extensions/dm-mcp-adapter/mcp-references.js +263 -0
  48. package/extensions/dm-mcp-adapter/mcp-script-worker.mjs +126 -0
  49. package/extensions/dm-mcp-adapter/mcp-setup-panel.js +578 -0
  50. package/extensions/dm-mcp-adapter/mcp-status.js +80 -0
  51. package/extensions/dm-mcp-adapter/mcp-trace.js +215 -0
  52. package/extensions/dm-mcp-adapter/metadata-cache.js +292 -0
  53. package/extensions/dm-mcp-adapter/namespace-tools.js +155 -0
  54. package/extensions/dm-mcp-adapter/npx-resolver.js +465 -0
  55. package/extensions/dm-mcp-adapter/oauth-handler.js +17 -0
  56. package/extensions/dm-mcp-adapter/oauth.js +17 -0
  57. package/extensions/dm-mcp-adapter/onboarding-state.js +62 -0
  58. package/extensions/dm-mcp-adapter/package-mcp-loader.js +144 -0
  59. package/extensions/dm-mcp-adapter/package.json +68 -0
  60. package/extensions/dm-mcp-adapter/panel-keys.js +26 -0
  61. package/extensions/dm-mcp-adapter/prompts.js +258 -0
  62. package/extensions/dm-mcp-adapter/proxy-modes.js +1336 -0
  63. package/extensions/dm-mcp-adapter/request-headers-command.js +293 -0
  64. package/extensions/dm-mcp-adapter/resource-tools.js +7 -0
  65. package/extensions/dm-mcp-adapter/runtime-owner.js +79 -0
  66. package/extensions/dm-mcp-adapter/sampling-handler.js +222 -0
  67. package/extensions/dm-mcp-adapter/sandbox-proxy-template.js +190 -0
  68. package/extensions/dm-mcp-adapter/search-ranking.js +198 -0
  69. package/extensions/dm-mcp-adapter/server-manager.js +1258 -0
  70. package/extensions/dm-mcp-adapter/session-recovery.js +82 -0
  71. package/extensions/dm-mcp-adapter/skills/mcp-scripting/SKILL.md +39 -0
  72. package/extensions/dm-mcp-adapter/state.js +0 -0
  73. package/extensions/dm-mcp-adapter/test-runner.mjs +18 -0
  74. package/extensions/dm-mcp-adapter/tool-approval.js +151 -0
  75. package/extensions/dm-mcp-adapter/tool-metadata.js +247 -0
  76. package/extensions/dm-mcp-adapter/tool-registrar.js +212 -0
  77. package/extensions/dm-mcp-adapter/tool-result-renderer.js +355 -0
  78. package/extensions/dm-mcp-adapter/ts-shape.js +157 -0
  79. package/extensions/dm-mcp-adapter/types.js +264 -0
  80. package/extensions/dm-mcp-adapter/ui-app-bridge-helpers.js +36 -0
  81. package/extensions/dm-mcp-adapter/ui-resource-handler.js +209 -0
  82. package/extensions/dm-mcp-adapter/ui-server.js +815 -0
  83. package/extensions/dm-mcp-adapter/ui-session.js +455 -0
  84. package/extensions/dm-mcp-adapter/ui-stream-types.js +58 -0
  85. package/extensions/dm-mcp-adapter/ui-tool-visibility.js +26 -0
  86. package/extensions/dm-mcp-adapter/unix-socket-transport.js +83 -0
  87. package/extensions/dm-mcp-adapter/utils.js +307 -0
  88. package/package.json +13 -1
  89. package/extensions/dm-localllm-provider/detect.test.js +0 -1051
  90. package/extensions/dm-localllm-provider/index.test.js +0 -149
  91. package/extensions/dm-localllm-provider/keychain.test.js +0 -61
@@ -0,0 +1,490 @@
1
+ import {
2
+ UnauthorizedError
3
+ } from "@modelcontextprotocol/client";
4
+ import {
5
+ getAuthForUrl,
6
+ updateTokens,
7
+ updateClientInfo,
8
+ clearAllCredentials,
9
+ clearCodeVerifier,
10
+ invalidateAuthEntryCache
11
+ } from "./mcp-auth.js";
12
+ import { OAuthMetadataSchema, OpenIdProviderDiscoveryMetadataSchema } from "@modelcontextprotocol/core";
13
+ import { resolveCommandSecret } from "./utils.js";
14
+ import { getAppClientUri, getAppName } from "./agent-dir.js";
15
+ function defaultClientName() {
16
+ const app = getAppName();
17
+ return app === "pi" ? "DM Coding Agent" : app;
18
+ }
19
+ function defaultClientUri() {
20
+ const declared = getAppClientUri();
21
+ if (declared)
22
+ return declared;
23
+ return getAppName() === "pi" ? "https://github.com/nicobailon/dm-mcp-adapter" : undefined;
24
+ }
25
+ function issuersMatch(first, second) {
26
+ return first === second || first.endsWith("/") && first.slice(0, -1) === second || second.endsWith("/") && second.slice(0, -1) === first;
27
+ }
28
+ function toOAuthTokens(tokens) {
29
+ const result = {
30
+ access_token: tokens.accessToken,
31
+ token_type: "Bearer"
32
+ };
33
+ if (tokens.refreshToken !== undefined)
34
+ result.refresh_token = tokens.refreshToken;
35
+ if (tokens.expiresAt !== undefined)
36
+ result.expires_in = Math.max(0, Math.floor(tokens.expiresAt - Date.now() / 1000));
37
+ if (tokens.scope !== undefined)
38
+ result.scope = tokens.scope;
39
+ if (tokens.issuer !== undefined)
40
+ result.issuer = tokens.issuer;
41
+ return result;
42
+ }
43
+ const DEFAULT_OAUTH_CALLBACK_PORT = 19876;
44
+ const DEFAULT_OAUTH_CALLBACK_PATH = "/callback";
45
+ let configuredOAuthCallbackPort = DEFAULT_OAUTH_CALLBACK_PORT;
46
+ if (process.env.MCP_OAUTH_CALLBACK_PORT) {
47
+ const parsedPort = Number.parseInt(process.env.MCP_OAUTH_CALLBACK_PORT, 10);
48
+ if (Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535) {
49
+ configuredOAuthCallbackPort = parsedPort;
50
+ }
51
+ }
52
+ let oauthCallbackPort = configuredOAuthCallbackPort;
53
+ let oauthCallbackPath = DEFAULT_OAUTH_CALLBACK_PATH;
54
+ export function getConfiguredOAuthCallbackPort() {
55
+ return configuredOAuthCallbackPort;
56
+ }
57
+ export function getOAuthCallbackPort() {
58
+ return oauthCallbackPort;
59
+ }
60
+ export function setOAuthCallbackPort(port) {
61
+ oauthCallbackPort = port;
62
+ }
63
+ export function getOAuthCallbackPath() {
64
+ return oauthCallbackPath;
65
+ }
66
+ export function setOAuthCallbackPath(path) {
67
+ oauthCallbackPath = path.startsWith("/") ? path : `/${path}`;
68
+ }
69
+ const reservedAuthorizationParams = new Set([
70
+ "client_id",
71
+ "code_challenge",
72
+ "code_challenge_method",
73
+ "redirect_uri",
74
+ "resource",
75
+ "response_type",
76
+ "scope",
77
+ "state"
78
+ ]);
79
+ function addAuthorizationParams(authorizationUrl, params) {
80
+ if (!params)
81
+ return authorizationUrl;
82
+ const nextUrl = new URL(authorizationUrl.toString());
83
+ for (const [key, value] of Object.entries(params)) {
84
+ if (reservedAuthorizationParams.has(key) || nextUrl.searchParams.has(key)) {
85
+ throw new Error(`OAuth authorizationParams.${key} cannot override an authorization flow parameter`);
86
+ }
87
+ nextUrl.searchParams.set(key, value);
88
+ }
89
+ return nextUrl;
90
+ }
91
+ function inferIssuerFromMetadataUrl(metadataUrl) {
92
+ const url = new URL(metadataUrl);
93
+ const pathname = url.pathname.replace(/\/+$/, "") || "/";
94
+ const oauthPrefix = "/.well-known/oauth-authorization-server";
95
+ if (pathname === oauthPrefix || pathname.startsWith(`${oauthPrefix}/`)) {
96
+ const issuerPath = pathname.slice(oauthPrefix.length) || "/";
97
+ return new URL(issuerPath, url.origin).toString();
98
+ }
99
+ const oidcPath = "/.well-known/openid-configuration";
100
+ if (pathname === oidcPath || pathname.startsWith(`${oidcPath}/`)) {
101
+ const issuerPath = pathname.slice(oidcPath.length) || "/";
102
+ return new URL(issuerPath, url.origin).toString();
103
+ }
104
+ if (pathname.endsWith(oidcPath)) {
105
+ const issuerPath = pathname.slice(0, -oidcPath.length) || "/";
106
+ return new URL(issuerPath, url.origin).toString();
107
+ }
108
+ return;
109
+ }
110
+ function validateConfiguredIssuer(metadataUrl, issuer, skipIssuerValidation) {
111
+ let parsedIssuer;
112
+ try {
113
+ parsedIssuer = new URL(issuer);
114
+ } catch (error) {
115
+ throw new Error("OAuth authorization-server metadata issuer must be an absolute URL", { cause: error });
116
+ }
117
+ if (parsedIssuer.protocol !== "http:" && parsedIssuer.protocol !== "https:") {
118
+ throw new Error("OAuth authorization-server metadata issuer must use http:// or https://");
119
+ }
120
+ const expectedIssuer = inferIssuerFromMetadataUrl(metadataUrl);
121
+ if (skipIssuerValidation)
122
+ return;
123
+ if (expectedIssuer !== undefined && !issuersMatch(expectedIssuer, issuer)) {
124
+ throw new Error(`OAuth authorization-server metadata issuer does not match authServerMetadataUrl: expected ${expectedIssuer}`);
125
+ }
126
+ if (expectedIssuer === undefined && !issuersMatch(new URL(metadataUrl).origin, issuer)) {
127
+ throw new Error(`OAuth authorization-server metadata issuer does not match authServerMetadataUrl origin: expected ${new URL(metadataUrl).origin}`);
128
+ }
129
+ }
130
+ async function loadConfiguredDiscoveryState(metadataUrl, serverUrl, skipIssuerValidation, signal) {
131
+ const response = await fetch(metadataUrl, {
132
+ headers: { accept: "application/json" },
133
+ ...signal !== undefined ? { signal } : {}
134
+ });
135
+ if (!response.ok) {
136
+ await response.text().catch(() => {});
137
+ throw new Error(`OAuth authServerMetadataUrl request failed with HTTP ${response.status}`);
138
+ }
139
+ const payload = await response.json();
140
+ const oauthResult = OAuthMetadataSchema.safeParse(payload);
141
+ const metadata = oauthResult.success ? oauthResult.data : OpenIdProviderDiscoveryMetadataSchema.parse(payload);
142
+ validateConfiguredIssuer(metadataUrl, metadata.issuer, skipIssuerValidation);
143
+ const resource = new URL(serverUrl);
144
+ resource.hash = "";
145
+ return {
146
+ authorizationServerUrl: metadata.issuer,
147
+ authorizationServerMetadata: metadata,
148
+ resourceMetadata: { resource: resource.toString() }
149
+ };
150
+ }
151
+
152
+ export class McpOAuthProvider {
153
+ serverName;
154
+ serverUrl;
155
+ config;
156
+ callbacks;
157
+ storageOptions;
158
+ runtimeSignal;
159
+ redirectUrlSnapshot;
160
+ active = true;
161
+ flowClientInfo;
162
+ flowCodeVerifier;
163
+ flowDiscoveryState;
164
+ flowIssuerMismatch = false;
165
+ flowState;
166
+ invalidatedAccessToken;
167
+ invalidatedClientId;
168
+ lastObservedClientId;
169
+ lastSavedAccessToken;
170
+ pendingAuthAccessToken;
171
+ constructor(serverName, serverUrl, config, callbacks, storageOptions = {}, runtimeSignal, initialState) {
172
+ this.serverName = serverName;
173
+ this.serverUrl = serverUrl;
174
+ this.config = config;
175
+ this.callbacks = callbacks;
176
+ this.storageOptions = storageOptions;
177
+ this.runtimeSignal = runtimeSignal;
178
+ this.flowState = initialState;
179
+ this.redirectUrlSnapshot = config.grantType === "client_credentials" ? undefined : config.redirectUri ?? `http://localhost:${getOAuthCallbackPort()}${getOAuthCallbackPath()}`;
180
+ }
181
+ get usesClientCredentials() {
182
+ return this.config.grantType === "client_credentials";
183
+ }
184
+ get discoveredIssuer() {
185
+ return this.flowDiscoveryState?.authorizationServerMetadata?.issuer ?? this.flowDiscoveryState?.authorizationServerUrl;
186
+ }
187
+ deactivate() {
188
+ this.active = false;
189
+ this.invalidatedAccessToken = undefined;
190
+ this.invalidatedClientId = undefined;
191
+ this.lastObservedClientId = undefined;
192
+ this.lastSavedAccessToken = undefined;
193
+ this.pendingAuthAccessToken = undefined;
194
+ }
195
+ assertStoredIssuerBindings(entry, issuer) {
196
+ if (this.flowIssuerMismatch) {
197
+ throw new Error(`OAuth authorization server issuer changed for ${this.serverName}; clear credentials before authenticating again`);
198
+ }
199
+ if (!entry || !issuer)
200
+ return;
201
+ const storedIssuers = [entry.clientInfo?.issuer, entry.tokens?.issuer].filter((storedIssuer) => storedIssuer !== undefined);
202
+ if (storedIssuers.some((storedIssuer) => !issuersMatch(storedIssuer, issuer))) {
203
+ this.flowIssuerMismatch = true;
204
+ throw new Error(`OAuth authorization server issuer changed for ${this.serverName}; clear credentials before authenticating again`);
205
+ }
206
+ }
207
+ throwIfInactive() {
208
+ if (!this.active)
209
+ throw new Error("OAuth flow is no longer active");
210
+ this.runtimeSignal?.throwIfAborted();
211
+ }
212
+ get redirectUrl() {
213
+ return this.redirectUrlSnapshot;
214
+ }
215
+ get clientUri() {
216
+ return this.config.clientUri ?? defaultClientUri();
217
+ }
218
+ get clientMetadata() {
219
+ if (this.usesClientCredentials) {
220
+ return {
221
+ client_name: this.config.clientName ?? defaultClientName(),
222
+ ...this.clientUri !== undefined ? { client_uri: this.clientUri } : {},
223
+ ...this.config.logoUri !== undefined ? { logo_uri: this.config.logoUri } : {},
224
+ redirect_uris: [],
225
+ grant_types: ["client_credentials"],
226
+ token_endpoint_auth_method: this.config.clientSecret ? "client_secret_post" : "none"
227
+ };
228
+ }
229
+ const redirectUrl = this.redirectUrl;
230
+ if (!redirectUrl) {
231
+ throw new Error("redirectUrl is required for authorization_code flow");
232
+ }
233
+ return {
234
+ redirect_uris: [redirectUrl],
235
+ client_name: this.config.clientName ?? defaultClientName(),
236
+ ...this.clientUri !== undefined ? { client_uri: this.clientUri } : {},
237
+ ...this.config.logoUri !== undefined ? { logo_uri: this.config.logoUri } : {},
238
+ grant_types: ["authorization_code", "refresh_token"],
239
+ response_types: ["code"],
240
+ token_endpoint_auth_method: this.config.clientSecret ? "client_secret_post" : "none",
241
+ ...this.config.scope !== undefined ? { scope: this.config.scope } : {}
242
+ };
243
+ }
244
+ async clientInformation() {
245
+ if (this.invalidatedClientId !== undefined) {
246
+ invalidateAuthEntryCache(this.serverName);
247
+ }
248
+ const issuer = this.discoveredIssuer;
249
+ const stored = await getAuthForUrl(this.serverName, this.serverUrl, this.storageOptions);
250
+ this.assertStoredIssuerBindings(stored, issuer);
251
+ if (this.config.clientId) {
252
+ const storedClient = stored?.clientInfo?.clientId === this.config.clientId ? stored.clientInfo : undefined;
253
+ if (issuer && (storedClient?.issuer !== issuer || storedClient.configPreRegistered !== true)) {
254
+ updateClientInfo(this.serverName, { clientId: this.config.clientId, issuer, configPreRegistered: true }, this.serverUrl, this.storageOptions);
255
+ }
256
+ const clientSecret = this.config.clientSecret?.startsWith("!") ? resolveCommandSecret(this.config.clientSecret, `MCP server "${this.serverName}" OAuth clientSecret`) : this.config.clientSecret;
257
+ return {
258
+ client_id: this.config.clientId,
259
+ client_secret: clientSecret,
260
+ ...issuer !== undefined ? { issuer } : {}
261
+ };
262
+ }
263
+ const clientInfo = this.flowClientInfo ?? stored?.clientInfo;
264
+ if (clientInfo?.clientId === this.invalidatedClientId)
265
+ return;
266
+ if (clientInfo) {
267
+ this.invalidatedClientId = undefined;
268
+ const isConfigStub = clientInfo.configPreRegistered === true || clientInfo.clientSecret === undefined && clientInfo.clientIdIssuedAt === undefined && clientInfo.clientSecretExpiresAt === undefined && clientInfo.redirectUris === undefined;
269
+ if (isConfigStub) {
270
+ return;
271
+ }
272
+ if (clientInfo.clientSecretExpiresAt && clientInfo.clientSecretExpiresAt < Date.now() / 1000) {
273
+ return;
274
+ }
275
+ if (issuer && clientInfo.issuer && !issuersMatch(clientInfo.issuer, issuer)) {
276
+ return;
277
+ }
278
+ if (issuer && clientInfo.issuer === undefined) {
279
+ clientInfo.issuer = issuer;
280
+ this.flowClientInfo = clientInfo;
281
+ updateClientInfo(this.serverName, clientInfo, this.serverUrl, this.storageOptions);
282
+ }
283
+ this.lastObservedClientId = clientInfo.clientId;
284
+ return {
285
+ client_id: clientInfo.clientId,
286
+ client_secret: clientInfo.clientSecret,
287
+ ...clientInfo.clientIdIssuedAt !== undefined ? { client_id_issued_at: clientInfo.clientIdIssuedAt } : {},
288
+ ...clientInfo.clientSecretExpiresAt !== undefined ? { client_secret_expires_at: clientInfo.clientSecretExpiresAt } : {},
289
+ ...clientInfo.redirectUris !== undefined ? { redirect_uris: clientInfo.redirectUris } : {},
290
+ ...clientInfo.issuer !== undefined ? { issuer: clientInfo.issuer } : {}
291
+ };
292
+ }
293
+ return;
294
+ }
295
+ async saveClientInformation(info) {
296
+ this.throwIfInactive();
297
+ const issuer = this.discoveredIssuer ?? info.issuer;
298
+ if (this.config.clientId && info.client_id === this.config.clientId) {
299
+ updateClientInfo(this.serverName, {
300
+ clientId: info.client_id,
301
+ ...issuer !== undefined ? { issuer } : {},
302
+ configPreRegistered: true
303
+ }, this.serverUrl, this.storageOptions);
304
+ return;
305
+ }
306
+ const redirectUris = ("redirect_uris" in info ? info.redirect_uris : undefined) ?? (this.redirectUrl ? [this.redirectUrl] : undefined);
307
+ const clientInfo = {
308
+ clientId: info.client_id,
309
+ ...info.client_secret !== undefined ? { clientSecret: info.client_secret } : {},
310
+ ...info.client_id_issued_at !== undefined ? { clientIdIssuedAt: info.client_id_issued_at } : {},
311
+ ...info.client_secret_expires_at !== undefined ? { clientSecretExpiresAt: info.client_secret_expires_at } : {},
312
+ ...redirectUris !== undefined ? { redirectUris } : {},
313
+ ...issuer !== undefined ? { issuer } : {}
314
+ };
315
+ this.flowClientInfo = clientInfo;
316
+ this.invalidatedClientId = undefined;
317
+ this.lastObservedClientId = clientInfo.clientId;
318
+ updateClientInfo(this.serverName, clientInfo, this.serverUrl, this.storageOptions);
319
+ }
320
+ async tokens(ctx) {
321
+ if (this.invalidatedAccessToken !== undefined) {
322
+ invalidateAuthEntryCache(this.serverName);
323
+ }
324
+ const entry = await getAuthForUrl(this.serverName, this.serverUrl, this.storageOptions);
325
+ if (!entry?.tokens || entry.tokens.accessToken === this.invalidatedAccessToken)
326
+ return;
327
+ this.invalidatedAccessToken = undefined;
328
+ const issuer = this.discoveredIssuer;
329
+ this.assertStoredIssuerBindings(entry, issuer);
330
+ if (issuer && entry.tokens.issuer === undefined) {
331
+ entry.tokens.issuer = issuer;
332
+ updateTokens(this.serverName, entry.tokens, this.serverUrl, this.storageOptions);
333
+ }
334
+ if (ctx !== undefined) {
335
+ this.pendingAuthAccessToken = entry.tokens.accessToken;
336
+ }
337
+ return toOAuthTokens(entry.tokens);
338
+ }
339
+ async saveTokens(tokens) {
340
+ const issuer = this.discoveredIssuer ?? tokens.issuer;
341
+ const storedTokens = {
342
+ accessToken: tokens.access_token,
343
+ ...tokens.refresh_token !== undefined ? { refreshToken: tokens.refresh_token } : {},
344
+ ...tokens.expires_in !== undefined ? { expiresAt: Date.now() / 1000 + tokens.expires_in } : {},
345
+ ...tokens.scope !== undefined ? { scope: tokens.scope } : {},
346
+ ...issuer !== undefined ? { issuer } : {}
347
+ };
348
+ this.throwIfInactive();
349
+ updateTokens(this.serverName, storedTokens, this.serverUrl, this.storageOptions);
350
+ this.invalidatedAccessToken = undefined;
351
+ this.lastSavedAccessToken = storedTokens.accessToken;
352
+ this.flowDiscoveryState = undefined;
353
+ }
354
+ async redirectToAuthorization(authorizationUrl) {
355
+ if (this.usesClientCredentials) {
356
+ throw new Error("redirectToAuthorization is not used for client_credentials flow");
357
+ }
358
+ this.throwIfInactive();
359
+ if (!this.flowState) {
360
+ throw new UnauthorizedError(`Re-authentication required for MCP server: ${this.serverName}`);
361
+ }
362
+ await this.callbacks.onRedirect(addAuthorizationParams(authorizationUrl, this.config.authorizationParams));
363
+ }
364
+ async saveCodeVerifier(codeVerifier) {
365
+ this.throwIfInactive();
366
+ this.flowCodeVerifier = codeVerifier;
367
+ }
368
+ async codeVerifier() {
369
+ if (this.usesClientCredentials) {
370
+ throw new Error("codeVerifier is not used for client_credentials flow");
371
+ }
372
+ this.throwIfInactive();
373
+ if (!this.flowCodeVerifier) {
374
+ throw new Error(`No code verifier saved for MCP server: ${this.serverName}`);
375
+ }
376
+ return this.flowCodeVerifier;
377
+ }
378
+ async saveDiscoveryState(state) {
379
+ this.throwIfInactive();
380
+ this.flowDiscoveryState = structuredClone(state);
381
+ }
382
+ async discoveryState() {
383
+ this.throwIfInactive();
384
+ if (!this.flowDiscoveryState && this.config.authServerMetadataUrl !== undefined) {
385
+ this.flowDiscoveryState = await loadConfiguredDiscoveryState(this.config.authServerMetadataUrl, this.serverUrl, this.config.skipIssuerMetadataValidation === true, this.runtimeSignal);
386
+ }
387
+ return this.flowDiscoveryState ? structuredClone(this.flowDiscoveryState) : undefined;
388
+ }
389
+ async saveState(state) {
390
+ this.throwIfInactive();
391
+ this.flowState = state;
392
+ }
393
+ async state() {
394
+ if (this.usesClientCredentials) {
395
+ throw new Error("state is not used for client_credentials flow");
396
+ }
397
+ this.throwIfInactive();
398
+ if (!this.flowState) {
399
+ throw new UnauthorizedError(`Re-authentication required for MCP server: ${this.serverName}`);
400
+ }
401
+ return this.flowState;
402
+ }
403
+ async invalidateCredentials(type) {
404
+ this.throwIfInactive();
405
+ switch (type) {
406
+ case "all":
407
+ this.flowClientInfo = undefined;
408
+ this.flowCodeVerifier = undefined;
409
+ this.flowDiscoveryState = undefined;
410
+ this.flowIssuerMismatch = false;
411
+ this.flowState = undefined;
412
+ this.invalidatedAccessToken = undefined;
413
+ this.invalidatedClientId = undefined;
414
+ this.lastObservedClientId = undefined;
415
+ this.lastSavedAccessToken = undefined;
416
+ this.pendingAuthAccessToken = undefined;
417
+ clearAllCredentials(this.serverName, this.storageOptions);
418
+ break;
419
+ case "client":
420
+ this.invalidatedClientId = this.lastObservedClientId;
421
+ this.lastObservedClientId = undefined;
422
+ this.flowClientInfo = undefined;
423
+ invalidateAuthEntryCache(this.serverName);
424
+ break;
425
+ case "tokens":
426
+ this.invalidatedAccessToken = this.pendingAuthAccessToken ?? this.lastSavedAccessToken;
427
+ this.lastSavedAccessToken = undefined;
428
+ this.pendingAuthAccessToken = undefined;
429
+ invalidateAuthEntryCache(this.serverName);
430
+ break;
431
+ case "verifier":
432
+ clearCodeVerifier(this.serverName, this.storageOptions);
433
+ break;
434
+ case "discovery":
435
+ this.flowDiscoveryState = undefined;
436
+ break;
437
+ }
438
+ }
439
+ addClientAuthentication = async (headers, params, _url, metadata) => {
440
+ this.throwIfInactive();
441
+ if (params.get("grant_type") === "authorization_code" && !params.has("scope") && this.config.scope) {
442
+ params.set("scope", this.config.scope);
443
+ }
444
+ const clientInfo = await this.clientInformation();
445
+ this.throwIfInactive();
446
+ if (!clientInfo) {
447
+ return;
448
+ }
449
+ const supportedMethods = metadata?.token_endpoint_auth_methods_supported ?? [];
450
+ const hasClientSecret = clientInfo.client_secret !== undefined;
451
+ let authMethod;
452
+ if (supportedMethods.length === 0) {
453
+ authMethod = hasClientSecret ? "client_secret_post" : "none";
454
+ } else if (hasClientSecret && supportedMethods.includes("client_secret_basic")) {
455
+ authMethod = "client_secret_basic";
456
+ } else if (hasClientSecret && supportedMethods.includes("client_secret_post")) {
457
+ authMethod = "client_secret_post";
458
+ } else if (supportedMethods.includes("none")) {
459
+ authMethod = "none";
460
+ } else {
461
+ authMethod = hasClientSecret ? "client_secret_post" : "none";
462
+ }
463
+ if (authMethod === "client_secret_basic") {
464
+ if (!clientInfo.client_secret) {
465
+ throw new Error("client_secret_basic authentication requires a client_secret");
466
+ }
467
+ headers.set("Authorization", `Basic ${Buffer.from(`${clientInfo.client_id}:${clientInfo.client_secret}`).toString("base64")}`);
468
+ return;
469
+ }
470
+ if (!params.has("client_id")) {
471
+ params.set("client_id", clientInfo.client_id);
472
+ }
473
+ if (authMethod === "client_secret_post" && clientInfo.client_secret && !params.has("client_secret")) {
474
+ params.set("client_secret", clientInfo.client_secret);
475
+ }
476
+ };
477
+ prepareTokenRequest(scope) {
478
+ if (!this.usesClientCredentials) {
479
+ return;
480
+ }
481
+ const params = new URLSearchParams({ grant_type: "client_credentials" });
482
+ const requestedScope = scope ?? this.config.scope;
483
+ if (requestedScope) {
484
+ params.set("scope", requestedScope);
485
+ }
486
+ return params;
487
+ }
488
+ }
489
+
490
+ export { DEFAULT_OAUTH_CALLBACK_PORT, DEFAULT_OAUTH_CALLBACK_PATH };