/** * MCPEngine Studio — Server Compiler * * Takes a ServerBundle of generated TypeScript files and produces a single * bundled JavaScript string ready for Cloudflare Workers deployment. * * NOTE: This is Phase-1 concatenation. Real esbuild bundling will be added later. */ import type { ServerBundle, GeneratedFile } from '@mcpengine/ai-pipeline/types'; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Strip TypeScript type annotations in a best-effort way (imports, type-only * exports, interface/type blocks). Good enough for simple generated code. */ function stripTypes(src: string): string { return src // Remove `import type …` lines .replace(/^import\s+type\s+.*$/gm, '') // Remove `export type …` and `export interface …` blocks (single-line + multi-line) .replace(/^export\s+(type|interface)\s+\w+[^{]*\{[^}]*\}/gm, '') .replace(/^export\s+(type|interface)\s+.*$/gm, '') // Remove inline type annotations `: Foo` after parameter names (rough) .replace(/:\s*[A-Z]\w+(\[\])?\s*(,|\)|\s*=>)/g, (_m, _arr, tail) => tail) // Remove `as Type` casts .replace(/\s+as\s+\w+/g, '') // Collapse blank lines .replace(/\n{3,}/g, '\n\n'); } /** Convert TS import paths to inline references (they all live in the same * concatenated scope so we just drop the import statements). */ function stripImports(src: string): string { return src.replace(/^import\s+.*$/gm, ''); } /** Wrap file content in an IIFE-scoped module to avoid collisions. */ function wrapModule(name: string, code: string): string { return [ `// ── ${name} ${'─'.repeat(Math.max(0, 60 - name.length))}`, `const __mod_${safeId(name)} = (() => {`, ` const exports = {};`, ` const module = { exports };`, code, ` return module.exports;`, `})();`, '', ].join('\n'); } function safeId(path: string): string { return path.replace(/[^a-zA-Z0-9]/g, '_').replace(/_+/g, '_'); } // --------------------------------------------------------------------------- // Main compiler // --------------------------------------------------------------------------- export function compileServer(bundle: ServerBundle): string { const sections: string[] = []; // 1. Banner sections.push( '// ═══════════════════════════════════════════════════════════', '// MCPEngine Studio — Compiled MCP Server', `// Generated at ${new Date().toISOString()}`, `// Tools: ${bundle.toolCount}`, '// ═══════════════════════════════════════════════════════════', '', ); // 2. Sort files: put the entry point last const sorted = [...bundle.files].sort((a, b) => { if (a.path === bundle.entryPoint) return 1; if (b.path === bundle.entryPoint) return -1; // Utility / shared modules first if (a.path.includes('utils') || a.path.includes('shared')) return -1; if (b.path.includes('utils') || b.path.includes('shared')) return 1; return a.path.localeCompare(b.path); }); // 3. Process each file for (const file of sorted) { if (file.language === 'json' || file.language === 'markdown') continue; let code = file.content; code = stripTypes(code); code = stripImports(code); sections.push(wrapModule(file.path, code)); } // 4. Export a fetch handler that the Worker template will consume sections.push( '// ── Fetch handler export ──────────────────────────────────', `const __entryModule = __mod_${safeId(bundle.entryPoint)};`, 'export const serverModule = __entryModule;', '', ); return sections.join('\n'); } /** Compile and return metadata useful for deploy targets. */ export function compileWithMeta(bundle: ServerBundle) { const compiledCode = compileServer(bundle); const toolNames = bundle.files .filter((f) => f.path.includes('tools/')) .map((f) => f.path.replace(/^.*tools\//, '').replace(/\.ts$/, '')); return { code: compiledCode, toolNames, toolCount: bundle.toolCount, fileCount: bundle.files.length, sizeBytes: new TextEncoder().encode(compiledCode).byteLength, }; }