mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
🚀 feat: Enhance MCP Connections For Multi-User Support (#6610)
* feat: first pass, multi-user connections * 🔧 refactor: Enhance MCPConnection logging with user-specific prefixes * 🔧 chore: Update @modelcontextprotocol/sdk dependency to version 1.8.0 * feat: idle timeout for user mcp connections * chore: increase user connection idle timeout to 15 minutes * feat: implement graceful shutdown for MCP servers on termination signal * feat: implement user idle timeout management and last activity tracking * feat: enhance MCP options to support custom headers and user ID in environment variable processing * feat: update user last activity tracking in MCPManager * refactor: remove default OpenRouter completions URL from OpenAIClient * refactor: simplify log messages by removing redundant 'App' prefix in MCPManager * refactor: show Agents Builder even if not using Agents endpoint * refactor: remove redundant 'App' prefix from disconnect error log messages in MCPManager * refactor: remove 'App' prefix from log prefix in MCPConnection * chore: remove unecessary comment * fix: allow error propagation during MCPManager initialization
This commit is contained in:
parent
e630c0a00d
commit
a10bc87979
8 changed files with 667 additions and 430 deletions
|
|
@ -226,10 +226,6 @@ class OpenAIClient extends BaseClient {
|
||||||
logger.debug('Using Azure endpoint');
|
logger.debug('Using Azure endpoint');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.useOpenRouter) {
|
|
||||||
this.completionsUrl = 'https://openrouter.ai/api/v1/chat/completions';
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const { logger, getMCPManager } = require('~/config');
|
||||||
* Creates a general tool for an entire action set.
|
* Creates a general tool for an entire action set.
|
||||||
*
|
*
|
||||||
* @param {Object} params - The parameters for loading action sets.
|
* @param {Object} params - The parameters for loading action sets.
|
||||||
* @param {ServerRequest} params.req - The name of the tool.
|
* @param {ServerRequest} params.req - The Express request object, containing user/request info.
|
||||||
* @param {string} params.toolKey - The toolKey for the tool.
|
* @param {string} params.toolKey - The toolKey for the tool.
|
||||||
* @param {import('@librechat/agents').Providers | EModelEndpoint} params.provider - The provider for the tool.
|
* @param {import('@librechat/agents').Providers | EModelEndpoint} params.provider - The provider for the tool.
|
||||||
* @param {string} params.model - The model for the tool.
|
* @param {string} params.model - The model for the tool.
|
||||||
|
|
@ -37,6 +37,15 @@ async function createMCPTool({ req, toolKey, provider }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter);
|
const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter);
|
||||||
|
const userId = req.user?.id;
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
logger.error(
|
||||||
|
`[MCP][${serverName}][${toolName}] User ID not found on request. Cannot create tool.`,
|
||||||
|
);
|
||||||
|
throw new Error(`User ID not found on request. Cannot create tool for ${toolKey}.`);
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {(toolArguments: Object | string, config?: GraphRunnableConfig) => Promise<unknown>} */
|
/** @type {(toolArguments: Object | string, config?: GraphRunnableConfig) => Promise<unknown>} */
|
||||||
const _call = async (toolArguments, config) => {
|
const _call = async (toolArguments, config) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -47,9 +56,11 @@ async function createMCPTool({ req, toolKey, provider }) {
|
||||||
provider,
|
provider,
|
||||||
toolArguments,
|
toolArguments,
|
||||||
options: {
|
options: {
|
||||||
|
userId,
|
||||||
signal: config?.signal,
|
signal: config?.signal,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isAssistantsEndpoint(provider) && Array.isArray(result)) {
|
if (isAssistantsEndpoint(provider) && Array.isArray(result)) {
|
||||||
return result[0];
|
return result[0];
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +69,6 @@ async function createMCPTool({ req, toolKey, provider }) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${toolName} MCP server tool call failed`, error);
|
|
||||||
return `${toolName} MCP server tool call failed.`;
|
return `${toolName} MCP server tool call failed.`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -70,13 +70,7 @@ export default function useSideNavLinks({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (hasAccessToAgents && hasAccessToCreateAgents && agents && agents.disableBuilder !== true) {
|
||||||
hasAccessToAgents &&
|
|
||||||
hasAccessToCreateAgents &&
|
|
||||||
isAgentsEndpoint(endpoint) &&
|
|
||||||
agents &&
|
|
||||||
agents.disableBuilder !== true
|
|
||||||
) {
|
|
||||||
links.push({
|
links.push({
|
||||||
title: 'com_sidepanel_agent_builder',
|
title: 'com_sidepanel_agent_builder',
|
||||||
label: '',
|
label: '',
|
||||||
|
|
|
||||||
644
package-lock.json
generated
644
package-lock.json
generated
|
|
@ -18409,6 +18409,304 @@
|
||||||
"node-fetch": "^2.6.7"
|
"node-fetch": "^2.6.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"content-type": "^1.0.5",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"cross-spawn": "^7.0.3",
|
||||||
|
"eventsource": "^3.0.2",
|
||||||
|
"express": "^5.0.1",
|
||||||
|
"express-rate-limit": "^7.5.0",
|
||||||
|
"pkce-challenge": "^4.1.0",
|
||||||
|
"raw-body": "^3.0.0",
|
||||||
|
"zod": "^3.23.8",
|
||||||
|
"zod-to-json-schema": "^3.24.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/accepts": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "^3.0.0",
|
||||||
|
"negotiator": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "^3.1.2",
|
||||||
|
"content-type": "^1.0.5",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"http-errors": "^2.0.0",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"on-finished": "^2.4.1",
|
||||||
|
"qs": "^6.14.0",
|
||||||
|
"raw-body": "^3.0.0",
|
||||||
|
"type-is": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/qs": {
|
||||||
|
"version": "6.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||||
|
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "5.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/cookie": {
|
||||||
|
"version": "0.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||||
|
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/express": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": "^2.0.0",
|
||||||
|
"body-parser": "^2.0.1",
|
||||||
|
"content-disposition": "^1.0.0",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"cookie": "0.7.1",
|
||||||
|
"cookie-signature": "^1.2.1",
|
||||||
|
"debug": "4.3.6",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"encodeurl": "~2.0.0",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"finalhandler": "^2.0.0",
|
||||||
|
"fresh": "2.0.0",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"merge-descriptors": "^2.0.0",
|
||||||
|
"methods": "~1.1.2",
|
||||||
|
"mime-types": "^3.0.0",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"once": "1.4.0",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"proxy-addr": "~2.0.7",
|
||||||
|
"qs": "6.13.0",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"router": "^2.0.0",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"send": "^1.1.0",
|
||||||
|
"serve-static": "^2.1.0",
|
||||||
|
"setprototypeof": "1.2.0",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"type-is": "^2.0.0",
|
||||||
|
"utils-merge": "1.0.1",
|
||||||
|
"vary": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/debug": {
|
||||||
|
"version": "4.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||||
|
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"encodeurl": "^2.0.0",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"on-finished": "^2.4.1",
|
||||||
|
"parseurl": "^1.3.3",
|
||||||
|
"statuses": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/fresh": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": {
|
||||||
|
"version": "1.54.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||||
|
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "^1.54.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.6.3",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/send": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.5",
|
||||||
|
"encodeurl": "^2.0.0",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"etag": "^1.8.1",
|
||||||
|
"fresh": "^2.0.0",
|
||||||
|
"http-errors": "^2.0.0",
|
||||||
|
"mime-types": "^3.0.1",
|
||||||
|
"ms": "^2.1.3",
|
||||||
|
"on-finished": "^2.4.1",
|
||||||
|
"range-parser": "^1.2.1",
|
||||||
|
"statuses": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"encodeurl": "^2.0.0",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"parseurl": "^1.3.3",
|
||||||
|
"send": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/sdk/node_modules/type-is": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
||||||
|
"dependencies": {
|
||||||
|
"content-type": "^1.0.5",
|
||||||
|
"media-typer": "^1.1.0",
|
||||||
|
"mime-types": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mongodb-js/saslprep": {
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
"version": "1.1.9",
|
"version": "1.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
|
||||||
|
|
@ -39115,10 +39413,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/router": {
|
"node_modules/router": {
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||||
"integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==",
|
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"depd": "^2.0.0",
|
||||||
"is-promise": "^4.0.0",
|
"is-promise": "^4.0.0",
|
||||||
"parseurl": "^1.3.3",
|
"parseurl": "^1.3.3",
|
||||||
"path-to-regexp": "^8.0.0"
|
"path-to-regexp": "^8.0.0"
|
||||||
|
|
@ -43705,7 +44005,7 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.7.0",
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"eventsource": "^3.0.2",
|
"eventsource": "^3.0.2",
|
||||||
"express": "^4.21.2"
|
"express": "^4.21.2"
|
||||||
|
|
@ -43742,144 +44042,6 @@
|
||||||
"keyv": "^4.5.4"
|
"keyv": "^4.5.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/mcp/node_modules/@modelcontextprotocol/sdk": {
|
|
||||||
"version": "1.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz",
|
|
||||||
"integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==",
|
|
||||||
"dependencies": {
|
|
||||||
"content-type": "^1.0.5",
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"eventsource": "^3.0.2",
|
|
||||||
"express": "^5.0.1",
|
|
||||||
"express-rate-limit": "^7.5.0",
|
|
||||||
"pkce-challenge": "^4.1.0",
|
|
||||||
"raw-body": "^3.0.0",
|
|
||||||
"zod": "^3.23.8",
|
|
||||||
"zod-to-json-schema": "^3.24.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/@modelcontextprotocol/sdk/node_modules/debug": {
|
|
||||||
"version": "4.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
|
||||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "2.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"supports-color": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/@modelcontextprotocol/sdk/node_modules/express": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"accepts": "^2.0.0",
|
|
||||||
"body-parser": "^2.0.1",
|
|
||||||
"content-disposition": "^1.0.0",
|
|
||||||
"content-type": "~1.0.4",
|
|
||||||
"cookie": "0.7.1",
|
|
||||||
"cookie-signature": "^1.2.1",
|
|
||||||
"debug": "4.3.6",
|
|
||||||
"depd": "2.0.0",
|
|
||||||
"encodeurl": "~2.0.0",
|
|
||||||
"escape-html": "~1.0.3",
|
|
||||||
"etag": "~1.8.1",
|
|
||||||
"finalhandler": "^2.0.0",
|
|
||||||
"fresh": "2.0.0",
|
|
||||||
"http-errors": "2.0.0",
|
|
||||||
"merge-descriptors": "^2.0.0",
|
|
||||||
"methods": "~1.1.2",
|
|
||||||
"mime-types": "^3.0.0",
|
|
||||||
"on-finished": "2.4.1",
|
|
||||||
"once": "1.4.0",
|
|
||||||
"parseurl": "~1.3.3",
|
|
||||||
"proxy-addr": "~2.0.7",
|
|
||||||
"qs": "6.13.0",
|
|
||||||
"range-parser": "~1.2.1",
|
|
||||||
"router": "^2.0.0",
|
|
||||||
"safe-buffer": "5.2.1",
|
|
||||||
"send": "^1.1.0",
|
|
||||||
"serve-static": "^2.1.0",
|
|
||||||
"setprototypeof": "1.2.0",
|
|
||||||
"statuses": "2.0.1",
|
|
||||||
"type-is": "^2.0.0",
|
|
||||||
"utils-merge": "1.0.1",
|
|
||||||
"vary": "~1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/@modelcontextprotocol/sdk/node_modules/ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/accepts": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-types": "^3.0.0",
|
|
||||||
"negotiator": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/body-parser": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"bytes": "^3.1.2",
|
|
||||||
"content-type": "^1.0.5",
|
|
||||||
"debug": "^4.4.0",
|
|
||||||
"http-errors": "^2.0.0",
|
|
||||||
"iconv-lite": "^0.5.2",
|
|
||||||
"on-finished": "^2.4.1",
|
|
||||||
"qs": "^6.14.0",
|
|
||||||
"raw-body": "^3.0.0",
|
|
||||||
"type-is": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/body-parser/node_modules/iconv-lite": {
|
|
||||||
"version": "0.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
|
|
||||||
"integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
|
|
||||||
"dependencies": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/body-parser/node_modules/qs": {
|
|
||||||
"version": "6.14.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
|
||||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
|
||||||
"dependencies": {
|
|
||||||
"side-channel": "^1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/brace-expansion": {
|
"packages/mcp/node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
|
|
@ -43889,57 +44051,6 @@
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/mcp/node_modules/content-disposition": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "5.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/cookie": {
|
|
||||||
"version": "0.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
|
||||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/cookie-signature": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
|
||||||
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/finalhandler": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.4.0",
|
|
||||||
"encodeurl": "^2.0.0",
|
|
||||||
"escape-html": "^1.0.3",
|
|
||||||
"on-finished": "^2.4.1",
|
|
||||||
"parseurl": "^1.3.3",
|
|
||||||
"statuses": "^2.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/fresh": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/glob": {
|
"packages/mcp/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||||
|
|
@ -43960,17 +44071,6 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/mcp/node_modules/iconv-lite": {
|
|
||||||
"version": "0.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
|
||||||
"dependencies": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/jackspeak": {
|
"packages/mcp/node_modules/jackspeak": {
|
||||||
"version": "3.4.3",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||||
|
|
@ -43986,44 +44086,6 @@
|
||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@pkgjs/parseargs": "^0.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/mcp/node_modules/media-typer": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/merge-descriptors": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/mime-db": {
|
|
||||||
"version": "1.54.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
|
||||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/mime-types": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "^1.53.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/minimatch": {
|
"packages/mcp/node_modules/minimatch": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
|
|
@ -44039,28 +44101,6 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/mcp/node_modules/negotiator": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/raw-body": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
|
||||||
"dependencies": {
|
|
||||||
"bytes": "3.1.2",
|
|
||||||
"http-errors": "2.0.0",
|
|
||||||
"iconv-lite": "0.6.3",
|
|
||||||
"unpipe": "1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/rimraf": {
|
"packages/mcp/node_modules/rimraf": {
|
||||||
"version": "5.0.10",
|
"version": "5.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
||||||
|
|
@ -44075,82 +44115,6 @@
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/send": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.3.5",
|
|
||||||
"destroy": "^1.2.0",
|
|
||||||
"encodeurl": "^2.0.0",
|
|
||||||
"escape-html": "^1.0.3",
|
|
||||||
"etag": "^1.8.1",
|
|
||||||
"fresh": "^0.5.2",
|
|
||||||
"http-errors": "^2.0.0",
|
|
||||||
"mime-types": "^2.1.35",
|
|
||||||
"ms": "^2.1.3",
|
|
||||||
"on-finished": "^2.4.1",
|
|
||||||
"range-parser": "^1.2.1",
|
|
||||||
"statuses": "^2.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/send/node_modules/fresh": {
|
|
||||||
"version": "0.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
|
||||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/send/node_modules/mime-db": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/send/node_modules/mime-types": {
|
|
||||||
"version": "2.1.35",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "1.52.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/serve-static": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==",
|
|
||||||
"dependencies": {
|
|
||||||
"encodeurl": "^2.0.0",
|
|
||||||
"escape-html": "^1.0.3",
|
|
||||||
"parseurl": "^1.3.3",
|
|
||||||
"send": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/mcp/node_modules/type-is": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==",
|
|
||||||
"dependencies": {
|
|
||||||
"content-type": "^1.0.5",
|
|
||||||
"media-typer": "^1.1.0",
|
|
||||||
"mime-types": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ export const WebSocketOptionsSchema = BaseOptionsSchema.extend({
|
||||||
|
|
||||||
export const SSEOptionsSchema = BaseOptionsSchema.extend({
|
export const SSEOptionsSchema = BaseOptionsSchema.extend({
|
||||||
type: z.literal('sse').optional(),
|
type: z.literal('sse').optional(),
|
||||||
|
headers: z.record(z.string(), z.string()).optional(),
|
||||||
url: z
|
url: z
|
||||||
.string()
|
.string()
|
||||||
.url()
|
.url()
|
||||||
|
|
@ -92,9 +93,10 @@ export type MCPOptions = z.infer<typeof MCPOptionsSchema>;
|
||||||
/**
|
/**
|
||||||
* Recursively processes an object to replace environment variables in string values
|
* Recursively processes an object to replace environment variables in string values
|
||||||
* @param {MCPOptions} obj - The object to process
|
* @param {MCPOptions} obj - The object to process
|
||||||
|
* @param {string} [userId] - The user ID
|
||||||
* @returns {MCPOptions} - The processed object with environment variables replaced
|
* @returns {MCPOptions} - The processed object with environment variables replaced
|
||||||
*/
|
*/
|
||||||
export function processMCPEnv(obj: MCPOptions): MCPOptions {
|
export function processMCPEnv(obj: MCPOptions, userId?: string): MCPOptions {
|
||||||
if (obj === null || obj === undefined) {
|
if (obj === null || obj === undefined) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
@ -105,6 +107,16 @@ export function processMCPEnv(obj: MCPOptions): MCPOptions {
|
||||||
processedEnv[key] = extractEnvVariable(value);
|
processedEnv[key] = extractEnvVariable(value);
|
||||||
}
|
}
|
||||||
obj.env = processedEnv;
|
obj.env = processedEnv;
|
||||||
|
} else if ('headers' in obj && obj.headers) {
|
||||||
|
const processedHeaders: Record<string, string> = {};
|
||||||
|
for (const [key, value] of Object.entries(obj.headers)) {
|
||||||
|
if (value === '{{LIBRECHAT_USER_ID}}' && userId != null && userId) {
|
||||||
|
processedHeaders[key] = userId;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
processedHeaders[key] = extractEnvVariable(value);
|
||||||
|
}
|
||||||
|
obj.headers = processedHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
"registry": "https://registry.npmjs.org/"
|
"registry": "https://registry.npmjs.org/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.7.0",
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"eventsource": "^3.0.2",
|
"eventsource": "^3.0.2",
|
||||||
"express": "^4.21.2"
|
"express": "^4.21.2"
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ function isSSEOptions(options: t.MCPOptions): options is t.SSEOptions {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||||
export class MCPConnection extends EventEmitter {
|
export class MCPConnection extends EventEmitter {
|
||||||
private static instance: MCPConnection | null = null;
|
private static instance: MCPConnection | null = null;
|
||||||
public client: Client;
|
public client: Client;
|
||||||
|
|
@ -44,21 +46,26 @@ export class MCPConnection extends EventEmitter {
|
||||||
private reconnectAttempts = 0;
|
private reconnectAttempts = 0;
|
||||||
iconPath?: string;
|
iconPath?: string;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
private readonly userId?: string;
|
||||||
|
private lastPingTime: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
serverName: string,
|
serverName: string,
|
||||||
private readonly options: t.MCPOptions,
|
private readonly options: t.MCPOptions,
|
||||||
private logger?: Logger,
|
private logger?: Logger,
|
||||||
|
userId?: string,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
this.userId = userId;
|
||||||
this.iconPath = options.iconPath;
|
this.iconPath = options.iconPath;
|
||||||
this.timeout = options.timeout;
|
this.timeout = options.timeout;
|
||||||
|
this.lastPingTime = Date.now();
|
||||||
this.client = new Client(
|
this.client = new Client(
|
||||||
{
|
{
|
||||||
name: 'librechat-mcp-client',
|
name: 'librechat-mcp-client',
|
||||||
version: '1.1.0',
|
version: '1.2.0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
capabilities: {},
|
capabilities: {},
|
||||||
|
|
@ -68,13 +75,20 @@ export class MCPConnection extends EventEmitter {
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Helper to generate consistent log prefixes */
|
||||||
|
private getLogPrefix(): string {
|
||||||
|
const userPart = this.userId ? `[User: ${this.userId}]` : '';
|
||||||
|
return `[MCP]${userPart}[${this.serverName}]`;
|
||||||
|
}
|
||||||
|
|
||||||
public static getInstance(
|
public static getInstance(
|
||||||
serverName: string,
|
serverName: string,
|
||||||
options: t.MCPOptions,
|
options: t.MCPOptions,
|
||||||
logger?: Logger,
|
logger?: Logger,
|
||||||
|
userId?: string,
|
||||||
): MCPConnection {
|
): MCPConnection {
|
||||||
if (!MCPConnection.instance) {
|
if (!MCPConnection.instance) {
|
||||||
MCPConnection.instance = new MCPConnection(serverName, options, logger);
|
MCPConnection.instance = new MCPConnection(serverName, options, logger, userId);
|
||||||
}
|
}
|
||||||
return MCPConnection.instance;
|
return MCPConnection.instance;
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +106,7 @@ export class MCPConnection extends EventEmitter {
|
||||||
|
|
||||||
private emitError(error: unknown, errorContext: string): void {
|
private emitError(error: unknown, errorContext: string): void {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
this.logger?.error(`[MCP][${this.serverName}] ${errorContext}: ${errorMessage}`);
|
this.logger?.error(`${this.getLogPrefix()} ${errorContext}: ${errorMessage}`);
|
||||||
this.emit('error', new Error(`${errorContext}: ${errorMessage}`));
|
this.emit('error', new Error(`${errorContext}: ${errorMessage}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,27 +147,28 @@ export class MCPConnection extends EventEmitter {
|
||||||
throw new Error('Invalid options for sse transport.');
|
throw new Error('Invalid options for sse transport.');
|
||||||
}
|
}
|
||||||
const url = new URL(options.url);
|
const url = new URL(options.url);
|
||||||
this.logger?.info(`[MCP][${this.serverName}] Creating SSE transport: ${url.toString()}`);
|
this.logger?.info(`${this.getLogPrefix()} Creating SSE transport: ${url.toString()}`);
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const transport = new SSEClientTransport(url, {
|
const transport = new SSEClientTransport(url, {
|
||||||
requestInit: {
|
requestInit: {
|
||||||
|
headers: options.headers,
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
transport.onclose = () => {
|
transport.onclose = () => {
|
||||||
this.logger?.info(`[MCP][${this.serverName}] SSE transport closed`);
|
this.logger?.info(`${this.getLogPrefix()} SSE transport closed`);
|
||||||
this.emit('connectionChange', 'disconnected');
|
this.emit('connectionChange', 'disconnected');
|
||||||
};
|
};
|
||||||
|
|
||||||
transport.onerror = (error) => {
|
transport.onerror = (error) => {
|
||||||
this.logger?.error(`[MCP][${this.serverName}] SSE transport error:`, error);
|
this.logger?.error(`${this.getLogPrefix()} SSE transport error:`, error);
|
||||||
this.emitError(error, 'SSE transport error:');
|
this.emitError(error, 'SSE transport error:');
|
||||||
};
|
};
|
||||||
|
|
||||||
transport.onmessage = (message) => {
|
transport.onmessage = (message) => {
|
||||||
this.logger?.info(
|
this.logger?.info(
|
||||||
`[MCP][${this.serverName}] Message received: ${JSON.stringify(message)}`,
|
`${this.getLogPrefix()} Message received: ${JSON.stringify(message)}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -193,7 +208,7 @@ export class MCPConnection extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
} else if (state === 'error' && !this.isReconnecting && !this.isInitializing) {
|
} else if (state === 'error' && !this.isReconnecting && !this.isInitializing) {
|
||||||
this.handleReconnection().catch((error) => {
|
this.handleReconnection().catch((error) => {
|
||||||
this.logger?.error(`[MCP][${this.serverName}] Reconnection handler failed:`, error);
|
this.logger?.error(`${this.getLogPrefix()} Reconnection handler failed:`, error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -218,7 +233,7 @@ export class MCPConnection extends EventEmitter {
|
||||||
const delay = backoffDelay(this.reconnectAttempts);
|
const delay = backoffDelay(this.reconnectAttempts);
|
||||||
|
|
||||||
this.logger?.info(
|
this.logger?.info(
|
||||||
`[MCP][${this.serverName}] Reconnecting ${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS} (delay: ${delay}ms)`,
|
`${this.getLogPrefix()} Reconnecting ${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS} (delay: ${delay}ms)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
|
@ -228,13 +243,13 @@ export class MCPConnection extends EventEmitter {
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger?.error(`[MCP][${this.serverName}] Reconnection attempt failed:`, error);
|
this.logger?.error(`${this.getLogPrefix()} Reconnection attempt failed:`, error);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.reconnectAttempts === this.MAX_RECONNECT_ATTEMPTS ||
|
this.reconnectAttempts === this.MAX_RECONNECT_ATTEMPTS ||
|
||||||
(this.shouldStopReconnecting as boolean)
|
(this.shouldStopReconnecting as boolean)
|
||||||
) {
|
) {
|
||||||
this.logger?.error(`[MCP][${this.serverName}] Stopping reconnection attempts`);
|
this.logger?.error(`${this.getLogPrefix()} Stopping reconnection attempts`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +293,7 @@ export class MCPConnection extends EventEmitter {
|
||||||
await this.client.close();
|
await this.client.close();
|
||||||
this.transport = null;
|
this.transport = null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger?.warn(`[MCP][${this.serverName}] Error closing connection:`, error);
|
this.logger?.warn(`${this.getLogPrefix()} Error closing connection:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,12 +330,18 @@ export class MCPConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.transport.onmessage = (msg) => {
|
this.transport.onmessage = (msg) => {
|
||||||
this.logger?.debug(`[MCP][${this.serverName}] Transport received: ${JSON.stringify(msg)}`);
|
this.logger?.debug(`${this.getLogPrefix()} Transport received: ${JSON.stringify(msg)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const originalSend = this.transport.send.bind(this.transport);
|
const originalSend = this.transport.send.bind(this.transport);
|
||||||
this.transport.send = async (msg) => {
|
this.transport.send = async (msg) => {
|
||||||
this.logger?.debug(`[MCP][${this.serverName}] Transport sending: ${JSON.stringify(msg)}`);
|
if ('result' in msg && !('method' in msg) && Object.keys(msg.result ?? {}).length === 0) {
|
||||||
|
if (Date.now() - this.lastPingTime < FIVE_MINUTES) {
|
||||||
|
throw new Error('Empty result');
|
||||||
|
}
|
||||||
|
this.lastPingTime = Date.now();
|
||||||
|
}
|
||||||
|
this.logger?.debug(`${this.getLogPrefix()} Transport sending: ${JSON.stringify(msg)}`);
|
||||||
return originalSend(msg);
|
return originalSend(msg);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -333,28 +354,16 @@ export class MCPConnection extends EventEmitter {
|
||||||
throw new Error('Connection not established');
|
throw new Error('Connection not established');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger?.error(`[MCP][${this.serverName}] Connection failed:`, error);
|
this.logger?.error(`${this.getLogPrefix()} Connection failed:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupTransportErrorHandlers(transport: Transport): void {
|
private setupTransportErrorHandlers(transport: Transport): void {
|
||||||
transport.onerror = (error) => {
|
transport.onerror = (error) => {
|
||||||
this.logger?.error(`[MCP][${this.serverName}] Transport error:`, error);
|
this.logger?.error(`${this.getLogPrefix()} Transport error:`, error);
|
||||||
this.emit('connectionChange', 'error');
|
this.emit('connectionChange', 'error');
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorHandler = (error: Error) => {
|
|
||||||
try {
|
|
||||||
this.logger?.error(`[MCP][${this.serverName}] Uncaught transport error:`, error);
|
|
||||||
} catch {
|
|
||||||
console.error(`[MCP][${this.serverName}] Critical error logging failed`, error);
|
|
||||||
}
|
|
||||||
this.emit('connectionChange', 'error');
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on('uncaughtException', errorHandler);
|
|
||||||
process.on('unhandledRejection', errorHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disconnect(): Promise<void> {
|
public async disconnect(): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
import { CallToolResultSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
||||||
import type { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
import type { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
||||||
import type { JsonSchemaType, MCPOptions } from 'librechat-data-provider';
|
import type { JsonSchemaType, MCPOptions } from 'librechat-data-provider';
|
||||||
import type { Logger } from 'winston';
|
import type { Logger } from 'winston';
|
||||||
|
|
@ -7,9 +7,21 @@ import { formatToolContent } from './parsers';
|
||||||
import { MCPConnection } from './connection';
|
import { MCPConnection } from './connection';
|
||||||
import { CONSTANTS } from './enum';
|
import { CONSTANTS } from './enum';
|
||||||
|
|
||||||
|
export interface CallToolOptions extends RequestOptions {
|
||||||
|
userId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class MCPManager {
|
export class MCPManager {
|
||||||
private static instance: MCPManager | null = null;
|
private static instance: MCPManager | null = null;
|
||||||
|
/** App-level connections initialized at startup */
|
||||||
private connections: Map<string, MCPConnection> = new Map();
|
private connections: Map<string, MCPConnection> = new Map();
|
||||||
|
/** User-specific connections initialized on demand */
|
||||||
|
private userConnections: Map<string, Map<string, MCPConnection>> = new Map();
|
||||||
|
/** Last activity timestamp for users (not per server) */
|
||||||
|
private userLastActivity: Map<string, number> = new Map();
|
||||||
|
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
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
private static getDefaultLogger(): Logger {
|
private static getDefaultLogger(): Logger {
|
||||||
|
|
@ -29,37 +41,39 @@ export class MCPManager {
|
||||||
if (!MCPManager.instance) {
|
if (!MCPManager.instance) {
|
||||||
MCPManager.instance = new MCPManager(logger);
|
MCPManager.instance = new MCPManager(logger);
|
||||||
}
|
}
|
||||||
|
// Check for idle connections when getInstance is called
|
||||||
|
MCPManager.instance.checkIdleConnections();
|
||||||
return MCPManager.instance;
|
return MCPManager.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Stores configs and initializes app-level connections */
|
||||||
public async initializeMCP(
|
public async initializeMCP(
|
||||||
mcpServers: t.MCPServers,
|
mcpServers: t.MCPServers,
|
||||||
processMCPEnv?: (obj: MCPOptions) => MCPOptions,
|
processMCPEnv?: (obj: MCPOptions) => MCPOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.logger.info('[MCP] Initializing servers');
|
this.logger.info('[MCP] Initializing app-level servers');
|
||||||
|
this.processMCPEnv = processMCPEnv; // Store the function
|
||||||
|
this.mcpConfigs = mcpServers;
|
||||||
|
|
||||||
const entries = Object.entries(mcpServers);
|
const entries = Object.entries(mcpServers);
|
||||||
const initializedServers = new Set();
|
const initializedServers = new Set();
|
||||||
const connectionResults = await Promise.allSettled(
|
const connectionResults = await Promise.allSettled(
|
||||||
entries.map(async ([serverName, _config], i) => {
|
entries.map(async ([serverName, _config], i) => {
|
||||||
const config = processMCPEnv ? processMCPEnv(_config) : _config;
|
/** Process env for app-level connections */
|
||||||
|
const config = this.processMCPEnv ? this.processMCPEnv(_config) : _config;
|
||||||
const connection = new MCPConnection(serverName, config, this.logger);
|
const connection = new MCPConnection(serverName, config, this.logger);
|
||||||
|
|
||||||
connection.on('connectionChange', (state) => {
|
|
||||||
this.logger.info(`[MCP][${serverName}] Connection state: ${state}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const connectionTimeout = new Promise<void>((_, reject) =>
|
const connectionTimeout = new Promise<void>((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Connection timeout')), 30000),
|
setTimeout(() => reject(new Error('Connection timeout')), 30000),
|
||||||
);
|
);
|
||||||
|
|
||||||
const connectionAttempt = this.initializeServer(connection, serverName);
|
const connectionAttempt = this.initializeServer(connection, `[MCP][${serverName}]`);
|
||||||
await Promise.race([connectionAttempt, connectionTimeout]);
|
await Promise.race([connectionAttempt, connectionTimeout]);
|
||||||
|
|
||||||
if (connection.isConnected()) {
|
if (connection.isConnected()) {
|
||||||
initializedServers.add(i);
|
initializedServers.add(i);
|
||||||
this.connections.set(serverName, connection);
|
this.connections.set(serverName, connection); // Store in app-level map
|
||||||
|
|
||||||
const serverCapabilities = connection.client.getServerCapabilities();
|
const serverCapabilities = connection.client.getServerCapabilities();
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
|
|
@ -88,11 +102,13 @@ export class MCPManager {
|
||||||
(result): result is PromiseRejectedResult => result.status === 'rejected',
|
(result): result is PromiseRejectedResult => result.status === 'rejected',
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.info(`[MCP] Initialized ${initializedServers.size}/${entries.length} server(s)`);
|
this.logger.info(
|
||||||
|
`[MCP] Initialized ${initializedServers.size}/${entries.length} app-level server(s)`,
|
||||||
|
);
|
||||||
|
|
||||||
if (failedConnections.length > 0) {
|
if (failedConnections.length > 0) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`[MCP] ${failedConnections.length}/${entries.length} server(s) failed to initialize`,
|
`[MCP] ${failedConnections.length}/${entries.length} app-level server(s) failed to initialize`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,49 +121,224 @@ export class MCPManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (initializedServers.size === entries.length) {
|
if (initializedServers.size === entries.length) {
|
||||||
this.logger.info('[MCP] All servers initialized successfully');
|
this.logger.info('[MCP] All app-level servers initialized successfully');
|
||||||
} else if (initializedServers.size === 0) {
|
} else if (initializedServers.size === 0) {
|
||||||
this.logger.error('[MCP] No servers initialized');
|
this.logger.warn('[MCP] No app-level servers initialized');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeServer(connection: MCPConnection, serverName: string): Promise<void> {
|
/** Generic server initialization logic */
|
||||||
|
private async initializeServer(connection: MCPConnection, logPrefix: string): Promise<void> {
|
||||||
const maxAttempts = 3;
|
const maxAttempts = 3;
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
|
|
||||||
while (attempts < maxAttempts) {
|
while (attempts < maxAttempts) {
|
||||||
try {
|
try {
|
||||||
await connection.connect();
|
await connection.connect();
|
||||||
|
|
||||||
if (connection.isConnected()) {
|
if (connection.isConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
throw new Error('Connection attempt succeeded but status is not connected');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
if (attempts === maxAttempts) {
|
if (attempts === maxAttempts) {
|
||||||
this.logger.error(`[MCP][${serverName}] Failed after ${maxAttempts} attempts`);
|
this.logger.error(`${logPrefix} Failed to connect after ${maxAttempts} attempts`, error);
|
||||||
throw error;
|
throw error; // Re-throw the last error
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000 * attempts));
|
await new Promise((resolve) => setTimeout(resolve, 2000 * attempts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check for and disconnect idle connections */
|
||||||
|
private checkIdleConnections(): void {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Iterate through all users to check for idle ones
|
||||||
|
for (const [userId, lastActivity] of this.userLastActivity.entries()) {
|
||||||
|
if (now - lastActivity > this.USER_CONNECTION_IDLE_TIMEOUT) {
|
||||||
|
this.logger.info(
|
||||||
|
`[MCP][User: ${userId}] User idle for too long. Disconnecting all connections...`,
|
||||||
|
);
|
||||||
|
// Disconnect all user connections asynchronously (fire and forget)
|
||||||
|
this.disconnectUserConnections(userId).catch((err) =>
|
||||||
|
this.logger.error(`[MCP][User: ${userId}] Error disconnecting idle connections:`, err),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the last activity timestamp for a user */
|
||||||
|
private updateUserLastActivity(userId: string): void {
|
||||||
|
const now = Date.now();
|
||||||
|
this.userLastActivity.set(userId, now);
|
||||||
|
this.logger.debug(
|
||||||
|
`[MCP][User: ${userId}] Updated last activity timestamp: ${new Date(now).toISOString()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets or creates a connection for a specific user */
|
||||||
|
public async getUserConnection(userId: string, serverName: string): Promise<MCPConnection> {
|
||||||
|
const userServerMap = this.userConnections.get(userId);
|
||||||
|
let connection = userServerMap?.get(serverName);
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Check if user is idle
|
||||||
|
const lastActivity = this.userLastActivity.get(userId);
|
||||||
|
if (lastActivity && now - lastActivity > this.USER_CONNECTION_IDLE_TIMEOUT) {
|
||||||
|
this.logger.info(
|
||||||
|
`[MCP][User: ${userId}] User idle for too long. Disconnecting all connections.`,
|
||||||
|
);
|
||||||
|
// Disconnect all user connections
|
||||||
|
await this.disconnectUserConnections(userId).catch((err) =>
|
||||||
|
this.logger.error(`[MCP][User: ${userId}] Error disconnecting idle connections:`, err),
|
||||||
|
);
|
||||||
|
connection = undefined; // Force creation of a new connection
|
||||||
|
} else if (connection) {
|
||||||
|
if (connection.isConnected()) {
|
||||||
|
this.logger.debug(`[MCP][User: ${userId}][${serverName}] Reusing active connection`);
|
||||||
|
// Update timestamp on reuse
|
||||||
|
this.updateUserLastActivity(userId);
|
||||||
|
return connection;
|
||||||
|
} else {
|
||||||
|
// Connection exists but is not connected, attempt to remove potentially stale entry
|
||||||
|
this.logger.warn(
|
||||||
|
`[MCP][User: ${userId}][${serverName}] Found existing but disconnected connection object. Cleaning up.`,
|
||||||
|
);
|
||||||
|
this.removeUserConnection(userId, serverName); // Clean up maps
|
||||||
|
connection = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no valid connection exists, create a new one
|
||||||
|
if (!connection) {
|
||||||
|
this.logger.info(`[MCP][User: ${userId}][${serverName}] Establishing new connection`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = this.mcpConfigs[serverName];
|
||||||
|
if (!config) {
|
||||||
|
throw new McpError(
|
||||||
|
ErrorCode.InvalidRequest,
|
||||||
|
`[MCP][User: ${userId}] Configuration for server "${serverName}" not found.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.processMCPEnv) {
|
||||||
|
config = { ...(this.processMCPEnv(config, userId) ?? {}) };
|
||||||
|
}
|
||||||
|
|
||||||
|
connection = new MCPConnection(serverName, config, this.logger, userId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const connectionTimeout = new Promise<void>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Connection timeout')), 30000),
|
||||||
|
);
|
||||||
|
const connectionAttempt = this.initializeServer(
|
||||||
|
connection,
|
||||||
|
`[MCP][User: ${userId}][${serverName}]`,
|
||||||
|
);
|
||||||
|
await Promise.race([connectionAttempt, connectionTimeout]);
|
||||||
|
|
||||||
|
if (!connection.isConnected()) {
|
||||||
|
throw new Error('Failed to establish connection after initialization attempt.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.userConnections.has(userId)) {
|
||||||
|
this.userConnections.set(userId, new Map());
|
||||||
|
}
|
||||||
|
this.userConnections.get(userId)?.set(serverName, connection);
|
||||||
|
this.logger.info(`[MCP][User: ${userId}][${serverName}] Connection successfully established`);
|
||||||
|
// Update timestamp on creation
|
||||||
|
this.updateUserLastActivity(userId);
|
||||||
|
return connection;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`[MCP][User: ${userId}][${serverName}] Failed to establish connection`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
// Ensure partial connection state is cleaned up if initialization fails
|
||||||
|
await connection.disconnect().catch((disconnectError) => {
|
||||||
|
this.logger.error(
|
||||||
|
`[MCP][User: ${userId}][${serverName}] Error during cleanup after failed connection`,
|
||||||
|
disconnectError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Ensure cleanup even if connection attempt fails
|
||||||
|
this.removeUserConnection(userId, serverName);
|
||||||
|
throw error; // Re-throw the error to the caller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes a specific user connection entry */
|
||||||
|
private removeUserConnection(userId: string, serverName: string): void {
|
||||||
|
// Remove connection object
|
||||||
|
const userMap = this.userConnections.get(userId);
|
||||||
|
if (userMap) {
|
||||||
|
userMap.delete(serverName);
|
||||||
|
if (userMap.size === 0) {
|
||||||
|
this.userConnections.delete(userId);
|
||||||
|
// Only remove user activity timestamp if all connections are gone
|
||||||
|
this.userLastActivity.delete(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`[MCP][User: ${userId}][${serverName}] Removed connection entry.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disconnects and removes a specific user connection */
|
||||||
|
public async disconnectUserConnection(userId: string, serverName: string): Promise<void> {
|
||||||
|
const userMap = this.userConnections.get(userId);
|
||||||
|
const connection = userMap?.get(serverName);
|
||||||
|
if (connection) {
|
||||||
|
this.logger.info(`[MCP][User: ${userId}][${serverName}] Disconnecting...`);
|
||||||
|
await connection.disconnect();
|
||||||
|
this.removeUserConnection(userId, serverName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disconnects and removes all connections for a specific user */
|
||||||
|
public async disconnectUserConnections(userId: string): Promise<void> {
|
||||||
|
const userMap = this.userConnections.get(userId);
|
||||||
|
if (userMap) {
|
||||||
|
this.logger.info(`[MCP][User: ${userId}] Disconnecting all servers...`);
|
||||||
|
const disconnectPromises = Array.from(userMap.keys()).map(async (serverName) => {
|
||||||
|
try {
|
||||||
|
await this.disconnectUserConnection(userId, serverName);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`[MCP][User: ${userId}][${serverName}] Error during disconnection:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.allSettled(disconnectPromises);
|
||||||
|
// Ensure user activity timestamp is removed
|
||||||
|
this.userLastActivity.delete(userId);
|
||||||
|
this.logger.info(`[MCP][User: ${userId}] All connections processed for disconnection.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the app-level connection (used for mapping tools, etc.) */
|
||||||
public getConnection(serverName: string): MCPConnection | undefined {
|
public getConnection(serverName: string): MCPConnection | undefined {
|
||||||
return this.connections.get(serverName);
|
return this.connections.get(serverName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns all app-level connections */
|
||||||
public getAllConnections(): Map<string, MCPConnection> {
|
public getAllConnections(): Map<string, MCPConnection> {
|
||||||
return this.connections;
|
return this.connections;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps available tools from all app-level connections into the provided object.
|
||||||
|
* The object is modified in place.
|
||||||
|
*/
|
||||||
public async mapAvailableTools(availableTools: t.LCAvailableTools): Promise<void> {
|
public async mapAvailableTools(availableTools: t.LCAvailableTools): Promise<void> {
|
||||||
for (const [serverName, connection] of this.connections.entries()) {
|
for (const [serverName, connection] of this.connections.entries()) {
|
||||||
try {
|
try {
|
||||||
if (connection.isConnected() !== true) {
|
if (connection.isConnected() !== true) {
|
||||||
this.logger.warn(`Connection ${serverName} is not connected. Skipping tool fetch.`);
|
this.logger.warn(
|
||||||
|
`[MCP][${serverName}] Connection not established. Skipping tool mapping.`,
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,16 +355,21 @@ export class MCPManager {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(`[MCP][${serverName}] Error fetching tools:`, error);
|
this.logger.warn(`[MCP][${serverName}] Error fetching tools for mapping:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads tools from all app-level connections into the manifest.
|
||||||
|
*/
|
||||||
public async loadManifestTools(manifestTools: t.LCToolManifest): Promise<void> {
|
public async loadManifestTools(manifestTools: t.LCToolManifest): Promise<void> {
|
||||||
for (const [serverName, connection] of this.connections.entries()) {
|
for (const [serverName, connection] of this.connections.entries()) {
|
||||||
try {
|
try {
|
||||||
if (connection.isConnected() !== true) {
|
if (connection.isConnected() !== true) {
|
||||||
this.logger.warn(`Connection ${serverName} is not connected. Skipping tool fetch.`);
|
this.logger.warn(
|
||||||
|
`[MCP][${serverName}] Connection not established. Skipping manifest loading.`,
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,11 +384,16 @@ export class MCPManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`[MCP][${serverName}] Error fetching tools:`, error);
|
this.logger.error(`[MCP][${serverName}] Error fetching tools for manifest:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a tool on an MCP server, using either a user-specific connection
|
||||||
|
* (if userId is provided) or an app-level connection. Updates the last activity timestamp
|
||||||
|
* for user-specific connections upon successful call initiation.
|
||||||
|
*/
|
||||||
async callTool({
|
async callTool({
|
||||||
serverName,
|
serverName,
|
||||||
toolName,
|
toolName,
|
||||||
|
|
@ -204,51 +405,102 @@ export class MCPManager {
|
||||||
toolName: string;
|
toolName: string;
|
||||||
provider: t.Provider;
|
provider: t.Provider;
|
||||||
toolArguments?: Record<string, unknown>;
|
toolArguments?: Record<string, unknown>;
|
||||||
options?: RequestOptions;
|
options?: CallToolOptions;
|
||||||
}): Promise<t.FormattedToolResponse> {
|
}): Promise<t.FormattedToolResponse> {
|
||||||
const connection = this.connections.get(serverName);
|
let connection: MCPConnection | undefined;
|
||||||
if (!connection) {
|
const { userId, ...callOptions } = options ?? {};
|
||||||
throw new Error(
|
const logPrefix = userId ? `[MCP][User: ${userId}][${serverName}]` : `[MCP][${serverName}]`;
|
||||||
`No connection found for server: ${serverName}. Please make sure to use MCP servers available under 'Connected MCP Servers'.`,
|
|
||||||
);
|
try {
|
||||||
}
|
if (userId) {
|
||||||
const result = await connection.client.request(
|
this.updateUserLastActivity(userId);
|
||||||
{
|
// Get or create user-specific connection
|
||||||
method: 'tools/call',
|
connection = await this.getUserConnection(userId, serverName);
|
||||||
params: {
|
} else {
|
||||||
name: toolName,
|
// Use app-level connection
|
||||||
arguments: toolArguments,
|
connection = this.connections.get(serverName);
|
||||||
|
if (!connection) {
|
||||||
|
throw new McpError(
|
||||||
|
ErrorCode.InvalidRequest,
|
||||||
|
`${logPrefix} No app-level connection found. Cannot execute tool ${toolName}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connection.isConnected()) {
|
||||||
|
// This might happen if getUserConnection failed silently or app connection dropped
|
||||||
|
throw new McpError(
|
||||||
|
ErrorCode.InternalError, // Use InternalError for connection issues
|
||||||
|
`${logPrefix} Connection is not active. Cannot execute tool ${toolName}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await connection.client.request(
|
||||||
|
{
|
||||||
|
method: 'tools/call',
|
||||||
|
params: {
|
||||||
|
name: toolName,
|
||||||
|
arguments: toolArguments,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
CallToolResultSchema,
|
||||||
CallToolResultSchema,
|
{
|
||||||
{
|
timeout: connection.timeout,
|
||||||
timeout: connection.timeout,
|
...callOptions,
|
||||||
...options,
|
},
|
||||||
},
|
);
|
||||||
);
|
if (userId) {
|
||||||
return formatToolContent(result, provider);
|
this.updateUserLastActivity(userId);
|
||||||
|
}
|
||||||
|
this.checkIdleConnections();
|
||||||
|
return formatToolContent(result, provider);
|
||||||
|
} catch (error) {
|
||||||
|
// Log with context and re-throw or handle as needed
|
||||||
|
this.logger.error(`${logPrefix}[${toolName}] Tool call failed`, error);
|
||||||
|
// Rethrowing allows the caller (createMCPTool) to handle the final user message
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Disconnects a specific app-level server */
|
||||||
public async disconnectServer(serverName: string): Promise<void> {
|
public async disconnectServer(serverName: string): Promise<void> {
|
||||||
const connection = this.connections.get(serverName);
|
const connection = this.connections.get(serverName);
|
||||||
if (connection) {
|
if (connection) {
|
||||||
|
this.logger.info(`[MCP][${serverName}] Disconnecting...`);
|
||||||
await connection.disconnect();
|
await connection.disconnect();
|
||||||
this.connections.delete(serverName);
|
this.connections.delete(serverName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Disconnects all app-level and user-level connections */
|
||||||
public async disconnectAll(): Promise<void> {
|
public async disconnectAll(): Promise<void> {
|
||||||
const disconnectPromises = Array.from(this.connections.values()).map((connection) =>
|
this.logger.info('[MCP] Disconnecting all app-level and user-level connections...');
|
||||||
connection.disconnect(),
|
|
||||||
|
const userDisconnectPromises = Array.from(this.userConnections.keys()).map((userId) =>
|
||||||
|
this.disconnectUserConnections(userId),
|
||||||
);
|
);
|
||||||
await Promise.all(disconnectPromises);
|
await Promise.allSettled(userDisconnectPromises);
|
||||||
|
this.userLastActivity.clear();
|
||||||
|
|
||||||
|
// Disconnect all app-level connections
|
||||||
|
const appDisconnectPromises = Array.from(this.connections.values()).map((connection) =>
|
||||||
|
connection.disconnect().catch((error) => {
|
||||||
|
this.logger.error(`[MCP][${connection.serverName}] Error during disconnectAll:`, error);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await Promise.allSettled(appDisconnectPromises);
|
||||||
this.connections.clear();
|
this.connections.clear();
|
||||||
|
|
||||||
|
this.logger.info('[MCP] All connections processed for disconnection.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Destroys the singleton instance and disconnects all connections */
|
||||||
public static async destroyInstance(): Promise<void> {
|
public static async destroyInstance(): Promise<void> {
|
||||||
if (MCPManager.instance) {
|
if (MCPManager.instance) {
|
||||||
await MCPManager.instance.disconnectAll();
|
await MCPManager.instance.disconnectAll();
|
||||||
MCPManager.instance = null;
|
MCPManager.instance = null;
|
||||||
|
const logger = MCPManager.getDefaultLogger();
|
||||||
|
logger.info('[MCP] Manager instance destroyed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue