mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🔄 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:
parent
386900fb4f
commit
5b1a31ef4d
15 changed files with 111 additions and 134 deletions
|
|
@ -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.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue