Skip to main content
Glama
kadykov

OpenAPI Schema Explorer

index.ts9.6 kB
#!/usr/bin/env node import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; // Import ResourceTemplate import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { OpenAPI } from 'openapi-types'; // Import OpenAPIV3 as well import { loadConfig } from './config.js'; // Import new handlers import { TopLevelFieldHandler } from './handlers/top-level-field-handler.js'; import { PathItemHandler } from './handlers/path-item-handler.js'; import { OperationHandler } from './handlers/operation-handler.js'; import { ComponentMapHandler } from './handlers/component-map-handler.js'; import { ComponentDetailHandler } from './handlers/component-detail-handler.js'; import { OpenAPITransformer, ReferenceTransformService } from './services/reference-transform.js'; import { SpecLoaderService } from './services/spec-loader.js'; import { createFormatter } from './services/formatters.js'; import { encodeUriPathComponent } from './utils/uri-builder.js'; // Import specific function import { isOpenAPIV3, getValidatedComponentMap } from './handlers/handler-utils.js'; // Import type guard and helper import { VERSION } from './version.js'; // Import the generated version async function main(): Promise<void> { try { // Get spec path and options from command line arguments const [, , specPath, ...args] = process.argv; const options = { outputFormat: args.includes('--output-format') ? args[args.indexOf('--output-format') + 1] : undefined, }; // Load configuration const config = loadConfig(specPath, options); // Initialize services const referenceTransform = new ReferenceTransformService(); referenceTransform.registerTransformer('openapi', new OpenAPITransformer()); const specLoader = new SpecLoaderService(config.specPath, referenceTransform); await specLoader.loadSpec(); // Get the loaded spec to extract the title const spec: OpenAPI.Document = await specLoader.getSpec(); // Rename back to spec // Get the transformed spec for use in completions const transformedSpec: OpenAPI.Document = await specLoader.getTransformedSpec({ resourceType: 'schema', // Use a default context format: 'openapi', }); const defaultServerName = 'OpenAPI Schema Explorer'; // Use original spec for title const serverName = spec.info?.title ? `Schema Explorer for ${spec.info.title}` : defaultServerName; // Brief help content for LLMs const helpContent = `Use resorces/templates/list to get a list of available resources. Use openapi://paths to get a list of all endpoints.`; // Create MCP server with dynamic name const server = new McpServer( { name: serverName, version: VERSION, // Use the imported version }, { instructions: helpContent, } ); // Set up formatter and new handlers const formatter = createFormatter(config.outputFormat); const topLevelFieldHandler = new TopLevelFieldHandler(specLoader, formatter); const pathItemHandler = new PathItemHandler(specLoader, formatter); const operationHandler = new OperationHandler(specLoader, formatter); const componentMapHandler = new ComponentMapHandler(specLoader, formatter); const componentDetailHandler = new ComponentDetailHandler(specLoader, formatter); // --- Define Resource Templates and Register Handlers --- // Helper to get dynamic field list for descriptions const getFieldList = (): string => Object.keys(transformedSpec).join(', '); // Helper to get dynamic component type list for descriptions const getComponentTypeList = (): string => { if (isOpenAPIV3(transformedSpec) && transformedSpec.components) { return Object.keys(transformedSpec.components).join(', '); } return ''; // Return empty if no components or not V3 }; // 1. openapi://{field} const fieldTemplate = new ResourceTemplate('openapi://{field}', { list: undefined, // List is handled by the handler logic based on field value complete: { field: (): string[] => Object.keys(transformedSpec), // Use transformedSpec }, }); server.resource( 'openapi-field', // Unique ID for the resource registration fieldTemplate, { // MimeType varies (text/plain for lists, JSON/YAML for details) description: `Access top-level fields like ${getFieldList()}. (e.g., openapi://info)`, title: 'OpenAPI Field/List', // Generic name }, topLevelFieldHandler.handleRequest ); // 2. openapi://paths/{path} const pathTemplate = new ResourceTemplate('openapi://paths/{path}', { list: undefined, // List is handled by the handler complete: { path: (): string[] => Object.keys(transformedSpec.paths ?? {}).map(encodeUriPathComponent), // Use imported function directly }, }); server.resource( 'openapi-path-methods', pathTemplate, { mimeType: 'text/plain', // This always returns a list description: 'List methods for a specific path (URL encode paths with slashes). (e.g., openapi://paths/users%2F%7Bid%7D)', title: 'Path Methods List', }, pathItemHandler.handleRequest ); // 3. openapi://paths/{path}/{method*} const operationTemplate = new ResourceTemplate('openapi://paths/{path}/{method*}', { list: undefined, // Detail view handled by handler complete: { path: (): string[] => Object.keys(transformedSpec.paths ?? {}).map(encodeUriPathComponent), // Use imported function directly method: (): string[] => [ // Provide static list of common methods 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'TRACE', ], }, }); server.resource( 'openapi-operation-detail', operationTemplate, { mimeType: formatter.getMimeType(), // Detail view uses formatter description: 'Get details for one or more operations (comma-separated). (e.g., openapi://paths/users%2F%7Bid%7D/get,post)', title: 'Operation Detail', }, operationHandler.handleRequest ); // 4. openapi://components/{type} const componentMapTemplate = new ResourceTemplate('openapi://components/{type}', { list: undefined, // List is handled by the handler complete: { type: (): string[] => { // Use type guard to ensure spec is V3 before accessing components if (isOpenAPIV3(transformedSpec)) { return Object.keys(transformedSpec.components ?? {}); } return []; // Return empty array if not V3 (shouldn't happen ideally) }, }, }); server.resource( 'openapi-component-list', componentMapTemplate, { mimeType: 'text/plain', // This always returns a list description: `List components of a specific type like ${getComponentTypeList()}. (e.g., openapi://components/schemas)`, title: 'Component List', }, componentMapHandler.handleRequest ); // 5. openapi://components/{type}/{name*} const componentDetailTemplate = new ResourceTemplate('openapi://components/{type}/{name*}', { list: undefined, // Detail view handled by handler complete: { type: (): string[] => { // Use type guard to ensure spec is V3 before accessing components if (isOpenAPIV3(transformedSpec)) { return Object.keys(transformedSpec.components ?? {}); } return []; // Return empty array if not V3 }, name: (): string[] => { // Provide names only if there's exactly one component type defined if ( isOpenAPIV3(transformedSpec) && transformedSpec.components && Object.keys(transformedSpec.components).length === 1 ) { // Get the single component type key (e.g., 'schemas') const componentTypeKey = Object.keys(transformedSpec.components)[0]; // Use the helper to safely get the map try { const componentTypeMap = getValidatedComponentMap(transformedSpec, componentTypeKey); return Object.keys(componentTypeMap); } catch (error) { // Should not happen if key came from Object.keys, but handle defensively console.error(`Error getting component map for key ${componentTypeKey}:`, error); return []; } } // Otherwise, return no completions for name return []; }, }, }); server.resource( 'openapi-component-detail', componentDetailTemplate, { mimeType: formatter.getMimeType(), // Detail view uses formatter description: 'Get details for one or more components (comma-separated). (e.g., openapi://components/schemas/User,Task)', title: 'Component Detail', }, componentDetailHandler.handleRequest ); // Start server const transport = new StdioServerTransport(); await server.connect(transport); } catch (error) { console.error( 'Failed to start server:', error instanceof Error ? error.message : String(error) ); process.exit(1); } } // Run the server main().catch(error => { console.error('Unhandled error:', error instanceof Error ? error.message : String(error)); process.exit(1); });

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/kadykov/mcp-openapi-schema-explorer'

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