Skip to main content
Glama
custom-tool-registry.ts6.63 kB
/** * Custom Tool Registry * Loads, validates, and provides access to custom tool definitions from TOML config */ import { ToolConfig } from "../types/config.js"; import { ConnectorManager } from "../connectors/manager.js"; import { validateParameters } from "../utils/parameter-mapper.js"; /** * Global registry of custom tools loaded from TOML configuration */ class CustomToolRegistry { private tools: ToolConfig[] = []; private initialized = false; /** * Initialize the registry with tool definitions from TOML * @param toolConfigs Tool definitions from TOML config * @throws Error if validation fails */ public initialize(toolConfigs: ToolConfig[] | undefined): void { if (this.initialized) { throw new Error("CustomToolRegistry already initialized"); } this.tools = []; if (!toolConfigs || toolConfigs.length === 0) { this.initialized = true; return; } // Validate and register each tool for (const toolConfig of toolConfigs) { this.validateAndRegister(toolConfig); } this.initialized = true; } /** * Validate a tool configuration and add it to the registry * @param toolConfig Tool configuration to validate * @throws Error if validation fails */ private validateAndRegister(toolConfig: ToolConfig): void { // 1. Validate required fields if (!toolConfig.name || toolConfig.name.trim() === "") { throw new Error("Tool definition missing required field: name"); } if (!toolConfig.description || toolConfig.description.trim() === "") { throw new Error( `Tool '${toolConfig.name}' missing required field: description` ); } if (!toolConfig.source || toolConfig.source.trim() === "") { throw new Error( `Tool '${toolConfig.name}' missing required field: source` ); } if (!toolConfig.statement || toolConfig.statement.trim() === "") { throw new Error( `Tool '${toolConfig.name}' missing required field: statement` ); } // 2. Validate source exists const availableSources = ConnectorManager.getAvailableSourceIds(); if (!availableSources.includes(toolConfig.source)) { throw new Error( `Tool '${toolConfig.name}' references unknown source '${toolConfig.source}'. ` + `Available sources: ${availableSources.join(", ")}` ); } // 3. Validate tool name doesn't conflict with built-in tools const builtInToolPrefixes = ["execute_sql", "search_objects"]; for (const prefix of builtInToolPrefixes) { if ( toolConfig.name === prefix || toolConfig.name.startsWith(`${prefix}_`) ) { throw new Error( `Tool name '${toolConfig.name}' conflicts with built-in tool naming pattern. ` + `Custom tools cannot use names starting with: ${builtInToolPrefixes.join(", ")}` ); } } // 4. Validate tool name is unique if (this.tools.some((t) => t.name === toolConfig.name)) { throw new Error( `Duplicate tool name '${toolConfig.name}'. Tool names must be unique.` ); } // 5. Validate parameters match SQL statement const sourceConfig = ConnectorManager.getSourceConfig(toolConfig.source); const connectorType = sourceConfig?.type || "postgres"; // Default to postgres if type not specified try { validateParameters( toolConfig.statement, toolConfig.parameters, connectorType ); } catch (error) { throw new Error( `Tool '${toolConfig.name}' validation failed: ${(error as Error).message}` ); } // 6. Validate parameter definitions if (toolConfig.parameters) { for (const param of toolConfig.parameters) { this.validateParameter(toolConfig.name, param); } } // All validations passed - add to registry this.tools.push(toolConfig); } /** * Validate a parameter definition * @param toolName Name of the tool (for error messages) * @param param Parameter configuration to validate * @throws Error if validation fails */ private validateParameter(toolName: string, param: any): void { if (!param.name || param.name.trim() === "") { throw new Error(`Tool '${toolName}' has parameter missing 'name' field`); } if (!param.type) { throw new Error( `Tool '${toolName}', parameter '${param.name}' missing 'type' field` ); } const validTypes = ["string", "integer", "float", "boolean", "array"]; if (!validTypes.includes(param.type)) { throw new Error( `Tool '${toolName}', parameter '${param.name}' has invalid type '${param.type}'. ` + `Valid types: ${validTypes.join(", ")}` ); } if (!param.description || param.description.trim() === "") { throw new Error( `Tool '${toolName}', parameter '${param.name}' missing 'description' field` ); } // Validate allowed_values if present if (param.allowed_values) { if (!Array.isArray(param.allowed_values)) { throw new Error( `Tool '${toolName}', parameter '${param.name}': allowed_values must be an array` ); } if (param.allowed_values.length === 0) { throw new Error( `Tool '${toolName}', parameter '${param.name}': allowed_values cannot be empty` ); } } // Validate that default value is compatible with allowed_values if both present if (param.default !== undefined && param.allowed_values) { if (!param.allowed_values.includes(param.default)) { throw new Error( `Tool '${toolName}', parameter '${param.name}': default value '${param.default}' ` + `is not in allowed_values: ${param.allowed_values.join(", ")}` ); } } } /** * Get all registered custom tools * @returns Array of tool configurations */ public getTools(): ToolConfig[] { return [...this.tools]; } /** * Get a specific tool by name * @param name Tool name * @returns Tool configuration or undefined if not found */ public getTool(name: string): ToolConfig | undefined { return this.tools.find((t) => t.name === name); } /** * Check if the registry has been initialized * @returns True if initialized */ public isInitialized(): boolean { return this.initialized; } /** * Reset the registry (primarily for testing) */ public reset(): void { this.tools = []; this.initialized = false; } } // Export singleton instance export const customToolRegistry = new CustomToolRegistry();

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/bytebase/dbhub'

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