summaryrefslogtreecommitdiff
path: root/packages/server/src
diff options
context:
space:
mode:
authorOlcan <[email protected]>2025-05-16 16:29:03 -0700
committerGitHub <[email protected]>2025-05-16 16:29:03 -0700
commitd9bd2b0e144560c8a82806bfb021a028c7cd43c9 (patch)
tree3573b27b520d6a83b56a16da41ecab700ddf2a26 /packages/server/src
parente158a0d59f46df4a8b76a7118c7cc10226fdb1c5 (diff)
improved mcp support, including standard "mcpServers" setting with multiple named servers with command/args/env/cwd (#392)
Diffstat (limited to 'packages/server/src')
-rw-r--r--packages/server/src/config/config.test.ts5
-rw-r--r--packages/server/src/config/config.ts10
-rw-r--r--packages/server/src/tools/tool-registry.ts98
3 files changed, 72 insertions, 41 deletions
diff --git a/packages/server/src/config/config.test.ts b/packages/server/src/config/config.test.ts
index b999b7fb..ade27a87 100644
--- a/packages/server/src/config/config.test.ts
+++ b/packages/server/src/config/config.test.ts
@@ -59,6 +59,7 @@ describe('Server Config (config.ts)', () => {
undefined, // toolDiscoveryCommand
undefined, // toolCallCommand
undefined, // mcpServerCommand
+ undefined, // mcpServers
USER_AGENT,
USER_MEMORY, // Pass memory here
);
@@ -83,6 +84,7 @@ describe('Server Config (config.ts)', () => {
undefined,
undefined,
undefined,
+ undefined,
USER_AGENT,
// No userMemory argument
);
@@ -102,6 +104,7 @@ describe('Server Config (config.ts)', () => {
undefined,
undefined,
undefined,
+ undefined,
USER_AGENT,
USER_MEMORY, // Pass memory here
);
@@ -125,6 +128,7 @@ describe('Server Config (config.ts)', () => {
undefined,
undefined,
undefined,
+ undefined,
USER_AGENT,
// No userMemory argument
);
@@ -147,6 +151,7 @@ describe('Server Config (config.ts)', () => {
undefined,
undefined,
undefined,
+ undefined,
USER_AGENT,
USER_MEMORY,
);
diff --git a/packages/server/src/config/config.ts b/packages/server/src/config/config.ts
index e104eaaa..4221b71e 100644
--- a/packages/server/src/config/config.ts
+++ b/packages/server/src/config/config.ts
@@ -20,6 +20,7 @@ import { WriteFileTool } from '../tools/write-file.js';
import { WebFetchTool } from '../tools/web-fetch.js';
import { ReadManyFilesTool } from '../tools/read-many-files.js';
import { BaseTool, ToolResult } from '../tools/tools.js';
+import { StdioServerParameters } from '@modelcontextprotocol/sdk/client/stdio.js';
export class Config {
private toolRegistry: ToolRegistry;
@@ -35,6 +36,9 @@ export class Config {
private readonly toolDiscoveryCommand: string | undefined,
private readonly toolCallCommand: string | undefined,
private readonly mcpServerCommand: string | undefined,
+ private readonly mcpServers:
+ | Record<string, StdioServerParameters>
+ | undefined,
private readonly userAgent: string,
private userMemory: string = '', // Made mutable for refresh
private geminiMdFileCount: number = 0,
@@ -86,6 +90,10 @@ export class Config {
return this.mcpServerCommand;
}
+ getMcpServers(): Record<string, StdioServerParameters> | undefined {
+ return this.mcpServers;
+ }
+
getUserAgent(): string {
return this.userAgent;
}
@@ -146,6 +154,7 @@ export function createServerConfig(
toolDiscoveryCommand?: string,
toolCallCommand?: string,
mcpServerCommand?: string,
+ mcpServers?: Record<string, StdioServerParameters>,
userAgent?: string,
userMemory?: string,
geminiMdFileCount?: number,
@@ -161,6 +170,7 @@ export function createServerConfig(
toolDiscoveryCommand,
toolCallCommand,
mcpServerCommand,
+ mcpServers,
userAgent ?? 'GeminiCLI/unknown', // Default user agent
userMemory ?? '',
geminiMdFileCount ?? 0,
diff --git a/packages/server/src/tools/tool-registry.ts b/packages/server/src/tools/tool-registry.ts
index f62a3256..67f47af0 100644
--- a/packages/server/src/tools/tool-registry.ts
+++ b/packages/server/src/tools/tool-registry.ts
@@ -7,6 +7,7 @@
import { FunctionDeclaration } from '@google/genai';
import { Tool, ToolResult, BaseTool } from './tools.js';
import { Config } from '../config/config.js';
+import { parse } from 'shell-quote';
import { spawn, execSync } from 'node:child_process';
// TODO: remove this dependency once MCP support is built into genai SDK
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -26,7 +27,7 @@ export class DiscoveredTool extends BaseTool<ToolParams, ToolResult> {
This tool was discovered from the project by executing the command \`${discoveryCmd}\` on project root.
When called, this tool will execute the command \`${callCommand} ${name}\` on project root.
-Tool discovery and call commands can be configured in project settings.
+Tool discovery and call commands can be configured in project or user settings.
When called, the tool call command is executed as a subprocess.
On success, tool output is returned as a json string.
@@ -99,13 +100,11 @@ export class DiscoveredMCPTool extends BaseTool<ToolParams, ToolResult> {
readonly description: string,
readonly parameterSchema: Record<string, unknown>,
) {
- const mcpServerCmd = config.getMcpServerCommand()!;
description += `
This MCP tool was discovered from a local MCP server using JSON RPC 2.0 over stdio transport protocol.
-The MCP server was started by executing the command \`${mcpServerCmd}\` on project root.
When called, this tool will invoke the \`tools/call\` method for tool name \`${name}\`.
-MCP server command can be configured in project settings.
+MCP servers can be configured in project or user settings.
Returns the MCP server response as a json string.
`;
super(name, name, description, parameterSchema);
@@ -125,7 +124,6 @@ Returns the MCP server response as a json string.
export class ToolRegistry {
private tools: Map<string, Tool> = new Map();
- private mcpClient: Client | null = null;
constructor(private readonly config: Config) {}
@@ -174,48 +172,66 @@ export class ToolRegistry {
);
}
}
- // discover tools using MCP server command, if configured
- const mcpServerCmd = this.config.getMcpServerCommand();
- if (mcpServerCmd) {
+ // discover tools using MCP servers, if configured
+ // convert mcpServerCommand (if any) to StdioServerParameters
+ const mcpServers = this.config.getMcpServers() || {};
+
+ if (this.config.getMcpServerCommand()) {
+ const cmd = this.config.getMcpServerCommand()!;
+ const args = parse(cmd, process.env) as string[];
+ if (args.some((arg) => typeof arg !== 'string')) {
+ throw new Error('failed to parse mcpServerCommand: ' + cmd);
+ }
+ // use generic server name 'mcp'
+ mcpServers['mcp'] = {
+ command: args[0],
+ args: args.slice(1),
+ };
+ }
+ for (const [mcpServerName, mcpServer] of Object.entries(mcpServers)) {
(async () => {
- if (!this.mcpClient) {
- this.mcpClient = new Client({
- name: 'mcp-client',
- version: '0.0.1',
- });
- const transport = new StdioClientTransport({
- command: mcpServerCmd,
- stderr: 'pipe',
- });
- try {
- await this.mcpClient.connect(transport);
- } catch (error) {
- console.error(
- 'failed to start or connect to MCP server using ' +
- `command '${mcpServerCmd}'; \n${error}`,
- );
- throw error;
- }
- this.mcpClient.onerror = (error) => {
- console.error('MCP ERROR', error.toString());
- };
- if (!transport.stderr) {
- throw new Error('transport missing stderr stream');
- }
- transport.stderr.on('data', (data) => {
- // filter out INFO messages logged for each request received
- if (!data.toString().includes('] INFO')) {
- console.log('MCP STDERR', data.toString());
- }
- });
+ const mcpClient = new Client({
+ name: 'mcp-client',
+ version: '0.0.1',
+ });
+ const transport = new StdioClientTransport({
+ ...mcpServer,
+ env: {
+ ...process.env,
+ ...(mcpServer.env || {}),
+ } as Record<string, string>,
+ stderr: 'pipe',
+ });
+ try {
+ await mcpClient.connect(transport);
+ } catch (error) {
+ console.error(
+ `failed to start or connect to MCP server '${mcpServerName}' ` +
+ `${JSON.stringify(mcpServer)}; \n${error}`,
+ );
+ throw error;
}
- const result = await this.mcpClient.listTools();
+ mcpClient.onerror = (error) => {
+ console.error('MCP ERROR', error.toString());
+ };
+ if (!transport.stderr) {
+ throw new Error('transport missing stderr stream');
+ }
+ transport.stderr.on('data', (data) => {
+ // filter out INFO messages logged for each request received
+ if (!data.toString().includes('] INFO')) {
+ console.log('MCP STDERR', data.toString());
+ }
+ });
+ const result = await mcpClient.listTools();
for (const tool of result.tools) {
this.registerTool(
new DiscoveredMCPTool(
- this.mcpClient,
+ mcpClient,
this.config,
- tool.name,
+ Object.keys(mcpServers).length > 1
+ ? mcpServerName + '__' + tool.name
+ : tool.name,
tool.description ?? '',
tool.inputSchema,
),