Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -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.",
Expand All @@ -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: {
Expand Down
92 changes: 92 additions & 0 deletions app/api/mcp-tool-handler.ts
Original file line number Diff line number Diff line change
@@ -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<string, any> = {};

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<string, z.ZodTypeAny> = {};
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();
}
}
20 changes: 19 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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
);
Expand All @@ -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<string, any> = {};
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(() => {
Expand Down Expand Up @@ -154,6 +171,7 @@ export default function ChatPage() {
body: {
pendingMessageConfig,
mcpUrls,
tools,
},
onToolCall: (arg) => {
console.debug("TOOL CALL", arg);
Expand Down
Loading