@pimzino/agentic-tools-mcp
Version:
A comprehensive MCP server for task management and agent memories with JSON file storage
191 lines (184 loc) ⢠9.5 kB
JavaScript
import { z } from 'zod';
/**
* List tasks with hierarchical display, optionally filtered by project and parent
* Version 2.0: Updated for unified task model supporting unlimited hierarchy
*
* @param storage - Storage instance
* @returns MCP tool handler for listing tasks
*/
export function createListTasksTool(storage) {
return {
name: 'list_tasks',
description: 'View tasks in hierarchical tree format. Filter by projectId (required) and/or parentId. Use parentId=null for top-level tasks, or specific parentId for subtasks.',
inputSchema: {
projectId: z.string(),
parentId: z.string().optional(),
showHierarchy: z.boolean().optional(),
includeCompleted: z.boolean().optional()
},
handler: async ({ projectId, parentId, showHierarchy = true, includeCompleted = true }) => {
try {
// Validate project exists
const project = await storage.getProject(projectId);
if (!project) {
return {
content: [{
type: 'text',
text: `Error: Project with ID "${projectId}" not found. Use list_projects to see all available projects.`
}],
isError: true
};
}
// If parentId is provided, validate parent task exists
let parentTask = null;
if (parentId) {
parentTask = await storage.getTask(parentId);
if (!parentTask) {
return {
content: [{
type: 'text',
text: `Error: Parent task with ID "${parentId}" not found.`
}],
isError: true
};
}
}
if (showHierarchy) {
// Show full hierarchy starting from parentId (or root if not specified)
const hierarchy = await storage.getTaskHierarchy(projectId, parentId);
if (hierarchy.length === 0) {
const scopeDescription = parentTask
? `under parent task "${parentTask.name}"`
: `at the top level of project "${project.name}"`;
return {
content: [{
type: 'text',
text: `No tasks found ${scopeDescription}. Create your first task using create_task.`
}]
};
}
const formatTaskHierarchy = (hierarchyList, baseLevel = 0) => {
return hierarchyList.map(item => {
const task = item.task;
// Skip completed tasks if not included
if (!includeCompleted && task.completed) {
return '';
}
const indent = ' '.repeat(baseLevel);
const icon = task.completed ? 'ā
' : 'ā³';
const priorityIcon = task.priority && task.priority >= 8 ? 'š„' :
task.priority && task.priority >= 6 ? 'ā”' : '';
const complexityIcon = task.complexity && task.complexity >= 8 ? 'š§©' : '';
const statusText = task.status ? ` [${task.status.toUpperCase()}]` : '';
const timeText = task.estimatedHours ? ` (${task.estimatedHours}h)` : '';
let taskLine = `${indent}${icon} **${task.name}** ${priorityIcon}${complexityIcon}${statusText}${timeText}\n`;
taskLine += `${indent} ID: ${task.id} | Level: ${task.level || 0}\n`;
taskLine += `${indent} ${task.details}\n`;
if (task.tags && task.tags.length > 0) {
taskLine += `${indent} Tags: ${task.tags.join(', ')}\n`;
}
if (task.dependsOn && task.dependsOn.length > 0) {
taskLine += `${indent} Dependencies: ${task.dependsOn.length} task(s)\n`;
}
// Add children recursively
if (item.children.length > 0) {
const childrenText = formatTaskHierarchy(item.children, baseLevel + 1);
if (childrenText.trim()) {
taskLine += childrenText;
}
}
return taskLine;
}).filter(line => line.trim()).join('\n');
};
const hierarchyText = formatTaskHierarchy(hierarchy);
const totalTasks = countTasksInHierarchy(hierarchy);
const completedTasks = countCompletedTasksInHierarchy(hierarchy);
const scopeInfo = parentTask
? `Showing hierarchy under "${parentTask.name}" in project "${project.name}"`
: `Showing full task hierarchy for project "${project.name}"`;
return {
content: [{
type: 'text',
text: `š² **Task Hierarchy**
${scopeInfo}
Total: ${totalTasks} tasks (${completedTasks} completed)
${hierarchyText}
š” **Tips:**
⢠Use \`create_task\` with parentId to add tasks at any level
⢠Use \`list_tasks\` with specific parentId to focus on a subtree
⢠Use \`update_task\` to change parent relationships`
}]
};
}
else {
// Show flat list for specific parent level
const tasks = await storage.getTasks(projectId, parentId);
if (tasks.length === 0) {
const scopeDescription = parentTask
? `under parent task "${parentTask.name}"`
: `at the top level of project "${project.name}"`;
return {
content: [{
type: 'text',
text: `No tasks found ${scopeDescription}. Create your first task using create_task.`
}]
};
}
const filteredTasks = includeCompleted ? tasks : tasks.filter(t => !t.completed);
const taskList = filteredTasks.map(task => {
const icon = task.completed ? 'ā
' : 'ā³';
const priorityIcon = task.priority && task.priority >= 8 ? 'š„' :
task.priority && task.priority >= 6 ? 'ā”' : '';
const complexityIcon = task.complexity && task.complexity >= 8 ? 'š§©' : '';
const statusText = task.status ? ` [${task.status.toUpperCase()}]` : '';
const timeText = task.estimatedHours ? ` (${task.estimatedHours}h)` : '';
return `${icon} **${task.name}** ${priorityIcon}${complexityIcon}${statusText}${timeText}
ID: ${task.id} | Level: ${task.level || 0}
${task.details}
${task.tags && task.tags.length > 0 ? `Tags: ${task.tags.join(', ')}` : ''}
Created: ${new Date(task.createdAt).toLocaleString()}`;
}).join('\n\n');
const completedCount = filteredTasks.filter(t => t.completed).length;
const scopeDescription = parentTask
? `under "${parentTask.name}"`
: `at top level of "${project.name}"`;
return {
content: [{
type: 'text',
text: `š **Tasks ${scopeDescription}**
Found ${filteredTasks.length} task(s) (${completedCount} completed):
${taskList}
š” Use \`list_tasks\` with \`showHierarchy: true\` to see the full tree structure.`
}]
};
}
}
catch (error) {
return {
content: [{
type: 'text',
text: `Error listing tasks: ${error instanceof Error ? error.message : 'Unknown error'}`
}],
isError: true
};
}
}
};
}
/**
* Count total tasks in hierarchy
*/
function countTasksInHierarchy(hierarchy) {
return hierarchy.reduce((count, item) => {
return count + 1 + countTasksInHierarchy(item.children);
}, 0);
}
/**
* Count completed tasks in hierarchy
*/
function countCompletedTasksInHierarchy(hierarchy) {
return hierarchy.reduce((count, item) => {
const thisCount = item.task.completed ? 1 : 0;
return count + thisCount + countCompletedTasksInHierarchy(item.children);
}, 0);
}