spec-workflow-mcp
Version:
MCP server for managing spec workflow (requirements, design, implementation)
166 lines • 5.82 kB
JavaScript
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);
// Singleton pattern for loading OpenAPI specification
export class OpenApiLoader {
static instance;
spec = null;
examples = new Map();
constructor() { }
static getInstance() {
if (!OpenApiLoader.instance) {
OpenApiLoader.instance = new OpenApiLoader();
}
return OpenApiLoader.instance;
}
// Load OpenAPI specification
loadSpec() {
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);
// Parse and cache all examples
this.cacheExamples();
return this.spec;
}
// Cache all response examples
cacheExamples() {
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, criteria) {
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) {
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() {
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
getNestedValue(obj, path) {
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, variables) {
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) {
if (!this.spec || !this.spec['x-shared-resources']) {
return null;
}
return this.spec['x-shared-resources'][resourceId] || null;
}
// Get global configuration
getGlobalConfig() {
if (!this.spec)
return {};
return this.spec['x-global-config'] || {};
}
// Get document template
getDocumentTemplate(templateType) {
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) {
if (!resources || resources.length === 0) {
return undefined;
}
const resolved = [];
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() {
if (!this.spec)
return null;
return this.spec['x-task-guidance-template'] || null;
}
// Debug method: get cached examples count
getExamplesCount(responseType) {
return this.examples.get(responseType)?.length || 0;
}
}
export const openApiLoader = OpenApiLoader.getInstance();
//# sourceMappingURL=openApiLoader.js.map