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
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
OAuthError,
|
|
9
9
|
type OAuthClientProvider,
|
|
10
10
|
type OAuthClientProviderOptions,
|
|
11
|
-
} from "
|
|
11
|
+
} from "mcp-oauth";
|
|
12
12
|
import type { Server as TinyStdioMcpServer } from "tiny-stdio-mcp-server";
|
|
13
13
|
import {
|
|
14
14
|
OAuthMetadataDiscovery,
|
|
@@ -28,7 +28,7 @@ export {
|
|
|
28
28
|
export {
|
|
29
29
|
createAuthStoreSessionStore,
|
|
30
30
|
createDefaultOAuthClientProvider,
|
|
31
|
-
} from "
|
|
31
|
+
} from "mcp-oauth";
|
|
32
32
|
export type {
|
|
33
33
|
OAuthDiscoveryCache,
|
|
34
34
|
} from "./oauth-discovery.js";
|
|
@@ -39,7 +39,7 @@ export type {
|
|
|
39
39
|
OAuthClientProviderOptions,
|
|
40
40
|
OAuthSessionStore,
|
|
41
41
|
StoredOAuthSession,
|
|
42
|
-
} from "
|
|
42
|
+
} from "mcp-oauth";
|
|
43
43
|
|
|
44
44
|
export type RequestId = number | string;
|
|
45
45
|
|
|
@@ -97,6 +97,7 @@ export interface InitializeResult {
|
|
|
97
97
|
|
|
98
98
|
export interface McpClientOptions {
|
|
99
99
|
clientInfo: Implementation;
|
|
100
|
+
requestTimeoutMs?: number;
|
|
100
101
|
capabilities?: ClientCapabilities;
|
|
101
102
|
onToolsChanged?: () => void | Promise<void>;
|
|
102
103
|
onResourcesChanged?: () => void | Promise<void>;
|
|
@@ -115,8 +116,11 @@ const MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
|
115
116
|
export class McpClient {
|
|
116
117
|
private currentState: "disconnected" | "initializing" | "ready" | "closed" = "disconnected";
|
|
117
118
|
private currentServerCapabilities: ServerCapabilities | null = null;
|
|
119
|
+
private currentClientCapabilities: ClientCapabilities | null = null;
|
|
118
120
|
private currentServerInfo: Implementation | null = null;
|
|
119
121
|
private currentInstructions: string | undefined;
|
|
122
|
+
private readonly subscribedResourceUris = new Set<string>();
|
|
123
|
+
private readonly activeProgressTokens = new Map<ProgressToken, number>();
|
|
120
124
|
private readonly options: McpClientOptions;
|
|
121
125
|
private transport: McpTransport | null = null;
|
|
122
126
|
private messageLayer: JsonRpcMessageLayer | null = null;
|
|
@@ -130,7 +134,9 @@ export class McpClient {
|
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
get serverCapabilities(): ServerCapabilities | null {
|
|
133
|
-
return this.currentServerCapabilities
|
|
137
|
+
return this.currentServerCapabilities === null
|
|
138
|
+
? null
|
|
139
|
+
: structuredClone(this.currentServerCapabilities);
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
get serverInfo(): Implementation | null {
|
|
@@ -162,6 +168,13 @@ export class McpClient {
|
|
|
162
168
|
throw new Error("MCP client is already connected");
|
|
163
169
|
}
|
|
164
170
|
|
|
171
|
+
this.currentServerCapabilities = null;
|
|
172
|
+
this.currentClientCapabilities = null;
|
|
173
|
+
this.currentServerInfo = null;
|
|
174
|
+
this.currentInstructions = undefined;
|
|
175
|
+
this.subscribedResourceUris.clear();
|
|
176
|
+
this.activeProgressTokens.clear();
|
|
177
|
+
|
|
165
178
|
const transportClosedReason = transport.closed
|
|
166
179
|
.then((closedEvent) => closedEvent.reason)
|
|
167
180
|
.catch((error: unknown) =>
|
|
@@ -170,7 +183,7 @@ export class McpClient {
|
|
|
170
183
|
const messageLayer = new JsonRpcMessageLayer(
|
|
171
184
|
transport.readable,
|
|
172
185
|
transport.writable,
|
|
173
|
-
|
|
186
|
+
this.options.requestTimeoutMs,
|
|
174
187
|
transportClosedReason
|
|
175
188
|
);
|
|
176
189
|
const {
|
|
@@ -192,14 +205,8 @@ export class McpClient {
|
|
|
192
205
|
);
|
|
193
206
|
}
|
|
194
207
|
|
|
195
|
-
if (onRootsList !== undefined) {
|
|
196
|
-
messageLayer.onRequest("roots/list", async () => ({
|
|
197
|
-
roots: await onRootsList(),
|
|
198
|
-
}));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
208
|
messageLayer.onNotification("notifications/tools/list_changed", async () => {
|
|
202
|
-
if (onToolsChanged === undefined) {
|
|
209
|
+
if (onToolsChanged === undefined || this.currentServerCapabilities?.tools?.listChanged !== true) {
|
|
203
210
|
return;
|
|
204
211
|
}
|
|
205
212
|
|
|
@@ -222,7 +229,7 @@ export class McpClient {
|
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
const { uri } = params as { uri?: unknown };
|
|
225
|
-
if (typeof uri !== "string") {
|
|
232
|
+
if (typeof uri !== "string" || !this.subscribedResourceUris.has(uri)) {
|
|
226
233
|
return;
|
|
227
234
|
}
|
|
228
235
|
|
|
@@ -265,7 +272,11 @@ export class McpClient {
|
|
|
265
272
|
}
|
|
266
273
|
|
|
267
274
|
const { progressToken, progress } = params;
|
|
268
|
-
if (
|
|
275
|
+
if (
|
|
276
|
+
!isRequestId(progressToken) ||
|
|
277
|
+
typeof progress !== "number" ||
|
|
278
|
+
!this.activeProgressTokens.has(progressToken)
|
|
279
|
+
) {
|
|
269
280
|
return;
|
|
270
281
|
}
|
|
271
282
|
|
|
@@ -297,6 +308,8 @@ export class McpClient {
|
|
|
297
308
|
this.transport = transport;
|
|
298
309
|
this.messageLayer = messageLayer;
|
|
299
310
|
this.currentState = "initializing";
|
|
311
|
+
this.subscribedResourceUris.clear();
|
|
312
|
+
this.activeProgressTokens.clear();
|
|
300
313
|
transport.closed
|
|
301
314
|
.then((closedEvent) => {
|
|
302
315
|
if (this.transport !== transport) {
|
|
@@ -334,26 +347,52 @@ export class McpClient {
|
|
|
334
347
|
};
|
|
335
348
|
}
|
|
336
349
|
|
|
337
|
-
|
|
338
|
-
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
339
|
-
clientInfo: this.options.clientInfo,
|
|
340
|
-
capabilities,
|
|
341
|
-
})) as InitializeResult;
|
|
350
|
+
this.currentClientCapabilities = structuredClone(capabilities);
|
|
342
351
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
352
|
+
try {
|
|
353
|
+
const initializeResultValue = await messageLayer.sendRequest("initialize", {
|
|
354
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
355
|
+
clientInfo: this.options.clientInfo,
|
|
356
|
+
capabilities,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (!isInitializeResult(initializeResultValue)) {
|
|
360
|
+
throw new McpError(ERROR_INVALID_REQUEST, "Invalid initialize result");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const initializeResult = initializeResultValue;
|
|
364
|
+
|
|
365
|
+
if (initializeResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
|
|
366
|
+
throw new McpError(
|
|
367
|
+
ERROR_INVALID_REQUEST,
|
|
368
|
+
`Unsupported protocol version: ${initializeResult.protocolVersion}`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
349
371
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
372
|
+
this.currentServerCapabilities = structuredClone(initializeResult.capabilities);
|
|
373
|
+
this.currentServerInfo = { ...initializeResult.serverInfo };
|
|
374
|
+
this.currentInstructions = initializeResult.instructions;
|
|
375
|
+
if (onRootsList !== undefined) {
|
|
376
|
+
messageLayer.onRequest("roots/list", async () => ({
|
|
377
|
+
roots: await onRootsList(),
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
messageLayer.sendNotification("notifications/initialized");
|
|
381
|
+
this.currentState = "ready";
|
|
382
|
+
|
|
383
|
+
return initializeResult;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (this.transport === transport) {
|
|
386
|
+
const reason = error instanceof Error ? error : new Error(String(error));
|
|
387
|
+
messageLayer.dispose(reason);
|
|
388
|
+
transport.dispose(reason);
|
|
389
|
+
this.messageLayer = null;
|
|
390
|
+
this.transport = null;
|
|
391
|
+
this.currentState = "disconnected";
|
|
392
|
+
}
|
|
355
393
|
|
|
356
|
-
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
357
396
|
}
|
|
358
397
|
|
|
359
398
|
private getServerCapabilitiesOrThrow(): ServerCapabilities {
|
|
@@ -372,10 +411,12 @@ export class McpClient {
|
|
|
372
411
|
}
|
|
373
412
|
|
|
374
413
|
const requestParams = params.cursor === undefined ? undefined : { cursor: params.cursor };
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
414
|
+
const result = await messageLayer.sendRequest("tools/list", requestParams);
|
|
415
|
+
if (!isToolsListResult(result)) {
|
|
416
|
+
throw new McpError(ERROR_INVALID_REQUEST, "Invalid tools/list result");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return result;
|
|
379
420
|
}
|
|
380
421
|
|
|
381
422
|
async callTool(params: CallToolParams, options: CallToolOptions = {}): Promise<CallToolResult> {
|
|
@@ -385,6 +426,10 @@ export class McpClient {
|
|
|
385
426
|
throw new Error("Server does not support tools");
|
|
386
427
|
}
|
|
387
428
|
|
|
429
|
+
if (options.signal?.aborted) {
|
|
430
|
+
throw options.signal.reason;
|
|
431
|
+
}
|
|
432
|
+
|
|
388
433
|
const requestParams =
|
|
389
434
|
options.progressToken === undefined
|
|
390
435
|
? params
|
|
@@ -395,39 +440,70 @@ export class McpClient {
|
|
|
395
440
|
},
|
|
396
441
|
};
|
|
397
442
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}) as Promise<CallToolResult>;
|
|
404
|
-
if (options.signal === undefined) {
|
|
405
|
-
return await requestPromise;
|
|
443
|
+
if (options.progressToken !== undefined) {
|
|
444
|
+
this.activeProgressTokens.set(
|
|
445
|
+
options.progressToken,
|
|
446
|
+
(this.activeProgressTokens.get(options.progressToken) ?? 0) + 1
|
|
447
|
+
);
|
|
406
448
|
}
|
|
407
|
-
const signal = options.signal;
|
|
408
449
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
450
|
+
try {
|
|
451
|
+
let requestId: RequestId | undefined;
|
|
452
|
+
let cancellationSent = false;
|
|
453
|
+
const sendCancellationNotification = () => {
|
|
454
|
+
if (requestId === undefined || cancellationSent) {
|
|
455
|
+
return;
|
|
414
456
|
}
|
|
415
|
-
|
|
457
|
+
cancellationSent = true;
|
|
458
|
+
messageLayer.sendNotification("notifications/cancelled", { requestId });
|
|
416
459
|
};
|
|
460
|
+
const requestPromise = messageLayer.sendRequest("tools/call", requestParams, {
|
|
461
|
+
onRequestId: (nextRequestId) => {
|
|
462
|
+
requestId = nextRequestId;
|
|
463
|
+
},
|
|
464
|
+
onTimeout: sendCancellationNotification,
|
|
465
|
+
}).then((result) => {
|
|
466
|
+
if (!isCallToolResult(result)) {
|
|
467
|
+
throw new McpError(ERROR_INVALID_REQUEST, "Invalid tool result");
|
|
468
|
+
}
|
|
417
469
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (signal
|
|
421
|
-
|
|
422
|
-
rejectWithAbortReason();
|
|
470
|
+
return result;
|
|
471
|
+
});
|
|
472
|
+
if (options.signal === undefined) {
|
|
473
|
+
return await requestPromise;
|
|
423
474
|
}
|
|
424
|
-
|
|
475
|
+
const signal = options.signal;
|
|
425
476
|
|
|
426
|
-
|
|
427
|
-
|
|
477
|
+
let abortListener: (() => void) | undefined;
|
|
478
|
+
const abortPromise = new Promise<CallToolResult>((_, reject) => {
|
|
479
|
+
const rejectWithAbortReason = () => {
|
|
480
|
+
sendCancellationNotification();
|
|
481
|
+
reject(signal.reason);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
abortListener = rejectWithAbortReason;
|
|
485
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
486
|
+
if (signal.aborted) {
|
|
487
|
+
signal.removeEventListener("abort", abortListener);
|
|
488
|
+
rejectWithAbortReason();
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
return (await Promise.race([requestPromise, abortPromise])) as CallToolResult;
|
|
494
|
+
} finally {
|
|
495
|
+
if (abortListener !== undefined) {
|
|
496
|
+
signal.removeEventListener("abort", abortListener);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
428
499
|
} finally {
|
|
429
|
-
if (
|
|
430
|
-
|
|
500
|
+
if (options.progressToken !== undefined) {
|
|
501
|
+
const activeCount = this.activeProgressTokens.get(options.progressToken);
|
|
502
|
+
if (activeCount === 1) {
|
|
503
|
+
this.activeProgressTokens.delete(options.progressToken);
|
|
504
|
+
} else if (activeCount !== undefined) {
|
|
505
|
+
this.activeProgressTokens.set(options.progressToken, activeCount - 1);
|
|
506
|
+
}
|
|
431
507
|
}
|
|
432
508
|
}
|
|
433
509
|
}
|
|
@@ -484,6 +560,7 @@ export class McpClient {
|
|
|
484
560
|
}
|
|
485
561
|
|
|
486
562
|
await messageLayer.sendRequest("resources/subscribe", { uri });
|
|
563
|
+
this.subscribedResourceUris.add(uri);
|
|
487
564
|
}
|
|
488
565
|
|
|
489
566
|
async unsubscribe(uri: string): Promise<void> {
|
|
@@ -494,6 +571,7 @@ export class McpClient {
|
|
|
494
571
|
}
|
|
495
572
|
|
|
496
573
|
await messageLayer.sendRequest("resources/unsubscribe", { uri });
|
|
574
|
+
this.subscribedResourceUris.delete(uri);
|
|
497
575
|
}
|
|
498
576
|
|
|
499
577
|
async listPrompts(params: PaginatedParams = {}): Promise<{ prompts: Prompt[]; nextCursor?: string }> {
|
|
@@ -553,6 +631,10 @@ export class McpClient {
|
|
|
553
631
|
|
|
554
632
|
async sendRootsChanged(): Promise<void> {
|
|
555
633
|
const messageLayer = this.getMessageLayerOrThrow();
|
|
634
|
+
if (this.currentClientCapabilities?.roots?.listChanged !== true) {
|
|
635
|
+
throw new Error("Client did not advertise roots list changes");
|
|
636
|
+
}
|
|
637
|
+
|
|
556
638
|
messageLayer.sendNotification("notifications/roots/list_changed");
|
|
557
639
|
}
|
|
558
640
|
|
|
@@ -571,6 +653,12 @@ export class McpClient {
|
|
|
571
653
|
this.transport?.dispose(closeError);
|
|
572
654
|
this.messageLayer = null;
|
|
573
655
|
this.transport = null;
|
|
656
|
+
this.currentServerCapabilities = null;
|
|
657
|
+
this.currentClientCapabilities = null;
|
|
658
|
+
this.currentServerInfo = null;
|
|
659
|
+
this.currentInstructions = undefined;
|
|
660
|
+
this.subscribedResourceUris.clear();
|
|
661
|
+
this.activeProgressTokens.clear();
|
|
574
662
|
this.currentState = "closed";
|
|
575
663
|
}
|
|
576
664
|
}
|
|
@@ -2402,7 +2490,6 @@ export class HttpTransport implements McpTransport {
|
|
|
2402
2490
|
this.disposed = true;
|
|
2403
2491
|
this.abortInFlightFetches();
|
|
2404
2492
|
this.cancelOpenSseReaders();
|
|
2405
|
-
this.terminateSession();
|
|
2406
2493
|
|
|
2407
2494
|
if (!this.writeStream.destroyed && !this.writeStream.writableEnded) {
|
|
2408
2495
|
this.writeStream.end();
|
|
@@ -2412,9 +2499,24 @@ export class HttpTransport implements McpTransport {
|
|
|
2412
2499
|
this.readStream.end();
|
|
2413
2500
|
}
|
|
2414
2501
|
|
|
2502
|
+
void this.closeWithSessionTermination(reason);
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
private async closeWithSessionTermination(reason: Error): Promise<void> {
|
|
2506
|
+
let closeReason = reason;
|
|
2507
|
+
if (this.sessionId !== undefined) {
|
|
2508
|
+
const sessionId = this.sessionId;
|
|
2509
|
+
this.sessionId = undefined;
|
|
2510
|
+
try {
|
|
2511
|
+
await this.sendSessionTerminationRequest(sessionId);
|
|
2512
|
+
} catch (error) {
|
|
2513
|
+
closeReason = error instanceof Error ? error : new Error(String(error));
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2415
2517
|
const resolveClosed = this.resolveClosed;
|
|
2416
2518
|
this.resolveClosed = undefined;
|
|
2417
|
-
resolveClosed?.({ reason });
|
|
2519
|
+
resolveClosed?.({ reason: closeReason });
|
|
2418
2520
|
}
|
|
2419
2521
|
|
|
2420
2522
|
private abortInFlightFetches(): void {
|
|
@@ -2469,7 +2571,9 @@ export class HttpTransport implements McpTransport {
|
|
|
2469
2571
|
await this.throwForPostHttpError(response);
|
|
2470
2572
|
this.captureSessionId(response);
|
|
2471
2573
|
this.maybeOpenGetSseStream();
|
|
2472
|
-
|
|
2574
|
+
void this.forwardResponseMessages(response).catch((error) => {
|
|
2575
|
+
this.dispose(error instanceof Error ? error : new Error(String(error)));
|
|
2576
|
+
});
|
|
2473
2577
|
}
|
|
2474
2578
|
}
|
|
2475
2579
|
|
|
@@ -2479,6 +2583,7 @@ export class HttpTransport implements McpTransport {
|
|
|
2479
2583
|
headers.set("Content-Type", "application/json");
|
|
2480
2584
|
if (this.sessionId !== undefined) {
|
|
2481
2585
|
headers.set("Mcp-Session-Id", this.sessionId);
|
|
2586
|
+
headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
|
|
2482
2587
|
}
|
|
2483
2588
|
return this.authorizeRequestHeaders(headers);
|
|
2484
2589
|
}
|
|
@@ -2488,6 +2593,7 @@ export class HttpTransport implements McpTransport {
|
|
|
2488
2593
|
headers.set("Accept", "text/event-stream");
|
|
2489
2594
|
if (this.sessionId !== undefined) {
|
|
2490
2595
|
headers.set("Mcp-Session-Id", this.sessionId);
|
|
2596
|
+
headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
|
|
2491
2597
|
}
|
|
2492
2598
|
if (this.lastEventId !== undefined) {
|
|
2493
2599
|
headers.set("Last-Event-ID", this.lastEventId);
|
|
@@ -2498,6 +2604,7 @@ export class HttpTransport implements McpTransport {
|
|
|
2498
2604
|
private async createDeleteHeaders(sessionId: string): Promise<Headers> {
|
|
2499
2605
|
const headers = new Headers(this.headers);
|
|
2500
2606
|
headers.set("Mcp-Session-Id", sessionId);
|
|
2607
|
+
headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
|
|
2501
2608
|
return this.authorizeRequestHeaders(headers);
|
|
2502
2609
|
}
|
|
2503
2610
|
|
|
@@ -2516,6 +2623,10 @@ export class HttpTransport implements McpTransport {
|
|
|
2516
2623
|
return;
|
|
2517
2624
|
}
|
|
2518
2625
|
|
|
2626
|
+
if (this.sessionId !== undefined && this.sessionId !== sessionId) {
|
|
2627
|
+
throw new Error("HTTP transport response changed active session ID");
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2519
2630
|
this.sessionId = sessionId;
|
|
2520
2631
|
}
|
|
2521
2632
|
|
|
@@ -2534,26 +2645,22 @@ export class HttpTransport implements McpTransport {
|
|
|
2534
2645
|
});
|
|
2535
2646
|
}
|
|
2536
2647
|
|
|
2537
|
-
private terminateSession(): void {
|
|
2538
|
-
if (this.sessionId === undefined) {
|
|
2539
|
-
return;
|
|
2540
|
-
}
|
|
2541
|
-
|
|
2542
|
-
const sessionId = this.sessionId;
|
|
2543
|
-
this.sessionId = undefined;
|
|
2544
|
-
|
|
2545
|
-
this.sendSessionTerminationRequest(sessionId).catch(() => undefined);
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
2648
|
private async sendSessionTerminationRequest(sessionId: string): Promise<void> {
|
|
2549
2649
|
const response = await this.fetchImpl(this.url, {
|
|
2550
2650
|
method: "DELETE",
|
|
2551
2651
|
headers: await this.createDeleteHeaders(sessionId),
|
|
2552
2652
|
});
|
|
2553
2653
|
|
|
2554
|
-
if (response.status === 405) {
|
|
2654
|
+
if (response.status === 405 || response.ok) {
|
|
2555
2655
|
return;
|
|
2556
2656
|
}
|
|
2657
|
+
|
|
2658
|
+
const responseBody = (await response.text()).trim();
|
|
2659
|
+
const statusDescriptor = `${response.status} ${response.statusText}`.trim();
|
|
2660
|
+
const message = responseBody.length === 0
|
|
2661
|
+
? `HTTP transport DELETE failed (${statusDescriptor})`
|
|
2662
|
+
: `HTTP transport DELETE failed (${statusDescriptor}): ${responseBody}`;
|
|
2663
|
+
throw new Error(message);
|
|
2557
2664
|
}
|
|
2558
2665
|
|
|
2559
2666
|
private async consumeGetSseStream(): Promise<void> {
|
|
@@ -2566,6 +2673,20 @@ export class HttpTransport implements McpTransport {
|
|
|
2566
2673
|
throw new HttpTransportGetSseNotSupportedError();
|
|
2567
2674
|
}
|
|
2568
2675
|
|
|
2676
|
+
if (response.status === 404) {
|
|
2677
|
+
this.sessionId = undefined;
|
|
2678
|
+
throw new Error("HTTP transport session expired (GET 404 response)");
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
if (!response.ok) {
|
|
2682
|
+
const responseBody = (await response.text()).trim();
|
|
2683
|
+
const statusDescriptor = `${response.status} ${response.statusText}`.trim();
|
|
2684
|
+
const message = responseBody.length === 0
|
|
2685
|
+
? `HTTP transport GET failed (${statusDescriptor})`
|
|
2686
|
+
: `HTTP transport GET failed (${statusDescriptor}): ${responseBody}`;
|
|
2687
|
+
throw new Error(message);
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2569
2690
|
const contentType = response.headers.get("Content-Type");
|
|
2570
2691
|
if (contentType === null) {
|
|
2571
2692
|
return;
|
|
@@ -2577,7 +2698,10 @@ export class HttpTransport implements McpTransport {
|
|
|
2577
2698
|
if (!this.disposed && this.sessionId !== undefined && this.lastEventId !== undefined) {
|
|
2578
2699
|
this.maybeOpenGetSseStream();
|
|
2579
2700
|
}
|
|
2701
|
+
return;
|
|
2580
2702
|
}
|
|
2703
|
+
|
|
2704
|
+
return;
|
|
2581
2705
|
}
|
|
2582
2706
|
|
|
2583
2707
|
private async throwForPostHttpError(response: Response): Promise<void> {
|
|
@@ -2645,7 +2769,10 @@ export class HttpTransport implements McpTransport {
|
|
|
2645
2769
|
|
|
2646
2770
|
if (normalizedContentType.includes("application/json")) {
|
|
2647
2771
|
await this.forwardJsonResponseMessage(response);
|
|
2772
|
+
return;
|
|
2648
2773
|
}
|
|
2774
|
+
|
|
2775
|
+
throw new Error("HTTP transport POST returned an unsupported response content type");
|
|
2649
2776
|
}
|
|
2650
2777
|
|
|
2651
2778
|
private async forwardSseResponseMessages(response: Response): Promise<void> {
|
|
@@ -2856,9 +2983,13 @@ function normalizeLine(line: string): string {
|
|
|
2856
2983
|
|
|
2857
2984
|
export async function* readLines(stream: Readable): AsyncGenerator<string> {
|
|
2858
2985
|
let buffer = "";
|
|
2986
|
+
const decoder = new TextDecoder();
|
|
2859
2987
|
|
|
2860
2988
|
for await (const chunk of stream as AsyncIterable<unknown>) {
|
|
2861
|
-
buffer +=
|
|
2989
|
+
buffer +=
|
|
2990
|
+
chunk instanceof Uint8Array
|
|
2991
|
+
? decoder.decode(chunk, { stream: true })
|
|
2992
|
+
: decoder.decode() + String(chunk);
|
|
2862
2993
|
|
|
2863
2994
|
while (true) {
|
|
2864
2995
|
const newlineIndex = buffer.indexOf("\n");
|
|
@@ -2872,6 +3003,8 @@ export async function* readLines(stream: Readable): AsyncGenerator<string> {
|
|
|
2872
3003
|
}
|
|
2873
3004
|
}
|
|
2874
3005
|
|
|
3006
|
+
buffer += decoder.decode();
|
|
3007
|
+
|
|
2875
3008
|
if (buffer.length > 0) {
|
|
2876
3009
|
yield normalizeLine(buffer);
|
|
2877
3010
|
}
|
|
@@ -3008,6 +3141,7 @@ interface ActiveIncomingRequest {
|
|
|
3008
3141
|
export interface JsonRpcRequestOptions {
|
|
3009
3142
|
timeoutMs?: number;
|
|
3010
3143
|
onRequestId?: (requestId: RequestId) => void;
|
|
3144
|
+
onTimeout?: (requestId: RequestId) => void;
|
|
3011
3145
|
}
|
|
3012
3146
|
|
|
3013
3147
|
interface JsonRpcRequestContext {
|
|
@@ -3116,6 +3250,7 @@ export class JsonRpcMessageLayer {
|
|
|
3116
3250
|
return new Promise((resolve, reject) => {
|
|
3117
3251
|
const timeout = setTimeout(() => {
|
|
3118
3252
|
this.pendingRequests.delete(id);
|
|
3253
|
+
options.onTimeout?.(id);
|
|
3119
3254
|
reject(new Error(`JSON-RPC request "${method}" timed out after ${timeoutMs}ms`));
|
|
3120
3255
|
}, timeoutMs);
|
|
3121
3256
|
|
|
@@ -3383,6 +3518,82 @@ function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
|
3383
3518
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3384
3519
|
}
|
|
3385
3520
|
|
|
3521
|
+
function isInitializeResult(value: unknown): value is InitializeResult {
|
|
3522
|
+
if (!isObjectRecord(value) || typeof value.protocolVersion !== "string") {
|
|
3523
|
+
return false;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
if (!isServerCapabilities(value.capabilities)) {
|
|
3527
|
+
return false;
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
if (
|
|
3531
|
+
!isObjectRecord(value.serverInfo)
|
|
3532
|
+
|| typeof value.serverInfo.name !== "string"
|
|
3533
|
+
|| value.serverInfo.name.length === 0
|
|
3534
|
+
|| typeof value.serverInfo.version !== "string"
|
|
3535
|
+
|| value.serverInfo.version.length === 0
|
|
3536
|
+
) {
|
|
3537
|
+
return false;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
return value.instructions === undefined || typeof value.instructions === "string";
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
function isServerCapabilities(value: unknown): value is ServerCapabilities {
|
|
3544
|
+
if (!isObjectRecord(value)) {
|
|
3545
|
+
return false;
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
for (const capability of ["prompts", "resources", "tools", "logging", "completions", "experimental"] as const) {
|
|
3549
|
+
if (value[capability] !== undefined && !isObjectRecord(value[capability])) {
|
|
3550
|
+
return false;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
return true;
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
function isCallToolResult(value: unknown): value is CallToolResult {
|
|
3558
|
+
if (!isObjectRecord(value) || !Array.isArray(value.content)) {
|
|
3559
|
+
return false;
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
if (value.isError !== undefined && typeof value.isError !== "boolean") {
|
|
3563
|
+
return false;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
return value.content.every(isContentItem);
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
function isToolsListResult(value: unknown): value is { tools: Tool[]; nextCursor?: string } {
|
|
3570
|
+
return isObjectRecord(value)
|
|
3571
|
+
&& Array.isArray(value.tools)
|
|
3572
|
+
&& (value.nextCursor === undefined || typeof value.nextCursor === "string");
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
function isContentItem(value: unknown): value is ContentItem {
|
|
3576
|
+
if (!isObjectRecord(value)) {
|
|
3577
|
+
return false;
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
if (value.type === "text") {
|
|
3581
|
+
return typeof value.text === "string";
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
if (value.type === "image" || value.type === "audio") {
|
|
3585
|
+
return typeof value.data === "string" && typeof value.mimeType === "string";
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
if (value.type !== "resource" || !isObjectRecord(value.resource)) {
|
|
3589
|
+
return false;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
return typeof value.resource.uri === "string"
|
|
3593
|
+
&& (value.resource.mimeType === undefined || typeof value.resource.mimeType === "string")
|
|
3594
|
+
&& (typeof value.resource.text === "string" || typeof value.resource.blob === "string");
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3386
3597
|
function hasOwn(
|
|
3387
3598
|
value: Record<string, unknown>,
|
|
3388
3599
|
property: string
|
|
@@ -293,6 +293,38 @@ describe("McpClient SDK integration callTool", () => {
|
|
|
293
293
|
}
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
+
it("cancels an in-flight slow tool call when the request timeout elapses", async () => {
|
|
297
|
+
const server = await createMockSlowToolServer({ delayMs: 1_000, pollIntervalMs: 5 });
|
|
298
|
+
const { client, cleanup } = await createSdkTestPair(server, () =>
|
|
299
|
+
new McpClient({
|
|
300
|
+
clientInfo: {
|
|
301
|
+
name: "test-client",
|
|
302
|
+
version: "1.0.0",
|
|
303
|
+
},
|
|
304
|
+
requestTimeoutMs: 100,
|
|
305
|
+
})
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const callPromise = client.callTool({
|
|
310
|
+
name: "slow",
|
|
311
|
+
arguments: {
|
|
312
|
+
delayMs: 500,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
await waitFor(() => server.wasStarted(), "Timed out waiting for slow tool to start");
|
|
317
|
+
|
|
318
|
+
await expect(callPromise).rejects.toThrow(
|
|
319
|
+
'JSON-RPC request "tools/call" timed out after 100ms'
|
|
320
|
+
);
|
|
321
|
+
await waitFor(() => server.wasCancelled(), "Timed out waiting for slow tool cancellation");
|
|
322
|
+
expect(server.getCancelledRequestIds()).toEqual(server.getStartedRequestIds());
|
|
323
|
+
} finally {
|
|
324
|
+
await cleanup();
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
296
328
|
it("rejects with JSON-RPC error code and message for unknown tool names", async () => {
|
|
297
329
|
const server = await createMockErrorServer();
|
|
298
330
|
const { client, cleanup } = await createSdkTestPair(server, () =>
|
|
@@ -17,7 +17,7 @@ describe("McpClient integration tools with tiny-stdio-mcp-test-server", () => {
|
|
|
17
17
|
try {
|
|
18
18
|
expect(client.serverInfo).toEqual({
|
|
19
19
|
name: "tiny-stdio-mcp-test-server",
|
|
20
|
-
version: "0.0
|
|
20
|
+
version: "0.1.0",
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
const { tools } = await client.listTools();
|