diff options
| author | Jack Wotherspoon <[email protected]> | 2025-08-06 11:52:29 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-06 15:52:29 +0000 |
| commit | ca4c745e3b620e3ac4eca24b610cc7b936c0a50d (patch) | |
| tree | eb1b9cfb46bd4fa2e7f9631828b0704df1050eb7 /packages/cli/src/commands/mcp/add.ts | |
| parent | b38f377c9a2672e88dd15119b38d908c0f00b54a (diff) | |
feat(mcp): add `gemini mcp` commands for `add`, `remove` and `list` (#5481)
Diffstat (limited to 'packages/cli/src/commands/mcp/add.ts')
| -rw-r--r-- | packages/cli/src/commands/mcp/add.ts | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/packages/cli/src/commands/mcp/add.ts b/packages/cli/src/commands/mcp/add.ts new file mode 100644 index 00000000..9537e131 --- /dev/null +++ b/packages/cli/src/commands/mcp/add.ts @@ -0,0 +1,211 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +// File for 'gemini mcp add' command +import type { CommandModule } from 'yargs'; +import { loadSettings, SettingScope } from '../../config/settings.js'; +import { MCPServerConfig } from '@google/gemini-cli-core'; + +async function addMcpServer( + name: string, + commandOrUrl: string, + args: Array<string | number> | undefined, + options: { + scope: string; + transport: string; + env: string[] | undefined; + header: string[] | undefined; + timeout?: number; + trust?: boolean; + description?: string; + includeTools?: string[]; + excludeTools?: string[]; + }, +) { + const { + scope, + transport, + env, + header, + timeout, + trust, + description, + includeTools, + excludeTools, + } = options; + const settingsScope = + scope === 'user' ? SettingScope.User : SettingScope.Workspace; + const settings = loadSettings(process.cwd()); + + let newServer: Partial<MCPServerConfig> = {}; + + const headers = header?.reduce( + (acc, curr) => { + const [key, ...valueParts] = curr.split(':'); + const value = valueParts.join(':').trim(); + if (key.trim() && value) { + acc[key.trim()] = value; + } + return acc; + }, + {} as Record<string, string>, + ); + + switch (transport) { + case 'sse': + newServer = { + url: commandOrUrl, + headers, + timeout, + trust, + description, + includeTools, + excludeTools, + }; + break; + case 'http': + newServer = { + httpUrl: commandOrUrl, + headers, + timeout, + trust, + description, + includeTools, + excludeTools, + }; + break; + case 'stdio': + default: + newServer = { + command: commandOrUrl, + args: args?.map(String), + env: env?.reduce( + (acc, curr) => { + const [key, value] = curr.split('='); + if (key && value) { + acc[key] = value; + } + return acc; + }, + {} as Record<string, string>, + ), + timeout, + trust, + description, + includeTools, + excludeTools, + }; + break; + } + + const existingSettings = settings.forScope(settingsScope).settings; + const mcpServers = existingSettings.mcpServers || {}; + + const isExistingServer = !!mcpServers[name]; + if (isExistingServer) { + console.log( + `MCP server "${name}" is already configured within ${scope} settings.`, + ); + } + + mcpServers[name] = newServer as MCPServerConfig; + + settings.setValue(settingsScope, 'mcpServers', mcpServers); + + if (isExistingServer) { + console.log(`MCP server "${name}" updated in ${scope} settings.`); + } else { + console.log( + `MCP server "${name}" added to ${scope} settings. (${transport})`, + ); + } +} + +export const addCommand: CommandModule = { + command: 'add <name> <commandOrUrl> [args...]', + describe: 'Add a server', + builder: (yargs) => + yargs + .usage('Usage: gemini mcp add [options] <name> <commandOrUrl> [args...]') + .positional('name', { + describe: 'Name of the server', + type: 'string', + demandOption: true, + }) + .positional('commandOrUrl', { + describe: 'Command (stdio) or URL (sse, http)', + type: 'string', + demandOption: true, + }) + .option('scope', { + alias: 's', + describe: 'Configuration scope (user or project)', + type: 'string', + default: 'project', + choices: ['user', 'project'], + }) + .option('transport', { + alias: 't', + describe: 'Transport type (stdio, sse, http)', + type: 'string', + default: 'stdio', + choices: ['stdio', 'sse', 'http'], + }) + .option('env', { + alias: 'e', + describe: 'Set environment variables (e.g. -e KEY=value)', + type: 'array', + string: true, + }) + .option('header', { + alias: 'H', + describe: + 'Set HTTP headers for SSE and HTTP transports (e.g. -H "X-Api-Key: abc123" -H "Authorization: Bearer abc123")', + type: 'array', + string: true, + }) + .option('timeout', { + describe: 'Set connection timeout in milliseconds', + type: 'number', + }) + .option('trust', { + describe: + 'Trust the server (bypass all tool call confirmation prompts)', + type: 'boolean', + }) + .option('description', { + describe: 'Set the description for the server', + type: 'string', + }) + .option('include-tools', { + describe: 'A comma-separated list of tools to include', + type: 'array', + string: true, + }) + .option('exclude-tools', { + describe: 'A comma-separated list of tools to exclude', + type: 'array', + string: true, + }), + handler: async (argv) => { + await addMcpServer( + argv.name as string, + argv.commandOrUrl as string, + argv.args as Array<string | number>, + { + scope: argv.scope as string, + transport: argv.transport as string, + env: argv.env as string[], + header: argv.header as string[], + timeout: argv.timeout as number | undefined, + trust: argv.trust as boolean | undefined, + description: argv.description as string | undefined, + includeTools: argv.includeTools as string[] | undefined, + excludeTools: argv.excludeTools as string[] | undefined, + }, + ); + }, +}; |
