diff --git a/docs/plans/2026-09-06-mcp-server-plan.md b/docs/plans/2026-09-06-mcp-server-plan.md index cf29d2f..73da9c6 100644 --- a/docs/plans/2026-09-06-mcp-server-plan.md +++ b/docs/plans/2026-09-06-mcp-server-plan.md @@ -1,3 +1,4 @@ + # 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. diff --git a/src/index.ts b/src/index.ts index 35fc906..fad3741 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,10 @@ async function main(): Promise { const mcpServer = createServer(config); // Bind graceful termination + let isShuttingDown = false; const shutdown = async () => { + if (isShuttingDown) return; + isShuttingDown = true; console.error("\n[openproject-mcp] Received termination signal, shutting down..."); await mcpServer.stop(); process.exit(0); diff --git a/tests/mcp-server.test.ts b/tests/mcp-server.test.ts index b956a88..838f105 100644 --- a/tests/mcp-server.test.ts +++ b/tests/mcp-server.test.ts @@ -199,32 +199,34 @@ describe("End-to-End Live Tool Calling over MCP Client", () => { const client = new Client({ name: "test-runner", version: "1" }); await client.connect(clientTransport); - // Call openproject_list_projects - const projRes = (await client.callTool({ - name: "openproject_list_projects", - arguments: { pageSize: 5 }, - })) as { - content: Array<{ type: string; text: string }>; - isError?: boolean; - }; - expect(projRes.isError).toBeFalsy(); - const projData = JSON.parse(projRes.content[0]?.text ?? "{}"); - expect(projData.projects.length).toBeGreaterThan(0); + try { + // Call openproject_list_projects + const projRes = (await client.callTool({ + name: "openproject_list_projects", + arguments: { pageSize: 5 }, + })) as { + content: Array<{ type: string; text: string }>; + isError?: boolean; + }; + expect(projRes.isError).toBeFalsy(); + const projData = JSON.parse(projRes.content[0]?.text ?? "{}"); + expect(projData.projects.length).toBeGreaterThan(0); - // Call openproject_get_work_package - const wpRes = (await client.callTool({ - name: "openproject_get_work_package", - arguments: { workPackageId: 38 }, - })) as { - content: Array<{ type: string; text: string }>; - isError?: boolean; - }; - expect(wpRes.isError).toBeFalsy(); - const wpData = JSON.parse(wpRes.content[0]?.text ?? "{}"); - expect(wpData.id).toBe(38); - - await client.close(); - await mcpServer.stop(); + // Call openproject_get_work_package + const wpRes = (await client.callTool({ + name: "openproject_get_work_package", + arguments: { workPackageId: 38 }, + })) as { + content: Array<{ type: string; text: string }>; + isError?: boolean; + }; + expect(wpRes.isError).toBeFalsy(); + const wpData = JSON.parse(wpRes.content[0]?.text ?? "{}"); + expect(wpData.id).toBe(38); + } finally { + await client.close(); + await mcpServer.stop(); + } }); 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" }); await client.connect(clientTransport); - const errRes = (await client.callTool({ - name: "openproject_get_project", - arguments: { projectId: 999999 }, - })) as { - content: Array<{ type: string; text: string }>; - isError?: boolean; - }; - expect(errRes.isError).toBe(true); - expect(errRes.content[0]?.text).toContain("OPENPROJECT_NOT_FOUND"); - - await client.close(); - await mcpServer.stop(); + try { + const errRes = (await client.callTool({ + name: "openproject_get_project", + arguments: { projectId: 999999 }, + })) as { + content: Array<{ type: string; text: string }>; + isError?: boolean; + }; + expect(errRes.isError).toBe(true); + expect(errRes.content[0]?.text).toContain("OPENPROJECT_NOT_FOUND"); + } finally { + await client.close(); + await mcpServer.stop(); + } }); });