mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00

- Added a new controller for handling MCP-specific tool endpoints, decoupled from regular LibreChat tools. - Implemented a route to fetch all available MCP tools for the user. - Updated caching mechanisms to support MCP server-specific tools. - Refactored existing code to replace user tools caching with server tools caching. - Introduced new hooks and queries for fetching MCP tools in the client application. - Updated the AgentPanelProvider to integrate MCP tools alongside regular tools. - Modified various components to utilize the new MCP tools query and ensure proper refetching of data. - Enhanced error handling and logging for MCP tools operations.
129 lines
4.8 KiB
JavaScript
129 lines
4.8 KiB
JavaScript
const { logger } = require('@librechat/data-schemas');
|
|
const { CacheKeys } = require('librechat-data-provider');
|
|
const { getToolkitKey, checkPluginAuth, filterUniquePlugins } = require('@librechat/api');
|
|
const { getCachedTools, setCachedTools } = require('~/server/services/Config');
|
|
const { availableTools, toolkits } = require('~/app/clients/tools');
|
|
const { getAppConfig } = require('~/server/services/Config');
|
|
const { getLogStores } = require('~/cache');
|
|
|
|
const getAvailablePluginsController = async (req, res) => {
|
|
try {
|
|
const cache = getLogStores(CacheKeys.CONFIG_STORE);
|
|
const cachedPlugins = await cache.get(CacheKeys.PLUGINS);
|
|
if (cachedPlugins) {
|
|
res.status(200).json(cachedPlugins);
|
|
return;
|
|
}
|
|
|
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
|
/** @type {{ filteredTools: string[], includedTools: string[] }} */
|
|
const { filteredTools = [], includedTools = [] } = appConfig;
|
|
/** @type {import('@librechat/api').LCManifestTool[]} */
|
|
const pluginManifest = availableTools;
|
|
|
|
const uniquePlugins = filterUniquePlugins(pluginManifest);
|
|
let authenticatedPlugins = [];
|
|
for (const plugin of uniquePlugins) {
|
|
authenticatedPlugins.push(
|
|
checkPluginAuth(plugin) ? { ...plugin, authenticated: true } : plugin,
|
|
);
|
|
}
|
|
|
|
let plugins = authenticatedPlugins;
|
|
|
|
if (includedTools.length > 0) {
|
|
plugins = plugins.filter((plugin) => includedTools.includes(plugin.pluginKey));
|
|
} else {
|
|
plugins = plugins.filter((plugin) => !filteredTools.includes(plugin.pluginKey));
|
|
}
|
|
|
|
await cache.set(CacheKeys.PLUGINS, plugins);
|
|
res.status(200).json(plugins);
|
|
} catch (error) {
|
|
res.status(500).json({ message: error.message });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Retrieves and returns a list of available tools, either from a cache or by reading a plugin manifest file.
|
|
*
|
|
* This function first attempts to retrieve the list of tools from a cache. If the tools are not found in the cache,
|
|
* it reads a plugin manifest file, filters for unique plugins, and determines if each plugin is authenticated.
|
|
* Only plugins that are marked as available in the application's local state are included in the final list.
|
|
* The resulting list of tools is then cached and sent to the client.
|
|
*
|
|
* @param {object} req - The request object, containing information about the HTTP request.
|
|
* @param {object} res - The response object, used to send back the desired HTTP response.
|
|
* @returns {Promise<void>} A promise that resolves when the function has completed.
|
|
*/
|
|
const getAvailableTools = async (req, res) => {
|
|
try {
|
|
const userId = req.user?.id;
|
|
if (!userId) {
|
|
logger.warn('[getAvailableTools] User ID not found in request');
|
|
return res.status(401).json({ message: 'Unauthorized' });
|
|
}
|
|
const cache = getLogStores(CacheKeys.CONFIG_STORE);
|
|
const cachedToolsArray = await cache.get(CacheKeys.TOOLS);
|
|
|
|
const appConfig = req.config ?? (await getAppConfig({ role: req.user?.role }));
|
|
|
|
// Return early if we have cached tools
|
|
if (cachedToolsArray != null) {
|
|
res.status(200).json(cachedToolsArray);
|
|
return;
|
|
}
|
|
|
|
/** @type {Record<string, FunctionTool> | null} Get tool definitions to filter which tools are actually available */
|
|
let toolDefinitions = await getCachedTools();
|
|
|
|
if (toolDefinitions == null && appConfig?.availableTools != null) {
|
|
logger.warn('[getAvailableTools] Tool cache was empty, re-initializing from app config');
|
|
await setCachedTools(appConfig.availableTools);
|
|
toolDefinitions = appConfig.availableTools;
|
|
}
|
|
|
|
/** @type {import('@librechat/api').LCManifestTool[]} */
|
|
let pluginManifest = availableTools;
|
|
|
|
/** @type {TPlugin[]} Deduplicate and authenticate plugins */
|
|
const uniquePlugins = filterUniquePlugins(pluginManifest);
|
|
const authenticatedPlugins = uniquePlugins.map((plugin) => {
|
|
if (checkPluginAuth(plugin)) {
|
|
return { ...plugin, authenticated: true };
|
|
} else {
|
|
return plugin;
|
|
}
|
|
});
|
|
|
|
/** Filter plugins based on availability */
|
|
const toolsOutput = [];
|
|
for (const plugin of authenticatedPlugins) {
|
|
const isToolDefined = toolDefinitions?.[plugin.pluginKey] !== undefined;
|
|
const isToolkit =
|
|
plugin.toolkit === true &&
|
|
Object.keys(toolDefinitions ?? {}).some(
|
|
(key) => getToolkitKey({ toolkits, toolName: key }) === plugin.pluginKey,
|
|
);
|
|
|
|
if (!isToolDefined && !isToolkit) {
|
|
continue;
|
|
}
|
|
|
|
toolsOutput.push(plugin);
|
|
}
|
|
|
|
const finalTools = filterUniquePlugins(toolsOutput);
|
|
await cache.set(CacheKeys.TOOLS, finalTools);
|
|
|
|
res.status(200).json(finalTools);
|
|
} catch (error) {
|
|
logger.error('[getAvailableTools]', error);
|
|
res.status(500).json({ message: error.message });
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
getAvailableTools,
|
|
getAvailablePluginsController,
|
|
};
|