diff --git a/apps/rowboat/app/actions/actions.ts b/apps/rowboat/app/actions/actions.ts index eb694019..b3385cd0 100644 --- a/apps/rowboat/app/actions/actions.ts +++ b/apps/rowboat/app/actions/actions.ts @@ -1,26 +1,16 @@ 'use server'; import { convertFromAgenticAPIChatMessages } from "../lib/types/agents_api_types"; import { AgenticAPIChatRequest } from "../lib/types/agents_api_types"; -import { WorkflowAgent } from "../lib/types/workflow_types"; -import { EmbeddingRecord } from "../lib/types/datasource_types"; import { WebpageCrawlResponse } from "../lib/types/tool_types"; -import { GetInformationToolResult } from "../lib/types/tool_types"; -import { EmbeddingDoc } from "../lib/types/datasource_types"; -import { generateObject, generateText, embed } from "ai"; -import { dataSourceDocsCollection, dataSourcesCollection, embeddingsCollection, webpagesCollection } from "../lib/mongodb"; +import { webpagesCollection } from "../lib/mongodb"; import { z } from 'zod'; -import { openai } from "@ai-sdk/openai"; import FirecrawlApp, { ScrapeResponse } from '@mendable/firecrawl-js'; -import { embeddingModel } from "../lib/embedding"; import { apiV1 } from "rowboat-shared"; import { Claims, getSession } from "@auth0/nextjs-auth0"; -import { callClientToolWebhook, getAgenticApiResponse, mockToolResponse, runRAGToolCall } from "../lib/utils"; +import { getAgenticApiResponse } from "../lib/utils"; import { check_query_limit } from "../lib/rate_limiting"; import { QueryLimitError } from "../lib/client_utils"; import { projectAuthCheck } from "./project_actions"; -import { qdrantClient } from "../lib/qdrant"; -import { ObjectId } from "mongodb"; -import { TestProfile } from "../lib/types/testing_types"; const crawler = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY || '' }); @@ -98,96 +88,3 @@ export async function getAssistantResponse( rawResponse: response.rawAPIResponse, }; } - -export async function suggestToolResponse(toolId: string, projectId: string, messages: z.infer[], mockInstructions: string): Promise { - await projectAuthCheck(projectId); - if (!await check_query_limit(projectId)) { - throw new QueryLimitError(); - } - - return await mockToolResponse(toolId, messages, mockInstructions); -} - -export async function getInformationTool( - projectId: string, - query: string, - sourceIds: string[], - returnType: z.infer['ragReturnType'], - k: number, -): Promise> { - await projectAuthCheck(projectId); - - return await runRAGToolCall(projectId, query, sourceIds, returnType, k); -} - -export async function simulateUserResponse( - projectId: string, - messages: z.infer[], - scenario: string, -): Promise { - await projectAuthCheck(projectId); - if (!await check_query_limit(projectId)) { - throw new QueryLimitError(); - } - - const scenarioPrompt = ` -# Your Specific Task: - -## Context: - -Here is a scenario: - -Scenario: - -{{scenario}} - - -## Task definition: - -Pretend to be a user reaching out to customer support. Chat with the -customer support assistant, assuming your issue is based on this scenario. -Ask follow-up questions and make it real-world like. Don't do dummy -conversations. Your conversation should be a maximum of 5 user turns. - -As output, simply provide your (user) turn of conversation. - -After you are done with the chat, keep replying with a single word EXIT -in all capitals. -`; - - await projectAuthCheck(projectId); - - // flip message assistant / user message - // roles from chat messages - // use only text response messages - const flippedMessages: { role: 'user' | 'assistant', content: string }[] = messages - .filter(m => m.role == 'assistant' || m.role == 'user') - .map(m => ({ - role: m.role == 'assistant' ? 'user' : 'assistant', - content: m.content || '', - })); - - // simulate user call - let prompt; - prompt = scenarioPrompt - .replace('{{scenario}}', scenario); - - const { text } = await generateText({ - model: openai("gpt-4o"), - system: prompt || '', - messages: flippedMessages, - }); - - return text.replace(/\. EXIT$/, '.'); -} - -export async function executeClientTool( - toolCall: z.infer['tool_calls'][number], - messages: z.infer[], - projectId: string, -): Promise { - await projectAuthCheck(projectId); - - const result = await callClientToolWebhook(toolCall, messages, projectId); - return result; -} \ No newline at end of file diff --git a/apps/rowboat/app/actions/mcp_actions.ts b/apps/rowboat/app/actions/mcp_actions.ts index 1062a372..eb2352f4 100644 --- a/apps/rowboat/app/actions/mcp_actions.ts +++ b/apps/rowboat/app/actions/mcp_actions.ts @@ -4,7 +4,6 @@ import { WorkflowTool } from "../lib/types/workflow_types"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; import { projectAuthCheck } from "./project_actions"; -import { callMcpTool } from "../lib/utils"; import { projectsCollection } from "../lib/mongodb"; import { Project } from "../lib/types/project_types"; @@ -72,11 +71,4 @@ export async function updateMcpServers(projectId: string, mcpServers: z.infer): Promise { - await projectAuthCheck(projectId); - - const result = await callMcpTool(projectId, mcpServerName, toolName, parameters); - return result; } \ No newline at end of file diff --git a/apps/rowboat/app/api/v1/[projectId]/chat/route.ts b/apps/rowboat/app/api/v1/[projectId]/chat/route.ts index 30be58af..4e542f2a 100644 --- a/apps/rowboat/app/api/v1/[projectId]/chat/route.ts +++ b/apps/rowboat/app/api/v1/[projectId]/chat/route.ts @@ -4,10 +4,9 @@ import { z } from "zod"; import { ObjectId } from "mongodb"; import { authCheck } from "../../utils"; import { ApiRequest, ApiResponse } from "../../../../lib/types/types"; -import { AgenticAPIChatRequest, AgenticAPIChatMessage, convertFromAgenticApiToApiMessages, convertFromApiToAgenticApiMessages, convertWorkflowToAgenticAPI } from "../../../../lib/types/agents_api_types"; -import { getAgenticApiResponse, callClientToolWebhook, runRAGToolCall, mockToolResponse, callMcpTool } from "../../../../lib/utils"; +import { AgenticAPIChatRequest, convertFromAgenticApiToApiMessages, convertFromApiToAgenticApiMessages, convertWorkflowToAgenticAPI } from "../../../../lib/types/agents_api_types"; +import { getAgenticApiResponse } from "../../../../lib/utils"; import { check_query_limit } from "../../../../lib/rate_limiting"; -import { apiV1 } from "rowboat-shared"; import { PrefixLogger } from "../../../../lib/utils"; import { TestProfile } from "@/app/lib/types/testing_types"; @@ -70,7 +69,7 @@ export async function POST( logger.log(`Workflow ${workflowId} not found for project ${projectId}`); return Response.json({ error: "Workflow not found" }, { status: 404 }); } - + // if test profile is provided in the request, use it let testProfile: z.infer | null = null; if (result.data.testProfileId) { @@ -84,138 +83,29 @@ export async function POST( } } - // if profile has a context available, overwrite the system message in the request (if there is one) - let currentMessages = reqMessages; - if (testProfile?.context) { - // if there is a system message, overwrite it - const systemMessageIndex = reqMessages.findIndex(m => m.role === "system"); - if (systemMessageIndex !== -1) { - currentMessages[systemMessageIndex].content = testProfile.context; - } else { - // if there is no system message, add one - currentMessages.unshift({ role: "system", content: testProfile.context }); - } - } - - const MAX_TURNS = result.data.maxTurns ?? 3; let currentState: unknown = reqState ?? { last_agent_name: workflow.agents[0].name }; - let turns = 0; - let hasToolCalls = false; - do { - hasToolCalls = false; - // get assistant response - const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow); - const request: z.infer = { - messages: convertFromApiToAgenticApiMessages(currentMessages), - state: currentState, - agents, - tools, - prompts, - startAgent, - }; + // get assistant response + const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow); + const request: z.infer = { + messages: convertFromApiToAgenticApiMessages(reqMessages), + state: currentState, + agents, + tools, + prompts, + startAgent, + testProfile: testProfile ?? undefined, + mcpServers: project.mcpServers ?? undefined, + toolWebhookUrl: project.webhookUrl ?? undefined, + }; - console.log(`turn ${turns}: sending agentic request from /chat api`, JSON.stringify(request, null, 2)); - logger.log(`Processing turn ${turns} for conversation`); - const { messages: agenticMessages, state } = await getAgenticApiResponse(request); - - const newMessages = convertFromAgenticApiToApiMessages(agenticMessages); - currentState = state; - - // if tool calls are to be skipped, return immediately - if (result.data.skipToolCalls) { - logger.log('Skipping tool calls as requested'); - const responseBody: z.infer = { - messages: newMessages, - state: currentState, - }; - return Response.json(responseBody); - } - - // get last message to check for tool calls - const lastMessage = newMessages[newMessages.length - 1]; - if (lastMessage?.role === "assistant" && - 'tool_calls' in lastMessage && - lastMessage.tool_calls?.length > 0) { - hasToolCalls = true; - const toolCallResultMessages: z.infer[] = []; - - // Process tool calls - for (const toolCall of lastMessage.tool_calls) { - let result: unknown; - if (toolCall.function.name === "getArticleInfo") { - logger.log(`Running RAG tool call for agent ${lastMessage.agenticSender}`); - // find the source ids attached to this agent in the workflow - const agent = workflow.agents.find(a => a.name === lastMessage.agenticSender); - if (!agent) { - return Response.json({ error: "Agent not found" }, { status: 404 }); - } - const sourceIds = agent.ragDataSources; - if (!sourceIds) { - return Response.json({ error: "Agent has no data sources" }, { status: 404 }); - } - try { - result = await runRAGToolCall(projectId, toolCall.function.arguments, sourceIds, agent.ragReturnType, agent.ragK); - logger.log(`RAG tool call completed for agent ${lastMessage.agenticSender}`); - } catch (e) { - logger.log(`Error running RAG tool call: ${e}`); - return Response.json({ error: "Error running RAG tool call" }, { status: 500 }); - } - } else { - logger.log(`Processing tool call ${toolCall.function.name}`); - - try { - // if tool is supposed to be mocked, mock it - const workflowTool = workflow.tools.find(t => t.name === toolCall.function.name); - if (testProfile?.mockTools || workflowTool?.mockTool) { - logger.log(`Mocking tool call ${toolCall.function.name}`); - result = await mockToolResponse(toolCall.id, currentMessages, testProfile?.mockPrompt || workflowTool?.mockInstructions || ''); - } else if (workflowTool?.isMcp) { - // else run the tool call by calling the MCP tool - logger.log(`Calling MCP tool: ${toolCall.function.name}`); - result = await callMcpTool(projectId, workflowTool.mcpServerName ?? 'default', toolCall.function.name, JSON.parse(toolCall.function.arguments)); - } else { - // else run the tool call by calling the client tool webhook - logger.log(`Running client tool webhook for tool ${toolCall.function.name}`); - result = await callClientToolWebhook( - toolCall, - currentMessages, - projectId, - ); - } - } catch (e) { - logger.log(`Error in tool call ${toolCall.function.name}: ${e}`); - return Response.json({ error: `Error in tool call ${toolCall.function.name}` }, { status: 500 }); - } - logger.log(`Tool call ${toolCall.function.name} completed`); - } - - toolCallResultMessages.push({ - role: "tool", - tool_call_id: toolCall.id, - content: JSON.stringify(result), - tool_name: toolCall.function.name, - }); - } - - // Add new messages to the conversation - currentMessages = [...currentMessages, ...newMessages, ...toolCallResultMessages]; - } else { - // No tool calls, just add the new messages - currentMessages = [...currentMessages, ...newMessages]; - } - - turns++; - if (turns >= MAX_TURNS && hasToolCalls) { - logger.log(`Max turns (${MAX_TURNS}) reached for conversation`); - return Response.json({ error: "Max turns reached" }, { status: 429 }); - } - - } while (hasToolCalls); + const { messages: agenticMessages, state } = await getAgenticApiResponse(request); + const newMessages = convertFromAgenticApiToApiMessages(agenticMessages); + const newState = state; const responseBody: z.infer = { - messages: currentMessages.slice(reqMessages.length), - state: currentState, + messages: newMessages, + state: newState, }; return Response.json(responseBody); }); diff --git a/apps/rowboat/app/api/widget/v1/chats/[chatId]/turn/route.ts b/apps/rowboat/app/api/widget/v1/chats/[chatId]/turn/route.ts index ccb4f3ea..d40b95ff 100644 --- a/apps/rowboat/app/api/widget/v1/chats/[chatId]/turn/route.ts +++ b/apps/rowboat/app/api/widget/v1/chats/[chatId]/turn/route.ts @@ -8,13 +8,10 @@ import { convertFromAgenticAPIChatMessages } from "../../../../../../lib/types/a import { convertToAgenticAPIChatMessages } from "../../../../../../lib/types/agents_api_types"; import { convertWorkflowToAgenticAPI } from "../../../../../../lib/types/agents_api_types"; import { AgenticAPIChatRequest } from "../../../../../../lib/types/agents_api_types"; -import { callClientToolWebhook, getAgenticApiResponse, runRAGToolCall, mockToolResponse, callMcpTool } from "../../../../../../lib/utils"; +import { getAgenticApiResponse } from "../../../../../../lib/utils"; import { check_query_limit } from "../../../../../../lib/rate_limiting"; import { PrefixLogger } from "../../../../../../lib/utils"; -// Add max turns constant at the top with other constants -const MAX_TURNS = 3; - // get next turn / agent response export async function POST( req: NextRequest, @@ -23,7 +20,7 @@ export async function POST( return await authCheck(req, async (session) => { const { chatId } = await params; const logger = new PrefixLogger(`widget-chat:${chatId}`); - + logger.log(`Processing turn request for chat ${chatId}`); // check query limit @@ -95,109 +92,32 @@ export async function POST( // get assistant response const { agents, tools, prompts, startAgent } = convertWorkflowToAgenticAPI(workflow); const unsavedMessages: z.infer[] = [userMessage]; - let resolvingToolCalls = true; - let state: unknown = chat.agenticState ?? {last_agent_name: startAgent}; - let turns = 0; // Add turns counter + let state: unknown = chat.agenticState ?? { last_agent_name: startAgent }; - while (resolvingToolCalls) { - if (turns >= MAX_TURNS) { - logger.log(`Max turns (${MAX_TURNS}) reached for chat ${chatId}`); - throw new Error("Max turns reached"); - } - turns++; - - const request: z.infer = { - messages: convertToAgenticAPIChatMessages([systemMessage, ...messages, ...unsavedMessages]), - state, - agents, - tools, - prompts, - startAgent, - }; - logger.log(`Turn ${turns}: sending agentic request`); - const response = await getAgenticApiResponse(request); - state = response.state; - if (response.messages.length === 0) { - throw new Error("No messages returned from assistant"); - } - const convertedMessages = convertFromAgenticAPIChatMessages(response.messages); - unsavedMessages.push(...convertedMessages.map(m => ({ - ...m, - version: 'v1' as const, - chatId, - createdAt: new Date().toISOString(), - }))); - - // if the last messages is tool call, execute them - const lastMessage = convertedMessages[convertedMessages.length - 1]; - if (lastMessage.role === 'assistant' && 'tool_calls' in lastMessage) { - logger.log(`Processing ${lastMessage.tool_calls.length} tool calls`); - const toolCallResults = await Promise.all(lastMessage.tool_calls.map(async toolCall => { - logger.log(`Executing tool call: ${toolCall.function.name}`); - try { - if (toolCall.function.name === "getArticleInfo") { - logger.log(`Processing RAG tool call for agent ${lastMessage.agenticSender}`); - const agent = workflow.agents.find(a => a.name === lastMessage.agenticSender); - if (!agent || !agent.ragDataSources) { - throw new Error("Agent not found or has no data sources"); - } - return await runRAGToolCall( - session.projectId, - toolCall.function.arguments, - agent.ragDataSources, - agent.ragReturnType, - agent.ragK - ); - } - - const workflowTool = workflow.tools.find(t => t.name === toolCall.function.name); - if (workflowTool?.mockTool) { - logger.log(`Using mock response for tool: ${toolCall.function.name}`); - return await mockToolResponse( - toolCall.id, - [...messages, ...unsavedMessages], - workflowTool.mockInstructions || '' - ); - } else if (workflowTool?.isMcp) { - logger.log(`Calling MCP tool: ${toolCall.function.name}`); - return await callMcpTool( - session.projectId, - workflowTool.mcpServerName ?? 'default', - toolCall.function.name, - JSON.parse(toolCall.function.arguments) - ); - } else { - logger.log(`Calling webhook for tool: ${toolCall.function.name}`); - return await callClientToolWebhook( - toolCall, - [...messages, ...unsavedMessages], - session.projectId, - ); - } - } catch (error) { - logger.log(`Error executing tool call ${toolCall.id}: ${error}`); - return { error: "Tool execution failed" }; - } - })); - unsavedMessages.push(...toolCallResults.map((result, index) => ({ - version: 'v1' as const, - chatId, - createdAt: new Date().toISOString(), - role: 'tool' as const, - tool_call_id: lastMessage.tool_calls[index].id, - tool_name: lastMessage.tool_calls[index].function.name, - content: JSON.stringify(result), - }))); - } else { - // ensure that the last message is from an assistant - // and is of an external type - if (lastMessage.role !== 'assistant' || lastMessage.agenticResponseType !== 'external') { - throw new Error("Last message is not from an assistant and is not of an external type"); - } - resolvingToolCalls = false; - break; - } + const request: z.infer = { + messages: convertToAgenticAPIChatMessages([systemMessage, ...messages, ...unsavedMessages]), + state, + agents, + tools, + prompts, + startAgent, + mcpServers: projectSettings.mcpServers ?? undefined, + toolWebhookUrl: projectSettings.webhookUrl ?? undefined, + testProfile: undefined, + }; + logger.log(`Sending agentic request`); + const response = await getAgenticApiResponse(request); + state = response.state; + if (response.messages.length === 0) { + throw new Error("No messages returned from assistant"); } + const convertedMessages = convertFromAgenticAPIChatMessages(response.messages); + unsavedMessages.push(...convertedMessages.map(m => ({ + ...m, + version: 'v1' as const, + chatId, + createdAt: new Date().toISOString(), + }))); logger.log(`Saving ${unsavedMessages.length} new messages and updating chat state`); await chatMessagesCollection.insertMany(unsavedMessages); diff --git a/apps/rowboat/app/lib/types/agents_api_types.ts b/apps/rowboat/app/lib/types/agents_api_types.ts index 5fd45758..1e6b132a 100644 --- a/apps/rowboat/app/lib/types/agents_api_types.ts +++ b/apps/rowboat/app/lib/types/agents_api_types.ts @@ -1,7 +1,9 @@ import { z } from "zod"; -import { ConnectedEntity, sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPrompt, WorkflowTool } from "./workflow_types"; +import { sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPrompt, WorkflowTool } from "./workflow_types"; import { apiV1 } from "rowboat-shared"; import { ApiMessage } from "./types"; +import { TestProfile } from "./testing_types"; +import { MCPServer } from "./types"; export const AgenticAPIChatMessage = z.object({ role: z.union([z.literal('user'), z.literal('assistant'), z.literal('tool'), z.literal('system')]), @@ -30,12 +32,8 @@ export const AgenticAPIAgent = WorkflowAgent locked: true, toggleAble: true, global: true, - ragDataSources: true, - ragReturnType: true, - ragK: true, }) .extend({ - hasRagSources: z.boolean().default(false).optional(), tools: z.array(z.string()), prompts: z.array(z.string()), connectedAgents: z.array(z.string()), @@ -43,10 +41,10 @@ export const AgenticAPIAgent = WorkflowAgent export const AgenticAPIPrompt = WorkflowPrompt; -export const AgenticAPITool = WorkflowTool.omit({ - mockTool: true, - autoSubmitMockedResponse: true, -}); +export const AgenticAPITool = WorkflowTool + .omit({ + autoSubmitMockedResponse: true, + }) export const AgenticAPIChatRequest = z.object({ messages: z.array(AgenticAPIChatMessage), @@ -55,6 +53,9 @@ export const AgenticAPIChatRequest = z.object({ tools: z.array(AgenticAPITool), prompts: z.array(WorkflowPrompt), startAgent: z.string(), + testProfile: TestProfile.optional(), + mcpServers: z.array(MCPServer).optional(), + toolWebhookUrl: z.string().optional(), }); export const AgenticAPIChatResponse = z.object({ @@ -82,8 +83,10 @@ export function convertWorkflowToAgenticAPI(workflow: z.infer): description: agent.description, instructions: sanitized, model: agent.model, - hasRagSources: agent.ragDataSources ? agent.ragDataSources.length > 0 : false, controlType: agent.controlType, + ragDataSources: agent.ragDataSources, + ragK: agent.ragK, + ragReturnType: agent.ragReturnType, tools: entities.filter(e => e.type == 'tool').map(e => e.name), prompts: entities.filter(e => e.type == 'prompt').map(e => e.name), connectedAgents: entities.filter(e => e.type === 'agent').map(e => e.name), @@ -91,10 +94,8 @@ export function convertWorkflowToAgenticAPI(workflow: z.infer): return agenticAgent; }), tools: workflow.tools.map(tool => { - const { mockTool, autoSubmitMockedResponse, ...rest } = tool; - return { - ...rest, - }; + const { autoSubmitMockedResponse, ...rest } = tool; + return rest; }), prompts: workflow.prompts .map(p => { diff --git a/apps/rowboat/app/lib/types/project_types.ts b/apps/rowboat/app/lib/types/project_types.ts index 54192314..6016e609 100644 --- a/apps/rowboat/app/lib/types/project_types.ts +++ b/apps/rowboat/app/lib/types/project_types.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { MCPServer } from "./types"; export const Project = z.object({ _id: z.string().uuid(), @@ -12,10 +13,7 @@ export const Project = z.object({ publishedWorkflowId: z.string().optional(), nextWorkflowNumber: z.number().optional(), testRunCounter: z.number().default(0), - mcpServers: z.array(z.object({ - name: z.string(), - url: z.string(), - })).optional(), + mcpServers: z.array(MCPServer).optional(), }); export const ProjectMember = z.object({ diff --git a/apps/rowboat/app/lib/types/types.ts b/apps/rowboat/app/lib/types/types.ts index b3f59ce9..911d857c 100644 --- a/apps/rowboat/app/lib/types/types.ts +++ b/apps/rowboat/app/lib/types/types.ts @@ -2,6 +2,11 @@ import { CoreMessage, ToolCallPart } from "ai"; import { z } from "zod"; import { apiV1 } from "rowboat-shared"; +export const MCPServer = z.object({ + name: z.string(), + url: z.string(), +}); + export const PlaygroundChat = z.object({ createdAt: z.string().datetime(), projectId: z.string(), diff --git a/apps/rowboat/app/lib/utils.ts b/apps/rowboat/app/lib/utils.ts index 8a0b522e..24568a89 100644 --- a/apps/rowboat/app/lib/utils.ts +++ b/apps/rowboat/app/lib/utils.ts @@ -1,136 +1,8 @@ -import { convertFromAgenticAPIChatMessages } from "./types/agents_api_types"; -import { ClientToolCallRequest } from "./types/tool_types"; -import { ClientToolCallJwt, GetInformationToolResult } from "./types/tool_types"; -import { ClientToolCallRequestBody } from "./types/tool_types"; -import { AgenticAPIChatResponse } from "./types/agents_api_types"; -import { AgenticAPIChatRequest } from "./types/agents_api_types"; -import { Workflow, WorkflowAgent } from "./types/workflow_types"; -import { AgenticAPIChatMessage } from "./types/agents_api_types"; +import { AgenticAPIChatResponse, AgenticAPIChatRequest, AgenticAPIChatMessage } from "./types/agents_api_types"; import { z } from "zod"; -import { dataSourceDocsCollection, dataSourcesCollection, projectsCollection } from "./mongodb"; -import { apiV1 } from "rowboat-shared"; -import { SignJWT } from "jose"; -import crypto from "crypto"; -import { ObjectId } from "mongodb"; -import { embeddingModel } from "./embedding"; -import { embed, generateObject } from "ai"; -import { qdrantClient } from "./qdrant"; -import { EmbeddingRecord } from "./types/datasource_types"; +import { generateObject } from "ai"; import { ApiMessage } from "./types/types"; import { openai } from "@ai-sdk/openai"; -import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; - -export async function callMcpTool( - projectId: string, - mcpServerName: string, - toolName: string, - parameters: Record, -): Promise { - const project = await projectsCollection.findOne({ - "_id": projectId, - }); - if (!project) { - throw new Error('Project not found'); - } - - const mcpServer = project.mcpServers?.find(s => s.name === mcpServerName); - if (!mcpServer) { - throw new Error('MCP server not found'); - } - - const transport = new SSEClientTransport(new URL(mcpServer.url)); - - const client = new Client( - { - name: "rowboat-client", - version: "1.0.0" - }, - { - capabilities: { - prompts: {}, - resources: {}, - tools: {} - } - } - ); - - await client.connect(transport); - - const result = await client.callTool({ - name: toolName, - arguments: parameters, - }); - - await client.close(); - - return result; -} - -export async function callClientToolWebhook( - toolCall: z.infer['tool_calls'][number], - messages: z.infer[], - projectId: string, -): Promise { - const project = await projectsCollection.findOne({ - "_id": projectId, - }); - if (!project) { - throw new Error('Project not found'); - } - - if (!project.webhookUrl) { - throw new Error('Webhook URL not found'); - } - - // prepare request body - const content = JSON.stringify({ - toolCall, - messages, - } as z.infer); - const requestId = crypto.randomUUID(); - const bodyHash = crypto - .createHash('sha256') - .update(content, 'utf8') - .digest('hex'); - - // sign request - const jwt = await new SignJWT({ - requestId, - projectId, - bodyHash, - } as z.infer) - .setProtectedHeader({ - alg: 'HS256', - typ: 'JWT', - }) - .setIssuer('rowboat') - .setAudience(project.webhookUrl) - .setSubject(`tool-call-${toolCall.id}`) - .setJti(requestId) - .setIssuedAt() - .setExpirationTime("5 minutes") - .sign(new TextEncoder().encode(project.secret)); - - // make request - const request: z.infer = { - requestId, - content, - }; - const response = await fetch(project.webhookUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-signature-jwt': jwt, - }, - body: JSON.stringify(request), - }); - if (!response.ok) { - throw new Error(`Failed to call webhook: ${response.status}: ${response.statusText}`); - } - const responseBody = await response.json(); - return responseBody; -} export async function getAgenticApiResponse( request: z.infer, @@ -163,85 +35,6 @@ export async function getAgenticApiResponse( }; } -export async function runRAGToolCall( - projectId: string, - query: string, - sourceIds: string[], - returnType: z.infer['ragReturnType'], - k: number, -): Promise> { - // create embedding for question - const embedResult = await embed({ - model: embeddingModel, - value: query, - }); - - // fetch all data sources for this project - const sources = await dataSourcesCollection.find({ - projectId: projectId, - active: true, - }).toArray(); - const validSourceIds = sources - .filter(s => sourceIds.includes(s._id.toString())) // id should be in sourceIds - .filter(s => s.active) // should be active - .map(s => s._id.toString()); - - // if no sources found, return empty response - if (validSourceIds.length === 0) { - return { - results: [], - }; - } - - // perform qdrant vector search - const qdrantResults = await qdrantClient.query("embeddings", { - query: embedResult.embedding, - filter: { - must: [ - { key: "projectId", match: { value: projectId } }, - { key: "sourceId", match: { any: validSourceIds } }, - ], - }, - limit: k, - with_payload: true, - }); - - // if return type is chunks, return the chunks - let results = qdrantResults.points.map((point) => { - const { title, name, content, docId, sourceId } = point.payload as z.infer['payload']; - return { - title, - name, - content, - docId, - sourceId, - }; - }); - - if (returnType === 'chunks') { - return { - results, - }; - } - - // otherwise, fetch the doc contents from mongodb - const docs = await dataSourceDocsCollection.find({ - _id: { $in: results.map(r => new ObjectId(r.docId)) }, - }).toArray(); - - // map the results to the docs - results = results.map(r => { - const doc = docs.find(d => d._id.toString() === r.docId); - return { - ...r, - content: doc?.content || '', - }; - }); - - return { - results, - }; -} // create a PrefixLogger class that wraps console.log with a prefix // and allows chaining with a parent logger export class PrefixLogger { diff --git a/apps/rowboat/app/projects/[projectId]/playground/app.tsx b/apps/rowboat/app/projects/[projectId]/playground/app.tsx index bab4dce6..69a5202f 100644 --- a/apps/rowboat/app/projects/[projectId]/playground/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/playground/app.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState } from "react"; import { z } from "zod"; -import { PlaygroundChat } from "../../../lib/types/types"; +import { MCPServer, PlaygroundChat } from "../../../lib/types/types"; import { Workflow } from "../../../lib/types/workflow_types"; import { Chat } from "./chat"; import { ActionButton, Pane } from "../workflow/pane"; @@ -17,11 +17,15 @@ export function App({ projectId, workflow, messageSubscriber, + mcpServerUrls, + toolWebhookUrl, }: { hidden?: boolean; projectId: string; workflow: z.infer; messageSubscriber?: (messages: z.infer[]) => void; + mcpServerUrls: Array>; + toolWebhookUrl: string; }) { const [counter, setCounter] = useState(0); const [testProfile, setTestProfile] = useState | null>(null); @@ -84,6 +88,8 @@ export function App({ onTestProfileChange={handleTestProfileChange} systemMessage={systemMessage} onSystemMessageChange={handleSystemMessageChange} + mcpServerUrls={mcpServerUrls} + toolWebhookUrl={toolWebhookUrl} /> diff --git a/apps/rowboat/app/projects/[projectId]/playground/chat.tsx b/apps/rowboat/app/projects/[projectId]/playground/chat.tsx index 62152ff0..f7f51345 100644 --- a/apps/rowboat/app/projects/[projectId]/playground/chat.tsx +++ b/apps/rowboat/app/projects/[projectId]/playground/chat.tsx @@ -1,9 +1,9 @@ 'use client'; -import { getAssistantResponse, simulateUserResponse } from "../../../actions/actions"; +import { getAssistantResponse } from "../../../actions/actions"; import { useEffect, useState } from "react"; import { Messages } from "./messages"; import z from "zod"; -import { PlaygroundChat } from "../../../lib/types/types"; +import { MCPServer, PlaygroundChat } from "../../../lib/types/types"; import { convertToAgenticAPIChatMessages } from "../../../lib/types/agents_api_types"; import { convertWorkflowToAgenticAPI } from "../../../lib/types/agents_api_types"; import { AgenticAPIChatRequest } from "../../../lib/types/agents_api_types"; @@ -15,7 +15,7 @@ import { CopyAsJsonButton } from "./copy-as-json-button"; import { TestProfile } from "@/app/lib/types/testing_types"; import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector"; import { WithStringId } from "@/app/lib/types/types"; -import { XCircleIcon, XIcon } from "lucide-react"; +import { XIcon } from "lucide-react"; export function Chat({ chat, @@ -26,6 +26,8 @@ export function Chat({ onTestProfileChange, systemMessage, onSystemMessageChange, + mcpServerUrls, + toolWebhookUrl, }: { chat: z.infer; projectId: string; @@ -35,6 +37,8 @@ export function Chat({ onTestProfileChange: (profile: WithStringId> | null) => void; systemMessage: string; onSystemMessageChange: (message: string) => void; + mcpServerUrls: Array>; + toolWebhookUrl: string; }) { const [messages, setMessages] = useState[]>(chat.messages); const [loadingAssistantResponse, setLoadingAssistantResponse] = useState(false); @@ -68,15 +72,6 @@ export function Chat({ setFetchResponseError(null); } - function handleToolCallResults(results: z.infer[]) { - setMessages([...messages, ...results.map((result) => ({ - ...result, - version: 'v1' as const, - chatId: '', - createdAt: new Date().toISOString(), - }))]); - } - // reset state when workflow changes useEffect(() => { setMessages([]); @@ -113,6 +108,9 @@ export function Chat({ tools, prompts, startAgent, + mcpServers: mcpServerUrls, + toolWebhookUrl: toolWebhookUrl, + testProfile: testProfile ?? undefined, }; setLastAgenticRequest(null); setLastAgenticResponse(null); @@ -164,105 +162,7 @@ export function Chat({ return () => { ignore = true; }; - }, [chat.simulated, messages, projectId, agenticState, workflow, fetchResponseError, systemMessage, simulationComplete]); - - // simulate user turn - useEffect(() => { - let ignore = false; - - async function process() { - if (chat.simulationScenario === undefined) { - return; - } - - // fetch next user prompt - setLoadingUserResponse(true); - try { - - const response = await simulateUserResponse(projectId, messages, chat.simulationScenario) - if (ignore) { - return; - } - if (simulationComplete) { - return; - } - if (response.trim() === 'EXIT') { - setSimulationComplete(true); - return; - } - setMessages([...messages, { - role: 'user', - content: response, - version: 'v1' as const, - chatId: '', - createdAt: new Date().toISOString(), - }]); - setFetchResponseError(null); - } catch (err) { - setFetchResponseError(`Failed to simulate user response: ${err instanceof Error ? err.message : 'Unknown error'}`); - } finally { - setLoadingUserResponse(false); - } - } - - // proceed only if chat is simulated - if (!chat.simulated) { - return; - } - - // dont proceed if simulation is complete - if (chat.simulated && simulationComplete) { - return; - } - - // check if there are no messages yet OR - // check if the last message is an assistant - // message containing a text response. If so, - // call the simulate user turn api to fetch - // user response - let last = messages[messages.length - 1]; - if (last && last.role !== 'assistant') { - return; - } - if (last && 'tool_calls' in last) { - return; - } - - process(); - - return () => { - ignore = true; - }; - }, [chat.simulated, messages, projectId, simulationComplete, chat.simulationScenario]); - - // save chat on every assistant message - // useEffect(() => { - // let ignore = false; - - // function process() { - // savePlaygroundChat(projectId, { - // ...chat, - // messages, - // simulationComplete, - // agenticState, - // }, chatId) - // .then((insertedChatId) => { - // if (!chatId) { - // setChatId(insertedChatId); - // } - // }); - // } - - // if (messages.length === 0) { - // return; - // } - - // const lastMessage = messages[messages.length - 1]; - // if (lastMessage && lastMessage.role !== 'assistant') { - // return; - // } - // process(); - // }, [chatId, chat, messages, projectId, simulationComplete, agenticState]); + }, [chat.simulated, messages, projectId, agenticState, workflow, fetchResponseError, systemMessage, simulationComplete, mcpServerUrls, toolWebhookUrl, testProfile]); const handleCopyChat = () => { const jsonString = JSON.stringify({ @@ -303,7 +203,6 @@ export function Chat({ projectId={projectId} messages={messages} toolCallResults={toolCallResults} - handleToolCallResults={handleToolCallResults} loadingAssistantResponse={loadingAssistantResponse} loadingUserResponse={loadingUserResponse} workflow={workflow} diff --git a/apps/rowboat/app/projects/[projectId]/playground/messages.tsx b/apps/rowboat/app/projects/[projectId]/playground/messages.tsx index 8c42b527..f4e52110 100644 --- a/apps/rowboat/app/projects/[projectId]/playground/messages.tsx +++ b/apps/rowboat/app/projects/[projectId]/playground/messages.tsx @@ -1,17 +1,14 @@ 'use client'; -import { Button, Spinner, Textarea } from "@heroui/react"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Spinner } from "@heroui/react"; +import { useEffect, useMemo, useRef, useState } from "react"; import z from "zod"; import { Workflow } from "../../../lib/types/workflow_types"; import { WorkflowTool } from "../../../lib/types/workflow_types"; -import { GetInformationToolResult } from "../../../lib/types/tool_types"; -import { executeClientTool, getInformationTool, suggestToolResponse } from "../../../actions/actions"; import MarkdownContent from "../../../lib/components/markdown-content"; import { apiV1 } from "rowboat-shared"; import { EditableField } from "../../../lib/components/editable-field"; import { MessageSquareIcon, EllipsisIcon, CircleCheckIcon, ChevronRightIcon, ChevronDownIcon, XIcon } from "lucide-react"; import { TestProfile } from "@/app/lib/types/testing_types"; -import { executeMcpTool } from "@/app/actions/mcp_actions"; function UserMessage({ content }: { content: string }) { return
@@ -88,7 +85,6 @@ function UserMessageLoading() { function ToolCalls({ toolCalls, results, - handleResults, projectId, messages, sender, @@ -98,7 +94,6 @@ function ToolCalls({ }: { toolCalls: z.infer['tool_calls']; results: Record>; - handleResults: (results: z.infer[]) => void; projectId: string; messages: z.infer[]; sender: string | null | undefined; @@ -108,21 +103,12 @@ function ToolCalls({ }) { const resultsMap: Record> = {}; - function handleToolCallResult(result: z.infer) { - resultsMap[result.tool_call_id] = result; - if (Object.keys(resultsMap).length === toolCalls.length) { - const results = Object.values(resultsMap); - handleResults(results); - } - } - return
{toolCalls.map(toolCall => { return ['tool_calls'][number]; result: z.infer | undefined; - handleResult: (result: z.infer) => void; projectId: string; messages: z.infer[]; sender: string | null | undefined; @@ -163,63 +147,17 @@ function ToolCall({ } } - switch (toolCall.function.name) { - case 'getArticleInfo': - return ; - default: - if (toolCall.function.name.startsWith('transfer_to_')) { - return ; - } - if (!matchingWorkflowTool || - matchingWorkflowTool.mockTool || - (testProfile && testProfile.mockTools)) { - return ; - } - if (matchingWorkflowTool?.isMcp) { - return ; - } - return ; + if (toolCall.function.name.startsWith('transfer_to_')) { + return ; } + return ; } function ToolCallHeader({ @@ -240,105 +178,11 @@ function ToolCallHeader({
; } -function GetInformationToolCall({ - toolCall, - result: availableResult, - handleResult, - projectId, - messages, - sender, - workflow, -}: { - toolCall: z.infer['tool_calls'][number]; - result: z.infer | undefined; - handleResult: (result: z.infer) => void; - projectId: string; - messages: z.infer[]; - sender: string | null | undefined; - workflow: z.infer; -}) { - const [result, setResult] = useState | undefined>(availableResult); - const args = JSON.parse(toolCall.function.arguments) as { question: string }; - let typedResult: z.infer | undefined; - if (result) { - typedResult = JSON.parse(result.content) as z.infer; - } - - useEffect(() => { - if (result) { - return; - } - let ignore = false; - - async function process() { - const result: z.infer = { - role: 'tool', - tool_call_id: toolCall.id, - tool_name: toolCall.function.name, - content: '', - }; - // find target agent - const agent = workflow.agents.find(agent => agent.name == sender); - if (!agent || !agent.ragDataSources) { - result.content = JSON.stringify({ - results: [], - }); - } else { - const matches = await getInformationTool(projectId, args.question, agent.ragDataSources, agent.ragReturnType, agent.ragK); - if (ignore) { - return; - } - result.content = JSON.stringify(matches); - } - setResult(result); - handleResult(result); - } - process(); - - return () => { - ignore = true; - }; - }, [result, toolCall.id, toolCall.function.name, projectId, args.question, workflow.agents, sender, handleResult]); - - return
- {sender &&
{sender}
} -
- - -
- {result ? 'Fetched' : 'Fetch'} information for question: {args['question']} - {result &&
- {typedResult && typedResult.results.length === 0 &&
No matches found.
} - {typedResult && typedResult.results.length > 0 &&
    - {typedResult.results.map((result, index) => { - return
  • - -
  • - })} -
} -
} -
-
-
; -} - function TransferToAgentToolCall({ - toolCall, result: availableResult, - handleResult, - projectId, - messages, sender, }: { - toolCall: z.infer['tool_calls'][number]; result: z.infer | undefined; - handleResult: (result: z.infer) => void; - projectId: string; - messages: z.infer[]; sender: string | null | undefined; }) { const typedResult = availableResult ? JSON.parse(availableResult.content) as { assistant: string } : undefined; @@ -355,282 +199,28 @@ function TransferToAgentToolCall({
; } -function McpToolCall({ - toolCall, - result: availableResult, - handleResult, - projectId, - messages, - sender, - workflowTool, -}: { - toolCall: z.infer['tool_calls'][number]; - result: z.infer | undefined; - handleResult: (result: z.infer) => void; - projectId: string; - messages: z.infer[]; - sender: string | null | undefined; - workflowTool: z.infer; -}) { - const [result, setResult] = useState | undefined>(availableResult); - - useEffect(() => { - if (result) { - return; - } - let ignore = false; - - async function process() { - let response; - try { - response = await executeMcpTool( - projectId, - workflowTool.mcpServerName || '', - workflowTool.name, - JSON.parse(toolCall.function.arguments), - ); - } catch (e) { - response = { - error: (e as Error).message, - }; - } - if (ignore) { - return; - } - - const result: z.infer = { - role: 'tool', - tool_call_id: toolCall.id, - tool_name: toolCall.function.name, - content: JSON.stringify(response), - }; - setResult(result); - handleResult(result); - } - process(); - - return () => { - ignore = true; - }; - }, [result, toolCall, projectId, messages, handleResult, workflowTool.mcpServerName, workflowTool.name]); - - return
- {sender &&
{sender}
} -
- - -
- - {result && } -
-
-
; -} - function ClientToolCall({ toolCall, result: availableResult, - handleResult, - projectId, - messages, sender, }: { toolCall: z.infer['tool_calls'][number]; result: z.infer | undefined; - handleResult: (result: z.infer) => void; - projectId: string; - messages: z.infer[]; sender: string | null | undefined; }) { - const [result, setResult] = useState | undefined>(availableResult); - - useEffect(() => { - if (result) { - return; - } - let ignore = false; - - async function process() { - let response; - try { - response = await executeClientTool( - toolCall, - messages, - projectId, - ); - } catch (e) { - response = { - error: (e as Error).message, - }; - } - if (ignore) { - return; - } - - const result: z.infer = { - role: 'tool', - tool_call_id: toolCall.id, - tool_name: toolCall.function.name, - content: JSON.stringify(response), - }; - setResult(result); - handleResult(result); - } - process(); - - return () => { - ignore = true; - }; - }, [result, toolCall, projectId, messages, handleResult]); - return
{sender &&
{sender}
}
- +
- {result && } + {availableResult && }
; } -function MockToolCall({ - toolCall, - result: availableResult, - handleResult, - projectId, - messages, - sender, - testProfile = null, - workflowTool, - systemMessage, -}: { - toolCall: z.infer['tool_calls'][number]; - result: z.infer | undefined; - handleResult: (result: z.infer) => void; - projectId: string; - messages: z.infer[]; - sender: string | null | undefined; - testProfile: z.infer | null; - workflowTool: z.infer | undefined; - systemMessage: string | undefined; -}) { - const [result, setResult] = useState | undefined>(availableResult); - const [response, setResponse] = useState(''); - const [generatingResponse, setGeneratingResponse] = useState(false); - - const handleSubmit = useCallback(() => { - let parsed; - try { - parsed = JSON.parse(response); - } catch (e) { - alert('Invalid JSON'); - return; - } - const result: z.infer = { - role: 'tool', - tool_call_id: toolCall.id, - tool_name: toolCall.function.name, - content: JSON.stringify(parsed), - }; - setResult(result); - handleResult(result); - }, [toolCall.id, toolCall.function.name, handleResult, response]); - - useEffect(() => { - if (result) { - return; - } - if (response) { - return; - } - let ignore = false; - - async function process() { - setGeneratingResponse(true); - - const response = await suggestToolResponse( - toolCall.id, - projectId, - [{ - role: 'system', - content: systemMessage || '', - createdAt: new Date().toISOString(), - version: 'v1', - chatId: '', - }, ...messages], - testProfile?.mockPrompt || workflowTool?.mockInstructions || '', - ); - if (ignore) { - return; - } - setResponse(response); - setGeneratingResponse(false); - } - process(); - - return () => { - ignore = true; - }; - }, [result, response, toolCall.id, projectId, messages, testProfile, systemMessage, workflowTool?.mockInstructions]); - - // auto submit if autoSubmitMockedResponse is true - useEffect(() => { - if (!workflowTool?.autoSubmitMockedResponse) { - return; - } - if (result) { - return; - } - if (response) { - handleSubmit(); - } - }, [workflowTool?.autoSubmitMockedResponse, response, handleSubmit, result]); - - return
- {sender &&
{sender}
} -
-
- {!result && } - {result && } - - Function Call: {toolCall.function.name} - -
- -
- - {result && } -
- - {!result && !workflowTool?.autoSubmitMockedResponse &&
-
Response:
- - -
} -
-
; -} - function ExpandableContent({ label, content, @@ -701,7 +291,6 @@ export function Messages({ projectId, messages, toolCallResults, - handleToolCallResults, loadingAssistantResponse, loadingUserResponse, workflow, @@ -712,7 +301,6 @@ export function Messages({ projectId: string; messages: z.infer[]; toolCallResults: Record>; - handleToolCallResults: (results: z.infer[]) => void; loadingAssistantResponse: boolean; loadingUserResponse: boolean; workflow: z.infer; @@ -742,7 +330,6 @@ export function Messages({ key={index} toolCalls={message.tool_calls} results={toolCallResults} - handleResults={handleToolCallResults} projectId={projectId} messages={messages} sender={message.agenticSender} diff --git a/apps/rowboat/app/projects/[projectId]/workflow/app.tsx b/apps/rowboat/app/projects/[projectId]/workflow/app.tsx index 47dae09e..331e20b7 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/app.tsx @@ -1,5 +1,5 @@ "use client"; -import { WithStringId } from "../../../lib/types/types"; +import { MCPServer, WithStringId } from "../../../lib/types/types"; import { Workflow } from "../../../lib/types/workflow_types"; import { DataSource } from "../../../lib/types/datasource_types"; import { z } from "zod"; @@ -13,9 +13,13 @@ import { listDataSources } from "../../../actions/datasource_actions"; export function App({ projectId, useRag, + mcpServerUrls, + toolWebhookUrl, }: { projectId: string; useRag: boolean; + mcpServerUrls: Array>; + toolWebhookUrl: string; }) { const [selectorKey, setSelectorKey] = useState(0); const [workflow, setWorkflow] = useState> | null>(null); @@ -108,6 +112,8 @@ export function App({ handleShowSelector={handleShowSelector} handleCloneVersion={handleCloneVersion} useRag={useRag} + mcpServerUrls={mcpServerUrls} + toolWebhookUrl={toolWebhookUrl} />} } diff --git a/apps/rowboat/app/projects/[projectId]/workflow/page.tsx b/apps/rowboat/app/projects/[projectId]/workflow/page.tsx index e4d99289..be6d588c 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/page.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/page.tsx @@ -1,8 +1,8 @@ import { Metadata } from "next"; import { App } from "./app"; import { USE_RAG } from "@/app/lib/feature_flags"; - -export const dynamic = 'force-dynamic'; +import { projectsCollection } from "@/app/lib/mongodb"; +import { notFound } from "next/navigation"; export const metadata: Metadata = { title: "Workflow" @@ -13,8 +13,18 @@ export default async function Page({ }: { params: { projectId: string }; }) { + const project = await projectsCollection.findOne({ + _id: params.projectId, + }); + if (!project) { + notFound(); + } + const toolWebhookUrl = project.webhookUrl ?? ''; + return ; } diff --git a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx index 002dc049..243a6827 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx @@ -1,5 +1,5 @@ "use client"; -import { WithStringId } from "../../../lib/types/types"; +import { MCPServer, WithStringId } from "../../../lib/types/types"; import { Workflow } from "../../../lib/types/workflow_types"; import { WorkflowTool } from "../../../lib/types/workflow_types"; import { WorkflowPrompt } from "../../../lib/types/workflow_types"; @@ -559,6 +559,8 @@ export function WorkflowEditor({ handleShowSelector, handleCloneVersion, useRag, + mcpServerUrls, + toolWebhookUrl, }: { dataSources: WithStringId>[]; workflow: WithStringId>; @@ -566,6 +568,8 @@ export function WorkflowEditor({ handleShowSelector: () => void; handleCloneVersion: (workflowId: string) => void; useRag: boolean; + mcpServerUrls: Array>; + toolWebhookUrl: string; }) { const [state, dispatch] = useReducer>(reducer, { patches: [], @@ -911,6 +915,8 @@ export function WorkflowEditor({ projectId={state.present.workflow.projectId} workflow={state.present.workflow} messageSubscriber={updateChatMessages} + mcpServerUrls={mcpServerUrls} + toolWebhookUrl={toolWebhookUrl} /> {state.present.selection?.type === "agent" &&