scrum4me-mcp/src/tools/get-claude-context.ts
Madhura68 233e0ef3b6 chore: sync schema + adapt to ACTIVE→OPEN, COMPLETED→CLOSED, EXCLUDED-task
Webapp had Prisma-schema migrated: SprintStatus.ACTIVE→OPEN,
SprintStatus.COMPLETED→CLOSED, plus new SprintStatus.ARCHIVED. Also new
TaskStatus.EXCLUDED. scrum4me-mcp Prisma client was 110 commits behind,
causing runtime errors when reading sprint.status from the live DB:

  Value 'OPEN' not found in enum 'SprintStatus'

Symptom: TASK_IMPLEMENTATION jobs in QUEUED status were claimed by
tryClaimJob (raw SQL succeeds), then getFullJobContext crashed on the
findUnique with the enum error → rollbackClaim → loop forever until
UNHEALTHY (5 consecutive failures).

Fix:
- Updated vendor/scrum4me submodule to current main (3c77342).
- Re-ran sync-schema.sh → prisma/schema.prisma now has
  SprintStatus { OPEN, CLOSED, ARCHIVED, FAILED } and
  TaskStatus including EXCLUDED.
- src/lib/tasks-status-update.ts: ACTIVE→OPEN, COMPLETED→CLOSED.
- src/status.ts: TASK_DB_TO_API + TASK_API_TO_DB krijgen EXCLUDED entry.
- src/tools/get-claude-context.ts: status: 'ACTIVE' → status: 'OPEN'.

Tests: 340 passed (38 files). Typecheck OK.

Na merge + docker rebuild met cache-bust pakt de runner sprint-tasks weer
op zonder enum-error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:39:13 +02:00

129 lines
3.8 KiB
TypeScript

import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { prisma } from '../prisma.js'
import { getAuth } from '../auth.js'
import { toolError, toolJson, withToolErrors } from '../errors.js'
import { storyStatusToApi, taskStatusToApi } from '../status.js'
const inputSchema = z.object({
product_id: z.string().min(1),
})
export function registerGetClaudeContextTool(server: McpServer) {
server.registerTool(
'get_claude_context',
{
title: 'Bundled context for Claude Code',
description:
'Fetch product, active sprint, next story (with tasks) and open todos in one call. ' +
'Always start a Scrum4Me workflow with this tool.',
inputSchema,
annotations: { readOnlyHint: true },
},
async ({ product_id }) =>
withToolErrors(async () => {
const auth = await getAuth()
const product = await prisma.product.findFirst({
where: {
id: product_id,
OR: [
{ user_id: auth.userId },
{ members: { some: { user_id: auth.userId } } },
],
},
select: {
id: true,
code: true,
name: true,
description: true,
repo_url: true,
definition_of_done: true,
},
})
if (!product) {
return toolError(`Product ${product_id} not found or not accessible`)
}
const activeSprint = await prisma.sprint.findFirst({
where: { product_id, status: 'OPEN' },
orderBy: { created_at: 'desc' },
select: { id: true, sprint_goal: true, status: true },
})
let nextStory = null
if (activeSprint) {
const story = await prisma.story.findFirst({
where: {
sprint_id: activeSprint.id,
status: { in: ['OPEN', 'IN_SPRINT'] },
OR: [
{ tasks: { none: {} } },
{ tasks: { some: { status: { not: 'DONE' } } } },
],
},
orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }],
select: {
id: true,
code: true,
title: true,
description: true,
acceptance_criteria: true,
priority: true,
status: true,
tasks: {
orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }],
select: {
id: true,
title: true,
description: true,
implementation_plan: true,
priority: true,
sort_order: true,
status: true,
},
},
},
})
if (story) {
nextStory = {
...story,
status: storyStatusToApi(story.status),
tasks: story.tasks.map((t, i) => ({
...t,
code: story.code ? `${story.code}.${i + 1}` : null,
status: taskStatusToApi(t.status),
})),
}
}
}
const openIdeas = await prisma.idea.findMany({
where: {
user_id: auth.userId,
archived: false,
status: { not: 'PLANNED' },
OR: [{ product_id: product_id }, { product_id: null }],
},
orderBy: { created_at: 'asc' },
take: 50,
select: {
id: true,
code: true,
title: true,
description: true,
status: true,
created_at: true,
},
})
return toolJson({
product,
active_sprint: activeSprint,
next_story: nextStory,
open_ideas: openIdeas,
})
}),
)
}