🔄 refactor: Optimize MCP Tool Initialization

🔄 refactor: Optimize MCP Tool Initialization

fix: update tool caching to use separated mcp logic

refactor: Replace `req.user` with `userId` in MCP handling functions

refactor: Replace `req` parameter with `userId` in file search tool functions

fix: Update user connection parameter to use object format in reinitMCPServer

refactor: Simplify MCP tool creation logic and improve handling of tool configurations to avoid capturing too much in closures

refactor: ensure MCP available tools are fetched from cache only when needed
This commit is contained in:
Danny Avila 2025-09-21 16:52:43 -04:00
parent 386900fb4f
commit 5b1a31ef4d
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
15 changed files with 111 additions and 134 deletions

View file

@ -68,19 +68,19 @@ const primeFiles = async (options) => {
/** /**
* *
* @param {Object} options * @param {Object} options
* @param {ServerRequest} options.req * @param {string} options.userId
* @param {Array<{ file_id: string; filename: string }>} options.files * @param {Array<{ file_id: string; filename: string }>} options.files
* @param {string} [options.entity_id] * @param {string} [options.entity_id]
* @param {boolean} [options.fileCitations=false] - Whether to include citation instructions * @param {boolean} [options.fileCitations=false] - Whether to include citation instructions
* @returns * @returns
*/ */
const createFileSearchTool = async ({ req, files, entity_id, fileCitations = false }) => { const createFileSearchTool = async ({ userId, files, entity_id, fileCitations = false }) => {
return tool( return tool(
async ({ query }) => { async ({ query }) => {
if (files.length === 0) { if (files.length === 0) {
return 'No files to search. Instruct the user to add files for the search.'; return 'No files to search. Instruct the user to add files for the search.';
} }
const jwtToken = generateShortLivedToken(req.user.id); const jwtToken = generateShortLivedToken(userId);
if (!jwtToken) { if (!jwtToken) {
return 'There was an error authenticating the file search request.'; return 'There was an error authenticating the file search request.';
} }

View file

@ -33,7 +33,7 @@ const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSe
const { getUserPluginAuthValue } = require('~/server/services/PluginService'); const { getUserPluginAuthValue } = require('~/server/services/PluginService');
const { createMCPTool, createMCPTools } = require('~/server/services/MCP'); const { createMCPTool, createMCPTools } = require('~/server/services/MCP');
const { loadAuthValues } = require('~/server/services/Tools/credentials'); const { loadAuthValues } = require('~/server/services/Tools/credentials');
const { getCachedTools } = require('~/server/services/Config'); const { getMCPServerTools } = require('~/server/services/Config');
const { getRoleByName } = require('~/models/Role'); const { getRoleByName } = require('~/models/Role');
/** /**
@ -250,7 +250,6 @@ const loadTools = async ({
/** @type {Record<string, string>} */ /** @type {Record<string, string>} */
const toolContextMap = {}; const toolContextMap = {};
const cachedTools = (await getCachedTools({ userId: user, includeGlobal: true })) ?? {};
const requestedMCPTools = {}; const requestedMCPTools = {};
for (const tool of tools) { for (const tool of tools) {
@ -307,7 +306,7 @@ const loadTools = async ({
} }
return createFileSearchTool({ return createFileSearchTool({
req: options.req, userId: user,
files, files,
entity_id: agent?.id, entity_id: agent?.id,
fileCitations, fileCitations,
@ -340,7 +339,7 @@ Current Date & Time: ${replaceSpecialVars({ text: '{{iso_datetime}}' })}
}); });
}; };
continue; continue;
} else if (tool && cachedTools && mcpToolPattern.test(tool)) { } else if (tool && mcpToolPattern.test(tool)) {
const [toolName, serverName] = tool.split(Constants.mcp_delimiter); const [toolName, serverName] = tool.split(Constants.mcp_delimiter);
if (toolName === Constants.mcp_server) { if (toolName === Constants.mcp_server) {
/** Placeholder used for UI purposes */ /** Placeholder used for UI purposes */
@ -353,33 +352,21 @@ Current Date & Time: ${replaceSpecialVars({ text: '{{iso_datetime}}' })}
continue; continue;
} }
if (toolName === Constants.mcp_all) { if (toolName === Constants.mcp_all) {
const currentMCPGenerator = async (index) => requestedMCPTools[serverName] = [
createMCPTools({ {
req: options.req, type: 'all',
res: options.res,
index,
serverName, serverName,
userMCPAuthMap, },
model: agent?.model ?? model, ];
provider: agent?.provider ?? endpoint,
signal,
});
requestedMCPTools[serverName] = [currentMCPGenerator];
continue; continue;
} }
const currentMCPGenerator = async (index) =>
createMCPTool({
index,
req: options.req,
res: options.res,
toolKey: tool,
userMCPAuthMap,
model: agent?.model ?? model,
provider: agent?.provider ?? endpoint,
signal,
});
requestedMCPTools[serverName] = requestedMCPTools[serverName] || []; requestedMCPTools[serverName] = requestedMCPTools[serverName] || [];
requestedMCPTools[serverName].push(currentMCPGenerator); requestedMCPTools[serverName].push({
type: 'single',
toolKey: tool,
serverName,
});
continue; continue;
} }
@ -422,20 +409,51 @@ Current Date & Time: ${replaceSpecialVars({ text: '{{iso_datetime}}' })}
const mcpToolPromises = []; const mcpToolPromises = [];
/** MCP server tools are initialized sequentially by server */ /** MCP server tools are initialized sequentially by server */
let index = -1; let index = -1;
for (const [serverName, generators] of Object.entries(requestedMCPTools)) { for (const [serverName, toolConfigs] of Object.entries(requestedMCPTools)) {
index++; index++;
for (const generator of generators) { /** @type {LCAvailableTools} */
let availableTools;
for (const config of toolConfigs) {
try { try {
if (generator && generators.length === 1) { const mcpParams = {
res: options.res,
userId: user,
index,
serverName: config.serverName,
userMCPAuthMap,
model: agent?.model ?? model,
provider: agent?.provider ?? endpoint,
signal,
};
if (config.type === 'all' && toolConfigs.length === 1) {
/** Handle async loading for single 'all' tool config */
mcpToolPromises.push( mcpToolPromises.push(
generator(index).catch((error) => { createMCPTools(mcpParams).catch((error) => {
logger.error(`Error loading ${serverName} tools:`, error); logger.error(`Error loading ${serverName} tools:`, error);
return null; return null;
}), }),
); );
continue; continue;
} }
const mcpTool = await generator(index); if (!availableTools) {
try {
availableTools = await getMCPServerTools(serverName);
} catch (error) {
logger.error(`Error fetching available tools for MCP server ${serverName}:`, error);
}
}
/** Handle synchronous loading */
const mcpTool =
config.type === 'all'
? await createMCPTools(mcpParams)
: await createMCPTool({
...mcpParams,
availableTools,
toolKey: config.toolKey,
});
if (Array.isArray(mcpTool)) { if (Array.isArray(mcpTool)) {
loadedTools.push(...mcpTool); loadedTools.push(...mcpTool);
} else if (mcpTool) { } else if (mcpTool) {

View file

@ -11,7 +11,7 @@ const {
getProjectByName, getProjectByName,
} = require('./Project'); } = require('./Project');
const { removeAllPermissions } = require('~/server/services/PermissionService'); const { removeAllPermissions } = require('~/server/services/PermissionService');
const { getCachedTools } = require('~/server/services/Config'); const { getMCPServerTools } = require('~/server/services/Config');
const { getActions } = require('./Action'); const { getActions } = require('./Action');
const { Agent } = require('~/db/models'); const { Agent } = require('~/db/models');
@ -69,8 +69,6 @@ const getAgents = async (searchParameter) => await Agent.find(searchParameter).l
*/ */
const loadEphemeralAgent = async ({ req, agent_id, endpoint, model_parameters: _m }) => { const loadEphemeralAgent = async ({ req, agent_id, endpoint, model_parameters: _m }) => {
const { model, ...model_parameters } = _m; const { model, ...model_parameters } = _m;
/** @type {Record<string, FunctionTool>} */
const availableTools = await getCachedTools({ userId: req.user.id, includeGlobal: true });
/** @type {TEphemeralAgent | null} */ /** @type {TEphemeralAgent | null} */
const ephemeralAgent = req.body.ephemeralAgent; const ephemeralAgent = req.body.ephemeralAgent;
const mcpServers = new Set(ephemeralAgent?.mcp); const mcpServers = new Set(ephemeralAgent?.mcp);
@ -88,22 +86,18 @@ const loadEphemeralAgent = async ({ req, agent_id, endpoint, model_parameters: _
const addedServers = new Set(); const addedServers = new Set();
if (mcpServers.size > 0) { if (mcpServers.size > 0) {
for (const toolName of Object.keys(availableTools)) {
if (!toolName.includes(mcp_delimiter)) {
continue;
}
const mcpServer = toolName.split(mcp_delimiter)?.[1];
if (mcpServer && mcpServers.has(mcpServer)) {
addedServers.add(mcpServer);
tools.push(toolName);
}
}
for (const mcpServer of mcpServers) { for (const mcpServer of mcpServers) {
if (addedServers.has(mcpServer)) { if (addedServers.has(mcpServer)) {
continue; continue;
} }
tools.push(`${mcp_all}${mcp_delimiter}${mcpServer}`); const serverTools = await getMCPServerTools(mcpServer);
if (!serverTools) {
tools.push(`${mcp_all}${mcp_delimiter}${mcpServer}`);
addedServers.add(mcpServer);
continue;
}
tools.push(...Object.keys(serverTools));
addedServers.add(mcpServer);
} }
} }

View file

@ -22,11 +22,11 @@ const {
const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService'); const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService');
const { updateUserPluginsService, deleteUserKey } = require('~/server/services/UserService'); const { updateUserPluginsService, deleteUserKey } = require('~/server/services/UserService');
const { verifyEmail, resendVerificationEmail } = require('~/server/services/AuthService'); const { verifyEmail, resendVerificationEmail } = require('~/server/services/AuthService');
const { getAppConfig, clearMCPServerTools } = require('~/server/services/Config');
const { needsRefresh, getNewS3URL } = require('~/server/services/Files/S3/crud'); const { needsRefresh, getNewS3URL } = require('~/server/services/Files/S3/crud');
const { processDeleteRequest } = require('~/server/services/Files/process'); const { processDeleteRequest } = require('~/server/services/Files/process');
const { Transaction, Balance, User, Token } = require('~/db/models'); const { Transaction, Balance, User, Token } = require('~/db/models');
const { getMCPManager, getFlowStateManager } = require('~/config'); const { getMCPManager, getFlowStateManager } = require('~/config');
const { getAppConfig } = require('~/server/services/Config');
const { deleteToolCalls } = require('~/models/ToolCall'); const { deleteToolCalls } = require('~/models/ToolCall');
const { getLogStores } = require('~/cache'); const { getLogStores } = require('~/cache');
@ -372,9 +372,6 @@ const maybeUninstallOAuthMCP = async (userId, pluginKey, appConfig) => {
const flowId = MCPOAuthHandler.generateFlowId(userId, serverName); const flowId = MCPOAuthHandler.generateFlowId(userId, serverName);
await flowManager.deleteFlow(flowId, 'mcp_get_tokens'); await flowManager.deleteFlow(flowId, 'mcp_get_tokens');
await flowManager.deleteFlow(flowId, 'mcp_oauth'); await flowManager.deleteFlow(flowId, 'mcp_oauth');
// 6. clear the tools cache for the server
await clearMCPServerTools({ userId, serverName });
}; };
module.exports = { module.exports = {

View file

@ -71,7 +71,7 @@ const createAgentHandler = async (req, res) => {
agentData.author = userId; agentData.author = userId;
agentData.tools = []; agentData.tools = [];
const availableTools = await getCachedTools({ includeGlobal: true }); const availableTools = await getCachedTools();
for (const tool of tools) { for (const tool of tools) {
if (availableTools[tool]) { if (availableTools[tool]) {
agentData.tools.push(tool); agentData.tools.push(tool);

View file

@ -31,7 +31,7 @@ const createAssistant = async (req, res) => {
delete assistantData.conversation_starters; delete assistantData.conversation_starters;
delete assistantData.append_current_datetime; delete assistantData.append_current_datetime;
const toolDefinitions = await getCachedTools({ includeGlobal: true }); const toolDefinitions = await getCachedTools();
assistantData.tools = tools assistantData.tools = tools
.map((tool) => { .map((tool) => {
@ -136,7 +136,7 @@ const patchAssistant = async (req, res) => {
...updateData ...updateData
} = req.body; } = req.body;
const toolDefinitions = await getCachedTools({ includeGlobal: true }); const toolDefinitions = await getCachedTools();
updateData.tools = (updateData.tools ?? []) updateData.tools = (updateData.tools ?? [])
.map((tool) => { .map((tool) => {

View file

@ -28,7 +28,7 @@ const createAssistant = async (req, res) => {
delete assistantData.conversation_starters; delete assistantData.conversation_starters;
delete assistantData.append_current_datetime; delete assistantData.append_current_datetime;
const toolDefinitions = await getCachedTools({ includeGlobal: true }); const toolDefinitions = await getCachedTools();
assistantData.tools = tools assistantData.tools = tools
.map((tool) => { .map((tool) => {
@ -125,7 +125,7 @@ const updateAssistant = async ({ req, openai, assistant_id, updateData }) => {
let hasFileSearch = false; let hasFileSearch = false;
for (const tool of updateData.tools ?? []) { for (const tool of updateData.tools ?? []) {
const toolDefinitions = await getCachedTools({ includeGlobal: true }); const toolDefinitions = await getCachedTools();
let actualTool = typeof tool === 'string' ? toolDefinitions[tool] : tool; let actualTool = typeof tool === 'string' ? toolDefinitions[tool] : tool;
if (!actualTool && manifestToolMap[tool] && manifestToolMap[tool].toolkit === true) { if (!actualTool && manifestToolMap[tool] && manifestToolMap[tool].toolkit === true) {

View file

@ -5,7 +5,11 @@
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { Constants } = require('librechat-data-provider'); const { Constants } = require('librechat-data-provider');
const { convertMCPToolToPlugin } = require('@librechat/api'); const { convertMCPToolToPlugin } = require('@librechat/api');
const { getAppConfig, getMCPServerTools } = require('~/server/services/Config'); const {
cacheMCPServerTools,
getMCPServerTools,
getAppConfig,
} = require('~/server/services/Config');
const { getMCPManager } = require('~/config'); const { getMCPManager } = require('~/config');
/** /**
@ -49,7 +53,6 @@ const getMCPTools = async (req, res) => {
// Cache server tools if found // Cache server tools if found
if (Object.keys(serverTools).length > 0) { if (Object.keys(serverTools).length > 0) {
const { cacheMCPServerTools } = require('~/server/services/Config');
await cacheMCPServerTools({ serverName, serverTools }); await cacheMCPServerTools({ serverName, serverTools });
} }
} }

View file

@ -300,9 +300,9 @@ router.post('/oauth/cancel/:serverName', requireJwtAuth, async (req, res) => {
router.post('/:serverName/reinitialize', requireJwtAuth, async (req, res) => { router.post('/:serverName/reinitialize', requireJwtAuth, async (req, res) => {
try { try {
const { serverName } = req.params; const { serverName } = req.params;
const user = req.user; const userId = req.user?.id;
if (!user?.id) { if (!userId) {
return res.status(401).json({ error: 'User not authenticated' }); return res.status(401).json({ error: 'User not authenticated' });
} }
@ -316,7 +316,7 @@ router.post('/:serverName/reinitialize', requireJwtAuth, async (req, res) => {
}); });
} }
await mcpManager.disconnectUserConnection(user.id, serverName); await mcpManager.disconnectUserConnection(userId, serverName);
logger.info( logger.info(
`[MCP Reinitialize] Disconnected existing user connection for server: ${serverName}`, `[MCP Reinitialize] Disconnected existing user connection for server: ${serverName}`,
); );
@ -325,14 +325,14 @@ router.post('/:serverName/reinitialize', requireJwtAuth, async (req, res) => {
let userMCPAuthMap; let userMCPAuthMap;
if (serverConfig.customUserVars && typeof serverConfig.customUserVars === 'object') { if (serverConfig.customUserVars && typeof serverConfig.customUserVars === 'object') {
userMCPAuthMap = await getUserMCPAuthMap({ userMCPAuthMap = await getUserMCPAuthMap({
userId: user.id, userId,
servers: [serverName], servers: [serverName],
findPluginAuthsByKeys, findPluginAuthsByKeys,
}); });
} }
const result = await reinitMCPServer({ const result = await reinitMCPServer({
req, userId,
serverName, serverName,
userMCPAuthMap, userMCPAuthMap,
}); });

View file

@ -95,21 +95,10 @@ async function getMCPServerTools(serverName) {
return null; return null;
} }
/**
* Middleware-friendly function to get tools for a request
* @function getToolsForRequest
* @param {Object} [req] - Express request object
* @returns {Promise<Object|null>} Available tools for the request
*/
async function getToolsForRequest(_req) {
return getCachedTools();
}
module.exports = { module.exports = {
ToolCacheKeys, ToolCacheKeys,
getCachedTools, getCachedTools,
setCachedTools, setCachedTools,
getMCPServerTools, getMCPServerTools,
getToolsForRequest,
invalidateCachedTools, invalidateCachedTools,
}; };

View file

@ -84,45 +84,8 @@ async function cacheMCPServerTools({ serverName, serverTools }) {
} }
} }
/**
* Clears all MCP tools for a specific server
* @param {Object} params - Parameters for clearing MCP tools
* @param {string} params.serverName - MCP server name
* @returns {Promise<void>}
*/
async function clearMCPServerTools({ serverName }) {
try {
const tools = await getCachedTools();
// Remove all tools for this server
const mcpDelimiter = Constants.mcp_delimiter;
let removedCount = 0;
for (const key of Object.keys(tools)) {
if (key.endsWith(`${mcpDelimiter}${serverName}`)) {
delete tools[key];
removedCount++;
}
}
if (removedCount > 0) {
await setCachedTools(tools);
const cache = getLogStores(CacheKeys.CONFIG_STORE);
await cache.delete(CacheKeys.TOOLS);
// Also clear the server-specific cache
await cache.delete(`tools:mcp:${serverName}`);
logger.debug(`[MCP Cache] Removed ${removedCount} tools for ${serverName} (global)`);
}
} catch (error) {
logger.error(`[MCP Cache] Failed to clear tools for ${serverName}:`, error);
throw error;
}
}
module.exports = { module.exports = {
mergeAppTools, mergeAppTools,
cacheMCPServerTools, cacheMCPServerTools,
clearMCPServerTools,
updateMCPServerTools, updateMCPServerTools,
}; };

View file

@ -22,8 +22,8 @@ const {
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const { getMCPManager, getFlowStateManager, getOAuthReconnectionManager } = require('~/config'); const { getMCPManager, getFlowStateManager, getOAuthReconnectionManager } = require('~/config');
const { findToken, createToken, updateToken } = require('~/models'); const { findToken, createToken, updateToken } = require('~/models');
const { getCachedTools, getAppConfig } = require('./Config');
const { reinitMCPServer } = require('./Tools/mcp'); const { reinitMCPServer } = require('./Tools/mcp');
const { getAppConfig } = require('./Config');
const { getLogStores } = require('~/cache'); const { getLogStores } = require('~/cache');
/** /**
@ -152,8 +152,8 @@ function createOAuthCallback({ runStepEmitter, runStepDeltaEmitter }) {
/** /**
* @param {Object} params * @param {Object} params
* @param {ServerRequest} params.req - The Express request object, containing user/request info.
* @param {ServerResponse} params.res - The Express response object for sending events. * @param {ServerResponse} params.res - The Express response object for sending events.
* @param {string} params.userId - The user ID from the request object.
* @param {string} params.serverName * @param {string} params.serverName
* @param {AbortSignal} params.signal * @param {AbortSignal} params.signal
* @param {string} params.model * @param {string} params.model
@ -161,9 +161,9 @@ function createOAuthCallback({ runStepEmitter, runStepDeltaEmitter }) {
* @param {Record<string, Record<string, string>>} [params.userMCPAuthMap] * @param {Record<string, Record<string, string>>} [params.userMCPAuthMap]
* @returns { Promise<Array<typeof tool | { _call: (toolInput: Object | string) => unknown}>> } An object with `_call` method to execute the tool input. * @returns { Promise<Array<typeof tool | { _call: (toolInput: Object | string) => unknown}>> } An object with `_call` method to execute the tool input.
*/ */
async function reconnectServer({ req, res, index, signal, serverName, userMCPAuthMap }) { async function reconnectServer({ res, userId, index, signal, serverName, userMCPAuthMap }) {
const runId = Constants.USE_PRELIM_RESPONSE_MESSAGE_ID; const runId = Constants.USE_PRELIM_RESPONSE_MESSAGE_ID;
const flowId = `${req.user?.id}:${serverName}:${Date.now()}`; const flowId = `${userId}:${serverName}:${Date.now()}`;
const flowManager = getFlowStateManager(getLogStores(CacheKeys.FLOWS)); const flowManager = getFlowStateManager(getLogStores(CacheKeys.FLOWS));
const stepId = 'step_oauth_login_' + serverName; const stepId = 'step_oauth_login_' + serverName;
const toolCall = { const toolCall = {
@ -192,7 +192,7 @@ async function reconnectServer({ req, res, index, signal, serverName, userMCPAut
flowManager, flowManager,
}); });
return await reinitMCPServer({ return await reinitMCPServer({
req, userId,
signal, signal,
serverName, serverName,
oauthStart, oauthStart,
@ -211,8 +211,8 @@ async function reconnectServer({ req, res, index, signal, serverName, userMCPAut
* i.e. `availableTools`, and will reinitialize the MCP server to ensure all tools are generated. * i.e. `availableTools`, and will reinitialize the MCP server to ensure all tools are generated.
* *
* @param {Object} params * @param {Object} params
* @param {ServerRequest} params.req - The Express request object, containing user/request info.
* @param {ServerResponse} params.res - The Express response object for sending events. * @param {ServerResponse} params.res - The Express response object for sending events.
* @param {string} params.userId - The user ID from the request object.
* @param {string} params.serverName * @param {string} params.serverName
* @param {string} params.model * @param {string} params.model
* @param {Providers | EModelEndpoint} params.provider - The provider for the tool. * @param {Providers | EModelEndpoint} params.provider - The provider for the tool.
@ -221,8 +221,16 @@ async function reconnectServer({ req, res, index, signal, serverName, userMCPAut
* @param {Record<string, Record<string, string>>} [params.userMCPAuthMap] * @param {Record<string, Record<string, string>>} [params.userMCPAuthMap]
* @returns { Promise<Array<typeof tool | { _call: (toolInput: Object | string) => unknown}>> } An object with `_call` method to execute the tool input. * @returns { Promise<Array<typeof tool | { _call: (toolInput: Object | string) => unknown}>> } An object with `_call` method to execute the tool input.
*/ */
async function createMCPTools({ req, res, index, signal, serverName, provider, userMCPAuthMap }) { async function createMCPTools({
const result = await reconnectServer({ req, res, index, signal, serverName, userMCPAuthMap }); res,
userId,
index,
signal,
serverName,
provider,
userMCPAuthMap,
}) {
const result = await reconnectServer({ res, userId, index, signal, serverName, userMCPAuthMap });
if (!result || !result.tools) { if (!result || !result.tools) {
logger.warn(`[MCP][${serverName}] Failed to reinitialize MCP server.`); logger.warn(`[MCP][${serverName}] Failed to reinitialize MCP server.`);
return; return;
@ -231,8 +239,8 @@ async function createMCPTools({ req, res, index, signal, serverName, provider, u
const serverTools = []; const serverTools = [];
for (const tool of result.tools) { for (const tool of result.tools) {
const toolInstance = await createMCPTool({ const toolInstance = await createMCPTool({
req,
res, res,
userId,
provider, provider,
userMCPAuthMap, userMCPAuthMap,
availableTools: result.availableTools, availableTools: result.availableTools,
@ -249,8 +257,8 @@ async function createMCPTools({ req, res, index, signal, serverName, provider, u
/** /**
* Creates a single tool from the specified MCP Server via `toolKey`. * Creates a single tool from the specified MCP Server via `toolKey`.
* @param {Object} params * @param {Object} params
* @param {ServerRequest} params.req - The Express request object, containing user/request info.
* @param {ServerResponse} params.res - The Express response object for sending events. * @param {ServerResponse} params.res - The Express response object for sending events.
* @param {string} params.userId - The user ID from the request object.
* @param {string} params.toolKey - The toolKey for the tool. * @param {string} params.toolKey - The toolKey for the tool.
* @param {string} params.model - The model for the tool. * @param {string} params.model - The model for the tool.
* @param {number} [params.index] * @param {number} [params.index]
@ -261,26 +269,31 @@ async function createMCPTools({ req, res, index, signal, serverName, provider, u
* @returns { Promise<typeof tool | { _call: (toolInput: Object | string) => unknown}> } An object with `_call` method to execute the tool input. * @returns { Promise<typeof tool | { _call: (toolInput: Object | string) => unknown}> } An object with `_call` method to execute the tool input.
*/ */
async function createMCPTool({ async function createMCPTool({
req,
res, res,
userId,
index, index,
signal, signal,
toolKey, toolKey,
provider, provider,
userMCPAuthMap, userMCPAuthMap,
availableTools: tools, availableTools,
}) { }) {
const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter); const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter);
const availableTools =
tools ?? (await getCachedTools({ userId: req.user?.id, includeGlobal: true }));
/** @type {LCTool | undefined} */ /** @type {LCTool | undefined} */
let toolDefinition = availableTools?.[toolKey]?.function; let toolDefinition = availableTools?.[toolKey]?.function;
if (!toolDefinition) { if (!toolDefinition) {
logger.warn( logger.warn(
`[MCP][${serverName}][${toolName}] Requested tool not found in available tools, re-initializing MCP server.`, `[MCP][${serverName}][${toolName}] Requested tool not found in available tools, re-initializing MCP server.`,
); );
const result = await reconnectServer({ req, res, index, signal, serverName, userMCPAuthMap }); const result = await reconnectServer({
res,
userId,
index,
signal,
serverName,
userMCPAuthMap,
});
toolDefinition = result?.availableTools?.[toolKey]?.function; toolDefinition = result?.availableTools?.[toolKey]?.function;
} }

View file

@ -74,7 +74,7 @@ async function processRequiredActions(client, requiredActions) {
requiredActions, requiredActions,
); );
const appConfig = client.req.config; const appConfig = client.req.config;
const toolDefinitions = await getCachedTools({ userId: client.req.user.id, includeGlobal: true }); const toolDefinitions = await getCachedTools();
const seenToolkits = new Set(); const seenToolkits = new Set();
const tools = requiredActions const tools = requiredActions
.map((action) => { .map((action) => {

View file

@ -7,7 +7,7 @@ const { getLogStores } = require('~/cache');
/** /**
* @param {Object} params * @param {Object} params
* @param {ServerRequest} params.req * @param {string} params.userId
* @param {string} params.serverName - The name of the MCP server * @param {string} params.serverName - The name of the MCP server
* @param {boolean} params.returnOnOAuth - Whether to initiate OAuth and return, or wait for OAuth flow to finish * @param {boolean} params.returnOnOAuth - Whether to initiate OAuth and return, or wait for OAuth flow to finish
* @param {AbortSignal} [params.signal] - The abort signal to handle cancellation. * @param {AbortSignal} [params.signal] - The abort signal to handle cancellation.
@ -18,7 +18,7 @@ const { getLogStores } = require('~/cache');
* @param {Record<string, Record<string, string>>} [params.userMCPAuthMap] * @param {Record<string, Record<string, string>>} [params.userMCPAuthMap]
*/ */
async function reinitMCPServer({ async function reinitMCPServer({
req, userId,
signal, signal,
forceNew, forceNew,
serverName, serverName,
@ -51,7 +51,7 @@ async function reinitMCPServer({
try { try {
userConnection = await mcpManager.getUserConnection({ userConnection = await mcpManager.getUserConnection({
user: req.user, user: { id: userId },
signal, signal,
forceNew, forceNew,
oauthStart, oauthStart,

View file

@ -46,7 +46,7 @@ describe('fileSearch.js - test only new file_id and page additions', () => {
queryVectors.mockResolvedValue(mockResults); queryVectors.mockResolvedValue(mockResults);
const fileSearchTool = await createFileSearchTool({ const fileSearchTool = await createFileSearchTool({
req: { user: { id: 'user1' } }, userId: 'user1',
files: mockFiles, files: mockFiles,
entity_id: 'agent-123', entity_id: 'agent-123',
}); });