Skip to main content
Glama

OpenAPI MCP Server

tools-manager.ts8.07 kB
import { Tool } from "@modelcontextprotocol/sdk/types.js" import { OpenAPISpecLoader, ExtendedTool } from "./openapi-loader" import { OpenAPIMCPServerConfig } from "./config" import { OpenAPIV3 } from "openapi-types" import { parseToolId as parseToolIdUtil } from "./utils/tool-id.js" /** * Manages the tools available in the MCP server */ export class ToolsManager { private tools: Map<string, Tool> = new Map() private specLoader: OpenAPISpecLoader private loadedSpec?: OpenAPIV3.Document constructor(private config: OpenAPIMCPServerConfig) { // Ensure toolsMode has a default value of 'all' this.config.toolsMode = this.config.toolsMode || "all" this.specLoader = new OpenAPISpecLoader({ disableAbbreviation: this.config.disableAbbreviation, }) } /** * Get the OpenAPI spec loader instance */ getSpecLoader(): OpenAPISpecLoader { return this.specLoader } /** * Get the loaded OpenAPI specification */ getOpenApiSpec(): OpenAPIV3.Document | undefined { return this.loadedSpec } /** * Create dynamic discovery meta-tools */ private createDynamicTools(): Map<string, Tool> { const dynamicTools = new Map<string, Tool>() // LIST-API-ENDPOINTS dynamicTools.set("LIST-API-ENDPOINTS", { name: "list-api-endpoints", description: "List all available API endpoints", inputSchema: { type: "object", properties: {} }, }) // GET-API-ENDPOINT-SCHEMA dynamicTools.set("GET-API-ENDPOINT-SCHEMA", { name: "get-api-endpoint-schema", description: "Get the JSON schema for a specified API endpoint", inputSchema: { type: "object", properties: { endpoint: { type: "string", description: "Endpoint path (e.g. /users/{id})" }, }, required: ["endpoint"], }, }) // INVOKE-API-ENDPOINT dynamicTools.set("INVOKE-API-ENDPOINT", { name: "invoke-api-endpoint", description: "Invoke an API endpoint with provided parameters", inputSchema: { type: "object", properties: { endpoint: { type: "string", description: "Endpoint path to invoke (e.g. /users/{id})" }, method: { type: "string", description: "HTTP method to use (optional, e.g. GET, POST, PUT, DELETE)", enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"], }, params: { type: "object", description: "Parameters for the API call", properties: {}, }, }, required: ["endpoint"], }, }) return dynamicTools } /** * Initialize tools from the OpenAPI specification */ async initialize(): Promise<void> { const spec = await this.specLoader.loadOpenAPISpec( this.config.openApiSpec, this.config.specInputMethod, this.config.inlineSpecContent, ) this.loadedSpec = spec // Store the loaded spec // Determine tools loading mode if (this.config.toolsMode === "dynamic") { // Use dynamic discovery meta-tools this.tools = this.createDynamicTools() return } if (this.config.toolsMode === "explicit") { // Only load tools explicitly listed in includeTools const rawTools = this.specLoader.parseOpenAPISpec(spec) const filtered = new Map<string, Tool>() if (this.config.includeTools && this.config.includeTools.length > 0) { const includeToolsLower = this.config.includeTools.map((t) => t.toLowerCase()) for (const [toolId, tool] of rawTools.entries()) { const toolIdLower = toolId.toLowerCase() const toolNameLower = tool.name.toLowerCase() // Include tool if it matches by ID or name if ( includeToolsLower.includes(toolIdLower) || includeToolsLower.includes(toolNameLower) ) { filtered.set(toolId, tool) } } } // If includeTools is empty or undefined, no tools are loaded in explicit mode this.tools = filtered // Log the registered tools for (const [toolId, tool] of this.tools.entries()) { console.error(`Registered tool: ${toolId} (${tool.name})`) } return } // Load and filter standard tools (for "all" mode) const rawTools = this.specLoader.parseOpenAPISpec(spec) const filtered = new Map<string, Tool>() // Precompute lowercase filter arrays for better performance const includeToolsLower = this.config.includeTools?.map((t) => t.toLowerCase()) || [] const includeOperationsLower = this.config.includeOperations?.map((op) => op.toLowerCase()) || [] const includeResourcesLower = this.config.includeResources?.map((res) => res.toLowerCase()) || [] const includeTagsLower = this.config.includeTags?.map((tag) => tag.toLowerCase()) || [] for (const [toolId, tool] of rawTools.entries()) { const extendedTool = tool as ExtendedTool // includeTools filter - takes highest priority if (includeToolsLower.length > 0) { const toolIdLower = toolId.toLowerCase() const toolNameLower = tool.name.toLowerCase() if ( !includeToolsLower.includes(toolIdLower) && !includeToolsLower.includes(toolNameLower) ) { continue } // If tool is explicitly included, skip other filters and add it filtered.set(toolId, tool) continue } // includeOperations filter if (includeOperationsLower.length > 0) { const httpMethod = typeof extendedTool.httpMethod === "string" ? extendedTool.httpMethod.toLowerCase() : undefined if (!httpMethod || !includeOperationsLower.includes(httpMethod)) { continue } } // includeResources filter if (includeResourcesLower.length > 0) { const resourceName = typeof extendedTool.resourceName === "string" ? extendedTool.resourceName.toLowerCase() : undefined if (!resourceName || !includeResourcesLower.includes(resourceName)) { continue } } // includeTags filter if (includeTagsLower.length > 0) { const toolTags = Array.isArray(extendedTool.tags) ? extendedTool.tags : [] const hasMatchingTag = toolTags.some( (tag) => typeof tag === "string" && includeTagsLower.includes(tag.toLowerCase()), ) if (!hasMatchingTag) { continue } } filtered.set(toolId, tool) } this.tools = filtered // Log the registered tools for (const [toolId, tool] of this.tools.entries()) { console.error(`Registered tool: ${toolId} (${tool.name})`) } } /** * Get all available tools */ getAllTools(): Tool[] { return Array.from(this.tools.values()) } /** * Get all available tools with their IDs * Returns array of [toolId, tool] pairs */ getToolsWithIds(): [string, Tool][] { return Array.from(this.tools.entries()) } /** * Find a tool by ID or name */ findTool(idOrName: string): { toolId: string; tool: Tool } | undefined { const lowerIdOrName = idOrName.toLowerCase() // Try to find by ID first (case-insensitive) for (const [toolId, tool] of this.tools.entries()) { if (toolId.toLowerCase() === lowerIdOrName) { return { toolId, tool } } } // Then try to find by name (case-insensitive) for (const [toolId, tool] of this.tools.entries()) { if (tool.name.toLowerCase() === lowerIdOrName) { return { toolId, tool } } } return undefined } /** * Get the path and method from a tool ID * * Note: This converts hyphens back to slashes to reconstruct the original API path. * This is consistent with ApiClient.parseToolId() which needs the actual path for HTTP requests. */ parseToolId(toolId: string): { method: string; path: string } { return parseToolIdUtil(toolId) } }

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/ivo-toby/mcp-openapi-server'

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