display verbiage for auto-executing tools and make sure auto-execute only happens when tools are being sandboxed

This commit is contained in:
A.K.M. Adib
2026-03-30 17:03:02 -04:00
parent 7233000464
commit e5d3235eaf
13 changed files with 294 additions and 14 deletions
+56
View File
@@ -27,6 +27,33 @@ vi.mock('fs/promises', () => ({
writeFile: vi.fn(),
}));
vi.mock('@modelcontextprotocol/sdk/client/index.js', () => {
class MockClient {
async connect() {}
async listTools() {
return {
tools: [
{ name: 'read_only_tool', annotations: { readOnlyHint: true } },
{ name: 'write_tool', annotations: { readOnlyHint: false } },
],
};
}
async close() {}
}
return {
Client: MockClient,
};
});
vi.mock('@google/gemini-cli-core', async () => {
const actual = await vi.importActual('@google/gemini-cli-core');
return {
...actual,
createTransport: vi.fn().mockResolvedValue({}),
createSandboxManager: vi.fn().mockReturnValue({}),
};
});
vi.mock('os', () => {
const homedir = vi.fn(() => '/home/user');
return {
@@ -68,6 +95,33 @@ describe('mcp add command', () => {
setValue: mockSetValue,
workspace: { path: '/path/to/project' },
user: { path: '/home/user' },
merged: {},
});
});
describe('sandboxing warnings', () => {
it('should discover tools and emit warning when sandboxing is enabled', async () => {
mockedLoadSettings.mockReturnValue({
forScope: () => ({ settings: {} }),
setValue: mockSetValue,
workspace: { path: '/path/to/project' },
user: { path: '/home/user' },
merged: {
tools: {
sandbox: { enabled: true },
},
},
});
const debugLoggerWarnSpy = vi.spyOn(debugLogger, 'warn').mockImplementation(() => {});
await parser.parseAsync('add sandbox-server /path/to/server');
expect(debugLoggerWarnSpy).toHaveBeenCalledWith(
expect.stringContaining(
'Warning: With sandboxing enabled, the following tools are marked as read-only and will AUTO-EXECUTE without confirmation:\n * read_only_tool',
),
);
});
});
@@ -216,6 +270,7 @@ describe('mcp add command', () => {
setValue: mockSetValue,
workspace: { path: workspacePath },
user: { path: '/home/user' },
merged: {},
});
};
@@ -383,6 +438,7 @@ describe('mcp add command', () => {
setValue: mockSetValue,
workspace: { path: '/path/to/project' },
user: { path: '/home/user' },
merged: {},
});
});
+64 -1
View File
@@ -7,7 +7,14 @@
// File for 'gemini mcp add' command
import type { CommandModule } from 'yargs';
import { loadSettings, SettingScope } from '../../config/settings.js';
import { debugLogger, type MCPServerConfig } from '@google/gemini-cli-core';
import {
debugLogger,
type MCPServerConfig,
type McpContext,
createTransport,
createSandboxManager,
} from '@google/gemini-cli-core';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { exitCli } from '../utils.js';
async function addMcpServer(
@@ -135,6 +142,62 @@ async function addMcpServer(
`MCP server "${name}" added to ${scope} settings. (${transport})`,
);
}
const isSandboxEnabled =
typeof settings.merged.tools?.sandbox === 'object'
? settings.merged.tools.sandbox.enabled
: !!settings.merged.tools?.sandbox;
if (isSandboxEnabled) {
const mcpContext: McpContext = {
sanitizationConfig: {
enableEnvironmentVariableRedaction: true,
allowedEnvironmentVariables: [],
blockedEnvironmentVariables: settings.merged.advanced?.excludedEnvVars ?? [],
},
emitMcpDiagnostic: () => {},
isTrustedFolder: () => true,
isSandboxEnabled: () => true,
sandboxManager: createSandboxManager(
{ enabled: true },
{ workspace: process.cwd(), modeConfig: { readonly: true } },
),
};
try {
const serverTransport = await createTransport(
name,
newServer as MCPServerConfig,
false,
mcpContext,
);
const client = new Client(
{ name: 'gemini-cli-discovery', version: '1.0.0' },
{ capabilities: {} },
);
await client.connect(serverTransport);
const result = await client.listTools();
const readOnlyTools = result.tools
.filter(
(t: { name: string; annotations?: { readOnlyHint?: boolean } }) =>
t.annotations?.readOnlyHint === true,
)
.map((t) => t.name);
if (readOnlyTools.length > 0) {
debugLogger.warn(
`Warning: With sandboxing enabled, the following tools are marked as read-only and will AUTO-EXECUTE without confirmation:\n${readOnlyTools
.map((t) => ` * ${t}`)
.join('\n')}`,
);
}
await client.close();
} catch (e) {
debugLogger.warn(
'Warning: With sandboxing enabled, any read-only tools provided by this server will AUTO-EXECUTE without confirmation.',
);
}
}
}
export const addCommand: CommandModule = {
+3
View File
@@ -17,6 +17,7 @@ import {
debugLogger,
applyAdminAllowlist,
getAdminBlockedMcpServersMessage,
NoopSandboxManager,
} from '@google/gemini-cli-core';
import type { MCPServerConfig } from '@google/gemini-cli-core';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -115,6 +116,8 @@ async function testMCPConnection(
}
},
isTrustedFolder: () => isTrusted,
isSandboxEnabled: () => false,
sandboxManager: new NoopSandboxManager(),
};
let transport;