Spaces:
Running
Running
import { experimental_createMCPClient as createMCPClient } from 'ai'; | |
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; | |
export interface KeyValuePair { | |
key: string; | |
value: string; | |
} | |
export interface MCPServerConfig { | |
url: string; | |
type: 'sse' | 'stdio'; | |
command?: string; | |
args?: string[]; | |
env?: KeyValuePair[]; | |
headers?: KeyValuePair[]; | |
} | |
export interface MCPClientManager { | |
tools: Record<string, any>; | |
clients: any[]; | |
cleanup: () => Promise<void>; | |
} | |
/** | |
* Initialize MCP clients for API calls | |
* This uses the already running persistent HTTP or SSE servers | |
*/ | |
export async function initializeMCPClients( | |
mcpServers: MCPServerConfig[] = [], | |
abortSignal?: AbortSignal | |
): Promise<MCPClientManager> { | |
// Initialize tools | |
let tools = {}; | |
const mcpClients: any[] = []; | |
// Process each MCP server configuration | |
for (const mcpServer of mcpServers) { | |
try { | |
const headers = mcpServer.headers?.reduce((acc, header) => { | |
if (header.key) acc[header.key] = header.value || ''; | |
return acc; | |
}, {} as Record<string, string>); | |
// All servers are handled as HTTP or SSE | |
// SSE is only when URL ends with /sse | |
// which is the heuristic used by other clients | |
const transport = mcpServer.url.endsWith('/sse') | |
? { | |
type: 'sse' as const, | |
url: mcpServer.url, | |
headers, | |
} | |
: new StreamableHTTPClientTransport(new URL(mcpServer.url), { | |
requestInit: { | |
headers, | |
}, | |
}); | |
const mcpClient = await createMCPClient({ transport }); | |
mcpClients.push(mcpClient); | |
const mcptools = await mcpClient.tools(); | |
console.log(`MCP tools from ${mcpServer.url}:`, Object.keys(mcptools)); | |
// Add MCP tools to tools object | |
tools = { ...tools, ...mcptools }; | |
} catch (error) { | |
console.error("Failed to initialize MCP client:", error); | |
// Continue with other servers instead of failing the entire request | |
} | |
} | |
// Register cleanup for all clients if an abort signal is provided | |
if (abortSignal && mcpClients.length > 0) { | |
abortSignal.addEventListener('abort', async () => { | |
await cleanupMCPClients(mcpClients); | |
}); | |
} | |
return { | |
tools, | |
clients: mcpClients, | |
cleanup: async () => await cleanupMCPClients(mcpClients) | |
}; | |
} | |
async function cleanupMCPClients(clients: any[]): Promise<void> { | |
// Clean up the MCP clients | |
for (const client of clients) { | |
try { | |
await client.close(); | |
} catch (error) { | |
console.error("Error closing MCP client:", error); | |
} | |
} | |
} |