Skip to content

Commit c807771

Browse files
committed
fix: forward MCP tool abort signals
1 parent f784349 commit c807771

3 files changed

Lines changed: 73 additions & 4 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
3+
import { describe, expect, it } from "vitest";
4+
import type { AnyAgentTool } from "../agents/tools/common.js";
5+
import { createToolsMcpServer } from "./tools-stdio-server.js";
6+
7+
describe("plugin tools MCP cancellation", () => {
8+
it("forwards host cancellation to tool.execute", async () => {
9+
let resolveObservedSignal: (signal: AbortSignal | undefined) => void;
10+
const observedSignal = new Promise<AbortSignal | undefined>((resolve) => {
11+
resolveObservedSignal = resolve;
12+
});
13+
let abortObserved = false;
14+
15+
const tool = {
16+
name: "probe_cancel",
17+
description: "Probe cancellation forwarding",
18+
parameters: { type: "object", properties: {} },
19+
execute: async (_toolCallId: string, _params: unknown, signal?: AbortSignal) => {
20+
resolveObservedSignal(signal);
21+
await new Promise<void>((resolve, reject) => {
22+
if (!signal) {
23+
reject(new Error("tool.execute did not receive AbortSignal"));
24+
return;
25+
}
26+
if (signal.aborted) {
27+
abortObserved = true;
28+
resolve();
29+
return;
30+
}
31+
signal.addEventListener(
32+
"abort",
33+
() => {
34+
abortObserved = true;
35+
resolve();
36+
},
37+
{ once: true },
38+
);
39+
});
40+
return { content: [{ type: "text", text: "done" }] };
41+
},
42+
} as unknown as AnyAgentTool;
43+
44+
const server = createToolsMcpServer({ name: "test", tools: [tool] });
45+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
46+
const client = new Client({ name: "test-client", version: "0.0.0" }, { capabilities: {} });
47+
48+
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
49+
50+
try {
51+
const controller = new AbortController();
52+
const callPromise = client.callTool({ name: "probe_cancel", arguments: {} }, undefined, {
53+
signal: controller.signal,
54+
});
55+
const signal = await observedSignal;
56+
57+
expect(signal).toBeInstanceOf(AbortSignal);
58+
expect(signal?.aborted).toBe(false);
59+
60+
controller.abort();
61+
62+
await expect(callPromise).rejects.toBeDefined();
63+
expect(abortObserved).toBe(true);
64+
} finally {
65+
await client.close();
66+
await server.close();
67+
}
68+
});
69+
});

src/mcp/plugin-tools-handlers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function createPluginToolsMcpHandlers(tools: AnyAgentTool[]) {
4242
inputSchema: resolveJsonSchemaForTool(tool),
4343
})),
4444
}),
45-
callTool: async (params: CallPluginToolParams) => {
45+
callTool: async (params: CallPluginToolParams, signal?: AbortSignal) => {
4646
const tool = toolMap.get(params.name);
4747
if (!tool) {
4848
return {
@@ -51,7 +51,7 @@ export function createPluginToolsMcpHandlers(tools: AnyAgentTool[]) {
5151
};
5252
}
5353
try {
54-
const result = await tool.execute(`mcp-${Date.now()}`, params.arguments ?? {});
54+
const result = await tool.execute(`mcp-${Date.now()}`, params.arguments ?? {}, signal);
5555
const rawContent =
5656
result && typeof result === "object" && "content" in result
5757
? (result as { content?: unknown }).content

src/mcp/tools-stdio-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ export function createToolsMcpServer(params: { name: string; tools: AnyAgentTool
1414
);
1515

1616
server.setRequestHandler(ListToolsRequestSchema, handlers.listTools);
17-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
18-
return await handlers.callTool(request.params);
17+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
18+
return await handlers.callTool(request.params, extra.signal);
1919
});
2020

2121
return server;

0 commit comments

Comments
 (0)