197 lines
5.7 KiB
TypeScript
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;
|
|
}
|