Skip to main content
Glama
tool-metadata.ts8.51 kB
import { z } from "zod"; import { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js"; import { ConnectorManager } from "../connectors/manager.js"; import { normalizeSourceId } from "./normalize-id.js"; import { executeSqlSchema } from "../tools/execute-sql.js"; import { getToolRegistry } from "../tools/registry.js"; import type { ParameterConfig, ToolConfig } from "../types/config.js"; /** * Tool parameter definition for API responses */ export interface ToolParameter { name: string; type: string; required: boolean; description: string; } /** * Tool metadata for API responses */ export interface Tool { name: string; description: string; parameters: ToolParameter[]; } /** * Tool metadata with Zod schema (used internally for registration) */ export interface ToolMetadata { name: string; description: string; schema: Record<string, z.ZodType<any>>; annotations: ToolAnnotations; } /** * Convert a Zod schema object to simplified parameter list * @param schema - Zod schema object (e.g., { sql: z.string().describe("...") }) * @returns Array of tool parameters */ export function zodToParameters(schema: Record<string, z.ZodType<any>>): ToolParameter[] { const parameters: ToolParameter[] = []; for (const [key, zodType] of Object.entries(schema)) { // Extract description from Zod schema const description = zodType.description || ""; // Determine if required (Zod types are required by default unless optional) const required = !(zodType instanceof z.ZodOptional); // Determine type from Zod type let type = "string"; // default if (zodType instanceof z.ZodString) { type = "string"; } else if (zodType instanceof z.ZodNumber) { type = "number"; } else if (zodType instanceof z.ZodBoolean) { type = "boolean"; } else if (zodType instanceof z.ZodArray) { type = "array"; } else if (zodType instanceof z.ZodObject) { type = "object"; } parameters.push({ name: key, type, required, description, }); } return parameters; } /** * Get execute_sql tool metadata for a specific source * @param sourceId - The source ID to get tool metadata for * @returns Tool metadata with name, description, and Zod schema */ export function getExecuteSqlMetadata(sourceId: string): ToolMetadata { const sourceIds = ConnectorManager.getAvailableSourceIds(); const sourceConfig = ConnectorManager.getSourceConfig(sourceId)!; const executeOptions = ConnectorManager.getCurrentExecuteOptions(sourceId); const dbType = sourceConfig.type; const isSingleSource = sourceIds.length === 1; // Determine tool name based on single vs multi-source configuration const toolName = isSingleSource ? "execute_sql" : `execute_sql_${normalizeSourceId(sourceId)}`; // Determine title (human-readable display name) const title = isSingleSource ? `Execute SQL (${dbType})` : `Execute SQL on ${sourceId} (${dbType})`; // Determine description with more context const readonlyNote = executeOptions.readonly ? " [READ-ONLY MODE]" : ""; const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows)` : ""; const description = isSingleSource ? `Execute SQL queries on the ${dbType} database${readonlyNote}${maxRowsNote}` : `Execute SQL queries on the '${sourceId}' ${dbType} database${readonlyNote}${maxRowsNote}`; // Build annotations object with all standard MCP hints const isReadonly = executeOptions.readonly === true; const annotations = { title, readOnlyHint: isReadonly, destructiveHint: !isReadonly, // Can be destructive if not readonly // In readonly mode, queries are more predictable (though still not strictly idempotent due to data changes) // In write mode, queries are definitely not idempotent idempotentHint: false, // Database operations are always against internal/closed systems, not open-world openWorldHint: false, }; return { name: toolName, description, schema: executeSqlSchema, annotations, }; } /** * Get search_objects tool metadata for a specific source * @param sourceId - The source ID to get tool metadata for * @returns Tool name, description, and annotations */ export function getSearchObjectsMetadata(sourceId: string): { name: string; description: string; title: string } { const sourceIds = ConnectorManager.getAvailableSourceIds(); const sourceConfig = ConnectorManager.getSourceConfig(sourceId)!; const dbType = sourceConfig.type; const isSingleSource = sourceIds.length === 1; const toolName = isSingleSource ? "search_objects" : `search_objects_${normalizeSourceId(sourceId)}`; const title = isSingleSource ? `Search Database Objects (${dbType})` : `Search Database Objects on ${sourceId} (${dbType})`; const description = isSingleSource ? `Search and list database objects (schemas, tables, columns, procedures, indexes) on the ${dbType} database` : `Search and list database objects (schemas, tables, columns, procedures, indexes) on the '${sourceId}' ${dbType} database`; return { name: toolName, description, title, }; } /** * Convert custom tool parameter configs to Tool parameter format * @param params - Parameter configurations from custom tool * @returns Array of tool parameters */ function customParamsToToolParams(params: ParameterConfig[] | undefined): ToolParameter[] { if (!params || params.length === 0) { return []; } return params.map((param) => ({ name: param.name, type: param.type, required: param.required !== false && param.default === undefined, description: param.description, })); } /** * Build execute_sql tool metadata for API response */ function buildExecuteSqlTool(sourceId: string): Tool { const executeSqlMetadata = getExecuteSqlMetadata(sourceId); const executeSqlParameters = zodToParameters(executeSqlMetadata.schema); return { name: executeSqlMetadata.name, description: executeSqlMetadata.description, parameters: executeSqlParameters, }; } /** * Build search_objects tool metadata for API response */ function buildSearchObjectsTool(sourceId: string): Tool { const searchMetadata = getSearchObjectsMetadata(sourceId); return { name: searchMetadata.name, description: searchMetadata.description, parameters: [ { name: "object_type", type: "string", required: true, description: "Object type to search", }, { name: "pattern", type: "string", required: false, description: "LIKE pattern (% = any chars, _ = one char). Default: %", }, { name: "schema", type: "string", required: false, description: "Filter to schema", }, { name: "table", type: "string", required: false, description: "Filter to table (requires schema; column/index only)", }, { name: "detail_level", type: "string", required: false, description: "Detail: names (minimal), summary (metadata), full (all)", }, { name: "limit", type: "integer", required: false, description: "Max results (default: 100, max: 1000)", }, ], }; } /** * Build custom tool metadata for API response */ function buildCustomTool(toolConfig: ToolConfig): Tool { return { name: toolConfig.name, description: toolConfig.description!, parameters: customParamsToToolParams(toolConfig.parameters), }; } /** * Get tools for a specific source (API response format) * Only includes tools that are actually enabled in the ToolRegistry * @param sourceId - The source ID to get tools for * @returns Array of enabled tools with simplified parameters */ export function getToolsForSource(sourceId: string): Tool[] { // Get enabled tools from registry const registry = getToolRegistry(); const enabledToolConfigs = registry.getEnabledToolConfigs(sourceId); // Uniform iteration: map each enabled tool config to its API representation return enabledToolConfigs.map((toolConfig) => { // Dispatch based on tool name if (toolConfig.name === "execute_sql") { return buildExecuteSqlTool(sourceId); } else if (toolConfig.name === "search_objects") { return buildSearchObjectsTool(sourceId); } else { // Custom tool return buildCustomTool(toolConfig); } }); }

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