Files
claude-task-master/apps/mcp/src/tools/tasks/get-tasks.tool.ts
2025-11-21 12:40:46 +01:00

158 lines
4.2 KiB
TypeScript

/**
* @fileoverview get-tasks MCP tool
* Get all tasks from Task Master with optional filtering
*/
import { z } from 'zod';
import {
handleApiResult,
withToolContext
} from '../../shared/utils.js';
import type { ToolContext } from '../../shared/types.js';
import type { TaskStatus, Task } from '@tm/core';
import type { FastMCP } from 'fastmcp';
const GetTasksSchema = z.object({
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
status: z
.string()
.optional()
.describe(
"Filter tasks by status (e.g., 'pending', 'done') or multiple statuses separated by commas (e.g., 'blocked,deferred')"
),
withSubtasks: z
.boolean()
.optional()
.describe('Include subtasks nested within their parent tasks in the response'),
tag: z.string().optional().describe('Tag context to operate on')
});
type GetTasksArgs = z.infer<typeof GetTasksSchema>;
/**
* Register the get_tasks tool with the MCP server
*/
export function registerGetTasksTool(server: FastMCP) {
server.addTool({
name: 'get_tasks',
description:
'Get all tasks from Task Master, optionally filtering by status and including subtasks.',
parameters: GetTasksSchema,
execute: withToolContext(
'get-tasks',
async (args: GetTasksArgs, { log, tmCore }: ToolContext) => {
const { projectRoot, status, withSubtasks, tag } = args;
try {
log.info(
`Getting tasks from ${projectRoot}${status ? ` with status filter: ${status}` : ''}${tag ? ` for tag: ${tag}` : ''}`
);
// Build filter
const filter =
status && status !== 'all'
? {
status: status
.split(',')
.map((s: string) => s.trim() as TaskStatus)
}
: undefined;
// Call tm-core tasks.list()
const result = await tmCore.tasks.list({
tag,
filter,
includeSubtasks: withSubtasks
});
log.info(
`Retrieved ${result.tasks?.length || 0} tasks (${result.filtered} filtered, ${result.total} total)`
);
// Calculate stats using reduce for cleaner code
const tasks = result.tasks ?? [];
const totalTasks = result.total;
const taskCounts = tasks.reduce(
(acc, task) => {
acc[task.status] = (acc[task.status] || 0) + 1;
return acc;
},
{} as Record<string, number>
);
const completionPercentage =
totalTasks > 0 ? ((taskCounts.done || 0) / totalTasks) * 100 : 0;
// Count subtasks using reduce
const subtaskCounts = tasks.reduce(
(acc, task) => {
task.subtasks?.forEach((st) => {
acc.total++;
acc[st.status] = (acc[st.status] || 0) + 1;
});
return acc;
},
{ total: 0 } as Record<string, number>
);
const subtaskCompletionPercentage =
subtaskCounts.total > 0
? ((subtaskCounts.done || 0) / subtaskCounts.total) * 100
: 0;
return handleApiResult({
result: {
success: true,
data: {
tasks: tasks as Task[],
filter: status || 'all',
stats: {
total: totalTasks,
completed: taskCounts.done || 0,
inProgress: taskCounts['in-progress'] || 0,
pending: taskCounts.pending || 0,
blocked: taskCounts.blocked || 0,
deferred: taskCounts.deferred || 0,
cancelled: taskCounts.cancelled || 0,
review: taskCounts.review || 0,
completionPercentage,
subtasks: {
total: subtaskCounts.total,
completed: subtaskCounts.done || 0,
inProgress: subtaskCounts['in-progress'] || 0,
pending: subtaskCounts.pending || 0,
blocked: subtaskCounts.blocked || 0,
deferred: subtaskCounts.deferred || 0,
cancelled: subtaskCounts.cancelled || 0,
completionPercentage: subtaskCompletionPercentage
}
}
}
},
log,
projectRoot,
tag: result.tag
});
} catch (error: any) {
log.error(`Error in get-tasks: ${error.message}`);
if (error.stack) {
log.debug(error.stack);
}
return handleApiResult({
result: {
success: false,
error: {
message: `Failed to get tasks: ${error.message}`
}
},
log,
projectRoot
});
}
}
)
});
}