mcp_kanban_stopwatch
Track time on Planka Kanban cards by starting, stopping, getting, or resetting stopwatches through the Planka MCP Server for efficient task management.
Instructions
Manage card stopwatches for time tracking
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | The action to perform | |
| id | Yes | The ID of the card |
Implementation Reference
- index.ts:386-423 (registration)Registers the mcp_kanban_stopwatch tool, defines input schema (action: start/stop/get/reset, id: card ID), and provides dispatcher handler that calls specific stopwatch functions from the cards module.server.tool( "mcp_kanban_stopwatch", "Manage card stopwatches for time tracking", { action: z .enum(["start", "stop", "get", "reset"]) .describe("The action to perform"), id: z.string().describe("The ID of the card"), }, async (args) => { let result; switch (args.action) { case "start": result = await cards.startCardStopwatch(args.id); break; case "stop": result = await cards.stopCardStopwatch(args.id); break; case "get": result = await cards.getCardStopwatch(args.id); break; case "reset": result = await cards.resetCardStopwatch(args.id); break; default: throw new Error(`Unknown action: ${args.action}`); } return { content: [{ type: "text", text: JSON.stringify(result) }], }; } );
- operations/cards.ts:382-413 (handler)Handler for starting the card stopwatch: sets startedAt to current time, preserves existing total, updates card via PATCH /api/cards/{id} with stopwatch objectexport async function startCardStopwatch(id: string) { try { // Get the current card to check if a stopwatch is already running const card = await getCard(id); // Calculate the stopwatch object let stopwatch = { startedAt: new Date().toISOString(), total: 0, }; // If there's an existing stopwatch, preserve the total time if (card.stopwatch && card.stopwatch.total) { stopwatch.total = card.stopwatch.total; } // Update the card with the new stopwatch const response = await plankaRequest(`/api/cards/${id}`, { method: "PATCH", body: { stopwatch }, }); const parsedResponse = CardResponseSchema.parse(response); return parsedResponse.item; } catch (error) { throw new Error( `Failed to start card stopwatch: ${ error instanceof Error ? error.message : String(error) }`, ); } }
- operations/cards.ts:421-461 (handler)Handler for stopping the card stopwatch: calculates elapsed time, adds to total, sets startedAt to null, updates via PATCHexport async function stopCardStopwatch(id: string) { try { // Get the current card to calculate elapsed time const card = await getCard(id); // If there's no stopwatch or it's not running, return the card as is if (!card.stopwatch || !card.stopwatch.startedAt) { return card; } // Calculate elapsed time const startedAt = new Date(card.stopwatch.startedAt); const now = new Date(); const elapsedSeconds = Math.floor( (now.getTime() - startedAt.getTime()) / 1000, ); // Calculate the new total time const totalSeconds = (card.stopwatch.total || 0) + elapsedSeconds; // Update the card with the stopped stopwatch (null startedAt but preserved total) const stopwatch = { startedAt: null, total: totalSeconds, }; const response = await plankaRequest(`/api/cards/${id}`, { method: "PATCH", body: { stopwatch }, }); const parsedResponse = CardResponseSchema.parse(response); return parsedResponse.item; } catch (error) { throw new Error( `Failed to stop card stopwatch: ${ error instanceof Error ? error.message : String(error) }`, ); } }
- operations/cards.ts:469-511 (handler)Handler for getting card stopwatch status: returns isRunning, total time, current elapsed if running, formatted durations using internal formatDuration helperexport async function getCardStopwatch(id: string) { try { const card = await getCard(id); // If there's no stopwatch, return default values if (!card.stopwatch) { return { isRunning: false, total: 0, current: 0, formattedTotal: formatDuration(0), formattedCurrent: formatDuration(0), }; } // Calculate current elapsed time if stopwatch is running let currentElapsed = 0; const isRunning = !!card.stopwatch.startedAt; if (isRunning && card.stopwatch.startedAt) { const startedAt = new Date(card.stopwatch.startedAt); const now = new Date(); currentElapsed = Math.floor( (now.getTime() - startedAt.getTime()) / 1000, ); } return { isRunning, total: card.stopwatch.total || 0, current: currentElapsed, startedAt: card.stopwatch.startedAt, formattedTotal: formatDuration(card.stopwatch.total || 0), formattedCurrent: formatDuration(currentElapsed), }; } catch (error) { throw new Error( `Failed to get card stopwatch: ${ error instanceof Error ? error.message : String(error) }`, ); } }
- operations/cards.ts:519-536 (handler)Handler for resetting the card stopwatch: patches the card with stopwatch: null to clear itexport async function resetCardStopwatch(id: string) { try { // Set stopwatch to null to clear it const response = await plankaRequest(`/api/cards/${id}`, { method: "PATCH", body: { stopwatch: null }, }); const parsedResponse = CardResponseSchema.parse(response); return parsedResponse.item; } catch (error) { throw new Error( `Failed to reset card stopwatch: ${ error instanceof Error ? error.message : String(error) }`, ); } }