Skip to content

Commit 7a07b42

Browse files
abhipatel12gemini-cli-robot
authored andcommitted
refactor(core): standardize MCP tool naming to mcp_ FQN format (#21425)
# Conflicts: # integration-tests/policy-headless.test.ts # packages/core/src/tools/mcp-tool.test.ts # packages/core/src/tools/mcp-tool.ts # packages/core/src/tools/tool-registry.test.ts
1 parent c316fc6 commit 7a07b42

19 files changed

Lines changed: 899 additions & 493 deletions

integration-tests/extensions-reload.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe('extension reloading', () => {
104104
return (
105105
output.includes(
106106
'test-server (from test-extension) - Ready (1 tool)',
107-
) && output.includes('- hello')
107+
) && output.includes('- mcp_test-server_hello')
108108
);
109109
},
110110
30000, // 30s timeout
@@ -148,7 +148,7 @@ describe('extension reloading', () => {
148148
return (
149149
output.includes(
150150
'test-server (from test-extension) - Ready (1 tool)',
151-
) && output.includes('- goodbye')
151+
) && output.includes('- mcp_test-server_goodbye')
152152
);
153153
},
154154
30000,
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8+
import { join } from 'node:path';
9+
import { TestRig } from './test-helper.js';
10+
11+
interface PromptCommand {
12+
prompt: (testFile: string) => string;
13+
tool: string;
14+
command: string;
15+
expectedSuccessResult: string;
16+
expectedFailureResult: string;
17+
}
18+
19+
const ECHO_PROMPT: PromptCommand = {
20+
command: 'echo',
21+
prompt: () =>
22+
`Use the \`echo POLICY_TEST_ECHO_COMMAND\` shell command. On success, ` +
23+
`your final response must ONLY be "POLICY_TEST_ECHO_COMMAND". If the ` +
24+
`command fails output AR NAR and stop.`,
25+
tool: 'run_shell_command',
26+
expectedSuccessResult: 'POLICY_TEST_ECHO_COMMAND',
27+
expectedFailureResult: 'AR NAR',
28+
};
29+
30+
const READ_FILE_PROMPT: PromptCommand = {
31+
prompt: (testFile: string) =>
32+
`Read the file ${testFile} and tell me what language it is, if the ` +
33+
`read_file tool fails output AR NAR and stop.`,
34+
tool: 'read_file',
35+
command: '',
36+
expectedSuccessResult: 'Latin',
37+
expectedFailureResult: 'AR NAR',
38+
};
39+
40+
async function waitForToolCallLog(
41+
rig: TestRig,
42+
tool: string,
43+
command: string,
44+
timeout: number = 15000,
45+
) {
46+
const foundToolCall = await rig.waitForToolCall(tool, timeout, (args) =>
47+
args.toLowerCase().includes(command.toLowerCase()),
48+
);
49+
50+
expect(foundToolCall).toBe(true);
51+
52+
const toolLogs = rig
53+
.readToolLogs()
54+
.filter((toolLog) => toolLog.toolRequest.name === tool);
55+
const log = toolLogs.find(
56+
(toolLog) =>
57+
!command ||
58+
toolLog.toolRequest.args.toLowerCase().includes(command.toLowerCase()),
59+
);
60+
61+
// The policy engine should have logged the tool call
62+
expect(log).toBeTruthy();
63+
return log;
64+
}
65+
66+
async function verifyToolExecution(
67+
rig: TestRig,
68+
promptCommand: PromptCommand,
69+
result: string,
70+
expectAllowed: boolean,
71+
expectedDenialString?: string,
72+
) {
73+
const log = await waitForToolCallLog(
74+
rig,
75+
promptCommand.tool,
76+
promptCommand.command,
77+
);
78+
79+
if (expectAllowed) {
80+
expect(log!.toolRequest.success).toBe(true);
81+
expect(result).not.toContain('Tool execution denied by policy');
82+
expect(result).not.toContain(`Tool "${promptCommand.tool}" not found`);
83+
expect(result).toContain(promptCommand.expectedSuccessResult);
84+
} else {
85+
expect(log!.toolRequest.success).toBe(false);
86+
expect(result).toContain(
87+
expectedDenialString || 'Tool execution denied by policy',
88+
);
89+
expect(result).toContain(promptCommand.expectedFailureResult);
90+
}
91+
}
92+
93+
interface TestCase {
94+
name: string;
95+
responsesFile: string;
96+
promptCommand: PromptCommand;
97+
policyContent?: string;
98+
expectAllowed: boolean;
99+
expectedDenialString?: string;
100+
}
101+
102+
describe('Policy Engine Headless Mode', () => {
103+
let rig: TestRig;
104+
let testFile: string;
105+
106+
beforeEach(() => {
107+
rig = new TestRig();
108+
});
109+
110+
afterEach(async () => {
111+
if (rig) {
112+
await rig.cleanup();
113+
}
114+
});
115+
116+
const runTestCase = async (tc: TestCase) => {
117+
const fakeResponsesPath = join(import.meta.dirname, tc.responsesFile);
118+
rig.setup(tc.name, { fakeResponsesPath });
119+
120+
testFile = rig.createFile('test.txt', 'Lorem\nIpsum\nDolor\n');
121+
const args = ['-p', tc.promptCommand.prompt(testFile)];
122+
123+
if (tc.policyContent) {
124+
const policyPath = rig.createFile('test-policy.toml', tc.policyContent);
125+
args.push('--policy', policyPath);
126+
}
127+
128+
const result = await rig.run({
129+
args,
130+
approvalMode: 'default',
131+
});
132+
133+
await verifyToolExecution(
134+
rig,
135+
tc.promptCommand,
136+
result,
137+
tc.expectAllowed,
138+
tc.expectedDenialString,
139+
);
140+
};
141+
142+
const testCases = [
143+
{
144+
name: 'should deny ASK_USER tools by default in headless mode',
145+
responsesFile: 'policy-headless-shell-denied.responses',
146+
promptCommand: ECHO_PROMPT,
147+
expectAllowed: false,
148+
expectedDenialString: 'Tool "run_shell_command" not found',
149+
},
150+
{
151+
name: 'should allow ASK_USER tools in headless mode if explicitly allowed via policy file',
152+
responsesFile: 'policy-headless-shell-allowed.responses',
153+
promptCommand: ECHO_PROMPT,
154+
policyContent: `
155+
[[rule]]
156+
toolName = "run_shell_command"
157+
decision = "allow"
158+
priority = 100
159+
`,
160+
expectAllowed: true,
161+
},
162+
{
163+
name: 'should allow read-only tools by default in headless mode',
164+
responsesFile: 'policy-headless-readonly.responses',
165+
promptCommand: READ_FILE_PROMPT,
166+
expectAllowed: true,
167+
},
168+
{
169+
name: 'should allow specific shell commands in policy file',
170+
responsesFile: 'policy-headless-shell-allowed.responses',
171+
promptCommand: ECHO_PROMPT,
172+
policyContent: `
173+
[[rule]]
174+
toolName = "run_shell_command"
175+
commandPrefix = "${ECHO_PROMPT.command}"
176+
decision = "allow"
177+
priority = 100
178+
`,
179+
expectAllowed: true,
180+
},
181+
{
182+
name: 'should deny other shell commands in policy file',
183+
responsesFile: 'policy-headless-shell-denied.responses',
184+
promptCommand: ECHO_PROMPT,
185+
policyContent: `
186+
[[rule]]
187+
toolName = "run_shell_command"
188+
commandPrefix = "node"
189+
decision = "allow"
190+
priority = 100
191+
`,
192+
expectAllowed: false,
193+
expectedDenialString: 'Tool execution denied by policy',
194+
},
195+
];
196+
197+
it.each(testCases)(
198+
'$name',
199+
async (tc) => {
200+
await runTestCase(tc);
201+
},
202+
// Large timeout for regeneration
203+
process.env['REGENERATE_MODEL_GOLDENS'] === 'true' ? 120000 : undefined,
204+
);
205+
});

0 commit comments

Comments
 (0)