Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/coding-agent/src/coordinator-mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5698,6 +5698,9 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
},
};
}
if (request.method === "ping") {
return { jsonrpc: "2.0", id, result: {} };
Comment on lines +5701 to +5702

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the ping fix to the coding-agent changelog

This changes user-visible MCP behavior for clients that use ping as a liveness probe, but the commit leaves packages/coding-agent/CHANGELOG.md unchanged, so the fix will be absent from the package's release notes. Add an entry under its ## [Unreleased] section.

AGENTS.md reference: AGENTS.md:L186-L186

Useful? React with 👍 / 👎.

Comment on lines +5701 to +5702

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle ping in the compatibility entry point

When an integration calls the exported handleCoordinatorMcpRequest compatibility handler rather than createCoordinatorMcpServer().handleJsonRpc (the package wildcard exports make this module addressable), ping still falls through to unknown_method:ping. Add the same empty-result branch to that duplicate JSON-RPC dispatcher so all coordinator MCP entry points implement the keepalive fix consistently.

Useful? React with 👍 / 👎.

}
if (request.method === "tools/list") {
return { jsonrpc: "2.0", id, result: { tools: COORDINATOR_MCP_TOOL_NAMES.map(toolSchema) } };
}
Expand Down
50 changes: 50 additions & 0 deletions packages/coding-agent/test/coordinator-mcp-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,56 @@ async function registerSdkSession(server: ReturnType<typeof createCoordinatorMcp
}

describe("Coordinator MCP canonical SDK controls", () => {
async function pingServer(root: string) {
const server = createCoordinatorMcpServer({
env: {
GJC_COORDINATOR_MCP_WORKDIR_ROOTS: root,
GJC_COORDINATOR_MCP_STATE_ROOT: path.join(root, ".gjc", "coordinator-state"),
GJC_COORDINATOR_MCP_PROFILE: "local",
GJC_COORDINATOR_MCP_REPO: "repo",
},
services: { getAgentDir: () => path.join(root, "agent-global") },
});
return server;
}

it("answers the MCP ping keepalive with an empty result instead of method-not-found", async () => {
const root = await tempRoot();
const server = await pingServer(root);
const response = await server.handleJsonRpc({ jsonrpc: "2.0", id: 1, method: "ping" });
expect(response).toEqual({ jsonrpc: "2.0", id: 1, result: {} });
});

it("preserves a string request id in the ping response", async () => {
const root = await tempRoot();
const server = await pingServer(root);
const response = await server.handleJsonRpc({ jsonrpc: "2.0", id: "keepalive-1", method: "ping" });
expect(response).toEqual({ jsonrpc: "2.0", id: "keepalive-1", result: {} });
});

it("answers ping with extra params by ignoring them (params carry no payload)", async () => {
const root = await tempRoot();
const server = await pingServer(root);
const response = await server.handleJsonRpc({
jsonrpc: "2.0",
id: 42,
method: "ping",
params: { unexpected: "ignored" },
});
expect(response).toEqual({ jsonrpc: "2.0", id: 42, result: {} });
});

it("does not write any coordinator state files for a ping keepalive", async () => {
const root = await tempRoot();
const stateRoot = path.join(root, ".gjc", "coordinator-state");
const server = await pingServer(root);
await server.handleJsonRpc({ jsonrpc: "2.0", id: 1, method: "ping" });
const exists = await fs
.stat(stateRoot)
.then(() => true)
.catch(() => false);
expect(exists).toBe(false);
});
it("uses agent-global SDK discovery and returns credential-free broker status", async () => {
const root = await tempRoot();
const controls: SdkControl[] = [];
Expand Down
10 changes: 10 additions & 0 deletions packages/coding-agent/test/coordinator-mcp/pump.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,14 @@ describe("pumpCoordinatorMcpStream — frame handling", () => {
await pumpCoordinatorMcpStream(handler as never, ch, l => void writes.push(JSON.parse(l)));
expect(writes.map(w => w.id)).toEqual([7]);
});
it("answers a ping request but emits nothing for a ping notification (no id)", async () => {
const handler = async (req: Rpc): Promise<Rpc> => ({ jsonrpc: "2.0", id: req.id ?? null, result: {} });
const writes: Rpc[] = [];
const ch = channel();
ch.push(`${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" })}\n`); // request → answered
ch.push(`${JSON.stringify({ jsonrpc: "2.0", method: "ping" })}\n`); // notification (no id) → no response
ch.close();
await pumpCoordinatorMcpStream(handler as never, ch, l => void writes.push(JSON.parse(l)));
expect(writes.map(w => w.id)).toEqual([1]); // only the request id gets a response
});
});
Loading