Skip to content

Commit 97b01f0

Browse files
committed
Ensure that MCP tool isn't called when rejected
1 parent 2c3ef1d commit 97b01f0

2 files changed

Lines changed: 48 additions & 8 deletions

File tree

src/__tests__/CodexACPAgent/e2e/acp-e2e-mcp-approval.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type * as acp from "@agentclientprotocol/sdk";
2+
import fs from "node:fs";
23
import path from "node:path";
34
import {afterEach, beforeEach, expect, it} from "vitest";
45
import {ApprovalOptionId} from "../../../ApprovalOptionId";
@@ -14,12 +15,15 @@ import {
1415
const MCP_SERVER_NAME = "integration-mcp";
1516
const MCP_ECHO_MESSAGE = "mcp approval e2e";
1617

17-
function createMcpServer(): acp.McpServerStdio {
18+
function createMcpServer(invocationMarkerPath: string): acp.McpServerStdio {
1819
return {
1920
name: MCP_SERVER_NAME,
2021
command: process.execPath,
21-
args: [path.join(process.cwd(), "node_modules/mcp-hello-world/build/stdio.js")],
22-
env: [],
22+
args: [path.join(process.cwd(), "src/__tests__/CodexACPAgent/e2e/fixtures/invocation-aware-mcp-server.mjs")],
23+
env: [{
24+
name: "MCP_TOOL_INVOCATION_MARKER_PATH",
25+
value: invocationMarkerPath,
26+
}],
2327
};
2428
}
2529

@@ -33,36 +37,39 @@ function createMcpPermissionResponder(optionId: ApprovalOptionId): PermissionRes
3337

3438
describeE2E("E2E MCP approval tests", () => {
3539
let fixture: SpawnedAgentFixture;
36-
let sessionId: string;
3740

3841
beforeEach(async () => {
3942
fixture = await createAuthenticatedFixture();
40-
sessionId = (await fixture.createSession([createMcpServer()])).sessionId;
4143
});
4244

4345
afterEach(async () => {
4446
await fixture.dispose();
4547
});
4648

47-
function expectMcpToolPermissionRequest(): void {
49+
function expectMcpToolPermissionRequest(sessionId: string): void {
4850
const requests = fixture.readPermissionRequests(sessionId, "execute");
4951
expect(requests.length).toBe(1);
5052
expect(isMcpPermissionRequest(requests[0]!)).toBe(true);
5153
}
5254

5355
it("executes an approved MCP tool call", async () => {
5456
fixture.setPermissionResponder(createMcpPermissionResponder(ApprovalOptionId.AllowOnce));
57+
const invocationMarkerPath = path.join(fixture.workspaceDir, `mcp-tool-invocation-${crypto.randomUUID()}.txt`);
58+
const sessionId = (await fixture.createSession([createMcpServer(invocationMarkerPath)])).sessionId;
5559

5660
await fixture.expectPromptText(
5761
sessionId,
5862
`Use the ${MCP_SERVER_NAME} MCP echo tool with message "${MCP_ECHO_MESSAGE}". Reply with exactly the tool result and no extra text.`,
5963
(text) => expect(text).toContain(`You said: ${MCP_ECHO_MESSAGE}`),
6064
);
61-
expectMcpToolPermissionRequest();
65+
expect(fs.readFileSync(invocationMarkerPath, "utf8")).toBe(MCP_ECHO_MESSAGE);
66+
expectMcpToolPermissionRequest(sessionId);
6267
});
6368

6469
it("ends turn when MCP tool call is rejected", async () => {
6570
fixture.setPermissionResponder(createMcpPermissionResponder(ApprovalOptionId.RejectOnce));
71+
const invocationMarkerPath = path.join(fixture.workspaceDir, `mcp-tool-invocation-${crypto.randomUUID()}.txt`);
72+
const sessionId = (await fixture.createSession([createMcpServer(invocationMarkerPath)])).sessionId;
6673

6774
expectEndTurn(await fixture.connection.prompt({
6875
sessionId,
@@ -71,6 +78,7 @@ describeE2E("E2E MCP approval tests", () => {
7178
text: `Use the ${MCP_SERVER_NAME} MCP echo tool with message "${MCP_ECHO_MESSAGE}". Stop if the tool call is rejected.`,
7279
}],
7380
}));
74-
expectMcpToolPermissionRequest();
81+
expect(fs.existsSync(invocationMarkerPath)).toBe(false);
82+
expectMcpToolPermissionRequest(sessionId);
7583
});
7684
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import fs from "node:fs/promises";
2+
import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
3+
import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
4+
import {z} from "zod";
5+
6+
const markerPath = process.env["MCP_TOOL_INVOCATION_MARKER_PATH"];
7+
8+
if (!markerPath) {
9+
throw new Error("MCP_TOOL_INVOCATION_MARKER_PATH is required.");
10+
}
11+
12+
const server = new McpServer({
13+
name: "integration-mcp",
14+
version: "1.0.0",
15+
});
16+
17+
server.tool(
18+
"echo",
19+
"Echoes back a message with a side effect for test assertions.",
20+
{message: z.string().describe("The message to echo")},
21+
async ({message}) => {
22+
await fs.writeFile(markerPath, message, "utf8");
23+
return {
24+
content: [{
25+
type: "text",
26+
text: `You said: ${message}`,
27+
}],
28+
};
29+
},
30+
);
31+
32+
await server.connect(new StdioServerTransport());

0 commit comments

Comments
 (0)