File size: 3,235 Bytes
52c6f5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import type { MCPServerConfig, McpToolSchema, OpenAIFunctionSchema } from "./types.js";
import { debugError, debugLog } from "./utils.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";

export const mcpToolToOpenAIFunction = (tool: McpToolSchema): OpenAIFunctionSchema => {
	return {
		type: "function",
		function: {
			name: tool.name,
			description: tool.name,
			parameters: tool.inputSchema,
			strict: true,
		},
	};
};

export type MCPServerConnection = { client: Client; tools: OpenAIFunctionSchema[] };

export const connectToMCPServers = async (servers: MCPServerConfig[]): Promise<MCPServerConnection[]> => {
	const connections: MCPServerConnection[] = [];

	await Promise.allSettled(
		servers.map(async server => {
			try {
				const conn: MCPServerConnection = {
					client: new Client({
						name: "playground-client" + crypto.randomUUID(),
						version: "0.0.1",
					}),
					tools: [],
				};

				debugLog(`Connecting to MCP server: ${server.name} (${server.url})`);

				let transport;
				const url = new URL(server.url);
				if (server.protocol === "sse") {
					transport = new SSEClientTransport(url);
				} else {
					transport = new StreamableHTTPClientTransport(url);
				}

				await conn.client.connect(transport);

				const { tools: mcpTools } = await conn.client.listTools();
				const serverTools = mcpTools.map(mcpToolToOpenAIFunction);
				conn.tools.push(...serverTools);
				debugLog(`Connected to ${server.name} with ${mcpTools.length} tools`);
				connections.push(conn);
			} catch (error) {
				debugError(`Failed to connect to MCP server ${server.name}:`, error);
			}
		}),
	);

	return connections;
};

export const executeMcpTool = async (
	connections: MCPServerConnection[],
	toolCall: { id: string; function: { name: string; arguments: string } },
) => {
	try {
		debugLog(`Executing tool: ${toolCall.function.name}`);
		debugLog(`Tool arguments:`, JSON.parse(toolCall.function.arguments));

		// Try to find the tool in any of the connected clients
		let result = null;
		for (const conn of connections) {
			try {
				const toolExists = conn.tools.some(tool => tool.function?.name === toolCall.function.name);
				if (!toolExists) continue;
				debugLog(`Found tool ${toolCall.function.name}`);
				result = await conn.client.callTool({
					name: toolCall.function.name,
					arguments: JSON.parse(toolCall.function.arguments),
				});
			} catch (clientError) {
				debugError(`Failed to execute tool on client:`, clientError);
				continue;
			}
		}

		if (!result) {
			throw new Error(`Tool ${toolCall.function.name} not found in any connected MCP server`);
		}

		// mcpLog(`Tool result:`, result.content);

		return {
			tool_call_id: toolCall.id,
			role: "tool" as const,
			content: JSON.stringify(result.content),
		};
	} catch (error) {
		debugError(`Tool execution failed:`, error);

		return {
			tool_call_id: toolCall.id,
			role: "tool" as const,
			content: JSON.stringify({ error: error instanceof Error ? error.message : "Tool execution failed" }),
		};
	}
};