mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
✨ feat: Add Normalization for MCP Server Names (#7421)
This commit is contained in:
parent
353adceb0c
commit
26780bddf0
4 changed files with 63 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
const { z } = require('zod');
|
const { z } = require('zod');
|
||||||
const { tool } = require('@langchain/core/tools');
|
const { tool } = require('@langchain/core/tools');
|
||||||
|
const { normalizeServerName } = require('librechat-mcp');
|
||||||
const { Constants: AgentConstants, Providers } = require('@librechat/agents');
|
const { Constants: AgentConstants, Providers } = require('@librechat/agents');
|
||||||
const {
|
const {
|
||||||
Constants,
|
Constants,
|
||||||
|
|
@ -38,6 +39,7 @@ async function createMCPTool({ req, toolKey, provider: _provider }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter);
|
const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter);
|
||||||
|
const normalizedToolKey = `${toolName}${Constants.mcp_delimiter}${normalizeServerName(serverName)}`;
|
||||||
|
|
||||||
if (!req.user?.id) {
|
if (!req.user?.id) {
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|
@ -83,7 +85,7 @@ async function createMCPTool({ req, toolKey, provider: _provider }) {
|
||||||
|
|
||||||
const toolInstance = tool(_call, {
|
const toolInstance = tool(_call, {
|
||||||
schema,
|
schema,
|
||||||
name: toolKey,
|
name: normalizedToolKey,
|
||||||
description: description || '',
|
description: description || '',
|
||||||
responseFormat: AgentConstants.CONTENT_AND_ARTIFACT,
|
responseFormat: AgentConstants.CONTENT_AND_ARTIFACT,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
/* MCP */
|
/* MCP */
|
||||||
export * from './manager';
|
export * from './manager';
|
||||||
|
/* Utilities */
|
||||||
|
export * from './utils';
|
||||||
/* Flow */
|
/* Flow */
|
||||||
export * from './flow/manager';
|
export * from './flow/manager';
|
||||||
/* types */
|
/* types */
|
||||||
|
|
|
||||||
28
packages/mcp/src/utils.test.ts
Normal file
28
packages/mcp/src/utils.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { normalizeServerName } from './utils';
|
||||||
|
|
||||||
|
describe('normalizeServerName', () => {
|
||||||
|
it('should not modify server names that already match the pattern', () => {
|
||||||
|
const result = normalizeServerName('valid-server_name.123');
|
||||||
|
expect(result).toBe('valid-server_name.123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize server names with non-ASCII characters', () => {
|
||||||
|
const result = normalizeServerName('我的服务');
|
||||||
|
// Should generate a fallback name with a hash
|
||||||
|
expect(result).toMatch(/^server_\d+$/);
|
||||||
|
expect(result).toMatch(/^[a-zA-Z0-9_.-]+$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize server names with special characters', () => {
|
||||||
|
const result = normalizeServerName('server@name!');
|
||||||
|
// The actual result doesn't have the trailing underscore after trimming
|
||||||
|
expect(result).toBe('server_name');
|
||||||
|
expect(result).toMatch(/^[a-zA-Z0-9_.-]+$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trim leading and trailing underscores', () => {
|
||||||
|
const result = normalizeServerName('!server-name!');
|
||||||
|
expect(result).toBe('server-name');
|
||||||
|
expect(result).toMatch(/^[a-zA-Z0-9_.-]+$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
30
packages/mcp/src/utils.ts
Normal file
30
packages/mcp/src/utils.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Normalizes a server name to match the pattern ^[a-zA-Z0-9_.-]+$
|
||||||
|
* This is required for Azure OpenAI models with Tool Calling
|
||||||
|
*/
|
||||||
|
export function normalizeServerName(serverName: string): string {
|
||||||
|
// Check if the server name already matches the pattern
|
||||||
|
if (/^[a-zA-Z0-9_.-]+$/.test(serverName)) {
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Replace non-matching characters with underscores.
|
||||||
|
This preserves the general structure while ensuring compatibility.
|
||||||
|
Trims leading/trailing underscores
|
||||||
|
*/
|
||||||
|
const normalized = serverName.replace(/[^a-zA-Z0-9_.-]/g, '_').replace(/^_+|_+$/g, '');
|
||||||
|
|
||||||
|
// If the result is empty (e.g., all characters were non-ASCII and got trimmed),
|
||||||
|
// generate a fallback name to ensure we always have a valid function name
|
||||||
|
if (!normalized) {
|
||||||
|
/** Hash of the original name to ensure uniqueness */
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < serverName.length; i++) {
|
||||||
|
hash = (hash << 5) - hash + serverName.charCodeAt(i);
|
||||||
|
hash |= 0; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return `server_${Math.abs(hash)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue