scira-chat / lib /mcp-client.ts
victor's picture
victor HF Staff
feat: switch to official openai client
dff2be9
raw
history blame
2.89 kB
import { Client as MCPClient } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
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")
? new SSEClientTransport(new URL(mcpServer.url), {
requestInit: {
headers,
},
})
: new StreamableHTTPClientTransport(new URL(mcpServer.url), {
requestInit: {
headers,
},
});
const mcpClient = new MCPClient({
name: "mcp-chat-client",
version: "0.1.0",
});
await mcpClient.connect(transport);
mcpClients.push(mcpClient);
const mcptools = await mcpClient.listTools();
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);
}
}
}