scrum4me-mcp/src/index.ts
Madhura68 25ab68073a PBI-50 F3: nieuwe MCP-tools voor SPRINT_IMPLEMENTATION-flow
Vier nieuwe tools + propagateStatusUpwards uitbreiding:

T1 — verify_sprint_task (src/tools/verify-sprint-task.ts):
  Execution-aware verify met frozen plan_snapshot. Input: execution_id +
  worktree_path + optionele summary (voor PARTIAL/DIVERGENT-rationale).
  Vult base_sha dynamisch voor task[1..N] op basis van vorige DONE-execution's
  head_sha. Schrijft verify_result + verify_summary op execution-row.
  Returns { result, reasoning, base_sha, allowed_for_done, reason? } —
  allowed_for_done via standaard checkVerifyGate met snapshot-velden.

T2 — update_task_execution (src/tools/update-task-execution.ts):
  Lifecycle-tool voor SprintTaskExecution: PENDING/RUNNING/DONE/FAILED/SKIPPED
  + base_sha/head_sha/skip_reason. Idempotent. Token-check via
  execution.sprint_job.claimed_by_token_id. started_at/finished_at automatisch.

T3 — job_heartbeat (src/tools/job-heartbeat.ts):
  Verlengt ClaudeJob.lease_until met 5 min via atomic conditional UPDATE
  (token-check + status-check in WHERE). Voor SPRINT-jobs: response bevat
  sprint_run_status + sprint_run_pause_reason zodat worker op UI-side cancel
  of MERGE_CONFLICT-pause kan breken zonder extra query.

T4 — update_task_status sprint_run_id-arg + token-coupling
  (src/tools/update-task-status.ts):
  Optionele sprint_run_id-arg voor expliciete cascade. Validaties: SprintRun
  bestaat + actief, task in deze sprint, current token heeft een actieve
  ClaudeJob in deze run geclaimd (403 anders). Response uitgebreid met
  sprint_run_status_change.

T5 — propagateStatusUpwards sprintRunId-param
  (src/lib/tasks-status-update.ts):
  Optionele sprintRunId-parameter. Resolve-volgorde: expliciete arg →
  ClaudeJob.task_id-lookup → Story → Sprint → SprintRun.findFirst({active}).
  De derde fallback dekt SPRINT_IMPLEMENTATION (geen task_id-koppeling) én
  handmatige task-statuswijzigingen via UI. cancelExceptJobId voor
  sibling-cancel; null voor SPRINT-job betekent geen siblings te cancellen.

src/index.ts: drie nieuwe tools geregistreerd.

Tests: 31 files, 243 passing (geen tests voor nieuwe tools nog — F5).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:40:18 +02:00

124 lines
5.8 KiB
JavaScript

