delete_task_file
Permanently delete a file attachment from a Kanboard task. Requires explicit confirmation to avoid accidental removal.
Instructions
Permanently delete a file attachment from a Kanboard task. DESTRUCTIVE — requires explicit confirm: true. Returns { ok: true, file_id } on success.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_id | Yes | File id to permanently delete (required). | |
| confirm | Yes | Must be exactly `true` to confirm permanent deletion. |
Implementation Reference
- src/tools/delete-task-file.ts:57-86 (handler)The deleteTaskFileTool handler — parses input, confirms destruction, calls handler.removeTaskFile(file_id), and returns success with { ok: true, file_id }.
export const deleteTaskFileTool = { name: "delete_task_file", description: "Permanently delete a file attachment from a Kanboard task. " + "DESTRUCTIVE — requires explicit `confirm: true`. " + "Returns { ok: true, file_id } on success.", inputSchema: DeleteTaskFileInput, handler: async (raw: unknown, deps: ToolDeps): Promise<DeleteTaskFileResult> => { const parsed = DeleteTaskFileInput.safeParse(raw); if (!parsed.success) { throw new ValidationError( "delete_task_file", parsed.error.issues.map((i) => i.message).join("; "), parsed.error.issues, ); } const input = parsed.data; assertConfirmed("delete_task_file", input.confirm); await deps.handler.removeTaskFile(input.file_id); return { content: [ { type: "text", text: `Task file ${String(input.file_id)} deleted permanently.` }, ], structuredContent: { ok: true, file_id: input.file_id }, }; }, }; - src/tools/delete-task-file.ts:20-33 (schema)DeleteTaskFileInput Zod schema: requires file_id (positive int) and confirm (literal true).
export const DeleteTaskFileInput = z .object({ file_id: z .number() .int() .positive() .describe("File id to permanently delete (required)."), confirm: z .literal(true) .describe("Must be exactly `true` to confirm permanent deletion."), }) .strict(); export type DeleteTaskFileInput = z.infer<typeof DeleteTaskFileInput>; - src/handler/kanboard.ts:1051-1060 (handler)KanboardHandler.removeTaskFile — calls Kanboard's removeTaskFile JSON-RPC method with file_id. This is the underlying API call executed by the tool handler.
/** * Permanently removes a task attachment (file). * Kanboard's wire param for this method is `file_id`. * @throws {KanboardApiError} when Kanboard returns false. */ public async removeTaskFile(fileId: number): Promise<void> { const raw = await this.#apiClient.call("removeTaskFile", { file_id: fileId }); this.#logger.debug({ method: "removeTaskFile" }, "removeTaskFile OK"); decodeMutation("removeTaskFile", raw); } - src/tools/index.ts:52-233 (registration)deleteTaskFileTool imported from ./delete-task-file.js and registered in the allTools array (line 175) so registerTools() mounts it on the MCP server.
import { deleteTaskFileTool } from "./delete-task-file.js"; import { getProjectTool } from "./get-project.js"; import { getTaskTool } from "./get-task.js"; import { listCategoriesTool } from "./list-categories.js"; import { listColumnsTool } from "./list-columns.js"; import { listMyTasksTool } from "./list-my-tasks.js"; import { listOverdueTasksTool } from "./list-overdue-tasks.js"; import { listProjectsTool } from "./list-projects.js"; import { listSubtasksTool } from "./list-subtasks.js"; import { listSwimlanesTool } from "./list-swimlanes.js"; import { listTasksTool } from "./list-tasks.js"; import { listProjectUsersTool } from "./list-project-users.js"; import { moveColumnTool } from "./move-column.js"; import { moveSwimlaneTool } from "./move-swimlane.js"; import { moveTaskPositionTool } from "./move-task-position.js"; import { removeProjectUserTool } from "./remove-project-user.js"; import { updateColumnTool } from "./update-column.js"; import { updateCommentTool } from "./update-comment.js"; import { updateProjectTool } from "./update-project.js"; import { updateSubtaskTool } from "./update-subtask.js"; import { updateSwimlaneTool } from "./update-swimlane.js"; import { updateTaskTool } from "./update-task.js"; // --------------------------------------------------------------------------- // Re-exports — individual tools (transports may pick them selectively) // --------------------------------------------------------------------------- export { addProjectUserTool } from "./add-project-user.js"; export { attachFileToTaskTool } from "./attach-file-to-task.js"; export { createColumnTool } from "./create-column.js"; export { createCommentTool } from "./create-comment.js"; export { createProjectTool } from "./create-project.js"; export { createSubtaskTool } from "./create-subtask.js"; export { createSwimlaneTool } from "./create-swimlane.js"; export { createTaskTool } from "./create-task.js"; export { createTasksBatchTool } from "./create-tasks-batch.js"; export { deleteColumnTool } from "./delete-column.js"; export { deleteCommentTool } from "./delete-comment.js"; export { deleteProjectTool } from "./delete-project.js"; export { deleteSubtaskTool } from "./delete-subtask.js"; export { deleteSwimlaneTool } from "./delete-swimlane.js"; export { deleteTaskTool } from "./delete-task.js"; export { deleteTaskFileTool } from "./delete-task-file.js"; export { getProjectTool } from "./get-project.js"; export { getTaskTool } from "./get-task.js"; export { listCategoriesTool } from "./list-categories.js"; export { listColumnsTool } from "./list-columns.js"; export { listMyTasksTool } from "./list-my-tasks.js"; export { listOverdueTasksTool } from "./list-overdue-tasks.js"; export { listProjectsTool } from "./list-projects.js"; export { listSubtasksTool } from "./list-subtasks.js"; export { listSwimlanesTool } from "./list-swimlanes.js"; export { listTasksTool } from "./list-tasks.js"; export { listProjectUsersTool } from "./list-project-users.js"; export { moveColumnTool } from "./move-column.js"; export { moveSwimlaneTool } from "./move-swimlane.js"; export { moveTaskPositionTool } from "./move-task-position.js"; export { removeProjectUserTool } from "./remove-project-user.js"; export { updateColumnTool } from "./update-column.js"; export { updateCommentTool } from "./update-comment.js"; export { updateProjectTool } from "./update-project.js"; export { updateSubtaskTool } from "./update-subtask.js"; export { updateSwimlaneTool } from "./update-swimlane.js"; export { updateTaskTool } from "./update-task.js"; // --------------------------------------------------------------------------- // Shared deps interface — used by transports to wire everything together // --------------------------------------------------------------------------- /** * Runtime dependencies required by every tool handler. * * `logger` is optional; tools that need logging should accept it defensively. */ export interface ToolDeps { handler: KanboardHandler; resolvers: Resolvers; logger?: pino.Logger; } // --------------------------------------------------------------------------- // ToolDef — canonical shape of every tool object in this project // --------------------------------------------------------------------------- /** * Canonical shape every tool object exported from `src/tools/<name>.ts` must * conform to. The `handler` signature uses `unknown` for the result so that * the registration loop is agnostic to each tool's concrete return type — the * MCP SDK handles serialisation. */ export interface ToolDef { name: string; description: string; inputSchema: ZodTypeAny; handler: (raw: unknown, deps: ToolDeps) => Promise<unknown>; } // --------------------------------------------------------------------------- // allTools — ordered registry // --------------------------------------------------------------------------- /** * All 37 Kanboard MCP tools in alphabetical order. * * Order is fixed so that any slice/comparison in tests and transports is * deterministic across environments. */ export const allTools: readonly ToolDef[] = [ addProjectUserTool, attachFileToTaskTool, createColumnTool, createCommentTool, createProjectTool, createSubtaskTool, createSwimlaneTool, createTaskTool, createTasksBatchTool, deleteColumnTool, deleteCommentTool, deleteProjectTool, deleteSubtaskTool, deleteSwimlaneTool, deleteTaskTool, deleteTaskFileTool, getProjectTool, getTaskTool, listCategoriesTool, listColumnsTool, listMyTasksTool, listOverdueTasksTool, listProjectsTool, listSubtasksTool, listSwimlanesTool, listTasksTool, listProjectUsersTool, moveColumnTool, moveSwimlaneTool, moveTaskPositionTool, removeProjectUserTool, updateColumnTool, updateCommentTool, updateProjectTool, updateSubtaskTool, updateSwimlaneTool, updateTaskTool, ] as const; // --------------------------------------------------------------------------- // registerTools — mount all tools on the MCP server // --------------------------------------------------------------------------- /** * Register all Kanboard tools on a given `McpServer` instance. * * Each tool is attached via `server.registerTool(name, config, callback)`. * The SDK validates incoming input against the tool's Zod `inputSchema` * before the callback is invoked, so the tool handler receives already-parsed * args — but we pass the raw record through `tool.handler(args, deps)` which * re-validates internally for belt-and-suspenders safety. * * @param server - The `McpServer` instance to register tools on. * @param deps - Shared tool dependencies (handler, resolvers, optional logger). */ export function registerTools(server: McpServer, deps: ToolDeps): void { for (const tool of allTools) { // Cast: each tool handler returns a `{ content, structuredContent }` object // that satisfies `CallToolResult`. We use `unknown` in `ToolDef.handler` to // keep the per-tool return types encapsulated, so we cast here at the // registration boundary where the MCP SDK takes ownership. const cb = ((args: Record<string, unknown>) => tool.handler(args, deps)) as unknown as ToolCallback; server.registerTool( tool.name, { description: tool.description, inputSchema: tool.inputSchema, }, cb, ); } } - src/shared/confirm.ts:28-38 (helper)assertConfirmed helper used by the tool handler to enforce confirm: true before performing the destructive delete.
export function assertConfirmed(toolName: string, confirm: boolean): void { // Strict identity check via Object.is rather than `!== true`: // - keeps the lint rule happy (no boolean-literal compare) // - still rejects ANY non-`true` value, including 1, "true", undefined. if (!Object.is(confirm, true)) { throw new ValidationError( toolName, `${toolName}: this is a destructive operation — pass confirm: true to proceed.`, ); } }