mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-29 04:37:12 -07:00
display verbiage for auto-executing tools and make sure auto-execute only happens when tools are being sandboxed
This commit is contained in:
@@ -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: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user