scrum4me-mcp/src/tools/cancel-question.ts
Madhura68 7b955d31ac feat(ST-1102): add 4 question-channel MCP tools (M11)
Vier nieuwe tools voor het Claude vraag-antwoord-kanaal:
- ask_user_question (write): post een gestructureerde vraag aan de actieve
  Scrum4Me-gebruiker over een story; default async (returnt direct met
  question_id + status='open'); optionele wait_seconds (max 600) polt elke 2s
  tot het antwoord er is of timeout — daarna status='pending' zodat Claude met
  get_question_answer later kan ophalen
- get_question_answer (read): huidige status + antwoord van een eerder
  gestelde vraag
- list_open_questions (read): eigen vragen met status open/answered, max 50,
  meest recente eerst
- cancel_question (write, asker-only): atomic UPDATE WHERE asked_by + status=
  'open' zodat alleen eigen open vragen geannuleerd worden

Allemaal achter access-check via userCanAccessStory/Product en demo-blok via
requireWriteAccess (volgt patroon van create-todo en bestaande log-tools).

Submodule vendor/scrum4me bumpt naar Scrum4Me commit 79367dd (M11 ST-1101) —
bevat het ClaudeQuestion-model en notify_question_change-trigger waar deze
tools tegen werken.

scripts/smoke-test.ts: 13 tools verwacht (was 9); list_open_questions
toegevoegd als read-tool-coverage. Build + tools/list groen — verdere e2e via
MCP Inspector na PR-merge omdat de seed een nieuwe API-token heeft
gegenereerd en .env een nieuwe waarde nodig heeft.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 01:00:59 +02:00

52 lines
1.8 KiB
TypeScript

// ST-1102: cancel_question — alleen de asker (Claude) annuleert een eigen open
// vraag, bv. wanneer hij in de loop van het werk de oplossing zelf vindt en
// het antwoord niet meer relevant is.
import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { prisma } from '../prisma.js'
import { requireWriteAccess } from '../auth.js'
import { toolError, toolJson, withToolErrors } from '../errors.js'
const inputSchema = z.object({
question_id: z.string().min(1),
})
export function registerCancelQuestionTool(server: McpServer) {
server.registerTool(
'cancel_question',
{
title: 'Cancel question',
description:
'Cancel an own open Claude question (only the asker may cancel). ' +
'Forbidden for demo accounts.',
inputSchema,
},
async ({ question_id }) =>
withToolErrors(async () => {
const auth = await requireWriteAccess()
// Atomic: alleen open vragen van deze asker mogen cancelled worden.
const result = await prisma.claudeQuestion.updateMany({
where: {
id: question_id,
asked_by: auth.userId,
status: 'open',
},
data: { status: 'cancelled' },
})
if (result.count !== 1) {
// Disambigueer: bestaat de vraag voor deze asker?
const exists = await prisma.claudeQuestion.findFirst({
where: { id: question_id, asked_by: auth.userId },
select: { status: true },
})
if (!exists) return toolError(`Question ${question_id} not found or not yours`)
return toolError(`Question ${question_id} is already ${exists.status}`)
}
return toolJson({ question_id, status: 'cancelled' })
}),
)
}