mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🧪 test: Guard Run-Scoped MCP Definition Handoff Boundaries
The original ClickHouse breaker storm regressed precisely at field pass-through boundaries that unit tests of each end could not see: initializeAgent dropping mcpAvailableTools from its destructure, and the agent tool context losing it on the way into ON_TOOL_EXECUTE. Add direct guards on both hops: the loadTools result must surface on the initialized agent, and the captured toolExecuteOptions closure must forward it to loadToolsForExecution.
This commit is contained in:
parent
1ca238cae1
commit
0cb6c27c67
2 changed files with 93 additions and 0 deletions
|
|
@ -539,6 +539,48 @@ describe('initializeClient — subagent loading', () => {
|
|||
expect(arg.actionsEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('threads run-scoped MCP tool definitions into ON_TOOL_EXECUTE loading', async () => {
|
||||
/** Regression guard for the request-scoped MCP/PTC handoff: the
|
||||
* `mcpAvailableTools` discovered at run start must survive
|
||||
* `buildAgentToolContext` and reach `loadToolsForExecution`, otherwise
|
||||
* request-scoped servers reinitialize on every programmatic tool call
|
||||
* and can trip the MCP circuit breaker under parallel calls. */
|
||||
const mcpTool = 'list_tables_mcp_ClickHouse';
|
||||
const mcpAvailableTools = {
|
||||
ClickHouse: {
|
||||
[mcpTool]: {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: mcpTool,
|
||||
description: 'List tables',
|
||||
parameters: { type: 'object', properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const primaryConfig = {
|
||||
...makePrimaryConfig({}),
|
||||
toolRegistry: new Map([[mcpTool, { name: mcpTool }]]),
|
||||
mcpAvailableTools,
|
||||
};
|
||||
mockInitializeAgent.mockResolvedValue(primaryConfig);
|
||||
|
||||
await initializeClient({
|
||||
req: makeSubagentReq(),
|
||||
res: {},
|
||||
signal: new AbortController().signal,
|
||||
endpointOption: makeEndpointOption(),
|
||||
});
|
||||
|
||||
expect(capturedToolExecuteOptions?.loadTools).toBeInstanceOf(Function);
|
||||
await capturedToolExecuteOptions.loadTools([mcpTool], PRIMARY_ID);
|
||||
|
||||
expect(mockLoadToolsForExecution).toHaveBeenCalledTimes(1);
|
||||
expect(mockLoadToolsForExecution).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ mcpAvailableTools }),
|
||||
);
|
||||
});
|
||||
|
||||
it('deduplicates repeated ids in subagents.agent_ids', async () => {
|
||||
const subAgent = await createAgent({
|
||||
id: DUPLICATE_SUBAGENT_ID,
|
||||
|
|
|
|||
|
|
@ -2068,3 +2068,54 @@ describe('initializeAgent — code-generated file thread filter (regression)', (
|
|||
expect(getUserCodeFiles).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeAgent — run-scoped MCP tool definitions', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('carries mcpAvailableTools from the loadTools result onto the initialized agent', async () => {
|
||||
/** Regression guard for the request-scoped MCP/PTC handoff: dropping this
|
||||
* field at the destructure boundary forces per-call reinitialization
|
||||
* downstream and can storm the MCP circuit breaker. */
|
||||
const { agent, req, res, loadTools, db } = createMocks();
|
||||
const mcpTool = 'list_tables_mcp_ClickHouse';
|
||||
const mcpAvailableTools = {
|
||||
ClickHouse: {
|
||||
[mcpTool]: {
|
||||
type: 'function' as const,
|
||||
function: {
|
||||
name: mcpTool,
|
||||
description: 'List tables',
|
||||
parameters: { type: 'object' as const, properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
loadTools.mockResolvedValue({
|
||||
tools: [],
|
||||
toolContextMap: {},
|
||||
dynamicToolContextMap: {},
|
||||
userMCPAuthMap: undefined,
|
||||
toolRegistry: undefined,
|
||||
toolDefinitions: [],
|
||||
hasDeferredTools: false,
|
||||
mcpAvailableTools,
|
||||
});
|
||||
|
||||
const result = await initializeAgent(
|
||||
{
|
||||
req,
|
||||
res,
|
||||
agent,
|
||||
loadTools,
|
||||
endpointOption: { endpoint: EModelEndpoint.agents },
|
||||
allowedProviders: new Set([Providers.OPENAI]),
|
||||
isInitialAgent: true,
|
||||
},
|
||||
db,
|
||||
);
|
||||
|
||||
expect(result.mcpAvailableTools).toEqual(mcpAvailableTools);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue