Skip to main content
Glama

Spec Workflow MCP

by kingkongshot
openApiLoader.ts7.02 kB
import * as yaml from 'js-yaml'; import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { isObject } from './typeGuards.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // OpenAPI specification type definitions export interface OpenApiSpec { paths: { '/spec': { post: { responses: { '200': { content: { 'application/json': { schema: { $ref: string; }; }; }; }; }; }; }; }; components: { schemas: Record<string, unknown>; }; 'x-error-responses': Record<string, { displayText: string; }>; 'x-shared-resources': Record<string, { uri: string; title?: string; mimeType: string; text?: string; }>; 'x-global-config': unknown; 'x-document-templates': Record<string, unknown>; 'x-task-guidance-template'?: { separator: string; header: string; instructions: { prefix: string; taskFocus: string; progressTracking: string; workflow: string; }; prompts: { firstTask: string; nextTask: string; continueTask: string; batchContinue: string; }; completionMessages: { taskCompleted: string; allCompleted: string; alreadyCompleted: string; batchSucceeded: string; batchCompleted: string; }; }; } // Singleton pattern for loading OpenAPI specification export class OpenApiLoader { private static instance: OpenApiLoader; private spec: OpenApiSpec | null = null; private examples: Map<string, unknown[]> = new Map(); private constructor() {} static getInstance(): OpenApiLoader { if (!OpenApiLoader.instance) { OpenApiLoader.instance = new OpenApiLoader(); } return OpenApiLoader.instance; } // Load OpenAPI specification loadSpec(): OpenApiSpec { if (this.spec) { return this.spec; } const specPath = path.join(__dirname, '../../../api/spec-workflow.openapi.yaml'); const specContent = fs.readFileSync(specPath, 'utf8'); this.spec = yaml.load(specContent) as OpenApiSpec; // Parse and cache all examples this.cacheExamples(); return this.spec; } // Cache all response examples private cacheExamples(): void { if (!this.spec) return; const schemas = this.spec.components.schemas; for (const [schemaName, schema] of Object.entries(schemas)) { if (!isObject(schema)) continue; // Support standard OpenAPI 3.1.0 examples field if ('examples' in schema && Array.isArray(schema.examples)) { this.examples.set(schemaName, schema.examples); } // Maintain backward compatibility with custom x-examples field else if ('x-examples' in schema && Array.isArray(schema['x-examples'])) { this.examples.set(schemaName, schema['x-examples']); } } } // Get response example getResponseExample(responseType: string, criteria?: Record<string, unknown>): unknown { const examples = this.examples.get(responseType); if (!examples || examples.length === 0) { return null; } // If no filter criteria, return the first example if (!criteria) { return examples[0]; } // Filter examples by criteria for (const example of examples) { let matches = true; for (const [key, value] of Object.entries(criteria)) { if (this.getNestedValue(example, key) !== value) { matches = false; break; } } if (matches) { return example; } } // No match found, return the first one return examples[0]; } // Get error response template getErrorResponse(errorType: string): string | null { if (!this.spec || !this.spec['x-error-responses']) { return null; } const errorResponse = this.spec['x-error-responses'][errorType]; return errorResponse?.displayText || null; } // Get progress calculation rules getProgressRules(): unknown { if (!this.spec) return null; const progressSchema = this.spec.components.schemas.Progress; if (isObject(progressSchema) && 'x-progress-rules' in progressSchema) { return progressSchema['x-progress-rules']; } return null; } // Utility function: get nested object value private getNestedValue(obj: unknown, path: string): unknown { const keys = path.split('.'); let current = obj; for (const key of keys) { if (isObject(current) && key in current) { current = current[key]; } else { return undefined; } } return current; } // Replace template variables static replaceVariables(template: string, variables: Record<string, unknown>): string { let result = template; for (const [key, value] of Object.entries(variables)) { const regex = new RegExp(`\\$\\{${key}\\}`, 'g'); result = result.replace(regex, String(value)); } return result; } // Get shared resource - directly return MCP format getSharedResource(resourceId: string): { uri: string; title?: string; mimeType: string; text?: string } | null { if (!this.spec || !this.spec['x-shared-resources']) { return null; } return this.spec['x-shared-resources'][resourceId] || null; } // Get global configuration getGlobalConfig(): unknown { if (!this.spec) return {}; return this.spec['x-global-config'] || {}; } // Get document template getDocumentTemplate(templateType: string): unknown { if (!this.spec) return null; return this.spec['x-document-templates']?.[templateType] || null; } // Resolve resource list - no conversion needed, use MCP format directly resolveResources(resources?: Array<unknown>): Array<{ uri: string; title?: string; mimeType: string; text?: string }> | undefined { if (!resources || resources.length === 0) { return undefined; } const resolved: Array<{ uri: string; title?: string; mimeType: string; text?: string }> = []; for (const resource of resources) { if (isObject(resource) && 'ref' in resource && typeof resource.ref === 'string') { // Get from shared resources - already in MCP format const sharedResource = this.getSharedResource(resource.ref); if (sharedResource) { resolved.push(sharedResource); } } } return resolved.length > 0 ? resolved : undefined; } // Get task guidance template getTaskGuidanceTemplate(): OpenApiSpec['x-task-guidance-template'] | null { if (!this.spec) return null; return this.spec['x-task-guidance-template'] || null; } // Debug method: get cached examples count getExamplesCount(responseType: string): number { return this.examples.get(responseType)?.length || 0; } } export const openApiLoader = OpenApiLoader.getInstance();

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/kingkongshot/specs-workflow-mcp'

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