MCP SSE support (#511)

Matches the config format used by other MCP clients.
This commit is contained in:
cornmander 2025-05-23 17:19:30 -04:00 committed by GitHub
parent 8590efd229
commit 635666dec9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 37 additions and 16 deletions

View File

@ -24,10 +24,14 @@ import { WebSearchTool } from '../tools/web-search.js';
export class MCPServerConfig { export class MCPServerConfig {
constructor( constructor(
readonly command: string, // For stdio transport
readonly command?: string,
readonly args?: string[], readonly args?: string[],
readonly env?: Record<string, string>, readonly env?: Record<string, string>,
readonly cwd?: string, readonly cwd?: string,
// For sse transport
readonly url?: string,
// Common
readonly timeout?: number, readonly timeout?: number,
) {} ) {}
} }

View File

@ -12,6 +12,7 @@ import { spawn, execSync } from 'node:child_process';
// TODO: remove this dependency once MCP support is built into genai SDK // TODO: remove this dependency once MCP support is built into genai SDK
import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
type ToolParams = Record<string, unknown>; type ToolParams = Record<string, unknown>;
const MCP_TOOL_DEFAULT_TIMEOUT_MSEC = 10 * 60 * 1000; // default to 10 minutes const MCP_TOOL_DEFAULT_TIMEOUT_MSEC = 10 * 60 * 1000; // default to 10 minutes
@ -206,14 +207,28 @@ export class ToolRegistry {
name: 'mcp-client', name: 'mcp-client',
version: '0.0.1', version: '0.0.1',
}); });
const transport = new StdioClientTransport({ let transport;
...mcpServerConfig, if (mcpServerConfig.url) {
env: { // SSE transport if URL is provided
...process.env, transport = new SSEClientTransport(new URL(mcpServerConfig.url));
...(mcpServerConfig.env || {}), } else if (mcpServerConfig.command) {
} as Record<string, string>, // Stdio transport if command is provided
stderr: 'pipe', transport = new StdioClientTransport({
}); command: mcpServerConfig.command,
args: mcpServerConfig.args || [],
env: {
...process.env,
...(mcpServerConfig.env || {}),
} as Record<string, string>,
cwd: mcpServerConfig.cwd,
stderr: 'pipe',
});
} else {
console.error(
`MCP server '${mcpServerName}' has invalid configuration: missing both url (for SSE) and command (for stdio). Skipping.`,
);
return;
}
try { try {
await mcpClient.connect(transport); await mcpClient.connect(transport);
} catch (error) { } catch (error) {
@ -227,15 +242,17 @@ export class ToolRegistry {
mcpClient.onerror = (error) => { mcpClient.onerror = (error) => {
console.error('MCP ERROR', error.toString()); console.error('MCP ERROR', error.toString());
}; };
if (!transport.stderr) { if (transport instanceof StdioClientTransport && !transport.stderr) {
throw new Error('transport missing stderr stream'); throw new Error('transport missing stderr stream');
} }
transport.stderr.on('data', (data) => { if (transport instanceof StdioClientTransport) {
// filter out INFO messages logged for each request received transport.stderr!.on('data', (data) => {
if (!data.toString().includes('] INFO')) { // filter out INFO messages logged for each request received
console.debug('MCP STDERR', data.toString()); if (!data.toString().includes('] INFO')) {
} console.debug('MCP STDERR', data.toString());
}); }
});
}
const result = await mcpClient.listTools(); const result = await mcpClient.listTools();
for (const tool of result.tools) { for (const tool of result.tools) {
// Recursively remove additionalProperties and $schema from the inputSchema // Recursively remove additionalProperties and $schema from the inputSchema