=== 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
100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
import Anthropic from '@anthropic-ai/sdk';
|
|
import { readFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
function getClient() { return new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }); }
|
|
|
|
function loadSkill(name: string): string {
|
|
const skillsDir = join(process.cwd(), '../../packages/ai-pipeline/skills/data');
|
|
return readFileSync(join(skillsDir, `${name}.md`), 'utf-8');
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const body = await request.json();
|
|
const { analysis, tools, serviceName } = body;
|
|
|
|
if (!analysis && !tools) {
|
|
return Response.json({ error: 'Provide analysis or tools config' }, { status: 400 });
|
|
}
|
|
|
|
// Load our server-builder + server-development skills
|
|
const builderSkill = loadSkill('mcp-server-builder');
|
|
const devSkill = loadSkill('mcp-server-development');
|
|
const systemPrompt = builderSkill + '\n\n---\n\n' + devSkill;
|
|
|
|
const encoder = new TextEncoder();
|
|
const stream = new ReadableStream({
|
|
async start(controller) {
|
|
try {
|
|
const send = (event: string, data: unknown) => {
|
|
controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
|
|
};
|
|
|
|
send('progress', { step: 'Generating MCP server...', percent: 10 });
|
|
|
|
const response = await getClient().messages.create({
|
|
model: 'claude-sonnet-4-5-20250514',
|
|
max_tokens: 16384,
|
|
system: systemPrompt + '\n\nIMPORTANT: Generate a complete, working MCP server. Output each file in this format:\n\n--- FILE: path/to/file ---\n```typescript\n// file content\n```\n\nGenerate at minimum: src/index.ts, package.json, tsconfig.json, README.md, .env.example',
|
|
messages: [{
|
|
role: 'user',
|
|
content: `Generate a complete MCP server for "${serviceName || 'custom'}" based on this analysis:\n\n${JSON.stringify(analysis || { tools })}\n\nThe server should compile with TypeScript and use the MCP SDK ^1.26.0.`
|
|
}],
|
|
});
|
|
|
|
send('progress', { step: 'Parsing generated files...', percent: 80 });
|
|
|
|
const text = response.content
|
|
.filter((b): b is Anthropic.TextBlock => b.type === 'text')
|
|
.map(b => b.text)
|
|
.join('');
|
|
|
|
// Parse files from the output
|
|
const files: { path: string; content: string }[] = [];
|
|
const filePattern = /--- FILE: (.+?) ---\s*```(?:typescript|json|markdown|ts|md)?\s*([\s\S]*?)```/g;
|
|
let match;
|
|
while ((match = filePattern.exec(text)) !== null) {
|
|
files.push({ path: match[1].trim(), content: match[2].trim() });
|
|
send('file_ready', { path: match[1].trim(), preview: match[2].trim().substring(0, 200) });
|
|
}
|
|
|
|
// If no file pattern found, try to extract code blocks
|
|
if (files.length === 0) {
|
|
const codeBlocks = text.match(/```(?:typescript|ts|json)\s*([\s\S]*?)```/g) || [];
|
|
if (codeBlocks.length > 0) {
|
|
files.push({
|
|
path: 'src/index.ts',
|
|
content: codeBlocks[0].replace(/```(?:typescript|ts)?\s*/, '').replace(/```$/, '').trim()
|
|
});
|
|
send('file_ready', { path: 'src/index.ts', preview: 'Main server file' });
|
|
}
|
|
}
|
|
|
|
send('complete', {
|
|
files,
|
|
totalFiles: files.length,
|
|
rawOutput: files.length === 0 ? text : undefined
|
|
});
|
|
controller.close();
|
|
} catch (err: unknown) {
|
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
controller.enqueue(encoder.encode(`event: error\ndata: ${JSON.stringify({ message: msg })}\n\n`));
|
|
controller.close();
|
|
}
|
|
}
|
|
});
|
|
|
|
return new Response(stream, {
|
|
headers: {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
Connection: 'keep-alive',
|
|
},
|
|
});
|
|
} catch (err: unknown) {
|
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
return Response.json({ error: msg }, { status: 500 });
|
|
}
|
|
}
|