import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { briefObjectContentSchema } from "@tasks/shared/convoy-lifecycle"; import { db } from "../db.js"; import { objects } from "../schema.js"; import { loadConvoyProject } from "../lib/convoy-objects.js"; import { resolveWorkspaceHandle } from "../lib/resolve-workspace.js"; import { toolCatch, toolErr, toolOk } from "./tool-result.js"; const createBriefInputSchema = z.object({ workspace: z.string().min(1), convoyId: z.string().uuid(), briefNumber: z.number().int().positive(), title: z.string().min(1).max(500), files: z.array(z.string()).default([]), sliceDependencies: z.array(z.number().int().positive()).default([]), acceptance: z.string().optional(), bodyMarkdown: z.string().optional(), }); export function registerCreateBriefTool(mcp: McpServer): void { mcp.registerTool( "create_brief", { description: "Create an implementer brief as a task object child of a convoy project.", inputSchema: createBriefInputSchema, }, async (args) => { try { const input = createBriefInputSchema.parse(args); const ws = await resolveWorkspaceHandle(input.workspace); const convoy = await loadConvoyProject(input.convoyId, ws.id); if (!convoy) { return toolErr(`Convoy ${input.convoyId} not found in workspace ${ws.slug}`); } const content = briefObjectContentSchema.parse({ briefNumber: input.briefNumber, files: input.files, sliceDependencies: input.sliceDependencies, acceptance: input.acceptance, }); const [created] = await db .insert(objects) .values({ type: "task", title: input.title, parentId: convoy.id, workspaceId: ws.id, description: input.bodyMarkdown ?? input.acceptance ?? null, status: "ready", content, sortOrder: input.briefNumber, }) .returning(); if (!created) { return toolErr("Failed to create brief"); } return toolOk({ workspace: { id: ws.id, slug: ws.slug, name: ws.name }, convoyId: convoy.id, brief: created, }); } catch (e) { return toolCatch(e); } }, ); }