mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 11:50:14 +01:00
🔧 fix: Handle Missing MCP Config Gracefully in Config/Plugin Routes (#9438)
* 🛠️ fix: Update Plugins and Config Routes to Handle No MCP Config
* refactor: Rename cachedMCPPlugins to mcpPlugins for clarity in PluginController
This commit is contained in:
parent
df17582103
commit
23bd4dfbfd
3 changed files with 42 additions and 12 deletions
|
|
@ -74,14 +74,23 @@ const getAvailableTools = async (req, res) => {
|
||||||
const cachedToolsArray = await cache.get(CacheKeys.TOOLS);
|
const cachedToolsArray = await cache.get(CacheKeys.TOOLS);
|
||||||
const cachedUserTools = await getCachedTools({ userId });
|
const cachedUserTools = await getCachedTools({ userId });
|
||||||
|
|
||||||
|
const appConfig = req.config ?? (await getAppConfig({ role: req.user?.role }));
|
||||||
|
|
||||||
|
/** @type {TPlugin[]} */
|
||||||
|
let mcpPlugins;
|
||||||
|
if (appConfig?.mcpConfig) {
|
||||||
const mcpManager = getMCPManager();
|
const mcpManager = getMCPManager();
|
||||||
const userPlugins =
|
mcpPlugins =
|
||||||
cachedUserTools != null
|
cachedUserTools != null
|
||||||
? convertMCPToolsToPlugins({ functionTools: cachedUserTools, mcpManager })
|
? convertMCPToolsToPlugins({ functionTools: cachedUserTools, mcpManager })
|
||||||
: undefined;
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (cachedToolsArray != null && userPlugins != null) {
|
if (
|
||||||
const dedupedTools = filterUniquePlugins([...userPlugins, ...cachedToolsArray]);
|
cachedToolsArray != null &&
|
||||||
|
(appConfig?.mcpConfig != null ? mcpPlugins != null && mcpPlugins.length > 0 : true)
|
||||||
|
) {
|
||||||
|
const dedupedTools = filterUniquePlugins([...(mcpPlugins ?? []), ...cachedToolsArray]);
|
||||||
res.status(200).json(dedupedTools);
|
res.status(200).json(dedupedTools);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -93,9 +102,9 @@ const getAvailableTools = async (req, res) => {
|
||||||
/** @type {import('@librechat/api').LCManifestTool[]} */
|
/** @type {import('@librechat/api').LCManifestTool[]} */
|
||||||
let pluginManifest = availableTools;
|
let pluginManifest = availableTools;
|
||||||
|
|
||||||
const appConfig = req.config ?? (await getAppConfig({ role: req.user?.role }));
|
|
||||||
if (appConfig?.mcpConfig != null) {
|
if (appConfig?.mcpConfig != null) {
|
||||||
try {
|
try {
|
||||||
|
const mcpManager = getMCPManager();
|
||||||
const mcpTools = await mcpManager.getAllToolFunctions(userId);
|
const mcpTools = await mcpManager.getAllToolFunctions(userId);
|
||||||
prelimCachedTools = prelimCachedTools ?? {};
|
prelimCachedTools = prelimCachedTools ?? {};
|
||||||
for (const [toolKey, toolData] of Object.entries(mcpTools)) {
|
for (const [toolKey, toolData] of Object.entries(mcpTools)) {
|
||||||
|
|
@ -175,7 +184,7 @@ const getAvailableTools = async (req, res) => {
|
||||||
const finalTools = filterUniquePlugins(toolsOutput);
|
const finalTools = filterUniquePlugins(toolsOutput);
|
||||||
await cache.set(CacheKeys.TOOLS, finalTools);
|
await cache.set(CacheKeys.TOOLS, finalTools);
|
||||||
|
|
||||||
const dedupedTools = filterUniquePlugins([...(userPlugins ?? []), ...finalTools]);
|
const dedupedTools = filterUniquePlugins([...(mcpPlugins ?? []), ...finalTools]);
|
||||||
res.status(200).json(dedupedTools);
|
res.status(200).json(dedupedTools);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[getAvailableTools]', error);
|
logger.error('[getAvailableTools]', error);
|
||||||
|
|
|
||||||
|
|
@ -174,10 +174,19 @@ describe('PluginController', () => {
|
||||||
mockCache.get.mockResolvedValue(null);
|
mockCache.get.mockResolvedValue(null);
|
||||||
getCachedTools.mockResolvedValueOnce(mockUserTools);
|
getCachedTools.mockResolvedValueOnce(mockUserTools);
|
||||||
mockReq.config = {
|
mockReq.config = {
|
||||||
mcpConfig: null,
|
mcpConfig: {
|
||||||
|
server1: {},
|
||||||
|
},
|
||||||
paths: { structuredTools: '/mock/path' },
|
paths: { structuredTools: '/mock/path' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mock MCP manager to return empty tools initially (since getAllToolFunctions is called)
|
||||||
|
const mockMCPManager = {
|
||||||
|
getAllToolFunctions: jest.fn().mockResolvedValue({}),
|
||||||
|
getRawConfig: jest.fn().mockReturnValue({}),
|
||||||
|
};
|
||||||
|
require('~/config').getMCPManager.mockReturnValue(mockMCPManager);
|
||||||
|
|
||||||
// Mock second call to return tool definitions (includeGlobal: true)
|
// Mock second call to return tool definitions (includeGlobal: true)
|
||||||
getCachedTools.mockResolvedValueOnce(mockUserTools);
|
getCachedTools.mockResolvedValueOnce(mockUserTools);
|
||||||
|
|
||||||
|
|
@ -505,7 +514,7 @@ describe('PluginController', () => {
|
||||||
expect(mockRes.json).toHaveBeenCalledWith([]);
|
expect(mockRes.json).toHaveBeenCalledWith([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle cachedToolsArray and userPlugins both being defined', async () => {
|
it('should handle `cachedToolsArray` and `mcpPlugins` both being defined', async () => {
|
||||||
const cachedTools = [{ name: 'CachedTool', pluginKey: 'cached-tool', description: 'Cached' }];
|
const cachedTools = [{ name: 'CachedTool', pluginKey: 'cached-tool', description: 'Cached' }];
|
||||||
// Use MCP delimiter for the user tool so convertMCPToolsToPlugins works
|
// Use MCP delimiter for the user tool so convertMCPToolsToPlugins works
|
||||||
const userTools = {
|
const userTools = {
|
||||||
|
|
@ -522,10 +531,19 @@ describe('PluginController', () => {
|
||||||
mockCache.get.mockResolvedValue(cachedTools);
|
mockCache.get.mockResolvedValue(cachedTools);
|
||||||
getCachedTools.mockResolvedValueOnce(userTools);
|
getCachedTools.mockResolvedValueOnce(userTools);
|
||||||
mockReq.config = {
|
mockReq.config = {
|
||||||
mcpConfig: null,
|
mcpConfig: {
|
||||||
|
server1: {},
|
||||||
|
},
|
||||||
paths: { structuredTools: '/mock/path' },
|
paths: { structuredTools: '/mock/path' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mock MCP manager to return empty tools initially
|
||||||
|
const mockMCPManager = {
|
||||||
|
getAllToolFunctions: jest.fn().mockResolvedValue({}),
|
||||||
|
getRawConfig: jest.fn().mockReturnValue({}),
|
||||||
|
};
|
||||||
|
require('~/config').getMCPManager.mockReturnValue(mockMCPManager);
|
||||||
|
|
||||||
// The controller expects a second call to getCachedTools
|
// The controller expects a second call to getCachedTools
|
||||||
getCachedTools.mockResolvedValueOnce({
|
getCachedTools.mockResolvedValueOnce({
|
||||||
'cached-tool': { type: 'function', function: { name: 'cached-tool' } },
|
'cached-tool': { type: 'function', function: { name: 'cached-tool' } },
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,9 @@ router.get('/', async function (req, res) {
|
||||||
payload.mcpServers = {};
|
payload.mcpServers = {};
|
||||||
const getMCPServers = () => {
|
const getMCPServers = () => {
|
||||||
try {
|
try {
|
||||||
|
if (appConfig?.mcpConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const mcpManager = getMCPManager();
|
const mcpManager = getMCPManager();
|
||||||
if (!mcpManager) {
|
if (!mcpManager) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue