This commit is contained in:
Gopal Bagaswar 2026-05-13 03:01:31 +08:00 committed by GitHub
commit 2b2a7fb99f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 0 deletions

View file

@ -3,6 +3,16 @@ import { createMCPToolCacheService } from './tools';
import type { LCAvailableTools } from './types';
import type { MCPToolInput, MCPToolCacheDeps } from './tools';
jest.mock('@librechat/data-schemas', () => ({
...jest.requireActual('@librechat/data-schemas'),
logger: {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
}));
function createMockDeps(overrides: Partial<MCPToolCacheDeps> = {}): MCPToolCacheDeps {
return {
getCachedTools: jest.fn().mockResolvedValue(null),
@ -68,6 +78,49 @@ describe('createMCPToolCacheService', () => {
updateMCPServerTools({ userId: 'u1', serverName: 'srv', tools }),
).rejects.toThrow('Redis down');
});
it('warns when the composed tool name exceeds the OpenAI 64-char limit', async () => {
const { logger } = jest.requireMock('@librechat/data-schemas') as {
logger: { warn: jest.Mock; debug: jest.Mock; error: jest.Mock };
};
logger.warn.mockClear();
const deps = createMockDeps();
const { updateMCPServerTools } = createMCPToolCacheService(deps);
// 25 + 5 (delimiter "_mcp_") + 40 = 70 > 64
const longServerName = 'my-very-long-mcp-server-1';
const longToolName = 'execute_extremely_descriptive_action_name';
const tools: MCPToolInput[] = [{ name: longToolName }];
await updateMCPServerTools({
userId: 'u1',
serverName: longServerName,
tools,
});
expect(logger.warn).toHaveBeenCalledTimes(1);
const message = logger.warn.mock.calls[0][0] as string;
expect(message).toContain(longServerName);
expect(message).toContain(longToolName);
expect(message).toContain('exceeds');
expect(message).toContain('64');
});
it('does not warn for tool names within the OpenAI 64-char limit', async () => {
const { logger } = jest.requireMock('@librechat/data-schemas') as {
logger: { warn: jest.Mock; debug: jest.Mock; error: jest.Mock };
};
logger.warn.mockClear();
const deps = createMockDeps();
const { updateMCPServerTools } = createMCPToolCacheService(deps);
const tools: MCPToolInput[] = [{ name: 'search' }];
await updateMCPServerTools({ userId: 'u1', serverName: 'brave', tools });
expect(logger.warn).not.toHaveBeenCalled();
});
});
describe('mergeAppTools', () => {

View file

@ -3,6 +3,18 @@ import { Constants } from 'librechat-data-provider';
import type { JsonSchemaType } from '@librechat/agents';
import type { LCAvailableTools, LCFunctionTool } from './types';
/**
* Maximum allowed length for tool function names accepted by OpenAI-compatible
* Chat Completions / Responses APIs. Tool calls whose names exceed this limit
* are rejected with HTTP 400 ("string too long. Expected ... maximum length 64").
* MCP tool names are constructed as `${toolName}${mcp_delimiter}${serverName}`,
* so callers must keep the combined length within this bound.
*
* Refs: https://platform.openai.com/docs/api-reference/chat/create
* https://github.com/danny-avila/LibreChat/issues/7435
*/
const MAX_OPENAI_TOOL_NAME_LENGTH = 64;
export interface MCPToolInput {
name: string;
description?: string;
@ -40,6 +52,16 @@ export function createMCPToolCacheService(deps: MCPToolCacheDeps) {
for (const tool of tools) {
const name = `${tool.name}${mcpDelimiter}${serverName}`;
if (name.length > MAX_OPENAI_TOOL_NAME_LENGTH) {
logger.warn(
`[MCP Cache] Tool name "${name}" (${name.length} chars) exceeds the ` +
`${MAX_OPENAI_TOOL_NAME_LENGTH}-char limit enforced by OpenAI-compatible APIs ` +
`(server: "${serverName}", tool: "${tool.name}", delimiter: "${mcpDelimiter}"). ` +
`Calls including this tool will be rejected with HTTP 400. ` +
`Shorten the MCP server name or the tool name to fit within ` +
`${MAX_OPENAI_TOOL_NAME_LENGTH - mcpDelimiter.length} characters combined.`,
);
}
const entry: LCFunctionTool = {
type: 'function',
['function']: {