Skip to main content
Glama
tasks.ts10.6 kB
/** * @fileoverview Task operations for the MCP Kanban server * * This module provides functions for interacting with tasks in the Planka Kanban board, * including creating, retrieving, updating, and deleting tasks, as well as batch operations. */ import { z } from "zod"; import { plankaRequest } from "../common/utils.js"; import { PlankaTaskSchema } from "../common/types.js"; // Schema definitions /** * Schema for creating a new task * @property {string} cardId - The ID of the card to create the task in * @property {string} name - The name of the task * @property {number} [position] - The position of the task in the card (default: 65535) */ export const CreateTaskSchema = z.object({ cardId: z.string().describe("Card ID"), name: z.string().describe("Task name"), position: z.number().optional().describe("Task position (default: 65535)"), }); /** * Schema for batch creating multiple tasks * @property {Array<CreateTaskSchema>} tasks - Array of tasks to create */ export const BatchCreateTasksSchema = z.object({ tasks: z.array(CreateTaskSchema).describe("Array of tasks to create"), }); /** * Schema for retrieving tasks from a card * @property {string} cardId - The ID of the card to get tasks from */ export const GetTasksSchema = z.object({ cardId: z.string().describe("Card ID"), }); /** * Schema for retrieving a specific task * @property {string} id - The ID of the task to retrieve * @property {string} [cardId] - The ID of the card containing the task */ export const GetTaskSchema = z.object({ id: z.string().describe("Task ID"), cardId: z.string().optional().describe("Card ID containing the task"), }); /** * Schema for updating a task * @property {string} id - The ID of the task to update * @property {string} [name] - The new name for the task * @property {boolean} [isCompleted] - Whether the task is completed * @property {number} [position] - The new position for the task */ export const UpdateTaskSchema = z.object({ id: z.string().describe("Task ID"), name: z.string().optional().describe("Task name"), isCompleted: z.boolean().optional().describe( "Whether the task is completed", ), position: z.number().optional().describe("Task position"), }); /** * Schema for deleting a task * @property {string} id - The ID of the task to delete */ export const DeleteTaskSchema = z.object({ id: z.string().describe("Task ID"), }); // Type exports /** * Type definition for task creation options */ export type CreateTaskOptions = z.infer<typeof CreateTaskSchema>; /** * Type definition for batch task creation options */ export type BatchCreateTasksOptions = z.infer<typeof BatchCreateTasksSchema>; /** * Type definition for task update options */ export type UpdateTaskOptions = z.infer<typeof UpdateTaskSchema>; // Response schemas const TasksResponseSchema = z.object({ items: z.array(PlankaTaskSchema), included: z.record(z.any()).optional(), }); const TaskResponseSchema = z.object({ item: PlankaTaskSchema, included: z.record(z.any()).optional(), }); // Map to store task ID to card ID mapping const taskCardIdMap: Record<string, string> = {}; // Function implementations /** * Creates a new task for a card * * @param {object} params - The task creation parameters * @param {string} params.cardId - The ID of the card to create the task in * @param {string} params.name - The name of the new task * @param {number} params.position - The position of the task in the card * @returns {Promise<object>} The created task */ export async function createTask(params: { cardId: string; name: string; position?: number; }) { try { const { cardId, name, position = 65535 } = params; const response: any = await plankaRequest( `/api/cards/${cardId}/tasks`, { method: "POST", body: { name, position }, }, ); // Store the task ID to card ID mapping for getTask if (response.item && response.item.id) { taskCardIdMap[response.item.id] = cardId; } return response.item; } catch (error) { console.error("Error creating task:", error); throw new Error( `Failed to create task: ${ error instanceof Error ? error.message : String(error) }`, ); } } /** * Creates multiple tasks for cards in a single operation * * @param {BatchCreateTasksOptions} options - The batch create tasks options * @returns {Promise<{results: any[], successes: any[], failures: TaskError[]}>} The results of the batch operation * @throws {Error} If the batch operation fails completely */ export async function batchCreateTasks(options: BatchCreateTasksOptions) { try { const results: Array<any> = []; const successes: Array<any> = []; const failures: Array<any> = []; /** * Interface for task operation result * @property {boolean} success - Whether the operation was successful * @property {any} [result] - The result of the operation if successful * @property {object} [error] - The error if the operation failed * @property {string} error.message - The error message */ interface TaskResult { success: boolean; result?: any; error?: { message: string }; } /** * Interface for task operation error * @property {number} index - The index of the task in the original array * @property {CreateTaskOptions} task - The task that failed * @property {string} error - The error message */ interface TaskError { index: number; task: CreateTaskOptions; error: string; } // Process each task in sequence for (let i = 0; i < options.tasks.length; i++) { const task = options.tasks[i]; // Ensure position is set if not provided if (!task.position) { task.position = 65535 * (i + 1); } try { const result = await createTask(task); results.push({ success: true, result, }); successes.push(result); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); results.push({ success: false, error: { message: errorMessage }, }); failures.push({ index: i, task, error: errorMessage, }); } } return { results, successes, failures, }; } catch (error) { throw new Error( `Failed to batch create tasks: ${ error instanceof Error ? error.message : String(error) }`, ); } } /** * Retrieves all tasks for a specific card * * @param {string} cardId - The ID of the card to get tasks from * @returns {Promise<Array<object>>} Array of tasks in the card */ export async function getTasks(cardId: string) { try { // Instead of using the tasks endpoint which returns HTML, // we'll get the card details which includes tasks const response = await plankaRequest(`/api/cards/${cardId}`) as { item: any; included?: { tasks?: any[]; }; }; // Extract tasks from the card response if ( response?.included?.tasks && Array.isArray(response.included.tasks) ) { const tasks = response.included.tasks; return tasks; } return []; } catch (error) { console.error(`Error getting tasks for card ${cardId}:`, error); // If there's an error, return an empty array return []; } } /** * Retrieves a specific task by ID * * @param {string} id - The ID of the task to retrieve * @param {string} [cardId] - Optional card ID to help find the task * @returns {Promise<object>} The requested task */ export async function getTask(id: string, cardId?: string) { try { // Tasks in Planka are always part of a card, so we need the card ID const taskCardId = cardId || taskCardIdMap[id]; if (!taskCardId) { throw new Error( "Card ID is required to get a task. Either provide it directly or create the task first.", ); } // Get the card details which includes tasks const response = await plankaRequest(`/api/cards/${taskCardId}`) as { item: any; included?: { tasks?: any[]; }; }; if ( !response?.included?.tasks || !Array.isArray(response.included.tasks) ) { throw new Error(`Failed to get tasks for card ${taskCardId}`); } // Find the task with the matching ID const task = response.included.tasks.find((task: any) => task.id === id ); if (!task) { throw new Error( `Task with ID ${id} not found in card ${taskCardId}`, ); } return task; } catch (error) { console.error(`Error getting task with ID ${id}:`, error); throw new Error( `Failed to get task: ${ error instanceof Error ? error.message : String(error) }`, ); } } /** * Updates a task's properties * * @param {string} id - The ID of the task to update * @param {Partial<Omit<CreateTaskOptions, "cardId">>} options - The properties to update * @returns {Promise<object>} The updated task */ export async function updateTask( id: string, options: Partial<Omit<CreateTaskOptions, "cardId">>, ) { const response = await plankaRequest(`/api/tasks/${id}`, { method: "PATCH", body: options, }); const parsedResponse = TaskResponseSchema.parse(response); return parsedResponse.item; } /** * Deletes a task by ID * * @param {string} id - The ID of the task to delete * @returns {Promise<{success: boolean}>} Success indicator */ export async function deleteTask(id: string) { await plankaRequest(`/api/tasks/${id}`, { method: "DELETE", }); return { success: true }; }

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