server.mjs 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. #!/usr/bin/env node
  2. import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  3. import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  4. import { resolve } from "node:path";
  5. import { fileURLToPath } from "node:url";
  6. import { z } from "zod";
  7. import { runImageSet } from "./tools/image-set-run.mjs";
  8. import { validateImageSetPlan } from "./tools/image-set-validate.mjs";
  9. const VERSION = "0.1.0";
  10. function asToolResult(result = {}) {
  11. return {
  12. content: [{
  13. type: "text",
  14. text: result.assistantMessage || JSON.stringify(result, null, 2),
  15. }],
  16. structuredContent: result,
  17. isError: !new Set(["ok", "partial"]).has(result.status),
  18. };
  19. }
  20. export function createServer() {
  21. const server = new McpServer({
  22. name: "fmode-image-set",
  23. version: VERSION,
  24. });
  25. const outputSchema = {
  26. status: z.string(),
  27. assistantMessage: z.string(),
  28. summary: z.object({}).passthrough().optional(),
  29. data: z.object({}).passthrough().optional(),
  30. files: z.array(z.string()).optional(),
  31. nextActions: z.array(z.string()).optional(),
  32. warnings: z.array(z.any()).optional(),
  33. errors: z.array(z.any()).optional(),
  34. };
  35. server.registerTool(
  36. "fmode_image_set_run",
  37. {
  38. title: "Run Fmode Image Set",
  39. description: [
  40. "Create, execute, resume, or inspect a product-truth-first ecommerce image-set run.",
  41. "Use start for visual understanding and run allocation.",
  42. "Use execute only after a valid prompt-plan exists and the user explicitly authorizes paid generation.",
  43. "Use resume only for the same runId; use status for read-only inspection.",
  44. ].join(" "),
  45. inputSchema: {
  46. action: z.enum(["start", "execute", "resume", "status"]),
  47. image: z.string().optional(),
  48. runId: z.string().optional(),
  49. maxCost: z.union([z.number().nonnegative(), z.string()]).optional(),
  50. workspaceRoot: z.string().optional(),
  51. cwd: z.string().optional(),
  52. claudeConfigDir: z.string().optional(),
  53. sessionId: z.string().optional(),
  54. },
  55. outputSchema,
  56. },
  57. async (input) => asToolResult(await runImageSet(input)),
  58. );
  59. server.registerTool(
  60. "fmode_image_set_validate",
  61. {
  62. title: "Validate Fmode Image Set Plan",
  63. 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.",
  64. annotations: {
  65. readOnlyHint: true,
  66. destructiveHint: false,
  67. idempotentHint: true,
  68. openWorldHint: false,
  69. },
  70. inputSchema: {
  71. planPath: z.string(),
  72. },
  73. outputSchema,
  74. },
  75. async (input) => asToolResult(await validateImageSetPlan(input)),
  76. );
  77. return server;
  78. }
  79. if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
  80. const server = createServer();
  81. const transport = new StdioServerTransport();
  82. await server.connect(transport);
  83. }