diff --git a/apps/webapp/app/trigger/chat/chat-utils.ts b/apps/webapp/app/trigger/chat/chat-utils.ts index 7d53818..47d6683 100644 --- a/apps/webapp/app/trigger/chat/chat-utils.ts +++ b/apps/webapp/app/trigger/chat/chat-utils.ts @@ -107,10 +107,31 @@ const websearchTool = tool({ parameters: WebSearchSchema, }); +const loadMCPTools = tool({ + description: + "Load tools for a specific integration. Call this when you need to use a third-party service.", + parameters: jsonSchema({ + type: "object", + properties: { + integration: { + type: "array", + items: { + type: "string", + }, + description: + 'Array of integration names to load (e.g., ["github", "linear", "slack"])', + }, + }, + required: ["integration"], + additionalProperties: false, + }), +}); + const internalTools = [ "core--progress_update", "core--search_memory", "core--add_memory", + "core--load_mcp", ]; async function addResources(messages: CoreMessage[], resources: Resource[]) { @@ -198,6 +219,7 @@ async function makeNextCall( TOOLS: ToolSet, totalCost: TotalCost, guardLoop: number, + mcpServers: string[], ): Promise { const { context, history, previousHistory } = executionState; @@ -205,6 +227,7 @@ async function makeNextCall( USER_MESSAGE: executionState.query, CONTEXT: context, USER_MEMORY: executionState.userMemoryContext, + AVAILABLE_MCP_TOOLS: mcpServers.join(", "), }; let messages: CoreMessage[] = []; @@ -257,6 +280,8 @@ export async function* run( previousHistory: CoreMessage[], mcp: MCP, stepHistory: HistoryStep[], + mcpServers: string[], + mcpHeaders: any, // eslint-disable-next-line @typescript-eslint/no-explicit-any ): AsyncGenerator { let guardLoop = 0; @@ -267,6 +292,7 @@ export async function* run( "core--search_memory": searchMemoryTool, "core--add_memory": addMemoryTool, "core--websearch": websearchTool, + "core--load_mcp": loadMCPTools, }; logger.info("Tools have been formed"); @@ -302,6 +328,7 @@ export async function* run( tools, totalCost, guardLoop, + mcpServers, ); let toolCallInfo; @@ -533,6 +560,14 @@ export async function* run( result = "Web search failed - please check your search configuration"; } + } else if (toolName === "load_mcp") { + // Load MCP integration and update available tools + await mcp.load(skillInput.integration, mcpHeaders); + tools = { + ...tools, + ...(await mcp.allTools()), + }; + result = "MCP integration loaded successfully"; } } // Handle other MCP tools diff --git a/apps/webapp/app/trigger/chat/chat.ts b/apps/webapp/app/trigger/chat/chat.ts index 5365571..efc8823 100644 --- a/apps/webapp/app/trigger/chat/chat.ts +++ b/apps/webapp/app/trigger/chat/chat.ts @@ -39,9 +39,10 @@ export const chat = task({ const { agents = [] } = payload.context; // Initialise mcp + const mcpHeaders = { Authorization: `Bearer ${init?.token}` }; const mcp = new MCP(); await mcp.init(); - await mcp.load(agents, { Authorization: `Bearer ${init?.token}` }); + await mcp.load(agents, mcpHeaders); // Prepare context with additional metadata const context = { @@ -77,6 +78,8 @@ export const chat = task({ previousExecutionHistory, mcp, stepHistory, + init?.mcpServers ?? [], + mcpHeaders, ); const stream = await metadata.stream("messages", llmResponse); diff --git a/apps/webapp/app/trigger/chat/prompt.ts b/apps/webapp/app/trigger/chat/prompt.ts index 93fd789..7f4e099 100644 --- a/apps/webapp/app/trigger/chat/prompt.ts +++ b/apps/webapp/app/trigger/chat/prompt.ts @@ -98,6 +98,15 @@ MEMORY USAGE: If memory access is unavailable, proceed to web search or rely on current conversation + +- Available integrations: {{AVAILABLE_MCP_TOOLS}} +- To use: load_mcp with EXACT integration name from the available list +- Can load multiple at once with an array +- Only load when tools are NOT already available in your current toolset +- If a tool is already available, use it directly without load_mcp +- If requested integration unavailable: inform user politely + + You have tools at your disposal to assist users: diff --git a/apps/webapp/app/trigger/utils/utils.ts b/apps/webapp/app/trigger/utils/utils.ts index 8e7fea8..1316c15 100644 --- a/apps/webapp/app/trigger/utils/utils.ts +++ b/apps/webapp/app/trigger/utils/utils.ts @@ -197,74 +197,16 @@ export const init = async ({ payload }: { payload: InitChatPayload }) => { return config; }); - - // Create MCP server configurations for each integration account - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const integrationMCPServers: Record = {}; - - for (const account of integrationAccounts) { - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const spec = account.integrationDefinition?.spec as any; - if (spec.mcp) { - const mcpSpec = spec.mcp; - const configuredMCP = { ...mcpSpec }; - - // Replace config placeholders in environment variables - if (configuredMCP.env) { - for (const [key, value] of Object.entries(configuredMCP.env)) { - if (typeof value === "string" && value.includes("${config:")) { - // Extract the config key from the placeholder - const configKey = value.match(/\$\{config:(.*?)\}/)?.[1]; - if ( - configKey && - account.integrationConfiguration && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (account.integrationConfiguration as any)[configKey] - ) { - configuredMCP.env[key] = value.replace( - `\${config:${configKey}}`, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (account.integrationConfiguration as any)[configKey], - ); - } - } - - if ( - typeof value === "string" && - value.includes("${integrationConfig:") - ) { - // Extract the config key from the placeholder - const configKey = value.match( - /\$\{integrationConfig:(.*?)\}/, - )?.[1]; - if ( - configKey && - account.integrationDefinition.config && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (account.integrationDefinition.config as any)[configKey] - ) { - configuredMCP.env[key] = value.replace( - `\${integrationConfig:${configKey}}`, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (account.integrationDefinition.config as any)[configKey], - ); - } - } - } - } - - // Add to the MCP servers collection - integrationMCPServers[account.integrationDefinition.slug] = - configuredMCP; + // Create MCP server for each integration account + const mcpServers: string[] = integrationAccounts + .map((account) => { + const integrationConfig = account.integrationConfiguration as any; + if (integrationConfig.mcp) { + return account.integrationDefinition.slug; } - } catch (error) { - logger.error( - `Failed to configure MCP for ${account.integrationDefinition?.slug}:`, - { error }, - ); - } - } + return undefined; + }) + .filter((slug): slug is string => slug !== undefined); return { conversation, @@ -273,6 +215,7 @@ export const init = async ({ payload }: { payload: InitChatPayload }) => { token: pat.token, userId: user?.id, userName: user?.name, + mcpServers, }; };