Skip to main content
Glama
gcorroto

Planka MCP Server

by gcorroto

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
NameRequiredDescriptionDefault
actionYesThe action to perform
idYesThe 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) }],
        };
      }
    );
  • Handler for starting the card stopwatch: sets startedAt to current time, preserves existing total, updates card via PATCH /api/cards/{id} with stopwatch object
    export 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)
                }`,
            );
        }
    }
  • Handler for stopping the card stopwatch: calculates elapsed time, adds to total, sets startedAt to null, updates via PATCH
    export 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)
                }`,
            );
        }
    }
  • Handler for getting card stopwatch status: returns isRunning, total time, current elapsed if running, formatted durations using internal formatDuration helper
    export 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)
                }`,
            );
        }
    }
  • Handler for resetting the card stopwatch: patches the card with stopwatch: null to clear it
    export 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)
                }`,
            );
        }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. 'Manage' implies mutation operations (starting/stopping/resetting), but the description does not specify permissions required, side effects (e.g., whether stopping records time data), or error conditions. It lacks details on what 'get' returns or how time data is stored, leaving significant gaps for a tool with multiple action types.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence with zero waste. It is front-loaded with the core purpose and avoids unnecessary elaboration. Every word earns its place by directly stating the tool's function, making it highly concise and well-structured for quick understanding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (multiple actions like start, stop, get, reset) and lack of annotations and output schema, the description is incomplete. It does not explain return values, error handling, or behavioral nuances (e.g., what happens if you start a stopwatch that's already running). For a mutation-heavy tool with no structured output, more context is needed to guide effective use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, with clear documentation for both parameters ('action' with enum values and 'id' as card ID). The description adds no additional meaning beyond the schema, such as explaining the relationship between actions or what 'id' refers to in the context of stopwatches. Since schema coverage is high, the baseline score of 3 is appropriate, as the description does not compensate but also does not detract.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose as 'Manage card stopwatches for time tracking', which includes a specific verb ('Manage') and resource ('card stopwatches') with a clear functional context ('for time tracking'). It distinguishes this tool from siblings like 'mcp_kanban_card_manager' or 'mcp_kanban_task_manager' by focusing on stopwatch functionality, but could be more precise about what 'manage' entails (e.g., starting, stopping, etc.).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It does not mention any prerequisites, such as needing an existing card, or differentiate from potential overlapping tools (e.g., if time tracking is also handled elsewhere). With no explicit usage context or exclusions, the agent must infer usage from the tool name and parameters alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gcorroto/mcp-planka'

If you have feedback or need assistance with the MCP directory API, please join our Discord server