toolcraft 0.0.23 → 0.0.25
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/README.md +2 -2
- package/dist/cli.compile-check.js +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +50 -13
- package/dist/error-report.js +32 -3
- package/dist/human-in-loop/approval-tasks.d.ts +1 -0
- package/dist/human-in-loop/approval-tasks.js +7 -5
- package/dist/human-in-loop/approvals-commands.js +51 -8
- package/dist/human-in-loop/runner.js +24 -19
- package/dist/human-in-loop/state-machine.d.ts +3 -3
- package/dist/human-in-loop/state-machine.js +13 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -1
- package/dist/mcp-proxy.js +85 -19
- package/dist/mcp.compile-check.js +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +50 -8
- package/dist/renderer.js +119 -13
- package/dist/sdk.compile-check.js +1 -0
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.js +56 -11
- package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +1 -1
- package/node_modules/@poe-code/agent-defs/dist/registry.js +22 -11
- package/node_modules/@poe-code/agent-defs/package.json +1 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +5 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +1 -1
- package/node_modules/@poe-code/agent-human-in-loop/package.json +1 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +1 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +41 -92
- package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +4 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +14 -2
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +11 -4
- package/node_modules/@poe-code/agent-mcp-config/package.json +1 -1
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +200 -22
- package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +7 -1
- package/node_modules/@poe-code/config-mutations/dist/formats/index.js +1 -1
- package/node_modules/@poe-code/config-mutations/dist/formats/json.js +11 -7
- package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +12 -9
- package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +12 -9
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +11 -1
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +10 -1
- package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +25 -1
- package/node_modules/@poe-code/config-mutations/dist/types.d.ts +12 -2
- package/node_modules/@poe-code/config-mutations/package.json +1 -1
- package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
- package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
- package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
- package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
- package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
- package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
- package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
- package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
- package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
- package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
- package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
- package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/design-system/dist/index.js +2 -1
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
- package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
- package/node_modules/@poe-code/design-system/package.json +2 -1
- package/node_modules/@poe-code/process-runner/dist/docker/args.d.ts +1 -0
- package/node_modules/@poe-code/process-runner/dist/docker/args.js +11 -3
- package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +377 -130
- package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +78 -10
- package/node_modules/@poe-code/process-runner/dist/docker/env-file.d.ts +6 -0
- package/node_modules/@poe-code/process-runner/dist/docker/env-file.js +49 -0
- package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
- package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +21 -5
- package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
- package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
- package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +6 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +61 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +503 -0
- package/node_modules/@poe-code/process-runner/package.json +1 -1
- package/node_modules/@poe-code/task-list/README.md +0 -2
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
- package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/task-list/dist/index.js +2 -0
- package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/move.js +215 -0
- package/node_modules/@poe-code/task-list/dist/open.js +3 -4
- package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
- package/node_modules/@poe-code/task-list/dist/state.js +9 -0
- package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
- package/node_modules/@poe-code/task-list/package.json +1 -2
- package/node_modules/auth-store/dist/create-secret-store.js +4 -1
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +8 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +104 -8
- package/node_modules/auth-store/dist/index.d.ts +1 -1
- package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
- package/node_modules/auth-store/dist/keychain-store.js +18 -16
- package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
- package/node_modules/auth-store/dist/provider-store.js +55 -7
- package/node_modules/auth-store/dist/types.d.ts +3 -1
- package/node_modules/auth-store/package.json +2 -1
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
- package/node_modules/mcp-oauth/package.json +1 -0
- package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
- package/node_modules/tiny-mcp-client/dist/internal.d.ts +9 -4
- package/node_modules/tiny-mcp-client/dist/internal.js +244 -66
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
- package/node_modules/tiny-mcp-client/package.json +2 -1
- package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
- package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
- package/node_modules/tiny-mcp-client/src/internal.ts +287 -76
- package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +32 -0
- package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
- package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
- package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
- package/package.json +10 -12
- package/node_modules/@poe-code/file-lock/README.md +0 -52
- package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
- package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
- package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
- package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
- package/node_modules/@poe-code/file-lock/package.json +0 -23
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { PassThrough } from "node:stream";
|
|
3
|
-
import { createOAuthClientProvider, OAuthError, } from "
|
|
3
|
+
import { createOAuthClientProvider, OAuthError, } from "mcp-oauth";
|
|
4
4
|
import { OAuthMetadataDiscovery, parseBearerWwwAuthenticateHeader, } from "./oauth-discovery.js";
|
|
5
5
|
export { OAuthMetadataDiscovery, discoverOAuthMetadata, parseBearerWwwAuthenticateHeader, resolveAuthorizationServerMetadataUrl, resolveProtectedResourceMetadataUrl, } from "./oauth-discovery.js";
|
|
6
|
-
export { createAuthStoreSessionStore, createDefaultOAuthClientProvider, } from "
|
|
6
|
+
export { createAuthStoreSessionStore, createDefaultOAuthClientProvider, } from "mcp-oauth";
|
|
7
7
|
const MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
8
8
|
export class McpClient {
|
|
9
9
|
currentState = "disconnected";
|
|
10
10
|
currentServerCapabilities = null;
|
|
11
|
+
currentClientCapabilities = null;
|
|
11
12
|
currentServerInfo = null;
|
|
12
13
|
currentInstructions;
|
|
14
|
+
subscribedResourceUris = new Set();
|
|
15
|
+
activeProgressTokens = new Map();
|
|
13
16
|
options;
|
|
14
17
|
transport = null;
|
|
15
18
|
messageLayer = null;
|
|
@@ -20,7 +23,9 @@ export class McpClient {
|
|
|
20
23
|
return this.currentState;
|
|
21
24
|
}
|
|
22
25
|
get serverCapabilities() {
|
|
23
|
-
return this.currentServerCapabilities
|
|
26
|
+
return this.currentServerCapabilities === null
|
|
27
|
+
? null
|
|
28
|
+
: structuredClone(this.currentServerCapabilities);
|
|
24
29
|
}
|
|
25
30
|
get serverInfo() {
|
|
26
31
|
return this.currentServerInfo;
|
|
@@ -44,22 +49,23 @@ export class McpClient {
|
|
|
44
49
|
if (this.currentState !== "disconnected" && this.currentState !== "closed") {
|
|
45
50
|
throw new Error("MCP client is already connected");
|
|
46
51
|
}
|
|
52
|
+
this.currentServerCapabilities = null;
|
|
53
|
+
this.currentClientCapabilities = null;
|
|
54
|
+
this.currentServerInfo = null;
|
|
55
|
+
this.currentInstructions = undefined;
|
|
56
|
+
this.subscribedResourceUris.clear();
|
|
57
|
+
this.activeProgressTokens.clear();
|
|
47
58
|
const transportClosedReason = transport.closed
|
|
48
59
|
.then((closedEvent) => closedEvent.reason)
|
|
49
60
|
.catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
50
|
-
const messageLayer = new JsonRpcMessageLayer(transport.readable, transport.writable,
|
|
61
|
+
const messageLayer = new JsonRpcMessageLayer(transport.readable, transport.writable, this.options.requestTimeoutMs, transportClosedReason);
|
|
51
62
|
const { onSamplingRequest, onRootsList, onToolsChanged, onResourcesChanged, onResourceUpdated, onPromptsChanged, onLog, onProgress, } = this.options;
|
|
52
63
|
messageLayer.onRequest("ping", () => ({}));
|
|
53
64
|
if (onSamplingRequest !== undefined) {
|
|
54
65
|
messageLayer.onRequest("sampling/createMessage", (params) => onSamplingRequest(params));
|
|
55
66
|
}
|
|
56
|
-
if (onRootsList !== undefined) {
|
|
57
|
-
messageLayer.onRequest("roots/list", async () => ({
|
|
58
|
-
roots: await onRootsList(),
|
|
59
|
-
}));
|
|
60
|
-
}
|
|
61
67
|
messageLayer.onNotification("notifications/tools/list_changed", async () => {
|
|
62
|
-
if (onToolsChanged === undefined) {
|
|
68
|
+
if (onToolsChanged === undefined || this.currentServerCapabilities?.tools?.listChanged !== true) {
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
await onToolsChanged();
|
|
@@ -78,7 +84,7 @@ export class McpClient {
|
|
|
78
84
|
return;
|
|
79
85
|
}
|
|
80
86
|
const { uri } = params;
|
|
81
|
-
if (typeof uri !== "string") {
|
|
87
|
+
if (typeof uri !== "string" || !this.subscribedResourceUris.has(uri)) {
|
|
82
88
|
return;
|
|
83
89
|
}
|
|
84
90
|
await onResourceUpdated(uri);
|
|
@@ -113,7 +119,9 @@ export class McpClient {
|
|
|
113
119
|
return;
|
|
114
120
|
}
|
|
115
121
|
const { progressToken, progress } = params;
|
|
116
|
-
if (!isRequestId(progressToken) ||
|
|
122
|
+
if (!isRequestId(progressToken) ||
|
|
123
|
+
typeof progress !== "number" ||
|
|
124
|
+
!this.activeProgressTokens.has(progressToken)) {
|
|
117
125
|
return;
|
|
118
126
|
}
|
|
119
127
|
const progressParams = {
|
|
@@ -138,6 +146,8 @@ export class McpClient {
|
|
|
138
146
|
this.transport = transport;
|
|
139
147
|
this.messageLayer = messageLayer;
|
|
140
148
|
this.currentState = "initializing";
|
|
149
|
+
this.subscribedResourceUris.clear();
|
|
150
|
+
this.activeProgressTokens.clear();
|
|
141
151
|
transport.closed
|
|
142
152
|
.then((closedEvent) => {
|
|
143
153
|
if (this.transport !== transport) {
|
|
@@ -169,20 +179,43 @@ export class McpClient {
|
|
|
169
179
|
...(capabilities.roots ?? {}),
|
|
170
180
|
};
|
|
171
181
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
this.currentClientCapabilities = structuredClone(capabilities);
|
|
183
|
+
try {
|
|
184
|
+
const initializeResultValue = await messageLayer.sendRequest("initialize", {
|
|
185
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
186
|
+
clientInfo: this.options.clientInfo,
|
|
187
|
+
capabilities,
|
|
188
|
+
});
|
|
189
|
+
if (!isInitializeResult(initializeResultValue)) {
|
|
190
|
+
throw new McpError(ERROR_INVALID_REQUEST, "Invalid initialize result");
|
|
191
|
+
}
|
|
192
|
+
const initializeResult = initializeResultValue;
|
|
193
|
+
if (initializeResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
|
|
194
|
+
throw new McpError(ERROR_INVALID_REQUEST, `Unsupported protocol version: ${initializeResult.protocolVersion}`);
|
|
195
|
+
}
|
|
196
|
+
this.currentServerCapabilities = structuredClone(initializeResult.capabilities);
|
|
197
|
+
this.currentServerInfo = { ...initializeResult.serverInfo };
|
|
198
|
+
this.currentInstructions = initializeResult.instructions;
|
|
199
|
+
if (onRootsList !== undefined) {
|
|
200
|
+
messageLayer.onRequest("roots/list", async () => ({
|
|
201
|
+
roots: await onRootsList(),
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
messageLayer.sendNotification("notifications/initialized");
|
|
205
|
+
this.currentState = "ready";
|
|
206
|
+
return initializeResult;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (this.transport === transport) {
|
|
210
|
+
const reason = error instanceof Error ? error : new Error(String(error));
|
|
211
|
+
messageLayer.dispose(reason);
|
|
212
|
+
transport.dispose(reason);
|
|
213
|
+
this.messageLayer = null;
|
|
214
|
+
this.transport = null;
|
|
215
|
+
this.currentState = "disconnected";
|
|
216
|
+
}
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
186
219
|
}
|
|
187
220
|
getServerCapabilitiesOrThrow() {
|
|
188
221
|
if (this.currentServerCapabilities === null) {
|
|
@@ -197,7 +230,11 @@ export class McpClient {
|
|
|
197
230
|
throw new Error("Server does not support tools");
|
|
198
231
|
}
|
|
199
232
|
const requestParams = params.cursor === undefined ? undefined : { cursor: params.cursor };
|
|
200
|
-
|
|
233
|
+
const result = await messageLayer.sendRequest("tools/list", requestParams);
|
|
234
|
+
if (!isToolsListResult(result)) {
|
|
235
|
+
throw new McpError(ERROR_INVALID_REQUEST, "Invalid tools/list result");
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
201
238
|
}
|
|
202
239
|
async callTool(params, options = {}) {
|
|
203
240
|
const messageLayer = this.getMessageLayerOrThrow();
|
|
@@ -205,6 +242,9 @@ export class McpClient {
|
|
|
205
242
|
if (serverCapabilities.tools === undefined) {
|
|
206
243
|
throw new Error("Server does not support tools");
|
|
207
244
|
}
|
|
245
|
+
if (options.signal?.aborted) {
|
|
246
|
+
throw options.signal.reason;
|
|
247
|
+
}
|
|
208
248
|
const requestParams = options.progressToken === undefined
|
|
209
249
|
? params
|
|
210
250
|
: {
|
|
@@ -213,37 +253,65 @@ export class McpClient {
|
|
|
213
253
|
progressToken: options.progressToken,
|
|
214
254
|
},
|
|
215
255
|
};
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const signal = options.signal;
|
|
226
|
-
let abortListener;
|
|
227
|
-
const abortPromise = new Promise((_, reject) => {
|
|
228
|
-
const rejectWithAbortReason = () => {
|
|
229
|
-
if (requestId !== undefined) {
|
|
230
|
-
messageLayer.sendNotification("notifications/cancelled", { requestId });
|
|
256
|
+
if (options.progressToken !== undefined) {
|
|
257
|
+
this.activeProgressTokens.set(options.progressToken, (this.activeProgressTokens.get(options.progressToken) ?? 0) + 1);
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
let requestId;
|
|
261
|
+
let cancellationSent = false;
|
|
262
|
+
const sendCancellationNotification = () => {
|
|
263
|
+
if (requestId === undefined || cancellationSent) {
|
|
264
|
+
return;
|
|
231
265
|
}
|
|
232
|
-
|
|
266
|
+
cancellationSent = true;
|
|
267
|
+
messageLayer.sendNotification("notifications/cancelled", { requestId });
|
|
233
268
|
};
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
269
|
+
const requestPromise = messageLayer.sendRequest("tools/call", requestParams, {
|
|
270
|
+
onRequestId: (nextRequestId) => {
|
|
271
|
+
requestId = nextRequestId;
|
|
272
|
+
},
|
|
273
|
+
onTimeout: sendCancellationNotification,
|
|
274
|
+
}).then((result) => {
|
|
275
|
+
if (!isCallToolResult(result)) {
|
|
276
|
+
throw new McpError(ERROR_INVALID_REQUEST, "Invalid tool result");
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
});
|
|
280
|
+
if (options.signal === undefined) {
|
|
281
|
+
return await requestPromise;
|
|
282
|
+
}
|
|
283
|
+
const signal = options.signal;
|
|
284
|
+
let abortListener;
|
|
285
|
+
const abortPromise = new Promise((_, reject) => {
|
|
286
|
+
const rejectWithAbortReason = () => {
|
|
287
|
+
sendCancellationNotification();
|
|
288
|
+
reject(signal.reason);
|
|
289
|
+
};
|
|
290
|
+
abortListener = rejectWithAbortReason;
|
|
291
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
292
|
+
if (signal.aborted) {
|
|
293
|
+
signal.removeEventListener("abort", abortListener);
|
|
294
|
+
rejectWithAbortReason();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
try {
|
|
298
|
+
return (await Promise.race([requestPromise, abortPromise]));
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
if (abortListener !== undefined) {
|
|
302
|
+
signal.removeEventListener("abort", abortListener);
|
|
303
|
+
}
|
|
239
304
|
}
|
|
240
|
-
});
|
|
241
|
-
try {
|
|
242
|
-
return (await Promise.race([requestPromise, abortPromise]));
|
|
243
305
|
}
|
|
244
306
|
finally {
|
|
245
|
-
if (
|
|
246
|
-
|
|
307
|
+
if (options.progressToken !== undefined) {
|
|
308
|
+
const activeCount = this.activeProgressTokens.get(options.progressToken);
|
|
309
|
+
if (activeCount === 1) {
|
|
310
|
+
this.activeProgressTokens.delete(options.progressToken);
|
|
311
|
+
}
|
|
312
|
+
else if (activeCount !== undefined) {
|
|
313
|
+
this.activeProgressTokens.set(options.progressToken, activeCount - 1);
|
|
314
|
+
}
|
|
247
315
|
}
|
|
248
316
|
}
|
|
249
317
|
}
|
|
@@ -280,6 +348,7 @@ export class McpClient {
|
|
|
280
348
|
throw new Error("Server does not support resource subscriptions");
|
|
281
349
|
}
|
|
282
350
|
await messageLayer.sendRequest("resources/subscribe", { uri });
|
|
351
|
+
this.subscribedResourceUris.add(uri);
|
|
283
352
|
}
|
|
284
353
|
async unsubscribe(uri) {
|
|
285
354
|
const messageLayer = this.getMessageLayerOrThrow();
|
|
@@ -288,6 +357,7 @@ export class McpClient {
|
|
|
288
357
|
throw new Error("Server does not support resource subscriptions");
|
|
289
358
|
}
|
|
290
359
|
await messageLayer.sendRequest("resources/unsubscribe", { uri });
|
|
360
|
+
this.subscribedResourceUris.delete(uri);
|
|
291
361
|
}
|
|
292
362
|
async listPrompts(params = {}) {
|
|
293
363
|
const messageLayer = this.getMessageLayerOrThrow();
|
|
@@ -332,6 +402,9 @@ export class McpClient {
|
|
|
332
402
|
}
|
|
333
403
|
async sendRootsChanged() {
|
|
334
404
|
const messageLayer = this.getMessageLayerOrThrow();
|
|
405
|
+
if (this.currentClientCapabilities?.roots?.listChanged !== true) {
|
|
406
|
+
throw new Error("Client did not advertise roots list changes");
|
|
407
|
+
}
|
|
335
408
|
messageLayer.sendNotification("notifications/roots/list_changed");
|
|
336
409
|
}
|
|
337
410
|
async ping() {
|
|
@@ -347,6 +420,12 @@ export class McpClient {
|
|
|
347
420
|
this.transport?.dispose(closeError);
|
|
348
421
|
this.messageLayer = null;
|
|
349
422
|
this.transport = null;
|
|
423
|
+
this.currentServerCapabilities = null;
|
|
424
|
+
this.currentClientCapabilities = null;
|
|
425
|
+
this.currentServerInfo = null;
|
|
426
|
+
this.currentInstructions = undefined;
|
|
427
|
+
this.subscribedResourceUris.clear();
|
|
428
|
+
this.activeProgressTokens.clear();
|
|
350
429
|
this.currentState = "closed";
|
|
351
430
|
}
|
|
352
431
|
}
|
|
@@ -1543,16 +1622,29 @@ export class HttpTransport {
|
|
|
1543
1622
|
this.disposed = true;
|
|
1544
1623
|
this.abortInFlightFetches();
|
|
1545
1624
|
this.cancelOpenSseReaders();
|
|
1546
|
-
this.terminateSession();
|
|
1547
1625
|
if (!this.writeStream.destroyed && !this.writeStream.writableEnded) {
|
|
1548
1626
|
this.writeStream.end();
|
|
1549
1627
|
}
|
|
1550
1628
|
if (!this.readStream.destroyed && !this.readStream.writableEnded) {
|
|
1551
1629
|
this.readStream.end();
|
|
1552
1630
|
}
|
|
1631
|
+
void this.closeWithSessionTermination(reason);
|
|
1632
|
+
}
|
|
1633
|
+
async closeWithSessionTermination(reason) {
|
|
1634
|
+
let closeReason = reason;
|
|
1635
|
+
if (this.sessionId !== undefined) {
|
|
1636
|
+
const sessionId = this.sessionId;
|
|
1637
|
+
this.sessionId = undefined;
|
|
1638
|
+
try {
|
|
1639
|
+
await this.sendSessionTerminationRequest(sessionId);
|
|
1640
|
+
}
|
|
1641
|
+
catch (error) {
|
|
1642
|
+
closeReason = error instanceof Error ? error : new Error(String(error));
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1553
1645
|
const resolveClosed = this.resolveClosed;
|
|
1554
1646
|
this.resolveClosed = undefined;
|
|
1555
|
-
resolveClosed?.({ reason });
|
|
1647
|
+
resolveClosed?.({ reason: closeReason });
|
|
1556
1648
|
}
|
|
1557
1649
|
abortInFlightFetches() {
|
|
1558
1650
|
for (const abortController of this.inFlightFetchAbortControllers) {
|
|
@@ -1598,7 +1690,9 @@ export class HttpTransport {
|
|
|
1598
1690
|
await this.throwForPostHttpError(response);
|
|
1599
1691
|
this.captureSessionId(response);
|
|
1600
1692
|
this.maybeOpenGetSseStream();
|
|
1601
|
-
|
|
1693
|
+
void this.forwardResponseMessages(response).catch((error) => {
|
|
1694
|
+
this.dispose(error instanceof Error ? error : new Error(String(error)));
|
|
1695
|
+
});
|
|
1602
1696
|
}
|
|
1603
1697
|
}
|
|
1604
1698
|
async createPostHeaders() {
|
|
@@ -1607,6 +1701,7 @@ export class HttpTransport {
|
|
|
1607
1701
|
headers.set("Content-Type", "application/json");
|
|
1608
1702
|
if (this.sessionId !== undefined) {
|
|
1609
1703
|
headers.set("Mcp-Session-Id", this.sessionId);
|
|
1704
|
+
headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
|
|
1610
1705
|
}
|
|
1611
1706
|
return this.authorizeRequestHeaders(headers);
|
|
1612
1707
|
}
|
|
@@ -1615,6 +1710,7 @@ export class HttpTransport {
|
|
|
1615
1710
|
headers.set("Accept", "text/event-stream");
|
|
1616
1711
|
if (this.sessionId !== undefined) {
|
|
1617
1712
|
headers.set("Mcp-Session-Id", this.sessionId);
|
|
1713
|
+
headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
|
|
1618
1714
|
}
|
|
1619
1715
|
if (this.lastEventId !== undefined) {
|
|
1620
1716
|
headers.set("Last-Event-ID", this.lastEventId);
|
|
@@ -1624,6 +1720,7 @@ export class HttpTransport {
|
|
|
1624
1720
|
async createDeleteHeaders(sessionId) {
|
|
1625
1721
|
const headers = new Headers(this.headers);
|
|
1626
1722
|
headers.set("Mcp-Session-Id", sessionId);
|
|
1723
|
+
headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
|
|
1627
1724
|
return this.authorizeRequestHeaders(headers);
|
|
1628
1725
|
}
|
|
1629
1726
|
async authorizeRequestHeaders(headers) {
|
|
@@ -1639,6 +1736,9 @@ export class HttpTransport {
|
|
|
1639
1736
|
if (sessionId === null || sessionId.length === 0) {
|
|
1640
1737
|
return;
|
|
1641
1738
|
}
|
|
1739
|
+
if (this.sessionId !== undefined && this.sessionId !== sessionId) {
|
|
1740
|
+
throw new Error("HTTP transport response changed active session ID");
|
|
1741
|
+
}
|
|
1642
1742
|
this.sessionId = sessionId;
|
|
1643
1743
|
}
|
|
1644
1744
|
maybeOpenGetSseStream() {
|
|
@@ -1653,22 +1753,20 @@ export class HttpTransport {
|
|
|
1653
1753
|
this.dispose(error instanceof Error ? error : new Error(String(error)));
|
|
1654
1754
|
});
|
|
1655
1755
|
}
|
|
1656
|
-
terminateSession() {
|
|
1657
|
-
if (this.sessionId === undefined) {
|
|
1658
|
-
return;
|
|
1659
|
-
}
|
|
1660
|
-
const sessionId = this.sessionId;
|
|
1661
|
-
this.sessionId = undefined;
|
|
1662
|
-
this.sendSessionTerminationRequest(sessionId).catch(() => undefined);
|
|
1663
|
-
}
|
|
1664
1756
|
async sendSessionTerminationRequest(sessionId) {
|
|
1665
1757
|
const response = await this.fetchImpl(this.url, {
|
|
1666
1758
|
method: "DELETE",
|
|
1667
1759
|
headers: await this.createDeleteHeaders(sessionId),
|
|
1668
1760
|
});
|
|
1669
|
-
if (response.status === 405) {
|
|
1761
|
+
if (response.status === 405 || response.ok) {
|
|
1670
1762
|
return;
|
|
1671
1763
|
}
|
|
1764
|
+
const responseBody = (await response.text()).trim();
|
|
1765
|
+
const statusDescriptor = `${response.status} ${response.statusText}`.trim();
|
|
1766
|
+
const message = responseBody.length === 0
|
|
1767
|
+
? `HTTP transport DELETE failed (${statusDescriptor})`
|
|
1768
|
+
: `HTTP transport DELETE failed (${statusDescriptor}): ${responseBody}`;
|
|
1769
|
+
throw new Error(message);
|
|
1672
1770
|
}
|
|
1673
1771
|
async consumeGetSseStream() {
|
|
1674
1772
|
const response = await this.fetchWithOAuthRetry({
|
|
@@ -1678,6 +1776,18 @@ export class HttpTransport {
|
|
|
1678
1776
|
if (response.status === 405) {
|
|
1679
1777
|
throw new HttpTransportGetSseNotSupportedError();
|
|
1680
1778
|
}
|
|
1779
|
+
if (response.status === 404) {
|
|
1780
|
+
this.sessionId = undefined;
|
|
1781
|
+
throw new Error("HTTP transport session expired (GET 404 response)");
|
|
1782
|
+
}
|
|
1783
|
+
if (!response.ok) {
|
|
1784
|
+
const responseBody = (await response.text()).trim();
|
|
1785
|
+
const statusDescriptor = `${response.status} ${response.statusText}`.trim();
|
|
1786
|
+
const message = responseBody.length === 0
|
|
1787
|
+
? `HTTP transport GET failed (${statusDescriptor})`
|
|
1788
|
+
: `HTTP transport GET failed (${statusDescriptor}): ${responseBody}`;
|
|
1789
|
+
throw new Error(message);
|
|
1790
|
+
}
|
|
1681
1791
|
const contentType = response.headers.get("Content-Type");
|
|
1682
1792
|
if (contentType === null) {
|
|
1683
1793
|
return;
|
|
@@ -1688,7 +1798,9 @@ export class HttpTransport {
|
|
|
1688
1798
|
if (!this.disposed && this.sessionId !== undefined && this.lastEventId !== undefined) {
|
|
1689
1799
|
this.maybeOpenGetSseStream();
|
|
1690
1800
|
}
|
|
1801
|
+
return;
|
|
1691
1802
|
}
|
|
1803
|
+
return;
|
|
1692
1804
|
}
|
|
1693
1805
|
async throwForPostHttpError(response) {
|
|
1694
1806
|
if (response.status < 400) {
|
|
@@ -1744,7 +1856,9 @@ export class HttpTransport {
|
|
|
1744
1856
|
}
|
|
1745
1857
|
if (normalizedContentType.includes("application/json")) {
|
|
1746
1858
|
await this.forwardJsonResponseMessage(response);
|
|
1859
|
+
return;
|
|
1747
1860
|
}
|
|
1861
|
+
throw new Error("HTTP transport POST returned an unsupported response content type");
|
|
1748
1862
|
}
|
|
1749
1863
|
async forwardSseResponseMessages(response) {
|
|
1750
1864
|
if (response.body === null) {
|
|
@@ -1866,8 +1980,12 @@ function normalizeLine(line) {
|
|
|
1866
1980
|
}
|
|
1867
1981
|
export async function* readLines(stream) {
|
|
1868
1982
|
let buffer = "";
|
|
1983
|
+
const decoder = new TextDecoder();
|
|
1869
1984
|
for await (const chunk of stream) {
|
|
1870
|
-
buffer +=
|
|
1985
|
+
buffer +=
|
|
1986
|
+
chunk instanceof Uint8Array
|
|
1987
|
+
? decoder.decode(chunk, { stream: true })
|
|
1988
|
+
: decoder.decode() + String(chunk);
|
|
1871
1989
|
while (true) {
|
|
1872
1990
|
const newlineIndex = buffer.indexOf("\n");
|
|
1873
1991
|
if (newlineIndex === -1) {
|
|
@@ -1878,6 +1996,7 @@ export async function* readLines(stream) {
|
|
|
1878
1996
|
yield normalizeLine(line);
|
|
1879
1997
|
}
|
|
1880
1998
|
}
|
|
1999
|
+
buffer += decoder.decode();
|
|
1881
2000
|
if (buffer.length > 0) {
|
|
1882
2001
|
yield normalizeLine(buffer);
|
|
1883
2002
|
}
|
|
@@ -2035,6 +2154,7 @@ export class JsonRpcMessageLayer {
|
|
|
2035
2154
|
return new Promise((resolve, reject) => {
|
|
2036
2155
|
const timeout = setTimeout(() => {
|
|
2037
2156
|
this.pendingRequests.delete(id);
|
|
2157
|
+
options.onTimeout?.(id);
|
|
2038
2158
|
reject(new Error(`JSON-RPC request "${method}" timed out after ${timeoutMs}ms`));
|
|
2039
2159
|
}, timeoutMs);
|
|
2040
2160
|
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
@@ -2253,6 +2373,64 @@ export class JsonRpcMessageLayer {
|
|
|
2253
2373
|
function isObjectRecord(value) {
|
|
2254
2374
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2255
2375
|
}
|
|
2376
|
+
function isInitializeResult(value) {
|
|
2377
|
+
if (!isObjectRecord(value) || typeof value.protocolVersion !== "string") {
|
|
2378
|
+
return false;
|
|
2379
|
+
}
|
|
2380
|
+
if (!isServerCapabilities(value.capabilities)) {
|
|
2381
|
+
return false;
|
|
2382
|
+
}
|
|
2383
|
+
if (!isObjectRecord(value.serverInfo)
|
|
2384
|
+
|| typeof value.serverInfo.name !== "string"
|
|
2385
|
+
|| value.serverInfo.name.length === 0
|
|
2386
|
+
|| typeof value.serverInfo.version !== "string"
|
|
2387
|
+
|| value.serverInfo.version.length === 0) {
|
|
2388
|
+
return false;
|
|
2389
|
+
}
|
|
2390
|
+
return value.instructions === undefined || typeof value.instructions === "string";
|
|
2391
|
+
}
|
|
2392
|
+
function isServerCapabilities(value) {
|
|
2393
|
+
if (!isObjectRecord(value)) {
|
|
2394
|
+
return false;
|
|
2395
|
+
}
|
|
2396
|
+
for (const capability of ["prompts", "resources", "tools", "logging", "completions", "experimental"]) {
|
|
2397
|
+
if (value[capability] !== undefined && !isObjectRecord(value[capability])) {
|
|
2398
|
+
return false;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
return true;
|
|
2402
|
+
}
|
|
2403
|
+
function isCallToolResult(value) {
|
|
2404
|
+
if (!isObjectRecord(value) || !Array.isArray(value.content)) {
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
if (value.isError !== undefined && typeof value.isError !== "boolean") {
|
|
2408
|
+
return false;
|
|
2409
|
+
}
|
|
2410
|
+
return value.content.every(isContentItem);
|
|
2411
|
+
}
|
|
2412
|
+
function isToolsListResult(value) {
|
|
2413
|
+
return isObjectRecord(value)
|
|
2414
|
+
&& Array.isArray(value.tools)
|
|
2415
|
+
&& (value.nextCursor === undefined || typeof value.nextCursor === "string");
|
|
2416
|
+
}
|
|
2417
|
+
function isContentItem(value) {
|
|
2418
|
+
if (!isObjectRecord(value)) {
|
|
2419
|
+
return false;
|
|
2420
|
+
}
|
|
2421
|
+
if (value.type === "text") {
|
|
2422
|
+
return typeof value.text === "string";
|
|
2423
|
+
}
|
|
2424
|
+
if (value.type === "image" || value.type === "audio") {
|
|
2425
|
+
return typeof value.data === "string" && typeof value.mimeType === "string";
|
|
2426
|
+
}
|
|
2427
|
+
if (value.type !== "resource" || !isObjectRecord(value.resource)) {
|
|
2428
|
+
return false;
|
|
2429
|
+
}
|
|
2430
|
+
return typeof value.resource.uri === "string"
|
|
2431
|
+
&& (value.resource.mimeType === undefined || typeof value.resource.mimeType === "string")
|
|
2432
|
+
&& (typeof value.resource.text === "string" || typeof value.resource.blob === "string");
|
|
2433
|
+
}
|
|
2256
2434
|
function hasOwn(value, property) {
|
|
2257
2435
|
return Object.prototype.hasOwnProperty.call(value, property);
|
|
2258
2436
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OAuthAuthorizationServerMetadata, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthUnauthorizedChallenge } from "
|
|
1
|
+
import type { OAuthAuthorizationServerMetadata, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthUnauthorizedChallenge } from "mcp-oauth";
|
|
2
2
|
export type { OAuthAuthorizationServerMetadata, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthUnauthorizedChallenge, };
|
|
3
3
|
export interface OAuthDiscoveryCache {
|
|
4
4
|
get(resourceUrl: string): OAuthDiscoveryResult | null | undefined | Promise<OAuthDiscoveryResult | null | undefined>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonicalizeResourceIndicator } from "
|
|
1
|
+
import { canonicalizeResourceIndicator } from "mcp-oauth";
|
|
2
2
|
function defaultOAuthMetadataFetch(input, init) {
|
|
3
3
|
return fetch(input, init);
|
|
4
4
|
}
|
|
@@ -129,16 +129,13 @@ export class OAuthMetadataDiscovery {
|
|
|
129
129
|
const cacheKey = canonicalizeResourceIndicator(resourceUrl);
|
|
130
130
|
const resourceMetadataLocation = resolveProtectedResourceMetadataUrl(cacheKey, resourceMetadataUrl);
|
|
131
131
|
const memoryCachedResult = this.memoryCache.get(cacheKey);
|
|
132
|
-
if (memoryCachedResult !== undefined &&
|
|
133
|
-
(resourceMetadataUrl === undefined
|
|
134
|
-
|| memoryCachedResult.resourceMetadataUrl === resourceMetadataLocation)) {
|
|
132
|
+
if (memoryCachedResult !== undefined && resourceMetadataUrl === undefined) {
|
|
135
133
|
return memoryCachedResult;
|
|
136
134
|
}
|
|
137
135
|
const sharedCachedResult = await this.cache?.get(cacheKey);
|
|
138
136
|
if (sharedCachedResult !== null &&
|
|
139
137
|
sharedCachedResult !== undefined &&
|
|
140
|
-
|
|
141
|
-
|| sharedCachedResult.resourceMetadataUrl === resourceMetadataLocation)) {
|
|
138
|
+
resourceMetadataUrl === undefined) {
|
|
142
139
|
this.memoryCache.set(cacheKey, sharedCachedResult);
|
|
143
140
|
return sharedCachedResult;
|
|
144
141
|
}
|
|
@@ -340,7 +337,7 @@ export function parseBearerWwwAuthenticateHeader(headerValue) {
|
|
|
340
337
|
break;
|
|
341
338
|
}
|
|
342
339
|
index = skipOptionalWhitespace(headerValue, scheme.nextIndex);
|
|
343
|
-
const params =
|
|
340
|
+
const params = Object.create(null);
|
|
344
341
|
if (index < headerValue.length && headerValue[index] !== ",") {
|
|
345
342
|
const token68 = readToken68(headerValue, index);
|
|
346
343
|
if (token68 !== null) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiny-mcp-client",
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"types": "./dist/index.d.ts",
|
|
7
8
|
"scripts": {
|
|
8
|
-
"build": "tsc"
|
|
9
|
+
"build": "node ../../scripts/guard-package-dist.mjs && tsc"
|
|
9
10
|
},
|
|
10
11
|
"repository": {
|
|
11
12
|
"type": "git",
|
|
@@ -427,6 +427,45 @@ describe("discoverOAuthMetadata", () => {
|
|
|
427
427
|
hintedAuthorizationServerMetadataUrl,
|
|
428
428
|
]);
|
|
429
429
|
});
|
|
430
|
+
|
|
431
|
+
it("refetches an explicitly repeated resource_metadata hint", async () => {
|
|
432
|
+
const resourceUrl = "https://resource.example.com/tenant/mcp";
|
|
433
|
+
const resourceMetadataUrl =
|
|
434
|
+
"https://resource.example.com/.well-known/oauth-protected-resource/tenant/mcp";
|
|
435
|
+
const firstIssuer = "https://auth.example.com/issuer-a";
|
|
436
|
+
const secondIssuer = "https://auth.example.com/issuer-b";
|
|
437
|
+
let rotated = false;
|
|
438
|
+
const fetchMock = vi.fn(async (input: string | URL): Promise<Response> => {
|
|
439
|
+
const url = input.toString();
|
|
440
|
+
if (url === resourceMetadataUrl) {
|
|
441
|
+
return jsonResponse({
|
|
442
|
+
resource: resourceUrl,
|
|
443
|
+
authorization_servers: [rotated ? secondIssuer : firstIssuer],
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
const issuer = url.includes("issuer-a") ? firstIssuer : secondIssuer;
|
|
447
|
+
return jsonResponse({
|
|
448
|
+
issuer,
|
|
449
|
+
authorization_endpoint: `${issuer}/authorize`,
|
|
450
|
+
token_endpoint: `${issuer}/token`,
|
|
451
|
+
response_types_supported: ["code"],
|
|
452
|
+
code_challenge_methods_supported: ["S256"],
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
const discoveryClient = new OAuthMetadataDiscovery({ fetch: fetchMock });
|
|
456
|
+
|
|
457
|
+
expect((await discoveryClient.discover(resourceUrl)).authorizationServer).toBe(firstIssuer);
|
|
458
|
+
rotated = true;
|
|
459
|
+
expect(
|
|
460
|
+
(await discoveryClient.discover(resourceUrl, { resourceMetadataUrl })).authorizationServer
|
|
461
|
+
).toBe(secondIssuer);
|
|
462
|
+
expect(fetchMock.mock.calls.map(([input]) => input.toString())).toEqual([
|
|
463
|
+
resourceMetadataUrl,
|
|
464
|
+
"https://auth.example.com/.well-known/oauth-authorization-server/issuer-a",
|
|
465
|
+
resourceMetadataUrl,
|
|
466
|
+
"https://auth.example.com/.well-known/oauth-authorization-server/issuer-b",
|
|
467
|
+
]);
|
|
468
|
+
});
|
|
430
469
|
});
|
|
431
470
|
|
|
432
471
|
describe("parseBearerWwwAuthenticateHeader", () => {
|
|
@@ -464,6 +503,13 @@ describe("parseBearerWwwAuthenticateHeader", () => {
|
|
|
464
503
|
'Digest abc123==, Bearer abc123==, Bearer realm="mcp", resource_metadata="https://resource.example.com/.well-known/oauth-protected-resource/mcp"',
|
|
465
504
|
});
|
|
466
505
|
});
|
|
506
|
+
|
|
507
|
+
it("preserves a __proto__ auth parameter as parsed data", () => {
|
|
508
|
+
const challenge = parseBearerWwwAuthenticateHeader('Bearer __proto__="visible"');
|
|
509
|
+
|
|
510
|
+
expect(Object.hasOwn(challenge!.params, "__proto__")).toBe(true);
|
|
511
|
+
expect(challenge?.params.__proto__).toBe("visible");
|
|
512
|
+
});
|
|
467
513
|
});
|
|
468
514
|
|
|
469
515
|
describe("resolveAuthorizationServerMetadataUrl", () => {
|