refactor(server): add shutdown idempotency guard and test teardown blocks

This commit is contained in:
Lucas Winther
2026-09-06 19:22:46 +02:00
parent f1e84d281a
commit 3d4a064593
3 changed files with 45 additions and 37 deletions
+1
View File
@@ -1,3 +1,4 @@
# MCP Stdio Server Assembly & Integration Implementation Plan # MCP Stdio Server Assembly & Integration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+3
View File
@@ -12,7 +12,10 @@ async function main(): Promise<void> {
const mcpServer = createServer(config); const mcpServer = createServer(config);
// Bind graceful termination // Bind graceful termination
let isShuttingDown = false;
const shutdown = async () => { const shutdown = async () => {
if (isShuttingDown) return;
isShuttingDown = true;
console.error("\n[openproject-mcp] Received termination signal, shutting down..."); console.error("\n[openproject-mcp] Received termination signal, shutting down...");
await mcpServer.stop(); await mcpServer.stop();
process.exit(0); process.exit(0);
+41 -37
View File
@@ -199,32 +199,34 @@ describe("End-to-End Live Tool Calling over MCP Client", () => {
const client = new Client({ name: "test-runner", version: "1" }); const client = new Client({ name: "test-runner", version: "1" });
await client.connect(clientTransport); await client.connect(clientTransport);
// Call openproject_list_projects try {
const projRes = (await client.callTool({ // Call openproject_list_projects
name: "openproject_list_projects", const projRes = (await client.callTool({
arguments: { pageSize: 5 }, name: "openproject_list_projects",
})) as { arguments: { pageSize: 5 },
content: Array<{ type: string; text: string }>; })) as {
isError?: boolean; content: Array<{ type: string; text: string }>;
}; isError?: boolean;
expect(projRes.isError).toBeFalsy(); };
const projData = JSON.parse(projRes.content[0]?.text ?? "{}"); expect(projRes.isError).toBeFalsy();
expect(projData.projects.length).toBeGreaterThan(0); const projData = JSON.parse(projRes.content[0]?.text ?? "{}");
expect(projData.projects.length).toBeGreaterThan(0);
// Call openproject_get_work_package // Call openproject_get_work_package
const wpRes = (await client.callTool({ const wpRes = (await client.callTool({
name: "openproject_get_work_package", name: "openproject_get_work_package",
arguments: { workPackageId: 38 }, arguments: { workPackageId: 38 },
})) as { })) as {
content: Array<{ type: string; text: string }>; content: Array<{ type: string; text: string }>;
isError?: boolean; isError?: boolean;
}; };
expect(wpRes.isError).toBeFalsy(); expect(wpRes.isError).toBeFalsy();
const wpData = JSON.parse(wpRes.content[0]?.text ?? "{}"); const wpData = JSON.parse(wpRes.content[0]?.text ?? "{}");
expect(wpData.id).toBe(38); expect(wpData.id).toBe(38);
} finally {
await client.close(); await client.close();
await mcpServer.stop(); await mcpServer.stop();
}
}); });
test("tool error propagation returns isError: true without crashing client", async () => { test("tool error propagation returns isError: true without crashing client", async () => {
@@ -235,18 +237,20 @@ describe("End-to-End Live Tool Calling over MCP Client", () => {
const client = new Client({ name: "test-runner", version: "1" }); const client = new Client({ name: "test-runner", version: "1" });
await client.connect(clientTransport); await client.connect(clientTransport);
const errRes = (await client.callTool({ try {
name: "openproject_get_project", const errRes = (await client.callTool({
arguments: { projectId: 999999 }, name: "openproject_get_project",
})) as { arguments: { projectId: 999999 },
content: Array<{ type: string; text: string }>; })) as {
isError?: boolean; content: Array<{ type: string; text: string }>;
}; isError?: boolean;
expect(errRes.isError).toBe(true); };
expect(errRes.content[0]?.text).toContain("OPENPROJECT_NOT_FOUND"); expect(errRes.isError).toBe(true);
expect(errRes.content[0]?.text).toContain("OPENPROJECT_NOT_FOUND");
await client.close(); } finally {
await mcpServer.stop(); await client.close();
await mcpServer.stop();
}
}); });
}); });