diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 56b7f74..3394ad5 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,23 +1,32 @@ import { streamText } from "ai"; -import { mcpConnectionManager } from "@/lib/mcp-connection-manager"; import { resolveModel } from "../apiUtils"; +import { createMcpTools } from "../mcp-tool-handler"; +import { getServerMcpManager } from "@/lib/server-mcp-manager"; export async function POST(req: Request) { - const { messages, pendingMessageConfig, mcpUrls } = await req.json(); + const body = await req.json(); + console.log("Full request body:", body); + console.log("Body keys:", Object.keys(body)); + + const { messages, pendingMessageConfig, tools, mcpUrls } = body; console.log("Received pendingMessageConfig:", pendingMessageConfig); - console.log("Received mcpUrls:", mcpUrls); - - // Get tools from the connection manager (which maintains persistent connections) - const { tools, breakdown } = - await mcpConnectionManager.updateConnections(mcpUrls || []); - - console.log("TOOLS", tools); - console.log("BREAKDOWN", breakdown); + console.log("Received tools:", tools); + console.log("Tools type:", typeof tools); + console.log("Tools keys:", tools ? Object.keys(tools) : "tools is null/undefined"); + // Create a unique request ID for this chat session + const requestId = crypto.randomUUID(); + + // Initialize MCP manager with the URLs + const mcpManager = await getServerMcpManager(requestId, mcpUrls || []); + + // Convert MCP tool descriptions to AI SDK tools + const aiTools = createMcpTools(tools, mcpManager); + const result = streamText({ model: resolveModel(pendingMessageConfig.modelName), - tools, + tools: aiTools, toolCallStreaming: true, system: "You are a helpful assistant that can browse the web. You are given a prompt and you may need to browse the web to find the answer. You may not need to browse the web at all; you may already know the answer.", @@ -32,9 +41,9 @@ export async function POST(req: Request) { throw error; }, onFinish: async (message) => { - console.debug("FINISHED", message); + // console.debug("FINISHED", message); // Log the usage data to verify it's being captured - console.debug("USAGE DATA:", message.usage); + // console.debug("USAGE DATA:", message.usage); // Note: We no longer need to close clients as connections are persistent }, experimental_telemetry: { diff --git a/app/api/mcp-tool-handler.ts b/app/api/mcp-tool-handler.ts new file mode 100644 index 0000000..193696f --- /dev/null +++ b/app/api/mcp-tool-handler.ts @@ -0,0 +1,92 @@ +import { tool } from "ai"; +import { z } from "zod"; +import { ServerMcpManager } from "@/lib/server-mcp-manager"; + +/** + * Create AI SDK tools from MCP tool descriptions + * These tools will execute via MCP when called + */ +export function createMcpTools( + toolDescriptions: any, + mcpManager: ServerMcpManager +) { + if (!toolDescriptions || typeof toolDescriptions !== 'object') { + return {}; + } + + const aiTools: Record = {}; + + for (const [toolName, toolDesc] of Object.entries(toolDescriptions)) { + if (!toolDesc || typeof toolDesc !== 'object') continue; + + const mcpTool = toolDesc as any; + + try { + // Create a Zod schema from the tool's parameters + const schema = createZodSchema(mcpTool.parameters || mcpTool.inputSchema || {}); + + aiTools[toolName] = tool({ + description: mcpTool.description || toolName, + parameters: schema, + execute: async (args) => { + // Execute the tool via MCP + try { + console.log(`Executing MCP tool ${toolName} with args:`, args); + const result = await mcpManager.executeTool(toolName, args); + return result; + } catch (error) { + console.error(`Error executing MCP tool ${toolName}:`, error); + throw error; + } + }, + }); + } catch (error) { + console.error(`Failed to create AI tool for ${toolName}:`, error); + } + } + + return aiTools; +} + +/** + * Create a Zod schema from a simplified schema description + */ +function createZodSchema(schemaDesc: any): z.ZodTypeAny { + // If it's already a Zod schema, return it + if (schemaDesc._def) { + return schemaDesc; + } + + // Handle JSON Schema format + if (schemaDesc.type === 'object' && schemaDesc.properties) { + const shape: Record = {}; + const required = schemaDesc.required || []; + + for (const [key, prop] of Object.entries(schemaDesc.properties as any)) { + let fieldSchema = createZodSchema(prop); + + // Make optional if not in required array + if (!required.includes(key)) { + fieldSchema = fieldSchema.optional(); + } + + shape[key] = fieldSchema; + } + + return z.object(shape); + } + + // Handle primitive types + switch (schemaDesc.type) { + case 'string': + return z.string(); + case 'number': + return z.number(); + case 'boolean': + return z.boolean(); + case 'array': + return z.array(schemaDesc.items ? createZodSchema(schemaDesc.items) : z.any()); + default: + return z.any(); + } +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index f585b1a..9c897f6 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ "use client"; -import type React from "react"; +import React from "react"; import { Message, useChat } from "@ai-sdk/react"; import { ChatMessage } from "@/components/message"; import { Button } from "@/components/ui/button"; @@ -47,6 +47,7 @@ import { breakdownAtom, isMcpConfigOpenAtom, mcpUrlsAtom, + toolsAtom, } from "@/services/mcp/atoms"; import mcpClient from "@/services/mcp/client"; import { GitHubStars } from "@/components/github-stars"; @@ -99,6 +100,7 @@ export default function ChatPage() { const setMcpConfigOpen = useSetAtom(isMcpConfigOpenAtom); const breakdown = useAtomValue(breakdownAtom); const mcpUrls = useAtomValue(mcpUrlsAtom); + const toolsData = useAtomValue(toolsAtom); const [keybindingsActive, setKeybindingsActive] = useAtom( keybindingsActiveAtom ); @@ -109,6 +111,21 @@ export default function ChatPage() { } }, [breakdown]); + // Extract tools from the breakdown structure + const tools = React.useMemo(() => { + if (!toolsData?.breakdown) return {}; + + // Flatten all tools from all servers into a single object + const allTools: Record = {}; + Object.values(toolsData.breakdown).forEach((serverTools) => { + Object.entries(serverTools).forEach(([name, tool]) => { + allTools[name] = tool; + }); + }); + console.log("Extracted tools from toolsData:", allTools); + return allTools; + }, [toolsData]); + // Initial MCP connection on app load (only if URLs are configured) const hasInitiallyLoaded = useRef(false); useEffect(() => { @@ -154,6 +171,7 @@ export default function ChatPage() { body: { pendingMessageConfig, mcpUrls, + tools, }, onToolCall: (arg) => { console.debug("TOOL CALL", arg); diff --git a/components/chatCompletion.tsx b/components/chatCompletion.tsx index 597127a..9ee6327 100644 --- a/components/chatCompletion.tsx +++ b/components/chatCompletion.tsx @@ -3,7 +3,7 @@ import { useChat } from "@ai-sdk/react"; import { MemoizedMarkdown } from "./memoized-markdown"; import { cn } from "@/lib/utils"; -import { useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import Image from "next/image"; import { Square, FileJson, FileText, Copy, Download } from "lucide-react"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -20,44 +20,62 @@ import { Textarea } from "@/components/ui/textarea"; import { Input } from "@/components/ui/input"; import type { Message } from "@ai-sdk/react"; import { useAtomValue } from "jotai"; -import { mcpUrlsAtom } from "@/services/mcp/atoms"; - -type MessagePart = { - type: string; - text?: string; - toolInvocation?: { - toolName: string; - state: string; - args?: Record; - }; -}; +import { toolsAtom } from "@/services/mcp/atoms"; +import { pendingMessageConfigAtom } from "@/services/commands/atoms"; +import type { Tool } from "ai"; export default function ChatCompletion() { - const mcpUrls = useAtomValue(mcpUrlsAtom); + const toolsData = useAtomValue(toolsAtom); + const pendingMessageConfig = useAtomValue(pendingMessageConfigAtom); + + console.log("toolsData from atom:", toolsData); + + // Extract tools from the breakdown structure + const tools = React.useMemo(() => { + if (!toolsData?.breakdown) return {}; + + // Flatten all tools from all servers into a single object + const allTools: Record = {}; + Object.values(toolsData.breakdown).forEach((serverTools) => { + Object.entries(serverTools).forEach(([name, tool]) => { + allTools[name] = tool; + }); + }); + console.log("Extracted tools from toolsData:", allTools); + return allTools; + }, [toolsData]); + + console.log("Tools being passed to useChat:", tools); + console.log("Tools keys:", Object.keys(tools)); const { messages, append, setInput, input, status, stop } = useChat({ api: "/api/chat", body: { - mcpUrls: mcpUrls, + pendingMessageConfig, + tools: tools, }, onToolCall: (arg) => { - console.debug("TOOL CALL", arg, messages); + console.debug("TOOL CALL", arg); + console.debug("Current messages:", messages); + console.debug("Tool call details:", JSON.stringify(arg, null, 2)); }, onFinish: (arg) => { - console.debug("FINISH", arg, messages); + console.debug("FINISH", arg); + console.debug("Final messages:", messages); setLoading(false); }, onResponse: (arg) => { - console.debug("RESPONSE", arg, messages); + console.debug("RESPONSE", arg); + console.debug("Response messages:", messages); }, onError: (arg) => { - console.debug("ERROR", arg, messages); + console.debug("ERROR", arg); + console.debug("Error messages:", messages); setLoading(false); }, }); const hasAppended = useRef(false); const [loading, setLoading] = useState(true); - const [messageParts, setMessageParts] = useState([]); const [stoppable, setStoppable] = useState(false); const chatContainerRef = useRef(null); const scrollAreaRef = useRef(null); @@ -67,6 +85,10 @@ export default function ChatCompletion() { "json" ); + useEffect(() => { + console.log("Messages updated:", messages); + }, [messages]); + const formatToJson = (msgs: Message[]): string => { return JSON.stringify(msgs, null, 2); }; @@ -114,7 +136,7 @@ export default function ChatCompletion() { messages[messages.length - 1]?.parts?.filter( (p) => p.type === "tool-invocation" && p.toolInvocation.state !== "result" - ).length === 0 + )?.length === 0 ); }, [status, messages]); @@ -125,14 +147,6 @@ export default function ChatCompletion() { } }, [append]); - useEffect(() => { - // Update messageParts when messages change - const parts = messages.flatMap((message) => - message.parts ? message.parts : [] - ); - setMessageParts(parts); - }, [messages]); - useEffect(() => { // Scroll to bottom when messageParts changes if (scrollAreaRef.current) { @@ -143,7 +157,7 @@ export default function ChatCompletion() { viewport.scrollTop = viewport.scrollHeight; } } - }, [messageParts]); + }, [messages]); const downloadTranscript = () => { const mimeType = @@ -171,6 +185,7 @@ export default function ChatCompletion() {
{messages?.map((message, index) => { + console.log("Message:", index, message); if (message.role === "user") { return (
@@ -187,76 +202,101 @@ export default function ChatCompletion() { key={`${message.id}-${index}`} className="flex flex-col gap-4 px-4" > - {message.parts.map((part, partIndex) => { - if (part.type === "tool-invocation") { - const hasIframe = part.toolInvocation.args?.hasIframe; - return ( -
- - {part.toolInvocation.toolName.split("_").pop()}{" "} - {hasIframe ? " (computer use agent)" : ""} - -
- {part.toolInvocation.args && - Object.entries(part.toolInvocation.args).map( - ([key, value]) => { - return ( - {value as string} - ); - } - )} -
-
- {part.toolInvocation.state === "result" && - part.toolInvocation.result && ( -
- {part.toolInvocation.toolName === - "screenshot" || - part.toolInvocation.toolName === - "stagehand_act" ? ( -
- Screenshot + {message.parts + ? message.parts.map((part, partIndex) => { + if (part.type === "tool-invocation") { + const hasIframe = + part.toolInvocation.args?.hasIframe; + return ( +
+ + {part.toolInvocation.toolName + .split("_") + .pop()}{" "} + {hasIframe ? " (computer use agent)" : ""} + +
+ {part.toolInvocation.args && + Object.entries( + part.toolInvocation.args + ).map(([key, value]) => { + return ( + {value as string} + ); + })} +
+
+ {part.toolInvocation.state === "result" ? ( + part.toolInvocation.result ? ( +
+ {part.toolInvocation.toolName === + "screenshot" || + part.toolInvocation.toolName === + "stagehand_act" ? ( +
+ Screenshot +
+ ) : ( + + )}
) : ( - - )} -
- )} -
-
- ); - } else if (part.type === "text") { - return ( +
+ No result available +
+ ) + ) : ( +
+ {part.toolInvocation.state} +
+ )} +
+
+ ); + } else if (part.type === "text") { + return ( +
+ +
+ ); + } + }) + : // Fallback for assistant messages without parts (during streaming) + message.content && (
- ); - } - })} + )} {message.annotations?.map((annotation, annotationIndex) => { if ( typeof annotation === "object" && diff --git a/lib/mcp-connection-manager.ts b/lib/mcp-connection-manager.ts index deb31f2..62cf0fa 100644 --- a/lib/mcp-connection-manager.ts +++ b/lib/mcp-connection-manager.ts @@ -1,6 +1,10 @@ -import { Tool } from "ai"; -import { experimental_createMCPClient as createMCPClient } from "ai"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { + ListToolsRequest, + ListToolsResultSchema, + Tool, + } from "@modelcontextprotocol/sdk/types.js"; export interface McpUrl { id: string; @@ -9,9 +13,9 @@ export interface McpUrl { } interface ManagedConnection { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - client: any; // The MCPClient type is not exported from 'ai' package - tools: Record; + client: Client; + sessionId: string; + tools: Awaited>; url: McpUrl; } @@ -26,7 +30,12 @@ class MCPConnectionManager { getAllTools(): Record { const allTools: Record = {}; for (const connection of this.connections.values()) { - Object.assign(allTools, connection.tools); + // Convert the tools array to a Record keyed by tool name + if (connection.tools.tools) { + for (const tool of connection.tools.tools) { + allTools[tool.name] = tool; + } + } } return allTools; } @@ -37,7 +46,13 @@ class MCPConnectionManager { getBreakdown(): Record> { const breakdown: Record> = {}; for (const [serverName, connection] of this.connections.entries()) { - breakdown[serverName] = connection.tools; + const serverTools: Record = {}; + if (connection.tools.tools) { + for (const tool of connection.tools.tools) { + serverTools[tool.name] = tool; + } + } + breakdown[serverName] = serverTools; } return breakdown; } @@ -53,7 +68,13 @@ class MCPConnectionManager { if (existingPromise) { const result = await existingPromise; if (result) { - return { tools: result.tools }; + const toolsRecord: Record = {}; + if (result.tools.tools) { + for (const tool of result.tools.tools) { + toolsRecord[tool.name] = tool; + } + } + return { tools: toolsRecord }; } else { return { tools: {}, error: "Failed to connect" }; } @@ -62,7 +83,13 @@ class MCPConnectionManager { // Check if already connected const existingConnection = this.connections.get(urlConfig.name); if (existingConnection) { - return { tools: existingConnection.tools }; + const toolsRecord: Record = {}; + if (existingConnection.tools.tools) { + for (const tool of existingConnection.tools.tools) { + toolsRecord[tool.name] = tool; + } + } + return { tools: toolsRecord }; } // Create connection promise @@ -73,7 +100,13 @@ class MCPConnectionManager { const connection = await connectionPromise; if (connection) { this.connections.set(urlConfig.name, connection); - return { tools: connection.tools }; + const toolsRecord: Record = {}; + if (connection.tools.tools) { + for (const tool of connection.tools.tools) { + toolsRecord[tool.name] = tool; + } + } + return { tools: toolsRecord }; } else { return { tools: {}, error: "Failed to connect" }; } @@ -167,12 +200,35 @@ class MCPConnectionManager { ): Promise { try { console.log("Connecting to MCP server:", urlConfig.name, urlConfig.url); - - const client = await createMCPClient({ - transport: new StreamableHTTPClientTransport(new URL(urlConfig.url)), - }); - - const tools = await client.tools(); + const client = new Client({ + name: "dotcom.chat", + version: "1.0.0", + title: "dotcom.chat", + }) + + // Try without providing a sessionId - let the server generate one + const transport = new StreamableHTTPClientTransport(new URL(urlConfig.url)); + console.log("Created transport with URL:", urlConfig.url); + + // Connect the client before making any requests + console.log("Attempting to connect client..."); + await client.connect(transport); + console.log("Client connected successfully"); + console.log("Transport session ID after connect:", transport.sessionId); + + const toolsRequest: ListToolsRequest = { + method: 'tools/list', + params: {} + } + + let tools; + try { + tools = await client.request(toolsRequest, ListToolsResultSchema); + } catch (error) { + console.error("Error requesting tools list:", error); + console.error("Transport session ID:", transport.sessionId); + throw error; + } console.log( `Successfully connected to ${urlConfig.name}, got ${ Object.keys(tools).length @@ -181,7 +237,8 @@ class MCPConnectionManager { return { client, - tools, + sessionId: transport.sessionId || crypto.randomUUID(), + tools: tools, url: urlConfig, }; } catch (error) { diff --git a/lib/server-mcp-manager.ts b/lib/server-mcp-manager.ts new file mode 100644 index 0000000..f030e35 --- /dev/null +++ b/lib/server-mcp-manager.ts @@ -0,0 +1,164 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { + CallToolRequest, + CallToolResultSchema, +} from "@modelcontextprotocol/sdk/types.js"; + +interface McpUrl { + id: string; + name: string; + url: string; +} + +interface ManagedConnection { + client: Client; + sessionId: string; + url: McpUrl; +} + +/** + * Server-side MCP connection manager for API routes + * Creates temporary connections for tool execution + */ +export class ServerMcpManager { + private connections: Map = new Map(); + + /** + * Initialize connections to MCP servers + */ + async initialize(mcpUrls: McpUrl[]): Promise { + const connectionPromises = mcpUrls.map(async (urlConfig) => { + try { + const client = new Client({ + name: "dotcom.chat-api", + version: "1.0.0", + title: "dotcom.chat API", + }); + + const transport = new StreamableHTTPClientTransport( + new URL(urlConfig.url) + ); + + await client.connect(transport); + + this.connections.set(urlConfig.name, { + client, + sessionId: transport.sessionId || crypto.randomUUID(), + url: urlConfig, + }); + + console.log(`API: Connected to MCP server ${urlConfig.name}`); + } catch (error) { + console.error(`API: Failed to connect to ${urlConfig.name}:`, error); + } + }); + + await Promise.all(connectionPromises); + } + + /** + * Execute a tool call on the appropriate MCP server + */ + async executeTool( + toolName: string, + args: Record + ): Promise { + // Find which server has this tool + let targetConnection: ManagedConnection | undefined; + + // For now, we'll try all connections until we find one that works + // In production, you'd want to maintain a tool->server mapping + for (const connection of this.connections.values()) { + // You could check if this server has the tool first + targetConnection = connection; + break; // Use first available connection for now + } + + if (!targetConnection) { + throw new Error(`No MCP server available for tool ${toolName}`); + } + + try { + const request: CallToolRequest = { + method: "tools/call", + params: { + name: toolName, + arguments: args, + }, + }; + + const result = await targetConnection.client.request( + request, + CallToolResultSchema + ); + + return result.result; + } catch (error) { + console.error(`Failed to execute tool ${toolName}:`, error); + throw error; + } + } + + /** + * Find which server provides a specific tool + */ + async findServerForTool(toolName: string): Promise { + for (const [serverName, connection] of this.connections.entries()) { + try { + const tools = await connection.client.listTools(); + if (tools.tools?.some(tool => tool.name === toolName)) { + return serverName; + } + } catch (error) { + console.error(`Error listing tools for ${serverName}:`, error); + } + } + return null; + } + + /** + * Close all connections + */ + async close(): Promise { + const closePromises = Array.from(this.connections.values()).map( + async (connection) => { + try { + await connection.client.close(); + } catch (error) { + console.error("Error closing connection:", error); + } + } + ); + + await Promise.all(closePromises); + this.connections.clear(); + } +} + +// Cache managers by request to reuse connections during streaming +const managerCache = new Map(); + +export async function getServerMcpManager( + requestId: string, + mcpUrls: McpUrl[] +): Promise { + let manager = managerCache.get(requestId); + + if (!manager) { + manager = new ServerMcpManager(); + await manager.initialize(mcpUrls); + managerCache.set(requestId, manager); + + // Clean up after 5 minutes + setTimeout(async () => { + const cached = managerCache.get(requestId); + if (cached) { + await cached.close(); + managerCache.delete(requestId); + } + }, 5 * 60 * 1000); + } + + return manager; +} \ No newline at end of file diff --git a/package.json b/package.json index b3ee281..ae63557 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@ai-sdk/google": "^1.2.17", "@ai-sdk/openai": "^1.3.21", "@ai-sdk/react": "^1.2.11", - "@modelcontextprotocol/sdk": "^1.15.1", + "@modelcontextprotocol/sdk": "^1.17.1", "@radix-ui/react-accordion": "^1.2.10", "@radix-ui/react-avatar": "^1.1.9", "@radix-ui/react-collapsible": "^1.1.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a313401..c0f3968 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: ^1.2.11 version: 1.2.11(react@19.1.0)(zod@3.24.4) '@modelcontextprotocol/sdk': - specifier: ^1.15.1 - version: 1.15.1 + specifier: latest + version: 1.17.1 '@radix-ui/react-accordion': specifier: ^1.2.10 version: 1.2.10(@types/react-dom@19.1.3(@types/react@19.1.3))(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -385,8 +385,8 @@ packages: cpu: [x64] os: [win32] - '@modelcontextprotocol/sdk@1.15.1': - resolution: {integrity: sha512-W/XlN9c528yYn+9MQkVjxiTPgPxoxt+oczfjHBDsJx0+59+O7B75Zhsp0B16Xbwbz8ANISDajh6+V7nIcPMc5w==} + '@modelcontextprotocol/sdk@1.17.1': + resolution: {integrity: sha512-CPle1OQehbWqd25La9Ack5B07StKIxh4+Bf19qnpZKJC1oI22Y0czZHbifjw1UoczIfKBwBDAp/dFxvHG13B5A==} engines: {node: '>=18'} '@napi-rs/wasm-runtime@0.2.9': @@ -3056,7 +3056,7 @@ snapshots: '@img/sharp-win32-x64@0.34.1': optional: true - '@modelcontextprotocol/sdk@1.15.1': + '@modelcontextprotocol/sdk@1.17.1': dependencies: ajv: 6.12.6 content-type: 1.0.5 @@ -4287,7 +4287,7 @@ snapshots: '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.2 - '@modelcontextprotocol/sdk': 1.15.1 + '@modelcontextprotocol/sdk': 1.17.1 '@types/estree': 1.0.7 '@types/json-schema': 7.0.15 ajv: 6.12.6 diff --git a/services/mcp/tools-service.ts b/services/mcp/tools-service.ts index b890c10..cd3fa3d 100644 --- a/services/mcp/tools-service.ts +++ b/services/mcp/tools-service.ts @@ -1,6 +1,5 @@ -import { Tool } from "ai"; -import { serializeParameters, SerializedTool } from "@/utils/tool-serialization"; import { mcpConnectionManager } from "@/lib/mcp-connection-manager"; +import { Tool } from "@modelcontextprotocol/sdk/types.js"; export interface McpUrl { id: string; @@ -12,27 +11,8 @@ export class ToolsService { /** * Serialize tools for UI display */ - serializeTools(tools: Record): Record { - const serializedTools: Record = {}; - - for (const [name, toolInstance] of Object.entries(tools)) { - try { - serializedTools[name] = { - description: toolInstance.description, - parameters: serializeParameters(toolInstance.parameters), - }; - } catch (error) { - console.error(`Error serializing tool ${name}:`, error); - serializedTools[name] = { - description: toolInstance.description, - parameters: { - error: `Failed to serialize parameters for tool ${name}`, - }, - }; - } - } - - return serializedTools; + serializeTools(tools: Record): Record { + return tools; } /**