#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { z } from "zod"; import { runImageSet } from "./tools/image-set-run.mjs"; import { validateImageSetPlan } from "./tools/image-set-validate.mjs"; const VERSION = "0.1.0"; function asToolResult(result = {}) { return { content: [{ type: "text", text: result.assistantMessage || JSON.stringify(result, null, 2), }], structuredContent: result, isError: !new Set(["ok", "partial"]).has(result.status), }; } export function createServer() { const server = new McpServer({ name: "fmode-image-set", version: VERSION, }); const outputSchema = { status: z.string(), assistantMessage: z.string(), summary: z.object({}).passthrough().optional(), data: z.object({}).passthrough().optional(), files: z.array(z.string()).optional(), nextActions: z.array(z.string()).optional(), warnings: z.array(z.any()).optional(), errors: z.array(z.any()).optional(), }; server.registerTool( "fmode_image_set_run", { title: "Run Fmode Image Set", description: [ "Create, execute, resume, or inspect a product-truth-first ecommerce image-set run.", "Use start for visual understanding and run allocation.", "Use execute only after a valid prompt-plan exists and the user explicitly authorizes paid generation.", "Use resume only for the same runId; use status for read-only inspection.", ].join(" "), inputSchema: { action: z.enum(["start", "execute", "resume", "status"]), image: z.string().optional(), runId: z.string().optional(), maxCost: z.union([z.number().nonnegative(), z.string()]).optional(), workspaceRoot: z.string().optional(), cwd: z.string().optional(), claudeConfigDir: z.string().optional(), sessionId: z.string().optional(), }, outputSchema, }, async (input) => asToolResult(await runImageSet(input)), ); server.registerTool( "fmode_image_set_validate", { title: "Validate Fmode Image Set Plan", description: "Validate a prompt-plan.json before any paid image generation. Checks schema, product identity, evidence, seven-slot allocation, prompt completeness, and cross-image diversity.", annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, inputSchema: { planPath: z.string(), }, outputSchema, }, async (input) => asToolResult(await validateImageSetPlan(input)), ); return server; } if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { const server = createServer(); const transport = new StdioServerTransport(); await server.connect(transport); }