Skip to main content
Glama
cameronsjo

MCP Server Template

by cameronsjo
registry.ts7.4 kB
/** * Tool registry for MCP server * * Provides a centralized registry for tool definitions and handlers. * Handles tool discovery, execution, and error formatting. */ import { z } from 'zod'; import { createLogger } from '../shared/logger.js'; import { formatErrorResponse, McpServerError } from '../shared/errors.js'; import type { ToolHandler, ToolResult, RegisteredTool, ResultMeta } from '../types/index.js'; const logger = createLogger('tools'); /** * Convert Zod schema to JSON Schema for MCP */ function zodToJsonSchema(schema: z.ZodType): Record<string, unknown> { // Handle ZodObject if (schema instanceof z.ZodObject) { const shape = schema.shape as Record<string, z.ZodType>; const properties: Record<string, unknown> = {}; const required: string[] = []; for (const [key, value] of Object.entries(shape)) { properties[key] = zodToJsonSchema(value); // Check if field is required (not optional) if (!(value instanceof z.ZodOptional)) { required.push(key); } } return { type: 'object', properties, required: required.length > 0 ? required : undefined, }; } // Handle ZodOptional if (schema instanceof z.ZodOptional) { return zodToJsonSchema(schema.unwrap()); } // Handle ZodDefault if (schema instanceof z.ZodDefault) { const inner = zodToJsonSchema(schema.removeDefault()); return { ...inner, default: schema._def.defaultValue() }; } // Handle ZodString if (schema instanceof z.ZodString) { const result: Record<string, unknown> = { type: 'string' }; if (schema.description) { result['description'] = schema.description; } return result; } // Handle ZodNumber if (schema instanceof z.ZodNumber) { const result: Record<string, unknown> = { type: 'number' }; if (schema.description) { result['description'] = schema.description; } return result; } // Handle ZodBoolean if (schema instanceof z.ZodBoolean) { const result: Record<string, unknown> = { type: 'boolean' }; if (schema.description) { result['description'] = schema.description; } return result; } // Handle ZodArray if (schema instanceof z.ZodArray) { return { type: 'array', items: zodToJsonSchema(schema.element), }; } // Handle ZodEnum if (schema instanceof z.ZodEnum) { return { type: 'string', enum: schema.options, }; } // Handle ZodLiteral if (schema instanceof z.ZodLiteral) { return { type: typeof schema.value, const: schema.value, }; } // Handle ZodUnion if (schema instanceof z.ZodUnion) { const options = schema.options as z.ZodType[]; return { oneOf: options.map((opt) => zodToJsonSchema(opt)), }; } // Fallback for unknown types return { type: 'object' }; } /** * Tool registry class */ export class ToolRegistry { private readonly tools = new Map<string, RegisteredTool>(); /** * Register a tool with its definition and handler */ register<TInput, TOutput>( name: string, description: string, inputSchema: z.ZodType<TInput>, handler: ToolHandler<TInput, TOutput> ): void { if (this.tools.has(name)) { logger.warning('Overwriting existing tool', { name }); } const tool: RegisteredTool = { definition: { name, description, inputSchema, }, handler: handler as ToolHandler, }; this.tools.set(name, tool); logger.debug('Registered tool', { name }); } /** * Get a tool by name */ get(name: string): RegisteredTool | undefined { return this.tools.get(name); } /** * Check if a tool exists */ has(name: string): boolean { return this.tools.has(name); } /** * Get all tool definitions for MCP ListToolsRequest */ getDefinitions(): Array<{ name: string; description: string; inputSchema: Record<string, unknown> }> { return Array.from(this.tools.values()).map((tool) => ({ name: tool.definition.name, description: tool.definition.description, inputSchema: zodToJsonSchema(tool.definition.inputSchema), })); } /** * Execute a tool by name with input validation */ async execute(name: string, input: unknown): Promise<ToolResult> { const startTime = Date.now(); const tool = this.tools.get(name); if (!tool) { logger.warning('Tool not found', { name }); return { success: false, error: { error: `Tool not found: ${name}`, code: 'NOT_FOUND', retryable: false, }, }; } try { // Validate input against schema const validatedInput = tool.definition.inputSchema.parse(input); logger.debug('Executing tool', { name, input: validatedInput }); // Execute handler const result = await tool.handler(validatedInput); // Add execution metadata const meta: ResultMeta = { durationMs: Date.now() - startTime, cached: false, timestamp: new Date().toISOString(), }; logger.debug('Tool executed successfully', { name, durationMs: meta.durationMs }); return { ...result, meta: { ...result.meta, ...meta }, }; } catch (error) { const durationMs = Date.now() - startTime; // Handle Zod validation errors if (error instanceof z.ZodError) { logger.warning('Tool input validation failed', { name, errors: error.errors }); return { success: false, error: { error: 'Input validation failed', code: 'VALIDATION_ERROR', details: { issues: error.errors }, retryable: false, }, meta: { durationMs, cached: false, timestamp: new Date().toISOString(), }, }; } // Handle MCP server errors if (error instanceof McpServerError) { logger.warning('Tool execution failed', { name, error: error.message }); return { success: false, error: error.toResponse(), meta: { durationMs, cached: false, timestamp: new Date().toISOString(), }, }; } // Handle unknown errors logger.error('Unexpected tool error', { name, error: String(error) }); return { success: false, error: formatErrorResponse(error), meta: { durationMs, cached: false, timestamp: new Date().toISOString(), }, }; } } /** * Get list of registered tool names */ getNames(): string[] { return Array.from(this.tools.keys()); } /** * Get count of registered tools */ get size(): number { return this.tools.size; } /** * Clear all registered tools (for testing) */ clear(): void { this.tools.clear(); logger.debug('Cleared all tools'); } } // Global tool registry singleton let globalRegistry: ToolRegistry | null = null; /** * Get the global tool registry */ export function getToolRegistry(): ToolRegistry { if (!globalRegistry) { globalRegistry = new ToolRegistry(); } return globalRegistry; } /** * Reset the global tool registry (for testing) */ export function resetToolRegistry(): void { globalRegistry = null; }

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/cameronsjo/mcp-server-template'

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