mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🪟 fix: Surface MCP Initialization Errors (#14529)
This commit is contained in:
parent
7bb6651883
commit
8af6414e13
11 changed files with 187 additions and 19 deletions
|
|
@ -1885,6 +1885,37 @@ describe('MCP Routes', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return structured reinitialization failure details', async () => {
|
||||
const mockMcpManager = {
|
||||
disconnectUserConnection: jest.fn().mockResolvedValue(),
|
||||
};
|
||||
|
||||
mockRegistryInstance.getServerConfig.mockResolvedValue({});
|
||||
require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
|
||||
require('~/server/services/Tools/mcp').reinitMCPServer.mockResolvedValue({
|
||||
success: false,
|
||||
message: "MCP server 'test-server' requires user-provided variables",
|
||||
serverName: 'test-server',
|
||||
oauthRequired: false,
|
||||
oauthUrl: null,
|
||||
failureReason: 'missing_custom_user_vars',
|
||||
missingUserVars: ['API_KEY'],
|
||||
});
|
||||
|
||||
const response = await request(app).post('/api/mcp/test-server/reinitialize');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
success: false,
|
||||
message: "MCP server 'test-server' requires user-provided variables",
|
||||
serverName: 'test-server',
|
||||
oauthRequired: false,
|
||||
oauthUrl: null,
|
||||
failureReason: 'missing_custom_user_vars',
|
||||
missingUserVars: ['API_KEY'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 500 when reinitialize fails with non-OAuth error', async () => {
|
||||
const mockMcpManager = {
|
||||
disconnectUserConnection: jest.fn().mockResolvedValue(),
|
||||
|
|
|
|||
|
|
@ -718,7 +718,15 @@ router.post(
|
|||
return res.status(500).json({ error: 'Failed to reinitialize MCP server for user' });
|
||||
}
|
||||
|
||||
const { success, message, oauthRequired, oauthUrl, connectionDeferred } = result;
|
||||
const {
|
||||
success,
|
||||
message,
|
||||
oauthRequired,
|
||||
oauthUrl,
|
||||
failureReason,
|
||||
missingUserVars,
|
||||
connectionDeferred,
|
||||
} = result;
|
||||
|
||||
if (oauthRequired) {
|
||||
const flowId = getOAuthFlowId(user.id, serverName);
|
||||
|
|
@ -731,6 +739,8 @@ router.post(
|
|||
oauthUrl,
|
||||
serverName,
|
||||
oauthRequired,
|
||||
failureReason,
|
||||
missingUserVars,
|
||||
connectionDeferred,
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ const { createOboTrustChecker } = require('~/server/services/OboPolicyService');
|
|||
const { updateMCPServerTools } = require('~/server/services/Config');
|
||||
const { getLogStores } = require('~/cache');
|
||||
|
||||
const MCP_REINITIALIZE_FAILURE_REASONS = {
|
||||
UNREACHABLE: 'unreachable',
|
||||
MISSING_CUSTOM_USER_VARS: 'missing_custom_user_vars',
|
||||
OAUTH_REQUIRED: 'oauth_required',
|
||||
INITIALIZATION_FAILED: 'initialization_failed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Reinitializes an MCP server connection and discovers available tools.
|
||||
* When OAuth is required, uses discovery mode to list tools without full authentication
|
||||
|
|
@ -72,6 +79,7 @@ async function reinitMCPServer({
|
|||
availableTools: null,
|
||||
success: false,
|
||||
message: `MCP server '${serverName}' is still unreachable`,
|
||||
failureReason: MCP_REINITIALIZE_FAILURE_REASONS.UNREACHABLE,
|
||||
oauthRequired: false,
|
||||
serverName,
|
||||
oauthUrl: null,
|
||||
|
|
@ -94,6 +102,7 @@ async function reinitMCPServer({
|
|||
availableTools: null,
|
||||
success: false,
|
||||
message: `MCP server '${serverName}' is still unreachable`,
|
||||
failureReason: MCP_REINITIALIZE_FAILURE_REASONS.UNREACHABLE,
|
||||
oauthRequired: false,
|
||||
serverName,
|
||||
oauthUrl: null,
|
||||
|
|
@ -118,6 +127,8 @@ async function reinitMCPServer({
|
|||
message: `MCP server '${serverName}' requires user-provided variable(s) [${missingUserVars.join(
|
||||
', ',
|
||||
)}] which are not set`,
|
||||
failureReason: MCP_REINITIALIZE_FAILURE_REASONS.MISSING_CUSTOM_USER_VARS,
|
||||
missingUserVars,
|
||||
oauthRequired: false,
|
||||
serverName,
|
||||
oauthUrl: null,
|
||||
|
|
@ -270,14 +281,20 @@ async function reinitMCPServer({
|
|||
return `Failed to reinitialize MCP server '${serverName}'`;
|
||||
};
|
||||
|
||||
const success = Boolean(
|
||||
(connection && !oauthRequired) || (oauthRequired && oauthUrl) || (tools && tools.length > 0),
|
||||
);
|
||||
let failureReason;
|
||||
if (!success) {
|
||||
failureReason = oauthRequired
|
||||
? MCP_REINITIALIZE_FAILURE_REASONS.OAUTH_REQUIRED
|
||||
: MCP_REINITIALIZE_FAILURE_REASONS.INITIALIZATION_FAILED;
|
||||
}
|
||||
const result = {
|
||||
availableTools,
|
||||
success: Boolean(
|
||||
(connection && !oauthRequired) ||
|
||||
(oauthRequired && oauthUrl) ||
|
||||
(tools && tools.length > 0),
|
||||
),
|
||||
success,
|
||||
message: getResponseMessage(),
|
||||
failureReason,
|
||||
oauthRequired,
|
||||
serverName,
|
||||
oauthUrl,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ describe('reinitMCPServer — customUserVars gating (issue #10969)', () => {
|
|||
availableTools: null,
|
||||
success: false,
|
||||
tools: null,
|
||||
failureReason: 'missing_custom_user_vars',
|
||||
missingUserVars: ['THINGY_TOKEN'],
|
||||
oauthRequired: false,
|
||||
serverName,
|
||||
});
|
||||
|
|
@ -125,7 +127,7 @@ describe('reinitMCPServer — customUserVars gating (issue #10969)', () => {
|
|||
mockDiscoverServerTools.mockResolvedValue({ tools: [], oauthRequired: true, oauthUrl: null });
|
||||
const requestBody = { conversationId: 'conv-456', messageId: 'msg-456' };
|
||||
|
||||
await reinitMCPServer({
|
||||
const result = await reinitMCPServer({
|
||||
user,
|
||||
serverName,
|
||||
serverConfig: { type: 'streamable-http', url: 'https://thingy.example.com/mcp' },
|
||||
|
|
@ -133,6 +135,12 @@ describe('reinitMCPServer — customUserVars gating (issue #10969)', () => {
|
|||
userMCPAuthMap: undefined,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
success: false,
|
||||
failureReason: 'oauth_required',
|
||||
oauthRequired: true,
|
||||
oauthUrl: null,
|
||||
});
|
||||
expect(mockDiscoverServerTools).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
requestBody,
|
||||
|
|
@ -280,6 +288,7 @@ describe('reinitMCPServer — runtime BODY placeholder pre-check (issue #14074)'
|
|||
|
||||
expect(mockDiscoverServerTools).not.toHaveBeenCalled();
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.failureReason).toBe('initialization_failed');
|
||||
expect(result.message).toBe(`Failed to reinitialize MCP server '${serverName}'`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue