diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index 31fd56930e..9631fe3801 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -32,11 +32,12 @@ const { getCustomEndpointConfig, checkCapability } = require('~/server/services/ const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts'); const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); const { getBufferString, HumanMessage } = require('@langchain/core/messages'); +const { DynamicStructuredTool } = require('@langchain/core/tools'); const { encodeAndFormat } = require('~/server/services/Files/images/encode'); const initOpenAI = require('~/server/services/Endpoints/openAI/initialize'); const Tokenizer = require('~/server/services/Tokenizer'); const BaseClient = require('~/app/clients/BaseClient'); -const { logger, sendEvent } = require('~/config'); +const { logger, sendEvent, getMCPManager } = require('~/config'); const { createRun } = require('./run'); /** @@ -370,6 +371,37 @@ class AgentClient extends BaseClient { systemContent = this.augmentedPrompt + systemContent; } + // Inject MCP server instructions if available + const ephemeralAgent = this.options.req.body.ephemeralAgent; + let mcpServers = []; + + // Check for ephemeral agent MCP servers + if (ephemeralAgent && ephemeralAgent.mcp && ephemeralAgent.mcp.length > 0) { + mcpServers = ephemeralAgent.mcp; + } + // Check for regular agent MCP tools + else if (this.options.agent && this.options.agent.tools) { + mcpServers = this.options.agent.tools + .filter( + (tool) => + tool instanceof DynamicStructuredTool && tool.name.includes(Constants.mcp_delimiter), + ) + .map((tool) => tool.name.split(Constants.mcp_delimiter).pop()) + .filter(Boolean); + } + + if (mcpServers.length > 0) { + try { + const mcpInstructions = await getMCPManager().formatInstructionsForContext(mcpServers); + if (mcpInstructions) { + systemContent = [systemContent, mcpInstructions].filter(Boolean).join('\n\n'); + logger.debug('[AgentClient] Injected MCP instructions for servers:', mcpServers); + } + } catch (error) { + logger.error('[AgentClient] Failed to inject MCP instructions:', error); + } + } + if (systemContent) { this.options.agent.instructions = systemContent; } diff --git a/packages/data-provider/src/mcp.ts b/packages/data-provider/src/mcp.ts index 258eb15ae3..1273274c54 100644 --- a/packages/data-provider/src/mcp.ts +++ b/packages/data-provider/src/mcp.ts @@ -7,6 +7,13 @@ const BaseOptionsSchema = z.object({ initTimeout: z.number().optional(), /** Controls visibility in chat dropdown menu (MCPSelect) */ chatMenu: z.boolean().optional(), + /** + * Controls server instruction behavior: + * - undefined/not set: No instructions included (default) + * - true: Use server-provided instructions + * - string: Use custom instructions (overrides server-provided) + */ + serverInstructions: z.union([z.boolean(), z.string()]).optional(), }); export const StdioOptionsSchema = BaseOptionsSchema.extend({ diff --git a/packages/mcp/src/manager.ts b/packages/mcp/src/manager.ts index 8fe9074b8f..0d8a1e4653 100644 --- a/packages/mcp/src/manager.ts +++ b/packages/mcp/src/manager.ts @@ -22,6 +22,8 @@ export class MCPManager { private readonly USER_CONNECTION_IDLE_TIMEOUT = 15 * 60 * 1000; // 15 minutes (TODO: make configurable) private mcpConfigs: t.MCPServers = {}; private processMCPEnv?: (obj: MCPOptions, userId?: string) => MCPOptions; // Store the processing function + /** Store MCP server instructions */ + private serverInstructions: Map = new Map(); private logger: Logger; private static getDefaultLogger(): Logger { @@ -75,6 +77,42 @@ export class MCPManager { initializedServers.add(i); this.connections.set(serverName, connection); // Store in app-level map + // Handle unified serverInstructions configuration + const configInstructions = config.serverInstructions; + + if (configInstructions !== undefined) { + if (typeof configInstructions === 'string') { + // Custom instructions provided + this.serverInstructions.set(serverName, configInstructions); + this.logger.info( + `[MCP][${serverName}] Custom instructions stored for context inclusion: ${configInstructions}`, + ); + } else if (configInstructions === true) { + // Use server-provided instructions + const serverInstructions = connection.client.getInstructions(); + + if (serverInstructions) { + this.serverInstructions.set(serverName, serverInstructions); + this.logger.info( + `[MCP][${serverName}] Server instructions stored for context inclusion: ${serverInstructions}`, + ); + } else { + this.logger.info( + `[MCP][${serverName}] serverInstructions=true but no server instructions available`, + ); + } + } else { + // configInstructions is false - explicitly disabled + this.logger.info( + `[MCP][${serverName}] Instructions explicitly disabled (serverInstructions=false)`, + ); + } + } else { + this.logger.info( + `[MCP][${serverName}] Instructions not included (serverInstructions not configured)`, + ); + } + const serverCapabilities = connection.client.getServerCapabilities(); this.logger.info( `[MCP][${serverName}] Capabilities: ${JSON.stringify(serverCapabilities)}`, @@ -519,4 +557,61 @@ export class MCPManager { logger.info('[MCP] Manager instance destroyed.'); } } + + /** + * Get instructions for MCP servers + * @param serverNames Optional array of server names. If not provided or empty, returns all servers. + * @returns Object mapping server names to their instructions + */ + public getInstructions(serverNames?: string[]): Record { + const instructions: Record = {}; + + if (!serverNames || serverNames.length === 0) { + // Return all instructions if no specific servers requested + for (const [serverName, serverInstructions] of this.serverInstructions.entries()) { + instructions[serverName] = serverInstructions; + } + } else { + // Return instructions for specific servers + for (const serverName of serverNames) { + const serverInstructions = this.serverInstructions.get(serverName); + if (serverInstructions) { + instructions[serverName] = serverInstructions; + } + } + } + + return instructions; + } + + /** + * Format MCP server instructions for injection into context + * @param serverNames Optional array of server names to include. If not provided, includes all servers. + * @returns Formatted instructions string ready for context injection + */ + public formatInstructionsForContext(serverNames?: string[]): string { + /** Instructions for specified servers or all stored instructions */ + const instructionsToInclude = this.getInstructions(serverNames); + + if (Object.keys(instructionsToInclude).length === 0) { + return ''; + } + + // Format instructions for context injection + const formattedInstructions = Object.entries(instructionsToInclude) + .map(([serverName, instructions]) => { + return `## ${serverName} MCP Server Instructions + +${instructions}`; + }) + .join('\n\n'); + + return `# MCP Server Instructions + +The following MCP servers are available with their specific instructions: + +${formattedInstructions} + +Please follow these instructions when using tools from the respective MCP servers.`; + } }