mcpengine/studio/apps/web/lib/deploy/worker-template.ts
Jake Shore 96e52666c5 MCPEngine full sync — studio scaffold, factory v2, server updates, state.json — 2026-02-12
=== NEW ===
- studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan)
- docs/FACTORY-V2.md — Factory v2 architecture doc
- docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report

=== UPDATED SERVERS ===
- fieldedge: Added jobs-tools, UI build script, main entry update
- lightspeed: Updated main + server entry points
- squarespace: Added collection-browser + page-manager apps
- toast: Added main + server entry points

=== INFRA ===
- infra/command-center/state.json — Updated pipeline state
- infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
2026-02-12 17:58:33 -05:00

205 lines
7.7 KiB
TypeScript

/**
* MCPEngine Studio — Cloudflare Worker Template
*
* Wraps a compiled MCP server in an HTTP handler:
* POST /mcp → MCP JSON-RPC handler
* GET /health → 200 OK
* GET / → Info page with server name and tool list
*/
export interface WorkerTemplateParams {
serverName: string;
serverSlug: string;
compiledCode: string;
toolNames: string[];
toolCount: number;
version?: string;
}
export function generateWorkerScript(params: WorkerTemplateParams): string {
const {
serverName,
serverSlug,
compiledCode,
toolNames,
toolCount,
version = '1.0.0',
} = params;
const toolListJSON = JSON.stringify(toolNames, null, 2);
const escapedName = serverName.replace(/'/g, "\\'");
const builtAt = new Date().toISOString();
return `
// ═══════════════════════════════════════════════════════════════════════════
// ${serverName} — MCPEngine Worker
// Deployed via MCPEngine Studio
// Built: ${builtAt} | Version: ${version} | Tools: ${toolCount}
// ═══════════════════════════════════════════════════════════════════════════
// ── Compiled server code ─────────────────────────────────────────────────
${compiledCode}
// ── Tool registry ────────────────────────────────────────────────────────
const SERVER_NAME = '${escapedName}';
const SERVER_SLUG = '${serverSlug}';
const SERVER_VERSION = '${version}';
const TOOL_NAMES = ${toolListJSON};
// ── MCP JSON-RPC handler ─────────────────────────────────────────────────
async function handleMCPRequest(request) {
try {
const body = await request.json();
const { method, params, id } = body;
// Standard MCP methods
if (method === 'initialize') {
return jsonResponse({
jsonrpc: '2.0',
id,
result: {
protocolVersion: '2024-11-05',
capabilities: { tools: { listChanged: false } },
serverInfo: {
name: SERVER_NAME,
version: SERVER_VERSION,
},
},
});
}
if (method === 'tools/list') {
// Delegate to server module if available
if (typeof serverModule?.listTools === 'function') {
const tools = await serverModule.listTools();
return jsonResponse({ jsonrpc: '2.0', id, result: { tools } });
}
return jsonResponse({
jsonrpc: '2.0',
id,
result: {
tools: TOOL_NAMES.map((name) => ({
name,
description: name + ' tool',
inputSchema: { type: 'object', properties: {} },
})),
},
});
}
if (method === 'tools/call') {
if (typeof serverModule?.callTool === 'function') {
const result = await serverModule.callTool(params?.name, params?.arguments ?? {});
return jsonResponse({ jsonrpc: '2.0', id, result });
}
return jsonResponse({
jsonrpc: '2.0',
id,
error: { code: -32601, message: 'Tool not found: ' + (params?.name ?? 'unknown') },
});
}
// Notifications (no response needed per spec)
if (method === 'notifications/initialized') {
return new Response(null, { status: 204 });
}
return jsonResponse({
jsonrpc: '2.0',
id,
error: { code: -32601, message: 'Method not found: ' + method },
});
} catch (err) {
return jsonResponse(
{ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error: ' + err.message } },
400,
);
}
}
// ── Info page ────────────────────────────────────────────────────────────
function infoPage() {
const toolList = TOOL_NAMES.map((t) => '<li><code>' + t + '</code></li>').join('\\n');
const html = \`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>\${SERVER_NAME} — MCP Server</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.card { max-width: 640px; width: 90%; background: #1e293b; border-radius: 16px; padding: 2.5rem; border: 1px solid #334155; }
h1 { font-size: 1.75rem; margin-bottom: .5rem; background: linear-gradient(135deg, #818cf8, #34d399); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.badge { display: inline-block; background: #10b981; color: #fff; padding: 2px 10px; border-radius: 99px; font-size: .75rem; margin-bottom: 1rem; }
.meta { color: #94a3b8; font-size: .875rem; margin-bottom: 1.5rem; }
h2 { font-size: 1rem; color: #94a3b8; margin-bottom: .75rem; }
ul { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: .5rem; }
li { background: #0f172a; padding: .5rem .75rem; border-radius: 8px; font-size: .875rem; }
code { color: #818cf8; }
.endpoint { margin-top: 1.5rem; background: #0f172a; padding: 1rem; border-radius: 8px; font-family: monospace; font-size: .8rem; color: #34d399; }
</style>
</head>
<body>
<div class="card">
<h1>\${SERVER_NAME}</h1>
<span class="badge">● Live</span>
<div class="meta">Version \${SERVER_VERSION} · \${TOOL_NAMES.length} tools · Powered by MCPEngine</div>
<h2>Available Tools</h2>
<ul>\${toolList}</ul>
<div class="endpoint">POST /mcp — JSON-RPC 2.0 endpoint</div>
</div>
</body>
</html>\`;
return new Response(html, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
// ── Utilities ────────────────────────────────────────────────────────────
function jsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
// ── Worker entry ─────────────────────────────────────────────────────────
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// CORS preflight
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
// Routes
if (url.pathname === '/mcp' && request.method === 'POST') {
return handleMCPRequest(request);
}
if (url.pathname === '/health') {
return jsonResponse({ status: 'ok', server: SERVER_NAME, tools: TOOL_NAMES.length });
}
if (url.pathname === '/') {
return infoPage();
}
return jsonResponse({ error: 'Not found' }, 404);
},
};
`.trimStart();
}