Skip to main content
Glama
index.ts6.92 kB
/** * Main entry point for tool handlers - maintains backward compatibility */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema, CallToolRequest, CallToolResult, } from '@modelcontextprotocol/sdk/types.js'; import { warn, clearLogContext, getLogContext, OperationType, } from '@/utils/logger.js'; import { ServerContext } from '@/server/createServer.js'; import { setGlobalContext } from '@/api/lazy-client.js'; // Import from modular components import { TOOL_DEFINITIONS } from '@/handlers/tools/registry.js'; import { getToolsListPayload } from '@/utils/mcp-discovery.js'; import { executeToolRequest } from '@/handlers/tools/dispatcher.js'; import { initializeToolContext } from '@/handlers/tools/dispatcher/logging.js'; import { createSecureToolErrorResult } from '@/utils/secure-error-handler.js'; // Constants for configuration const DEBUG_ENV_VAR = 'MCP_DEBUG_REQUESTS'; const MAX_ARGUMENT_SIZE = 1024 * 1024; // 1MB limit for arguments /** * Extended type to handle requests with loose arguments */ interface LooseCallToolRequest extends Omit<CallToolRequest, 'params'> { params: { name: string; arguments?: Record<string, unknown>; [key: string]: unknown; // Allow additional properties }; } /** * Normalize tool request to handle missing arguments wrapper * Fixes Issue #344: MCP protocol mismatch where arguments are not wrapped * * This function handles two cases: * 1. Standard format: { name: "tool", arguments: { query: "value" } } * 2. Loose format: { name: "tool", query: "value" } * * @param request - The incoming tool request (may have loose arguments) * @returns Normalized request with proper arguments structure */ function normalizeToolRequest( request: CallToolRequest | LooseCallToolRequest ): CallToolRequest { // Validate request structure if (!request.params || typeof request.params.name !== 'string') { throw new Error('Invalid tool request: missing params or tool name'); } // Type guard to check if arguments are already properly wrapped if ('arguments' in request.params && request.params.arguments !== undefined) { // Validate argument size to prevent DoS const argSize = JSON.stringify(request.params.arguments).length; if (argSize > MAX_ARGUMENT_SIZE) { throw new Error( `Tool arguments too large: ${argSize} bytes (max: ${MAX_ARGUMENT_SIZE})` ); } // Return clean request with only name and arguments // This ensures no extra parameters pollute the request return { ...request, params: { name: request.params.name, arguments: request.params.arguments, }, }; } // Handle loose arguments format const params = request.params as LooseCallToolRequest['params']; const { name, ...potentialArgs } = params; // If there are additional params beyond 'name', treat them as arguments const hasAdditionalParams = Object.keys(potentialArgs).length > 0; if (hasAdditionalParams) { // Validate argument size const argSize = JSON.stringify(potentialArgs).length; if (argSize > MAX_ARGUMENT_SIZE) { throw new Error( `Tool arguments too large: ${argSize} bytes (max: ${MAX_ARGUMENT_SIZE})` ); } // Use structured logging instead of console.error if (process.env[DEBUG_ENV_VAR] === 'true') { warn( 'tool:normalization', 'Normalizing loose arguments format (Issue #344)', { tool: name, originalParamKeys: Object.keys(request.params), normalizedArgKeys: Object.keys(potentialArgs), argumentCount: Object.keys(potentialArgs).length, }, 'normalization' ); } // Create normalized request with wrapped arguments return { ...request, params: { name, arguments: potentialArgs, }, }; } // No additional params, tool might not need arguments return { ...request, params: { name: request.params.name, arguments: undefined, }, }; } /** * Registers tool-related request handlers with the server * * @param server - The MCP server instance * @param context - Server context with configuration */ export function registerToolHandlers( server: Server, context?: ServerContext ): void { // Set the global context for lazy initialization if provided if (context) { setGlobalContext(context); } // Handler for listing available tools server.setRequestHandler(ListToolsRequestSchema, async () => { const { tools } = getToolsListPayload(); return { tools, }; }); // Handler for calling tools server.setRequestHandler( CallToolRequestSchema, async (request): Promise<CallToolResult> => { const toolName = typeof request.params?.name === 'string' ? request.params.name : 'unknown_tool'; const correlationId = initializeToolContext(toolName); try { // Normalize request to handle missing arguments wrapper (Issue #344) // Cast is safe because we're handling the protocol mismatch const normalizedRequest = normalizeToolRequest( request as CallToolRequest | LooseCallToolRequest ); const result = (await executeToolRequest( normalizedRequest )) as CallToolResult; return result; } catch (error: unknown) { warn( 'tool:normalization', 'Tool request failed before execution', { tool: toolName, correlationId, errorMessage: error instanceof Error ? error.message : String(error), }, 'call_tool', OperationType.TOOL_EXECUTION ); const { requestId, userId } = getLogContext(); return createSecureToolErrorResult(error, { module: 'handlers.tools', operation: `callTool:${toolName}`, resourceType: toolName, correlationId, requestId, userId, errorType: 'normalization_error', clientMessage: error instanceof Error ? error.message : 'Tool request normalization failed', fallbackMessage: 'Tool request normalization failed', suggestion: 'Ensure the MCP request includes a tool name and wraps arguments inside the "arguments" object.', }); } finally { clearLogContext(); } } ); } // Re-export commonly used components for backward compatibility export { TOOL_DEFINITIONS, TOOL_CONFIGS } from './registry.js'; export { findToolConfig } from './registry.js'; export { executeToolRequest } from './dispatcher.js'; export { formatSearchResults, formatRecordDetails, formatListEntries, formatBatchResults, formatResponse, } from './formatters.js';

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server