From 0266b4871e4d342b4d80cd50ad5755da0f09c4a7 Mon Sep 17 00:00:00 2001 From: Jerome Date: Thu, 12 Jun 2025 14:23:51 +0100 Subject: [PATCH] Fix server name normalization for tool routing compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem MCP server names with invalid characters (spaces, special chars, etc.) caused tool routing failures when prefixed to tool names, as OpenRouter API requires tool names to match pattern ^[a-zA-Z0-9_-]{1,64}$ ## Solution - **Added normalizeServerName utility**: Converts invalid characters to underscores - **Centralized normalization logic**: Shared utility prevents code duplication - **Fixed tool routing**: Agent loop now matches normalized server names correctly - **Updated tool discovery**: MCP tools use normalized prefixes consistently ## Examples - "My Weather Service\!" → "My_Weather_Service" - "Test@Server#123" → "Test_Server_123" - "Server...with...dots" → "Server_with_dots" - " spaces_and__double__underscores " → "spaces_and_double_underscores" ## Technical Details - Created /src/utils/mcpUtils.ts for shared normalization function - Updated MCPConnectionManager to use normalized prefixes in tool names - Fixed agent loop to match connections using normalized names - Maintains original server names in UI while using normalized names for API 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/hooks/useAgentLoop.ts | 13 ++++++++----- src/mcp/connection.ts | 9 ++++++--- src/providers/openrouter/client.ts | 4 ---- src/utils/mcpUtils.ts | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 src/utils/mcpUtils.ts diff --git a/src/hooks/useAgentLoop.ts b/src/hooks/useAgentLoop.ts index 63e9e38..61b4ef6 100644 --- a/src/hooks/useAgentLoop.ts +++ b/src/hooks/useAgentLoop.ts @@ -15,6 +15,7 @@ import type { import type { ChatMessage, Tool, ToolCall, InferenceRequest } from '@/types/inference'; import { useInference } from '@/contexts/InferenceContext'; import { useMCP } from '@/contexts/MCPContext'; +import { normalizeServerName } from '@/utils/mcpUtils'; // Test tools that work alongside MCP tools const testTools: TestTool[] = [ @@ -226,17 +227,19 @@ export function useAgentLoop(config: Partial = {}): UseAgentLoo // Check if it's an MCP tool (prefixed with server name using double underscore) if (toolCall.function.name.includes('__')) { - // Extract server name from tool name (format: "server__tool_name") - const [serverName] = toolCall.function.name.split('__'); + // Extract normalized server name from tool name (format: "normalized_server__tool_name") + const [normalizedServerName] = toolCall.function.name.split('__'); - // Find the connection ID for this server name - const connection = connections.find(conn => conn.name === serverName); + // Find the connection ID by comparing normalized server names + const connection = connections.find(conn => { + return normalizeServerName(conn.name) === normalizedServerName; + }); if (connection) { const result = await callMCPTool(connection.id, toolCall.function.name, toolCall.function.arguments); return { result }; } else { - throw new Error(`MCP server "${serverName}" not found or not connected`); + throw new Error(`MCP server with normalized name "${normalizedServerName}" not found or not connected`); } } diff --git a/src/mcp/connection.ts b/src/mcp/connection.ts index 92ca4a9..1b13ad0 100644 --- a/src/mcp/connection.ts +++ b/src/mcp/connection.ts @@ -17,6 +17,7 @@ import type { MCPError, } from '@/types/mcp'; import type { Tool } from '@/types/inference'; +import { normalizeServerName } from '@/utils/mcpUtils'; interface MCPOAuthState { codeVerifier: string; @@ -587,10 +588,11 @@ export class MCPConnectionManager { // Transform MCP tools to our Tool interface with name prefixing // Use double underscore instead of dot to comply with OpenRouter API requirements + const normalizedServerName = normalizeServerName(this.connection.name); return result.tools.map(tool => ({ type: 'function' as const, function: { - name: `${this.connection.name}__${tool.name}`, + name: `${normalizedServerName}__${tool.name}`, description: `[${this.connection.name}] ${tool.description || ''}`, parameters: tool.inputSchema || {}, }, @@ -643,8 +645,9 @@ export class MCPConnectionManager { } // Remove the server prefix from the tool name (using double underscore separator) - const unprefixedName = toolName.startsWith(`${this.connection.name}__`) - ? toolName.slice(this.connection.name.length + 2) + const normalizedServerName = normalizeServerName(this.connection.name); + const unprefixedName = toolName.startsWith(`${normalizedServerName}__`) + ? toolName.slice(normalizedServerName.length + 2) : toolName; try { diff --git a/src/providers/openrouter/client.ts b/src/providers/openrouter/client.ts index f26fc0f..2243cbd 100644 --- a/src/providers/openrouter/client.ts +++ b/src/providers/openrouter/client.ts @@ -126,10 +126,6 @@ export class OpenRouterClient { const inputCost = parseFloat(openRouterModel.pricing.prompt); const outputCost = parseFloat(openRouterModel.pricing.completion); - // Check if model supports tools by looking at supported_parameters - const supportsTools = openRouterModel.supported_parameters?.includes('tools') || false; - console.log(`Model ${openRouterModel.id} supports tools: ${supportsTools}`); - return { id: openRouterModel.id, name: openRouterModel.name, diff --git a/src/utils/mcpUtils.ts b/src/utils/mcpUtils.ts new file mode 100644 index 0000000..eebc103 --- /dev/null +++ b/src/utils/mcpUtils.ts @@ -0,0 +1,14 @@ +// Utility functions for MCP server name normalization + +/** + * Normalize server name to comply with OpenRouter API tool naming requirements + * Pattern: ^[a-zA-Z0-9_-]{1,64}$ + */ +export function normalizeServerName(name: string): string { + return name + .replace(/[^a-zA-Z0-9_-]/g, '_') // Replace invalid characters with underscore + .replace(/_{2,}/g, '_') // Replace multiple underscores with single + .replace(/^_+|_+$/g, '') // Remove leading/trailing underscores + .substring(0, 32) // Limit length to leave room for tool name + || 'server'; // Fallback if name becomes empty +} \ No newline at end of file