#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { registerHealthTool } from './tools/health.js'
import { registerListProductsTool } from './tools/list-products.js'
import { registerGetClaudeContextTool } from './tools/get-claude-context.js'
import { registerUpdateTaskStatusTool } from './tools/update-task-status.js'
import { registerUpdateTaskPlanTool } from './tools/update-task-plan.js'
import { registerLogImplementationTool } from './tools/log-implementation.js'
import { registerLogTestResultTool } from './tools/log-test-result.js'
import { registerLogCommitTool } from './tools/log-commit.js'
import { registerCreatePbiTool } from './tools/create-pbi.js'
import { registerCreateStoryTool } from './tools/create-story.js'
import { registerCreateTaskTool } from './tools/create-task.js'
import { registerAskUserQuestionTool } from './tools/ask-user-question.js'
import { registerGetQuestionAnswerTool } from './tools/get-question-answer.js'
import { registerListOpenQuestionsTool } from './tools/list-open-questions.js'
import { registerCancelQuestionTool } from './tools/cancel-question.js'
import { registerWaitForJobTool } from './tools/wait-for-job.js'
import { registerUpdateJobStatusTool } from './tools/update-job-status.js'
import { registerVerifyTaskAgainstPlanTool } from './tools/verify-task-against-plan.js'
import { registerCleanupMyWorktreesTool } from './tools/cleanup-my-worktrees.js'
import { registerCheckQueueEmptyTool } from './tools/check-queue-empty.js'
import { registerSetPbiPrTool } from './tools/set-pbi-pr.js'
import { registerMarkPbiPrMergedTool } from './tools/mark-pbi-pr-merged.js'
import { registerGetIdeaContextTool } from './tools/get-idea-context.js'
import { registerUpdateIdeaGrillMdTool } from './tools/update-idea-grill-md.js'
import { registerUpdateIdeaPlanMdTool } from './tools/update-idea-plan-md.js'
import { registerLogIdeaDecisionTool } from './tools/log-idea-decision.js'
import { registerGetWorkerSettingsTool } from './tools/get-worker-settings.js'
import { registerWorkerHeartbeatTool } from './tools/worker-heartbeat.js'
// PBI-50: SPRINT_IMPLEMENTATION-tools
import { registerVerifySprintTaskTool } from './tools/verify-sprint-task.js'
import { registerUpdateTaskExecutionTool } from './tools/update-task-execution.js'
import { registerJobHeartbeatTool } from './tools/job-heartbeat.js'
import { registerImplementNextStoryPrompt } from './prompts/implement-next-story.js'
import { getAuth } from './auth.js'
import { registerWorker } from './presence/worker.js'
import { startHeartbeat } from './presence/heartbeat.js'
import { registerShutdownHandlers } from './presence/shutdown.js'
import { readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
// Read version dynamically from package.json — voorheen hardcoded en
// veroorzaakte sync-issues bij deployment. Lees op module-load.
function readPkgVersion(): string {
try {
const here = dirname(fileURLToPath(import.meta.url))
const pkgPath = join(here, '..', 'package.json')
return (JSON.parse(readFileSync(pkgPath, 'utf8')) as { version?: string }).version ?? '0.0.0'
} catch {
return '0.0.0'
}
}
const VERSION = readPkgVersion()
async function main() {
const server = new McpServer(
{ name: 'scrum4me-mcp', version: VERSION },
{
instructions:
'Scrum4Me dev-flow tools: read product/sprint/story context, update tasks, log activity. ' +
'Always call get_claude_context before starting work to fetch the next story.',
},
)
registerHealthTool(server)
registerListProductsTool(server)
registerGetClaudeContextTool(server)
registerUpdateTaskStatusTool(server)
registerUpdateTaskPlanTool(server)
registerLogImplementationTool(server)
registerLogTestResultTool(server)
registerLogCommitTool(server)
registerCreatePbiTool(server)
registerCreateStoryTool(server)
registerCreateTaskTool(server)
registerAskUserQuestionTool(server)
registerGetQuestionAnswerTool(server)
registerListOpenQuestionsTool(server)
registerCancelQuestionTool(server)
registerWaitForJobTool(server)
registerUpdateJobStatusTool(server)
registerVerifyTaskAgainstPlanTool(server)
registerCleanupMyWorktreesTool(server)
registerCheckQueueEmptyTool(server)
registerSetPbiPrTool(server)
registerMarkPbiPrMergedTool(server)
// M12: idee-job tools
registerGetIdeaContextTool(server)
registerUpdateIdeaGrillMdTool(server)
registerUpdateIdeaPlanMdTool(server)
registerLogIdeaDecisionTool(server)
// M13: worker quota-gate tools
registerGetWorkerSettingsTool(server)
registerWorkerHeartbeatTool(server)
// PBI-50: SPRINT_IMPLEMENTATION-tools
registerVerifySprintTaskTool(server)
registerUpdateTaskExecutionTool(server)
registerJobHeartbeatTool(server)
registerImplementNextStoryPrompt(server)
// Presence bootstrap MUST run before server.connect — the stdio transport
// can stall the await on incoming messages, so anything after server.connect
// may never execute reliably. Registering the worker + starting the
// heartbeat first guarantees the UI sees the agent as soon as the process
// is up, regardless of when the MCP client sends its first request.
const auth = await getAuth()
await registerWorker({ userId: auth.userId, tokenId: auth.tokenId })
const { stop: stopHeartbeat } = startHeartbeat({ userId: auth.userId, tokenId: auth.tokenId })
registerShutdownHandlers({ userId: auth.userId, tokenId: auth.tokenId, stopHeartbeat })
const transport = new StdioServerTransport()
await server.connect(transport)
console.error(`scrum4me-mcp ${VERSION} running on stdio`)
}
main().catch((err) => {
console.error('Fatal error in scrum4me-mcp:', err)
process.exit(1)
})