2026-02-06 23:01:30 -05:00

197 lines
5.7 KiB
TypeScript

/**
* MCPEngine Studio — Deploy to MCPEngine (Cloudflare Workers)
*
* Phase 1: Simulates the Cloudflare Workers upload flow.
* Compile → Package → "Upload" (writes to local output dir).
* TODO: Real Wrangler API integration.
*/
import { promises as fs } from 'fs';
import path from 'path';
import type {
ServerBundle,
DeployConfig,
DeployResult,
} from '@mcpengine/ai-pipeline/types';
import { compileWithMeta } from '../compiler';
import { generateWorkerScript } from '../worker-template';
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface DeployProgress {
step: string;
message: string;
percent: number;
level: 'info' | 'success' | 'warning' | 'error';
}
// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------
const OUTPUT_BASE = path.join(process.cwd(), '.mcpengine-output', 'workers');
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
export async function* deployToMCPEngine(
projectId: string,
bundle: ServerBundle,
config: DeployConfig,
): AsyncGenerator<DeployProgress, DeployResult, void> {
const slug = config.slug ?? projectId.slice(0, 8);
const deployId = crypto.randomUUID();
const startedAt = new Date().toISOString();
const logs: string[] = [];
const log = (msg: string) => {
logs.push(`[${new Date().toISOString()}] ${msg}`);
};
// ── Step 1: Compile ────────────────────────────────────────────────────
yield {
step: 'compile',
message: 'Compiling server bundle…',
percent: 10,
level: 'info',
};
log('Starting compilation…');
const compiled = compileWithMeta(bundle);
log(`Compiled ${compiled.fileCount} files (${(compiled.sizeBytes / 1024).toFixed(1)} KB)`);
yield {
step: 'compile',
message: `Compiled ${compiled.fileCount} files (${compiled.toolCount} tools)`,
percent: 25,
level: 'success',
};
// ── Step 2: Generate Worker script ─────────────────────────────────────
yield {
step: 'package',
message: 'Generating Cloudflare Worker script…',
percent: 35,
level: 'info',
};
log('Generating worker script…');
const workerScript = generateWorkerScript({
serverName: config.slug ?? 'MCP Server',
serverSlug: slug,
compiledCode: compiled.code,
toolNames: compiled.toolNames,
toolCount: compiled.toolCount,
});
const workerSize = new TextEncoder().encode(workerScript).byteLength;
log(`Worker script generated (${(workerSize / 1024).toFixed(1)} KB)`);
yield {
step: 'package',
message: `Worker packaged (${(workerSize / 1024).toFixed(1)} KB)`,
percent: 50,
level: 'success',
};
// ── Step 3: "Upload" to local output (simulated) ──────────────────────
yield {
step: 'deploy',
message: 'Deploying to MCPEngine…',
percent: 60,
level: 'info',
};
log('Uploading worker to MCPEngine…');
const outputDir = path.join(OUTPUT_BASE, slug);
await fs.mkdir(outputDir, { recursive: true });
// Write the worker script
await fs.writeFile(path.join(outputDir, 'worker.js'), workerScript, 'utf-8');
// Write wrangler.toml (for future real deploys)
const wranglerToml = `
# MCPEngine Studio — Auto-generated Wrangler config
name = "${slug}"
main = "worker.js"
compatibility_date = "${new Date().toISOString().split('T')[0]}"
compatibility_flags = ["nodejs_compat"]
[vars]
SERVER_NAME = "${slug}"
DEPLOY_ID = "${deployId}"
# TODO: Add KV/D1/R2 bindings as needed
# [[kv_namespaces]]
# binding = "CACHE"
# id = "xxx"
`.trimStart();
await fs.writeFile(path.join(outputDir, 'wrangler.toml'), wranglerToml, 'utf-8');
// Write deploy metadata
const metadata = {
deployId,
projectId,
slug,
target: 'mcpengine' as const,
toolCount: compiled.toolCount,
toolNames: compiled.toolNames,
sizeBytes: workerSize,
createdAt: startedAt,
url: `https://${slug}.mcpengine.run`,
endpoint: `https://${slug}.mcpengine.run/mcp`,
};
await fs.writeFile(
path.join(outputDir, 'deploy-meta.json'),
JSON.stringify(metadata, null, 2),
'utf-8',
);
log(`Worker written to ${outputDir}`);
yield {
step: 'deploy',
message: 'Worker deployed successfully',
percent: 85,
level: 'success',
};
// ── Step 4: Verify ─────────────────────────────────────────────────────
yield {
step: 'verify',
message: 'Verifying deployment…',
percent: 90,
level: 'info',
};
// TODO: Hit the real health endpoint once we have actual CF Workers deploy
// For now, simulate verification
log('Health check: OK (simulated)');
log(`Server live at https://${slug}.mcpengine.run`);
yield {
step: 'verify',
message: `Live at https://${slug}.mcpengine.run`,
percent: 100,
level: 'success',
};
// ── Return result ──────────────────────────────────────────────────────
const result: DeployResult = {
id: deployId,
target: 'mcpengine',
status: 'live',
url: `https://${slug}.mcpengine.run`,
endpoint: `https://${slug}.mcpengine.run/mcp`,
logs,
createdAt: startedAt,
};
return result;
}