Skip to content

Commit abde38c

Browse files
committed
feat: refresh cached MCP tool definitions after server crash
1 parent 9740e09 commit abde38c

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

src/mcp/mcp-manager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ export class McpManager {
236236
this.tools = this.tools.filter((t) => t.serverName !== name);
237237
this.prompts = this.prompts.filter((p) => p.serverName !== name);
238238
this.resources = this.resources.filter((r) => r.serverName !== name);
239+
this.onToolsListChanged?.();
239240
this.setStatus({
240241
name,
241242
status: "failed",

src/tests/session.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,60 @@ rl.on("line", (line) => {
482482
assert.deepEqual(manager.getMcpStatus(), []);
483483
});
484484

485+
test("SessionManager refreshes cached MCP tool definitions after server crash", async () => {
486+
const workspace = createTempDir("deepcode-mcp-crash-cache-workspace-");
487+
const serverPath = path.join(workspace, "mcp-server-crash.cjs");
488+
fs.writeFileSync(
489+
serverPath,
490+
`
491+
const readline = require("readline");
492+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
493+
function send(message) {
494+
process.stdout.write(JSON.stringify(message) + "\\n");
495+
}
496+
rl.on("line", (line) => {
497+
const request = JSON.parse(line);
498+
if (!("id" in request)) {
499+
return;
500+
}
501+
if (request.method === "initialize") {
502+
send({ jsonrpc: "2.0", id: request.id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} } } });
503+
return;
504+
}
505+
if (request.method === "tools/list") {
506+
send({ jsonrpc: "2.0", id: request.id, result: { tools: [
507+
{ name: "echo", inputSchema: { type: "object", properties: {} } }
508+
] } });
509+
return;
510+
}
511+
if (request.method === "prompts/list") {
512+
send({ jsonrpc: "2.0", id: request.id, result: { prompts: [] } });
513+
return;
514+
}
515+
if (request.method === "resources/list") {
516+
send({ jsonrpc: "2.0", id: request.id, result: { resources: [] } });
517+
setTimeout(() => process.exit(9), 10);
518+
return;
519+
}
520+
send({ jsonrpc: "2.0", id: request.id, result: { content: [] } });
521+
});
522+
`,
523+
"utf8"
524+
);
525+
526+
const manager = createSessionManager(workspace, "machine-id-mcp-crash-cache");
527+
await manager.initMcpServers({ crashy: { command: process.execPath, args: [serverPath] } });
528+
529+
assert.equal(manager.getMcpStatus()[0]?.status, "ready");
530+
assert.equal((manager as any).mcpToolDefinitions.length, 1);
531+
532+
await waitForMcpStatus(manager, "failed");
533+
534+
assert.equal((manager as any).mcpToolDefinitions.length, 0);
535+
536+
manager.dispose();
537+
});
538+
485539
test("SessionManager reports configured MCP servers as starting before initialization", () => {
486540
const workspace = createTempDir("deepcode-mcp-configured-workspace-");
487541
const manager = new SessionManager({
@@ -2276,6 +2330,16 @@ async function waitForNotifyRecords(
22762330
assert.fail(`expected ${expectedCount} notify records in ${outputPath}`);
22772331
}
22782332

2333+
async function waitForMcpStatus(manager: SessionManager, expectedStatus: string): Promise<void> {
2334+
for (let attempt = 0; attempt < 100; attempt += 1) {
2335+
if (manager.getMcpStatus()[0]?.status === expectedStatus) {
2336+
return;
2337+
}
2338+
await new Promise((resolve) => setTimeout(resolve, 20));
2339+
}
2340+
assert.fail(`expected MCP status ${expectedStatus}`);
2341+
}
2342+
22792343
function escapeRegExp(value: string): string {
22802344
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22812345
}

0 commit comments

Comments
 (0)