Adds the 4 new MCP-tools for the Scrum4Me M12 Idea-entity flow + extends
3 existing tools to handle the new ClaudeJobKind discriminator.
New tools:
- get_idea_context: full idea + product + open questions + recent logs
- update_idea_grill_md: save grill-result + status → GRILLED + IdeaLog
- update_idea_plan_md: server-side yaml parser validates frontmatter;
ok → PLAN_READY, fail → PLAN_FAILED + line-info errors
- log_idea_decision: DECISION/NOTE entries on the timeline
Extended tools:
- ask_user_question: xor schema (story_id | idea_id); idea-questions are
user-private with productId derived from idea.product_id
- wait_for_job: returns \`kind\` discriminator; IDEA_* payloads include
idea + prompt_text (from src/prompts/idea/) and skip worktree creation
- update_job_status: failed on IDEA_* auto-transitions idea-status to
GRILL_FAILED / PLAN_FAILED + IdeaLog{JOB_EVENT}; auto-PR + worktree-
cleanup skipped for idea-jobs
Other changes:
- Health version now read dynamically from package.json (was hardcoded
'0.1.0' which caused deploy-sync confusion)
- Schema synced to Scrum4Me M12 (Idea + IdeaLog + enums + ClaudeJob/
Question nullable-FKs + check-constraints + pg_notify-trigger update)
- New @scrum4me-mcp/lib/idea-plan-parser duplicates Scrum4Me's parser
(drift detected by vendor schema-watchdog)
- Embedded grill+make-plan prompts copied to src/prompts/idea/
- New userOwnsIdea access helper
Tests: 153/153 green; tsc + build clean.
Migration: requires Scrum4Me M12 migration (20260504172747_add_ideas_and_grill_jobs)
applied on the target DB. See vendor/scrum4me/docs/runbooks/mcp-integration.md
for the updated batch-loop with kind-switch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90 lines
3 KiB
TypeScript
90 lines
3 KiB
TypeScript
// MCP-tool: schrijft het plan_md-resultaat na een IDEA_MAKE_PLAN-job en
|
|
// transitioneert de idea-status naar PLAN_READY (bij geldige yaml-frontmatter)
|
|
// of PLAN_FAILED (bij parse-fout).
|
|
//
|
|
// Wordt aangeroepen door de worker als laatste stap van een make-plan-sessie.
|
|
|
|
import { z } from 'zod'
|
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
|
|
import { prisma } from '../prisma.js'
|
|
import { requireWriteAccess } from '../auth.js'
|
|
import { userOwnsIdea } from '../access.js'
|
|
import { toolError, toolJson, withToolErrors } from '../errors.js'
|
|
import { parsePlanMd } from '../lib/idea-plan-parser.js'
|
|
|
|
const inputSchema = z.object({
|
|
idea_id: z.string().min(1),
|
|
markdown: z.string().min(1).max(64_000),
|
|
})
|
|
|
|
export function registerUpdateIdeaPlanMdTool(server: McpServer) {
|
|
server.registerTool(
|
|
'update_idea_plan_md',
|
|
{
|
|
title: 'Update idea plan_md',
|
|
description:
|
|
'Save the make-plan-result markdown for an idea. Server validates yaml-frontmatter; on success status → PLAN_READY, on parse-fail → PLAN_FAILED. Forbidden for demo accounts.',
|
|
inputSchema,
|
|
},
|
|
async ({ idea_id, markdown }) =>
|
|
withToolErrors(async () => {
|
|
const auth = await requireWriteAccess()
|
|
if (!(await userOwnsIdea(idea_id, auth.userId))) {
|
|
return toolError('Idea not found')
|
|
}
|
|
|
|
const parsed = parsePlanMd(markdown)
|
|
|
|
if (!parsed.ok) {
|
|
// Persist md + flip to PLAN_FAILED + log de errors zodat de UI ze
|
|
// aan de user kan tonen.
|
|
const result = await prisma.$transaction([
|
|
prisma.idea.update({
|
|
where: { id: idea_id },
|
|
data: { plan_md: markdown, status: 'PLAN_FAILED' },
|
|
select: { id: true, status: true, code: true },
|
|
}),
|
|
prisma.ideaLog.create({
|
|
data: {
|
|
idea_id,
|
|
type: 'JOB_EVENT',
|
|
content: 'plan_md parse failed',
|
|
metadata: { errors: parsed.errors },
|
|
},
|
|
}),
|
|
])
|
|
return toolJson({
|
|
ok: false,
|
|
idea: result[0],
|
|
errors: parsed.errors,
|
|
})
|
|
}
|
|
|
|
const result = await prisma.$transaction([
|
|
prisma.idea.update({
|
|
where: { id: idea_id },
|
|
data: { plan_md: markdown, status: 'PLAN_READY' },
|
|
select: { id: true, status: true, code: true },
|
|
}),
|
|
prisma.ideaLog.create({
|
|
data: {
|
|
idea_id,
|
|
type: 'PLAN_RESULT',
|
|
content: `Plan ready: ${parsed.plan.stories.length} stories, ${parsed.plan.stories.reduce((n, s) => n + s.tasks.length, 0)} tasks`,
|
|
metadata: {
|
|
pbi_title: parsed.plan.pbi.title,
|
|
story_count: parsed.plan.stories.length,
|
|
task_count: parsed.plan.stories.reduce((n, s) => n + s.tasks.length, 0),
|
|
},
|
|
},
|
|
}),
|
|
])
|
|
|
|
return toolJson({
|
|
ok: true,
|
|
idea: result[0],
|
|
})
|
|
}),
|
|
)
|
|
}
|