Skip to main content
Glama
rest-registry.ts6.58 kB
/** * REST API Tool Registry * * Central registry for all MCP tools exposed via REST API. * Manages tool metadata, schema conversion, and tool discovery capabilities. */ import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { Logger } from '../core/error-handling'; /** * Tool metadata stored in the registry */ export interface ToolMetadata { name: string; description: string; inputSchema: Record<string, z.ZodSchema>; handler: (...args: any[]) => Promise<any>; category?: string; tags?: string[]; } /** * JSON Schema representation converted from Zod */ export interface JsonSchema { type?: string; properties?: Record<string, any>; required?: string[]; description?: string; $ref?: string; definitions?: Record<string, any>; [key: string]: any; } /** * Tool information exposed via discovery endpoint */ export interface ToolInfo { name: string; description: string; schema: JsonSchema; category?: string; tags?: string[]; } /** * Registry for managing all tools available via REST API */ export class RestToolRegistry { private tools: Map<string, ToolMetadata> = new Map(); private logger: Logger; private schemaCache: Map<string, JsonSchema> = new Map(); constructor(logger: Logger) { this.logger = logger; } /** * Register a tool in the registry */ registerTool(metadata: ToolMetadata): void { this.tools.set(metadata.name, metadata); // Clear schema cache for this tool this.schemaCache.delete(metadata.name); this.logger.debug('Tool registered in REST registry', { name: metadata.name, description: metadata.description, category: metadata.category, tags: metadata.tags }); } /** * Get tool metadata by name */ getTool(name: string): ToolMetadata | undefined { return this.tools.get(name); } /** * Get all registered tool names */ getToolNames(): string[] { return Array.from(this.tools.keys()).sort(); } /** * Get all tools with their discovery information */ getAllTools(): ToolInfo[] { return this.getToolNames().map(name => { const tool = this.tools.get(name)!; return { name: tool.name, description: tool.description, schema: this.convertZodSchemaToJsonSchema(tool.name, tool.inputSchema), category: tool.category, tags: tool.tags }; }); } /** * Check if a tool is registered */ hasTool(name: string): boolean { return this.tools.has(name); } /** * Get the number of registered tools */ getToolCount(): number { return this.tools.size; } /** * Clear all registered tools */ clear(): void { this.tools.clear(); this.schemaCache.clear(); this.logger.debug('REST tool registry cleared'); } /** * Convert Zod schema to JSON Schema using zod-to-json-schema library */ private convertZodSchemaToJsonSchema(toolName: string, zodSchemas: Record<string, z.ZodSchema>): JsonSchema { const cached = this.schemaCache.get(toolName); if (cached) { return cached; } try { // Create a Zod object schema from the individual field schemas const zodObjectSchema = z.object(zodSchemas); // Convert to JSON Schema using OpenAPI3 conventions, inlining all subschemas // Type cast needed for Zod v4 compatibility with zod-to-json-schema const jsonSchema = zodToJsonSchema(zodObjectSchema as any, { name: `${toolName}Request`, target: 'openApi3', // Place definitions where OpenAPI expects them definitionPath: 'components.schemas', $refStrategy: 'none' // inline sub-schemas to avoid unresolved refs }); let result = jsonSchema as JsonSchema; // Extract the actual schema from components.schemas if it's using $ref if (result.$ref && (result as any)['components.schemas']) { const refKey = result.$ref.replace('#/components.schemas/', ''); if ((result as any)['components.schemas'][refKey]) { result = (result as any)['components.schemas'][refKey] as JsonSchema; } } this.schemaCache.set(toolName, result); this.logger.debug('Converted Zod schema to JSON Schema', { toolName, schemaKeys: Object.keys(zodSchemas), resultType: result.type }); return result; } catch (error) { this.logger.error('Failed to convert Zod schema to JSON Schema', error instanceof Error ? error : new Error(String(error)), { toolName, errorMessage: error instanceof Error ? error.message : String(error) }); // Fallback to a basic schema const fallbackSchema: JsonSchema = { type: 'object', description: `Schema conversion failed for ${toolName} - using fallback`, properties: {}, additionalProperties: true }; return fallbackSchema; } } /** * Get tool discovery information with filtering */ getToolsFiltered(options: { category?: string; tag?: string; search?: string; } = {}): ToolInfo[] { let tools = this.getAllTools(); if (options.category) { tools = tools.filter(tool => tool.category === options.category); } if (options.tag) { tools = tools.filter(tool => tool.tags?.includes(options.tag!)); } if (options.search) { const searchLower = options.search.toLowerCase(); tools = tools.filter(tool => tool.name.toLowerCase().includes(searchLower) || tool.description.toLowerCase().includes(searchLower) ); } return tools; } /** * Get all unique categories */ getCategories(): string[] { const categories = new Set<string>(); for (const tool of this.tools.values()) { if (tool.category) { categories.add(tool.category); } } return Array.from(categories).sort(); } /** * Get all unique tags */ getTags(): string[] { const tags = new Set<string>(); for (const tool of this.tools.values()) { if (tool.tags) { tool.tags.forEach(tag => tags.add(tag)); } } return Array.from(tags).sort(); } /** * Get registry statistics */ getStats(): { totalTools: number; categories: string[]; tags: string[]; cacheSize: number; } { return { totalTools: this.tools.size, categories: this.getCategories(), tags: this.getTags(), cacheSize: this.schemaCache.size }; } }

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/vfarcic/dot-ai'

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