mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
💉 feat: Optionally Inject MCP Server Instructions (#7660)
* feat: Add MCP server instructions to context * chore: remove async method as no async code is performed Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: remove co-pilot promise resolution --------- Co-authored-by: Danny Avila <danacordially@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
53df6a1a71
commit
2c39ccd2af
3 changed files with 135 additions and 1 deletions
|
|
@ -32,11 +32,12 @@ const { getCustomEndpointConfig, checkCapability } = require('~/server/services/
|
||||||
const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts');
|
const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts');
|
||||||
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
|
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
|
||||||
const { getBufferString, HumanMessage } = require('@langchain/core/messages');
|
const { getBufferString, HumanMessage } = require('@langchain/core/messages');
|
||||||
|
const { DynamicStructuredTool } = require('@langchain/core/tools');
|
||||||
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
|
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
|
||||||
const initOpenAI = require('~/server/services/Endpoints/openAI/initialize');
|
const initOpenAI = require('~/server/services/Endpoints/openAI/initialize');
|
||||||
const Tokenizer = require('~/server/services/Tokenizer');
|
const Tokenizer = require('~/server/services/Tokenizer');
|
||||||
const BaseClient = require('~/app/clients/BaseClient');
|
const BaseClient = require('~/app/clients/BaseClient');
|
||||||
const { logger, sendEvent } = require('~/config');
|
const { logger, sendEvent, getMCPManager } = require('~/config');
|
||||||
const { createRun } = require('./run');
|
const { createRun } = require('./run');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -370,6 +371,37 @@ class AgentClient extends BaseClient {
|
||||||
systemContent = this.augmentedPrompt + systemContent;
|
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) {
|
if (systemContent) {
|
||||||
this.options.agent.instructions = systemContent;
|
this.options.agent.instructions = systemContent;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ const BaseOptionsSchema = z.object({
|
||||||
initTimeout: z.number().optional(),
|
initTimeout: z.number().optional(),
|
||||||
/** Controls visibility in chat dropdown menu (MCPSelect) */
|
/** Controls visibility in chat dropdown menu (MCPSelect) */
|
||||||
chatMenu: z.boolean().optional(),
|
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({
|
export const StdioOptionsSchema = BaseOptionsSchema.extend({
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ export class MCPManager {
|
||||||
private readonly USER_CONNECTION_IDLE_TIMEOUT = 15 * 60 * 1000; // 15 minutes (TODO: make configurable)
|
private readonly USER_CONNECTION_IDLE_TIMEOUT = 15 * 60 * 1000; // 15 minutes (TODO: make configurable)
|
||||||
private mcpConfigs: t.MCPServers = {};
|
private mcpConfigs: t.MCPServers = {};
|
||||||
private processMCPEnv?: (obj: MCPOptions, userId?: string) => MCPOptions; // Store the processing function
|
private processMCPEnv?: (obj: MCPOptions, userId?: string) => MCPOptions; // Store the processing function
|
||||||
|
/** Store MCP server instructions */
|
||||||
|
private serverInstructions: Map<string, string> = new Map();
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
private static getDefaultLogger(): Logger {
|
private static getDefaultLogger(): Logger {
|
||||||
|
|
@ -75,6 +77,42 @@ export class MCPManager {
|
||||||
initializedServers.add(i);
|
initializedServers.add(i);
|
||||||
this.connections.set(serverName, connection); // Store in app-level map
|
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();
|
const serverCapabilities = connection.client.getServerCapabilities();
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`[MCP][${serverName}] Capabilities: ${JSON.stringify(serverCapabilities)}`,
|
`[MCP][${serverName}] Capabilities: ${JSON.stringify(serverCapabilities)}`,
|
||||||
|
|
@ -519,4 +557,61 @@ export class MCPManager {
|
||||||
logger.info('[MCP] Manager instance destroyed.');
|
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<string, string> {
|
||||||
|
const instructions: Record<string, string> = {};
|
||||||
|
|
||||||
|
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.`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue