/** * src/utils/swagger-to-mcp.ts * * Auto-converter: Swagger (OpenAPI) → MCP manifest (real-time) * * - Fetch swagger JSON dynamically from process.env.BUN_PUBLIC_BASE_URL + "/docs/json" * - Generate MCP manifest for AI discovery (/.well-known/mcp.json) * - Can be used as Bun CLI or integrated in Elysia route */ import { writeFileSync } from "fs" interface OpenAPI { info: { title?: string; description?: string; version?: string } paths: Record } interface McpManifest { schema_version: string name: string description: string version?: string endpoints: Record capabilities: Record contact?: { email?: string } } /** * Convert OpenAPI JSON to MCP manifest format */ async function convertOpenApiToMcp(baseUrl: string): Promise { const res = await fetch(`${baseUrl}/docs/json`) if (!res.ok) throw new Error(`Failed to fetch Swagger JSON from ${baseUrl}/docs/json`) const openapi: OpenAPI = await res.json() const manifest: McpManifest = { schema_version: "1.0", name: openapi.info?.title ?? "MCP Server", description: openapi.info?.description ?? "Auto-generated MCP manifest from Swagger", version: openapi.info?.version ?? "0.0.0", endpoints: { openapi: `${baseUrl}/docs/json`, mcp: `${baseUrl}/.well-known/mcp.json` }, capabilities: {} } for (const [path, methods] of Object.entries(openapi.paths || {})) { for (const [method, def] of Object.entries(methods)) { const tags = def.tags || ["default"] const tag = tags[0] const operationId = def.operationId || `${method}_${path.replace(/[\/{}]/g, "_")}` manifest.capabilities[tag] ??= {} // Extract parameters and body schema const params: Record = {} const required: string[] = [] if (Array.isArray(def.parameters)) { for (const p of def.parameters) { const type = p.schema?.type || "string" params[p.name] = type if (p.required) required.push(p.name) } } const bodySchema = def.requestBody?.content?.["application/json"]?.schema if (bodySchema?.properties) { for (const [key, prop] of Object.entries(bodySchema.properties)) { params[key] = prop.type || "string" } if (Array.isArray(bodySchema.required)) required.push(...bodySchema.required) } // Generate example cURL const sampleCurl = [ `curl -X ${method.toUpperCase()} ${baseUrl}${path}`, Object.keys(params).length > 0 ? ` -H 'Content-Type: application/json' -d '${JSON.stringify( Object.fromEntries(Object.keys(params).map(k => [k, params[k] === "string" ? k : "value"])) )}'` : "" ] .filter(Boolean) .join(" \\\n") manifest.capabilities[tag][operationId] = { method: method.toUpperCase(), path, summary: def.summary || def.description || "", parameters: Object.keys(params).length > 0 ? params : undefined, required: required.length > 0 ? required : undefined, command: sampleCurl } } } return manifest } /** * CLI entry * bun run src/utils/swagger-to-mcp.ts */ if (import.meta.main) { const baseUrl = process.env.BUN_PUBLIC_BASE_URL if (!baseUrl) { console.error("❌ Missing BUN_PUBLIC_BASE_URL environment variable.") process.exit(1) } convertOpenApiToMcp(baseUrl) .then(manifest => { writeFileSync(".well-known/mcp.json", JSON.stringify(manifest, null, 2)) console.log("✅ Generated .well-known/mcp.json") }) .catch(err => console.error("❌ Failed to convert Swagger → MCP:", err)) } /** * Optional: Elysia integration * Automatically serve /.well-known/mcp.json */ // import Elysia from "elysia" // new Elysia() // .get("/.well-known/mcp.json", async () => { // const baseUrl = process.env.BUN_PUBLIC_BASE_URL! // return await convertOpenApiToMcp(baseUrl) // }) // .listen(3000)