refactor(server): add shutdown idempotency guard and test teardown blocks
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user