Skip to content

Instantly share code, notes, and snippets.

@grahama1970
Last active June 16, 2025 15:19
Show Gist options
  • Save grahama1970/44a9da6a3da6769132037f06966945c2 to your computer and use it in GitHub Desktop.
Save grahama1970/44a9da6a3da6769132037f06966945c2 to your computer and use it in GitHub Desktop.
Claude Task Manager - Python library for managing Claude agent tasks

Claude Task Manager

A specialized tool to manage context isolation and focused task execution with Claude Code, solving the critical challenge of context length limitations and task focus when working with Claude on complex, multi-step projects.

What is Claude Task Manager?

Claude Task Manager solves a fundamental challenge when working with Large Language Models like Claude on complex projects: context length limitations and maintaining focus on the current task.

The Problem

When working with Claude Code (or any LLM) on complex projects, you typically face several challenges:

  1. Context Length Limitations: Claude has a limited context window. Long, complex projects can exceed this limit.
  2. Task Switching Confusion: When handling multiple tasks in a single conversation, Claude may get confused about which task it's currently working on.
  3. Project Organization: Large projects need structure and organization to track progress.
  4. Effective Prompting: Each task requires specific, focused instructions to get optimal results.

The Solution

Claude Task Manager implements a "Boomerang" approach:

  1. Task Breakdown: It analyzes a large project specification and intelligently breaks it down into smaller, self-contained tasks.
  2. Context Isolation: Each task is executed in a clean context window, ensuring Claude focuses solely on that task.
  3. Project Organization: Tasks are organized into projects with proper sequencing and metadata.
  4. Execution Management: Tasks can be run individually or in sequence, with results captured and organized.

Why Use Claude Task Manager?

  • Overcome Context Limitations: Break down large projects into manageable chunks that fit within Claude's context window.
  • Maintain Focus: Ensure Claude stays focused on the current task without being distracted by previous context.
  • Improve Quality: Get better results by providing Claude with clear, focused instructions for each task.
  • Organize Complex Projects: Manage multi-step projects with proper structure and sequencing.
  • Track Progress: Monitor task completion and project status.
  • MCP Integration: Seamlessly integrate with agent workflows through the Model Context Protocol.

Prerequisites

This package requires the following to be installed on your system:

  1. Claude Desktop - You need to have Claude Desktop application installed
  2. Claude Code - The claude command-line tool must be accessible in your PATH
  3. Desktop Commander - Required for file system access (see installation instructions below)

Installing Desktop Commander

Desktop Commander is a critical dependency that enables Claude to access your file system and execute commands. To install:

# Using npx (recommended)
npx @wonderwhy-er/desktop-commander@latest setup

# Or using Smithery
npx -y @smithery/cli install @wonderwhy-er/desktop-commander --client claude

For more details, visit the Desktop Commander website.

After installation, restart Claude Desktop and ensure you see the hammer icon in the chat interface, indicating that Desktop Commander is properly connected.

Core Features

  • Task Breakdown: Intelligently parse complex projects into focused, self-contained tasks
  • Context Isolation: Execute each task with a clean context window using the /clear command
  • Project Management: Organize tasks into projects with proper metadata and sequencing
  • Execution Control: Run tasks individually or in sequence, with proper result management
  • Status Tracking: Monitor project progress and task completion status
  • Modern CLI: Intuitive command-line interface with rich formatting
  • MCP Integration: Seamless integration with agent workflows via FastMCP

Installation

# From claude_mcp_configs root directory:
python setup_libs.py task-manager

# Or install directly from the directory
cd libs/claude_task_manager
pip install -e .

The package uses modern Python packaging with pyproject.toml, making it compatible with the latest pip and build tools.

Dependencies

For FastMCP integration, you'll need to install the fastmcp package:

pip install fastmcp

Usage

Basic Workflow

The typical workflow with Claude Task Manager involves:

  1. Create a project: Set up a new project structure
  2. Break down tasks: Convert a large task specification into smaller, self-contained tasks
  3. Run tasks: Execute tasks individually or as a complete project
  4. Monitor progress: Track task completion and project status

Command Line Interface

# Create a new project
claude-tasks create-project my_project /path/to/task_list.md

# Break down a task into individual tasks
claude-tasks break-task my_project /path/to/task_list.md

# Run a single task
claude-tasks run-task my_project 001_first_task.md

# Run all tasks in a project
claude-tasks run-project my_project

# List available projects
claude-tasks list-projects

# List tasks in a project
claude-tasks list-tasks my_project

# Check project status
claude-tasks check-status my_project

# Get machine-readable schema (MCP-compatible)
claude-tasks schema

# Get MCP-compatible schema format
claude-tasks schema --mcp

MCP-Compatible Features

The CLI provides several MCP-compatible features:

  1. Machine-readable JSON output:

    # Add --json to any command for structured JSON output
    claude-tasks list-tasks my_project --json
  2. Schema command:

    # Get a complete JSON schema of all commands and parameters
    claude-tasks schema
    
    # Output in MCP format
    claude-tasks schema --mcp
  3. Consistent result structure: All JSON outputs follow a consistent structure with:

    • success flag (boolean)
    • Command-specific result data
    • Error information when applicable
  4. Rich human-readable output: When used without --json, commands provide well-formatted tables and panels

Python API

from claude_task_manager import TaskManager

# Initialize the task manager
manager = TaskManager('/path/to/base/directory')

# Create a new project
project_dir = manager.create_project('my_project', '/path/to/task_list.md')

# Break down a task into individual tasks
project_dir, created_files = manager.break_down_task('my_project', '/path/to/task_list.md')

# Run a single task
result_file = manager.run_task('my_project', '001_first_task.md')

# Run all tasks in a project
results = manager.run_project('my_project')

# List available projects
projects = manager.list_projects()

# List tasks in a project
tasks = manager.list_tasks('my_project')

# Check project status
status = manager.check_project_status('my_project')

MCP Server

To run the Task Manager as an MCP server:

# Start the server with default settings
python run_task_manager_server.py start

# Start with custom settings
python run_task_manager_server.py start --host 0.0.0.0 --port 5000 --base-dir /path/to/tasks --debug

# Run diagnostics
python run_task_manager_server.py diagnostic

# Output schema
python run_task_manager_server.py schema

The server exposes the following MCP functions:

  • create_project - Create a new project
  • break_task - Break down a task into individual task files
  • run_task - Run a single task with context isolation
  • run_project - Run all tasks in a project
  • list_projects - List all projects
  • list_tasks - List all tasks in a project
  • check_status - Check project status

How It Works

Task Breakdown Process

When you provide a large task description, Claude Task Manager:

  1. Analyzes the content: Uses Claude itself to understand the task structure
  2. Identifies logical divisions: Determines natural breakpoints in the task
  3. Creates self-contained tasks: Generates task files with all necessary context
  4. Establishes dependencies: Determines the correct execution sequence
  5. Generates metadata: Creates project information and execution plans

Visual Workflow

The following diagram illustrates the complete workflow from task breakdown to execution:

flowchart TD
    %% Phase labels
    PhaseA[Task Breakdown Phase]:::phaseLabel
    PhaseB[Execution Phase]:::phaseLabel
    PhaseC[Context Isolation Mechanism]:::phaseLabel
    
    %% Task Breakdown Phase nodes
    A[📋 Original Task List] --> B[🧠 Claude Analysis]
    B --> C{✂️ Split into Tasks}
    C --> D[📑 Task 000: Project Overview]
    C --> E[📑 Task 001: First Subtask]
    C --> F[📑 Task 002: Second Subtask]
    C --> G[📑 Task 003: Third Subtask]
    D & E & F & G --> H[📋 Generate Task Sequence]
    
    %% Connect phases
    H --> I
    
    %% Execution Phase nodes
    I[🚀 Execute Task 000] --> J[🚀 Execute Task 001]
    J --> K[🚀 Execute Task 002]
    K --> L[🚀 Execute Task 003]
    
    %% Context Isolation for Task 000
    I --> I1[🧹 /clear Context]
    I1 --> I2[📥 Load Task 000]
    I2 --> I3[⚙️ Execute with Claude Code]
    I3 --> I4[💾 Save Results]

    %% Context Isolation for Task 001
    J --> J1[🧹 /clear Context]
    J1 --> J2[📥 Load Task 001]
    J2 --> J3[⚙️ Execute with Claude Code]
    J3 --> J4[💾 Save Results]

    %% Context Isolation for Task 002
    K --> K1[🧹 /clear Context]
    K1 --> K2[📥 Load Task 002]
    K2 --> K3[⚙️ Execute with Claude Code]
    K3 --> K4[💾 Save Results]

    %% Context Isolation for Task 003
    L --> L1[🧹 /clear Context]
    L1 --> L2[📥 Load Task 003]
    L2 --> L3[⚙️ Execute with Claude Code]
    L3 --> L4[💾 Save Results]

    %% Results collection
    I4 & J4 & K4 & L4 --> M[📊 Collect All Results]
    M --> N[✅ Complete Project]

    %% Place phase labels
    PhaseA -.-> A
    PhaseB -.-> I
    PhaseC -.-> I1
    
    %% Styling for light/dark mode compatibility
    classDef phase fill:#d8b4fe,stroke:#6b21a8,stroke-width:2px,color:#000;
    classDef task fill:#93c5fd,stroke:#1e40af,stroke-width:1px,color:#000;
    classDef result fill:#86efac,stroke:#166534,stroke-width:1px,color:#000;
    classDef isolation fill:#fef08a,stroke:#854d0e,stroke-width:1px,color:#000;
    classDef phaseLabel fill:none,stroke:none,color:#6b21a8,font-weight:bold,font-size:16px;

    class A,B,C phase;
    class D,E,F,G,H,I,J,K,L task;
    class I4,J4,K4,L4,M,N result;
    class I1,I2,I3,J1,J2,J3,K1,K2,K3,L1,L2,L3 isolation;
Loading

This diagram shows how:

  1. A large task list is broken down into smaller, manageable tasks
  2. Each task is executed in sequence
  3. Context isolation is maintained by clearing Claude's context before each task
  4. Results are collected and integrated into a complete project

Diagram Legend:

  • Task Breakdown Phase:

    • 📋 Document/list operations
    • 🧠 Analysis processes
    • ✂️ Task splitting
  • Execution Phase:

    • 🚀 Task execution
  • Context Isolation:

    • 🧹 Context clearing
    • 📥 Content loading
    • ⚙️ Process execution
    • 💾 Result saving
  • Final Steps:

    • 📊 Result collection
    • ✅ Project completion

Context Isolation Mechanism

The key to Claude Task Manager's effectiveness is its context isolation mechanism:

  1. Clean context: Each task starts with the /clear command, wiping Claude's context window
  2. Focused instructions: The task file contains all necessary context and instructions
  3. Independent execution: Tasks run without knowledge of other tasks
  4. Result capture: Outputs are stored as separate result files

This approach ensures Claude maintains focus on the current task without being confused by previous instructions or outputs.

Project Structure

/base_directory/
├── project_name/
│   ├── project.json          # Project metadata
│   ├── task_list.md          # Original task list
│   ├── task_sequence.txt     # Task execution sequence
│   ├── tasks/                # Individual task files
│   │   ├── 000_project_overview.md
│   │   ├── 001_first_task.md
│   │   └── ...
│   ├── results/              # Execution results
│   │   ├── 000_project_overview.result
│   │   ├── 001_first_task.result
│   │   └── ...
│   └── temp/                 # Temporary files
└── ...

Three-Layer Architecture

The Task Manager implements a clean three-layer architecture:

  1. Core Layer: The TaskManager class in task_manager.py provides the core business logic.
  2. Presentation Layer: The CLI in cli.py handles user interaction and output formatting.
  3. Integration Layer: The FastMCP wrapper in fast_mcp_wrapper.py connects the core functionality to the MCP ecosystem.

Each layer has a distinct responsibility, making the code more maintainable, testable, and adaptable.

MCP Integration and Debugging

The Task Manager integrates with FastMCP to provide a Model Context Protocol (MCP) interface, allowing it to be used by AI agents. This section covers how to set up, use, and debug the MCP integration.

Setting Up MCP Configuration

  1. Add the Task Manager to your .mcp.json file:
{
  "mcpServers": {
    "task_manager": {
      "command": "/path/to/python",
      "args": [
        "/path/to/run_task_manager_server.py",
        "start"
      ]
    }
  }
}
  1. Place this file in your home directory or specify the path with the MCP_CONFIG_PATH environment variable.

Debugging with MCP Inspector

The MCP Inspector is a powerful tool for debugging MCP servers. Follow these steps to debug the Task Manager MCP integration:

1. Install MCP Inspector

git clone https://github.com/modelcontextprotocol/inspector.git
cd inspector
npm install

2. Start the Task Manager MCP Server

In a separate terminal, start the Task Manager server:

python /path/to/run_task_manager_server.py start --debug

3. Launch the MCP Inspector

cd /path/to/inspector
npm start

This will open the MCP Inspector in your browser, typically at http://localhost:3000.

4. Connect to the Task Manager Server

In the MCP Inspector:

  • Click "Connect to Server"
  • Enter the URL (e.g., "http://localhost:3000" - the default for the Task Manager)
  • Click "Connect"

5. Explore Available Functions

Once connected, you'll see the available MCP functions. You can:

  • See function descriptions, parameters, and return types
  • Expand each function to see more details

6. Test a Function

To test a function:

  1. Click on the function name (e.g., "list_projects")
  2. Fill in the required parameters
  3. Click "Call Function"
  4. View the function response

7. Debug Common Issues

If you encounter issues, try these debugging steps:

Server Connection Issues
  • Check that the server is running and accessible
  • Ensure the port isn't in use by another application
  • Look for error messages in the server's terminal output
Function Call Errors
  • Examine the server logs to see detailed error messages
  • Check for validation errors in your parameters
  • Look for file system permission issues
Permission Problems

The most common issues relate to file system access:

  • Ensure Desktop Commander is properly installed and running
  • Check that the Task Manager has permission to access the specified directories
  • Look for "Access denied" or similar error messages in the logs
MCP Format Issues
  • Use the MCP Inspector to check the function schema
  • Ensure parameter names and types match the schema
  • Check that the function is registered correctly in the MCP wrapper

8. Using MCP Schema for Debugging

The Task Manager provides a built-in schema command that can be helpful for debugging:

python run_task_manager_server.py schema > mcp_schema.json

You can then use this schema to:

  • Compare against the schema reported by the MCP Inspector
  • Validate your function calls
  • Understand the expected parameters and return types

9. Testing End-to-End Workflows

To test a complete workflow:

  1. Create a project:

    • Call create_project with a name and optional source file
    • Check the return value for the project directory
  2. Break down a task:

    • Call break_task with the project name and source file
    • Verify the created task files
  3. Run tasks:

    • Call run_task or run_project to execute tasks
    • Check the results directory for output files

10. Logging and Monitoring

For ongoing monitoring:

  • Set the server to debug mode (--debug flag)
  • Redirect logs to a file for analysis
  • Use a tool like tail -f logfile.log to watch logs in real time

By following these steps, you can effectively debug and test the Task Manager MCP integration.

Real-World Example

Let's say you have a complex project to build a recommendation system that requires:

  1. Data analysis
  2. Algorithm design
  3. Implementation
  4. Testing
  5. Documentation

This would likely exceed Claude's context window if sent as a single task. With Claude Task Manager:

  1. Create a project:

    claude-tasks create-project recommendation_system project_spec.md
  2. Break down the task:

    claude-tasks break-task recommendation_system project_spec.md
  3. Claude Task Manager breaks it into manageable tasks:

    • 000_project_overview.md - Project summary and goals
    • 001_data_analysis.md - Data exploration and insights
    • 002_algorithm_design.md - Design of recommendation algorithm
    • 003_implementation.md - Code implementation
    • 004_testing.md - Test cases and validation
    • 005_documentation.md - User and technical documentation
  4. Run the entire project:

    claude-tasks run-project recommendation_system

Each task is executed in isolation, with Claude focusing solely on that specific task, ensuring high-quality results without context confusion.

Requirements

  • Python 3.7+
  • Claude Desktop with the hammer icon (Desktop Commander) enabled
  • Desktop Commander installed (see Prerequisites)
  • claude command-line tool accessible in your PATH
  • typer and rich Python packages (automatically installed)
  • fastmcp package (for MCP integration)
"""
Claude Task Manager - A system for managing complex task execution with context isolation
This package provides tools for breaking down and executing complex Claude Code tasks
with proper context isolation (Boomerang-style).
"""
__version__ = "1.0.0"
__author__ = "Anthropic"
__license__ = "MIT"
# Core components
from claude_task_manager.task_manager import TaskManager
# Make type definitions available
from claude_task_manager.types import (
ProjectInfo, ProjectResult, TaskBreakdownResult, TaskRunResult,
ProjectRunResult, ProjectListResult, TaskListResult, ErrorResult,
SchemaCommandResult, LogLevel, OutputFormat
)
# Optional MCP integration
try:
from claude_task_manager.fast_mcp_wrapper import TaskManagerMCP
except ImportError:
# FastMCP dependency might be missing
pass
"""
Command-line interface for Claude Task Manager.
This module provides a command-line interface to interact with the TaskManager class,
using Typer for a more modern command-line experience.
"""
import os
import sys
import logging
import json
from pathlib import Path
from typing import List, Optional, Dict, Any, Literal, Union, cast
import typer
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich import print as rprint
from claude_task_manager.task_manager import TaskManager
from claude_task_manager.types import (
ProjectInfo, ProjectResult, TaskBreakdownResult, TaskRunResult,
ProjectRunResult, ProjectListResult, TaskListResult, ErrorResult,
SchemaCommandResult, LogLevel, OutputFormat, MCPSchema, MCPFunction
)
# Initialize Typer app with metadata
app = typer.Typer(
name="claude-tasks",
help="Claude Task Manager - Manage complex tasks with context isolation",
add_completion=True,
)
# Create a rich console for pretty output
console = Console()
# Set up environment variables
DEFAULT_BASE_DIR = os.environ.get('CLAUDE_TASKS_DIR', str(Path.home() / 'claude_tasks'))
def get_manager(base_dir: str, log_level: str) -> TaskManager:
"""
Create and return a TaskManager instance.
Args:
base_dir: Base directory for task management
log_level: Logging level to use
Returns:
TaskManager instance
"""
level = getattr(logging, log_level.upper())
return TaskManager(base_dir, log_level=level)
@app.callback()
def common(
ctx: typer.Context,
base_dir: str = typer.Option(
DEFAULT_BASE_DIR,
"--base-dir",
"-b",
help="Base directory for task management",
envvar="CLAUDE_TASKS_DIR",
),
log_level: LogLevel = typer.Option(
"INFO",
"--log-level",
"-l",
help="Set the logging level",
case_sensitive=False,
show_default=True,
),
json_output: bool = typer.Option(
False,
"--json",
"-j",
help="Output results as JSON (for machine consumption)",
),
):
"""
Common parameters for all commands.
"""
# Store parameters in context for use in commands
ctx.obj = {
"base_dir": base_dir,
"log_level": log_level,
"json_output": json_output,
}
@app.command(name="create-project")
def create_project(
project_name: str = typer.Argument(..., help="Name of the project"),
source_file: Optional[Path] = typer.Argument(None, help="Path to source task file (optional)"),
ctx: typer.Context = typer.Context,
):
"""
Create a new project structure.
"""
try:
manager = get_manager(ctx.obj["base_dir"], ctx.obj["log_level"])
# Convert source_file to string if provided
source_file_str = str(source_file) if source_file else None
# Create project
project_dir = manager.create_project(project_name, source_file_str)
# Prepare result data
result: ProjectResult = {
"success": True,
"project_name": project_name,
"project_dir": str(project_dir),
"source_file": source_file_str,
}
# Output in JSON or human-readable format
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(result, indent=2))
else:
panel = Panel(
f"[green]Project created at:[/green] {project_dir}",
title="Project Creation Successful",
border_style="green",
)
console.print(panel)
except ValueError as e:
# Handle validation errors
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {"project_name": project_name}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Validation error:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
except FileNotFoundError as e:
# Handle file not found errors
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {"project_name": project_name, "source_file": source_file_str}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]File not found:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
except Exception as e:
# Handle unexpected errors
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {"project_name": project_name}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error creating project:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
@app.command(name="break-task")
def break_task(
project_name: str = typer.Argument(..., help="Name of the project"),
source_file: str = typer.Argument(..., help="Path to source task file"),
ctx: typer.Context = typer.Context,
):
"""
Break down a task into individual task files.
"""
try:
manager = get_manager(ctx.obj["base_dir"], ctx.obj["log_level"])
# Don't show spinner in JSON mode
if ctx.obj.get("json_output", False):
project_dir, created_files = manager.break_down_task(project_name, source_file)
else:
with console.status("[bold blue]Breaking down task file...[/]"):
project_dir, created_files = manager.break_down_task(project_name, source_file)
# Prepare result data
result: TaskBreakdownResult = {
"success": True,
"project_name": project_name,
"project_dir": str(project_dir),
"source_file": source_file,
"created_files": created_files,
"file_count": len(created_files),
"next_step_command": f"claude-tasks run-project {project_name}"
}
# Output in JSON or human-readable format
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(result, indent=2))
else:
# Create a pretty table with the results
table = Table(title=f"Task Breakdown Results for {project_name}")
table.add_column("Task File", style="cyan")
table.add_column("Status", style="green")
for filename in created_files:
table.add_row(filename, "✅ Created")
console.print(table)
# Print next steps
console.print("\n[bold]Next Steps:[/bold]")
console.print(f" Run the tasks with: [cyan]claude-tasks run-project {project_name}[/cyan]")
except Exception as e:
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {
"project_name": project_name,
"source_file": source_file
}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error breaking down task:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
@app.command(name="run-task")
def run_task(
project_name: str = typer.Argument(..., help="Name of the project"),
task_filename: str = typer.Argument(..., help="Name of the task file"),
ctx: typer.Context = typer.Context,
):
"""
Run a single task with context isolation.
"""
try:
manager = get_manager(ctx.obj["base_dir"], ctx.obj["log_level"])
# Don't show spinner in JSON mode
if ctx.obj.get("json_output", False):
result_file = manager.run_task(project_name, task_filename)
else:
with console.status(f"[bold blue]Running task: {task_filename}[/]"):
result_file = manager.run_task(project_name, task_filename)
# Prepare result data
result: TaskRunResult = {
"success": True,
"project_name": project_name,
"task_filename": task_filename,
"result_file": result_file,
}
# Output in JSON or human-readable format
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(result, indent=2))
else:
panel = Panel(
f"Task execution complete.\n[green]Result saved to:[/green] {result_file}",
title=f"Task Execution Successful: {task_filename}",
border_style="green",
)
console.print(panel)
except Exception as e:
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {
"project_name": project_name,
"task_filename": task_filename
}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error running task:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
@app.command(name="run-project")
def run_project(
project_name: str = typer.Argument(..., help="Name of the project"),
skip_confirmation: bool = typer.Option(
False,
"--yes",
"-y",
help="Skip confirmation prompt and run all tasks"
),
ctx: typer.Context = typer.Context,
):
"""
Run all tasks in a project with context isolation.
"""
try:
manager = get_manager(ctx.obj["base_dir"], ctx.obj["log_level"])
json_output = ctx.obj.get("json_output", False)
# Get task list first to show plan
project_dir = Path(ctx.obj["base_dir"]) / project_name
tasks_dir = project_dir / "tasks"
sequence_file = project_dir / "task_sequence.txt"
task_files = []
if sequence_file.exists():
with open(sequence_file, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
task_files.append(line)
else:
task_files = sorted([f.name for f in tasks_dir.glob('*.md')])
# Check if we have any tasks
if not task_files:
error_data: ErrorResult = {
"success": False,
"error": "No tasks found in project",
"context": {"project_name": project_name}
}
if json_output:
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error:[/red] No tasks found in project {project_name}", style="bold red")
raise typer.Exit(code=1)
# Display execution plan
if not json_output:
table = Table(title=f"Task Execution Plan for {project_name}")
table.add_column("#", style="cyan", justify="right")
table.add_column("Task File", style="cyan")
table.add_column("Status", style="yellow")
for i, task in enumerate(task_files, 1):
table.add_row(str(i), task, "⏳ Pending")
console.print(table)
# Confirm execution if needed
if not skip_confirmation and not json_output:
if not typer.confirm("\nExecute all tasks?", default=True):
console.print("[yellow]Task execution aborted.[/yellow]")
raise typer.Exit(code=0)
# Execute tasks
results = []
failed_tasks = []
task_results = []
for i, task_file in enumerate(task_files, 1):
try:
if json_output:
result = manager.run_task(project_name, task_file)
else:
with console.status(f"[bold blue]Running task {i}/{len(task_files)}: {task_file}[/]"):
result = manager.run_task(project_name, task_file)
results.append(result)
task_results.append({
"task_file": task_file,
"result_file": result,
"status": "success"
})
if not json_output:
console.print(f"[green]✓[/green] Task {i}/{len(task_files)} completed: {task_file}")
except Exception as e:
failed_tasks.append(task_file)
task_results.append({
"task_file": task_file,
"error": str(e),
"status": "failed"
})
if not json_output:
console.print(f"[red]✗[/red] Task {i}/{len(task_files)} failed: {task_file} - {e}")
# Prepare result data
result_data: ProjectRunResult = {
"success": len(failed_tasks) == 0,
"project_name": project_name,
"total_tasks": len(task_files),
"completed_tasks": len(results),
"failed_tasks": len(failed_tasks),
"tasks": task_results,
"results": results
}
# Output in JSON or human-readable format
if json_output:
typer.echo(json.dumps(result_data, indent=2))
else:
# Print summary
console.print("\n[bold]Execution Summary:[/bold]")
success_count = len(results)
fail_count = len(failed_tasks)
console.print(f" [green]✓ {success_count} tasks completed successfully[/green]")
if fail_count > 0:
console.print(f" [red]✗ {fail_count} tasks failed[/red]")
console.print("\n[bold]Results saved to:[/bold]")
for result in results:
console.print(f" {result}")
except typer.Abort:
if not ctx.obj.get("json_output", False):
console.print("[yellow]Task execution aborted.[/yellow]")
else:
typer.echo(json.dumps({
"success": False,
"error": "Task execution aborted by user",
"context": {"project_name": project_name}
}, indent=2))
raise typer.Exit(code=0)
except Exception as e:
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {"project_name": project_name}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error running project:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
@app.command(name="list-projects")
def list_projects(
ctx: typer.Context = typer.Context,
):
"""
List all projects.
"""
try:
manager = get_manager(ctx.obj["base_dir"], ctx.obj["log_level"])
projects = manager.list_projects()
# Prepare result data
result: ProjectListResult = {
"success": True,
"base_dir": ctx.obj["base_dir"],
"projects": projects,
"count": len(projects)
}
# Output in JSON or human-readable format
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(result, indent=2))
else:
if projects:
table = Table(title="Available Projects")
table.add_column("Project Name", style="cyan")
for project in projects:
table.add_row(project)
console.print(table)
else:
console.print("[yellow]No projects found[/yellow]")
except Exception as e:
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {"base_dir": ctx.obj["base_dir"]}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error listing projects:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
@app.command(name="list-tasks")
def list_tasks(
project_name: str = typer.Argument(..., help="Name of the project"),
ctx: typer.Context = typer.Context,
):
"""
List all tasks in a project.
"""
try:
manager = get_manager(ctx.obj["base_dir"], ctx.obj["log_level"])
tasks = manager.list_tasks(project_name)
# Prepare result data
result: TaskListResult = {
"success": True,
"project_name": project_name,
"tasks": tasks,
"count": len(tasks)
}
# Output in JSON or human-readable format
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(result, indent=2))
else:
if tasks:
table = Table(title=f"Tasks in Project: {project_name}")
table.add_column("#", style="cyan", justify="right")
table.add_column("Task File", style="cyan")
for i, task in enumerate(tasks, 1):
table.add_row(str(i), task)
console.print(table)
else:
console.print(f"[yellow]No tasks found in project {project_name}[/yellow]")
except Exception as e:
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {"project_name": project_name}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error listing tasks:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
@app.command(name="check-status")
def check_status(
project_name: str = typer.Argument(..., help="Name of the project"),
ctx: typer.Context = typer.Context,
):
"""
Check the status of a project.
"""
try:
manager = get_manager(ctx.obj["base_dir"], ctx.obj["log_level"])
status = manager.check_project_status(project_name)
# Check if project exists
if not status["exists"]:
error_data: ErrorResult = {
"success": False,
"error": f"Project {project_name} not found",
"context": {"project_name": project_name}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error:[/red] Project {project_name} not found", style="bold red")
raise typer.Exit(code=1)
# Prepare result data
result = {
"success": True,
"project_name": project_name,
"total_tasks": status["total_tasks"],
"completed_tasks": status["completed_tasks"],
"pending_tasks": status["pending_tasks"],
"completion_percentage": status["completion_percentage"]
}
# Output in JSON or human-readable format
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(result, indent=2))
else:
# Create progress display
table = Table(title=f"Project Status: {project_name}")
table.add_column("Metric", style="cyan")
table.add_column("Value", style="green")
table.add_row("Total Tasks", str(status["total_tasks"]))
table.add_row("Completed", str(status["completed_tasks"]))
table.add_row("Pending", str(status["pending_tasks"]))
table.add_row("Completion", f"{status['completion_percentage']:.1f}%")
console.print(table)
# Progress bar
completion = status["completion_percentage"] / 100
bar_width = 50
filled = int(completion * bar_width)
bar = "█" * filled + "░" * (bar_width - filled)
console.print(f"\n[bold]Progress:[/bold] [{get_progress_color(completion)}]{bar}[/] {completion:.1%}")
except Exception as e:
error_data: ErrorResult = {
"success": False,
"error": str(e),
"context": {"project_name": project_name}
}
if ctx.obj.get("json_output", False):
typer.echo(json.dumps(error_data, indent=2))
else:
console.print(f"[red]Error checking project status:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
def get_progress_color(completion: float) -> str:
"""Get color for progress bar based on completion percentage."""
if completion < 0.3:
return "red"
elif completion < 0.7:
return "yellow"
else:
return "green"
@app.command(name="schema")
def schema(
ctx: typer.Context = typer.Context,
format: OutputFormat = typer.Option(
"json",
"--format",
"-f",
help="Output format (json or yaml)",
case_sensitive=False,
),
mcp_format: bool = typer.Option(
False,
"--mcp",
"-m",
help="Output schema in MCP-compatible format",
),
):
"""
Output the CLI schema in a machine-readable format.
This is useful for automated tooling, documentation generation, and MCP integration.
"""
try:
# Create standard CLI schema
schema_dict: SchemaCommandResult = {
"name": "claude-task-manager",
"version": "1.0.0",
"description": "Claude Task Manager - Manage complex tasks with context isolation",
"commands": {
"create-project": {
"description": "Create a new project structure",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True
},
"source_file": {
"type": "string",
"description": "Path to source task file (optional)",
"required": False
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the created project"
},
"project_dir": {
"type": "string",
"description": "Path to the created project directory"
},
"source_file": {
"type": "string",
"description": "Path to the source task file (if provided)"
}
}
},
"examples": [
{
"description": "Create a new empty project",
"command": "claude-tasks create-project my_project"
},
{
"description": "Create a project with a source task file",
"command": "claude-tasks create-project my_project /path/to/task_list.md"
}
]
},
"break-task": {
"description": "Break down a task into individual task files",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True
},
"source_file": {
"type": "string",
"description": "Path to source task file",
"required": True
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"project_dir": {
"type": "string",
"description": "Path to the project directory"
},
"created_files": {
"type": "array",
"description": "List of created task files",
"items": {
"type": "string"
}
},
"file_count": {
"type": "integer",
"description": "Number of files created"
},
"next_step_command": {
"type": "string",
"description": "Suggested next command to run"
}
}
},
"examples": [
{
"description": "Break down a task file",
"command": "claude-tasks break-task my_project /path/to/task_list.md"
}
]
},
"run-task": {
"description": "Run a single task with context isolation",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True
},
"task_filename": {
"type": "string",
"description": "Name of the task file",
"required": True
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"task_filename": {
"type": "string",
"description": "Name of the task file"
},
"result_file": {
"type": "string",
"description": "Path to the result file"
}
}
},
"examples": [
{
"description": "Run a single task",
"command": "claude-tasks run-task my_project 001_first_task.md"
}
]
},
"run-project": {
"description": "Run all tasks in a project with context isolation",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True
},
"skip_confirmation": {
"type": "boolean",
"description": "Skip confirmation prompt and run all tasks",
"required": False,
"default": False
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether all tasks completed successfully"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"total_tasks": {
"type": "integer",
"description": "Total number of tasks"
},
"completed_tasks": {
"type": "integer",
"description": "Number of tasks completed successfully"
},
"failed_tasks": {
"type": "integer",
"description": "Number of tasks that failed"
},
"tasks": {
"type": "array",
"description": "Details of each task execution",
"items": {
"type": "object"
}
},
"results": {
"type": "array",
"description": "List of result file paths",
"items": {
"type": "string"
}
}
}
},
"examples": [
{
"description": "Run all tasks in a project",
"command": "claude-tasks run-project my_project"
},
{
"description": "Run all tasks without confirmation",
"command": "claude-tasks run-project my_project --yes"
}
]
},
"list-projects": {
"description": "List all projects",
"parameters": {},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"base_dir": {
"type": "string",
"description": "Base directory for task management"
},
"projects": {
"type": "array",
"description": "List of project names",
"items": {
"type": "string"
}
},
"count": {
"type": "integer",
"description": "Number of projects found"
}
}
},
"examples": [
{
"description": "List all projects",
"command": "claude-tasks list-projects"
}
]
},
"list-tasks": {
"description": "List all tasks in a project",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"tasks": {
"type": "array",
"description": "List of task filenames",
"items": {
"type": "string"
}
},
"count": {
"type": "integer",
"description": "Number of tasks found"
}
}
},
"examples": [
{
"description": "List all tasks in a project",
"command": "claude-tasks list-tasks my_project"
}
]
},
"check-status": {
"description": "Check the status of a project",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"total_tasks": {
"type": "integer",
"description": "Total number of tasks"
},
"completed_tasks": {
"type": "integer",
"description": "Number of tasks completed"
},
"pending_tasks": {
"type": "integer",
"description": "Number of tasks pending"
},
"completion_percentage": {
"type": "number",
"description": "Percentage of tasks completed"
}
}
},
"examples": [
{
"description": "Check the status of a project",
"command": "claude-tasks check-status my_project"
}
]
},
"schema": {
"description": "Output the CLI schema in a machine-readable format",
"parameters": {
"format": {
"type": "string",
"description": "Output format (json or yaml)",
"default": "json",
"enum": ["json", "yaml"]
},
"mcp_format": {
"type": "boolean",
"description": "Output schema in MCP-compatible format",
"default": False
}
},
"returns": {
"type": "object",
"description": "Schema definition"
},
"examples": [
{
"description": "Output schema in JSON format",
"command": "claude-tasks schema"
},
{
"description": "Output schema in YAML format",
"command": "claude-tasks schema --format yaml"
},
{
"description": "Output schema in MCP-compatible format",
"command": "claude-tasks schema --mcp"
}
]
}
},
"options": {
"base_dir": {
"type": "string",
"description": "Base directory for task management",
"default": DEFAULT_BASE_DIR,
"env_var": "CLAUDE_TASKS_DIR"
},
"log_level": {
"type": "string",
"description": "Set the logging level",
"default": "INFO",
"enum": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
},
"json_output": {
"type": "boolean",
"description": "Output results as JSON (for machine consumption)",
"default": False
}
},
"metadata": {
"requires_desktop_commander": True,
"requires_claude_code": True,
"mcp_compatible": True,
"fastmcp_ready": True,
"version": "1.0.0",
"author": "Anthropic",
"license": "MIT"
}
}
# If MCP format is requested, convert to MCP-compatible schema
if mcp_format:
mcp_schema: MCPSchema = {
"functions": [],
"metadata": {
"name": "claude-task-manager",
"version": "1.0.0",
"description": "Claude Task Manager - Manage complex tasks with context isolation",
"requires_desktop_commander": True,
"requires_claude_code": True,
"mcp_compatible": True
}
}
# Convert each command to an MCP function
for cmd_name, cmd_data in schema_dict["commands"].items():
# Skip schema command in MCP format
if cmd_name == "schema":
continue
# Create MCP function
mcp_function: MCPFunction = {
"name": cmd_name.replace("-", "_"),
"description": cmd_data["description"],
"parameters": {},
"returns": cmd_data["returns"],
"examples": cmd_data["examples"]
}
# Convert parameters
for param_name, param_data in cmd_data["parameters"].items():
mcp_function["parameters"][param_name] = {
"type": param_data["type"],
"description": param_data["description"],
"required": param_data.get("required", False),
"default": param_data.get("default", None)
}
# Add to functions list
mcp_schema["functions"].append(mcp_function)
# Output MCP schema
if format.lower() == "yaml":
try:
import yaml
yaml_output = yaml.dump(mcp_schema, sort_keys=False, default_flow_style=False)
typer.echo(yaml_output)
except ImportError:
console.print("[yellow]PyYAML not installed. Falling back to JSON format.[/yellow]")
typer.echo(json.dumps(mcp_schema, indent=2))
else:
typer.echo(json.dumps(mcp_schema, indent=2))
else:
# Output standard schema
if format.lower() == "yaml":
try:
import yaml
yaml_output = yaml.dump(schema_dict, sort_keys=False, default_flow_style=False)
typer.echo(yaml_output)
except ImportError:
console.print("[yellow]PyYAML not installed. Falling back to JSON format.[/yellow]")
typer.echo(json.dumps(schema_dict, indent=2))
else:
typer.echo(json.dumps(schema_dict, indent=2))
except Exception as e:
console.print(f"[red]Error generating schema:[/red] {e}", style="bold red")
raise typer.Exit(code=1)
def main():
"""Main entry point for the CLI."""
app()
if __name__ == "__main__":
app()
"""
FastMCP wrapper for Claude Task Manager.
This module provides the FastMCP integration, allowing the TaskManager to be used
as an MCP server with proper function mapping and parameter conversion.
"""
import os
import sys
import logging
import json
from pathlib import Path
from typing import Dict, List, Any, Optional, Union, cast
# Import FastMCP
from fastmcp import FastMCP
# Import TaskManager
from claude_task_manager.task_manager import TaskManager
from claude_task_manager.types import (
ProjectInfo, ProjectResult, TaskBreakdownResult, TaskRunResult,
ProjectRunResult, ProjectListResult, TaskListResult, ErrorResult
)
class TaskManagerMCP:
"""
MCP wrapper for TaskManager class.
This class provides the FastMCP integration, mapping MCP functions to TaskManager methods.
"""
def __init__(self, base_dir: Optional[str] = None, log_level: str = "INFO"):
"""
Initialize TaskManagerMCP with base directory and log level.
Args:
base_dir: Base directory for task management (default: $HOME/claude_tasks)
log_level: Logging level (default: INFO)
Raises:
RuntimeError: If Claude Code is not available
"""
# Setup logging first for proper initialization messages
self.log_level = getattr(logging, log_level.upper())
logging.basicConfig(
level=self.log_level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger("TaskManagerMCP")
self.logger.setLevel(self.log_level)
# Check if Claude Code is available
self._check_dependencies()
# Set base directory
if base_dir is None:
base_dir = os.environ.get('CLAUDE_TASKS_DIR', str(Path.home() / 'claude_tasks'))
self.base_dir = base_dir
# Initialize TaskManager
self.task_manager = TaskManager(self.base_dir, self.log_level)
# Initialize FastMCP
self.app = FastMCP(name="Claude Task Manager")
# Register functions
self._register_functions()
self.logger.info(f"TaskManagerMCP initialized with base directory: {self.base_dir}")
def _check_dependencies(self) -> None:
"""
Check if all required dependencies are available.
Raises:
RuntimeError: If any dependencies are missing
"""
# Check for Claude Code
try:
import subprocess
result = subprocess.run(["claude", "--version"],
capture_output=True, text=True, check=False)
if result.returncode != 0:
self.logger.error("Claude Code is not available or not in PATH")
raise RuntimeError("Claude Code is not available. Please install Claude Desktop Commander.")
self.logger.debug(f"Claude Code version: {result.stdout.strip()}")
except FileNotFoundError:
self.logger.error("Claude Code is not available or not in PATH")
raise RuntimeError("Claude Code is not available. Please install Claude Desktop Commander.")
def _register_functions(self) -> None:
"""Register all MCP functions."""
# Project management functions
self.app.function(self.create_project, name="create_project",
description="Create a new project structure")
self.app.function(self.list_projects, name="list_projects",
description="List all projects")
self.app.function(self.check_status, name="check_status",
description="Check the status of a project")
# Task management functions
self.app.function(self.break_task, name="break_task",
description="Break down a task into individual task files")
self.app.function(self.list_tasks, name="list_tasks",
description="List all tasks in a project")
# Task execution functions
self.app.function(self.run_task, name="run_task",
description="Run a single task with context isolation")
self.app.function(self.run_project, name="run_project",
description="Run all tasks in a project with context isolation")
def create_project(self, project_name: str, source_file: Optional[str] = None) -> Dict[str, Any]:
"""
Create a new project structure.
Args:
project_name: Name of the project
source_file: Optional path to a source task file
Returns:
Dict with project creation result
"""
self.logger.info(f"MCP: Creating project {project_name}")
try:
project_dir = self.task_manager.create_project(project_name, source_file)
result: ProjectResult = {
"success": True,
"project_name": project_name,
"project_dir": str(project_dir),
"source_file": source_file
}
return result
except ValueError as e:
# Handle validation errors
self.logger.error(f"Validation error creating project: {e}")
return {
"success": False,
"error": str(e),
"error_type": "validation_error",
"context": {"project_name": project_name}
}
except FileNotFoundError as e:
# Handle file not found errors
self.logger.error(f"File not found error creating project: {e}")
return {
"success": False,
"error": str(e),
"error_type": "file_not_found",
"context": {"project_name": project_name, "source_file": source_file}
}
except Exception as e:
# Handle unexpected errors
self.logger.error(f"Error creating project: {e}")
return {
"success": False,
"error": str(e),
"error_type": "unexpected_error",
"context": {"project_name": project_name}
}
def break_task(self, project_name: str, source_file: str) -> Dict[str, Any]:
"""
Break down a task into individual task files.
Args:
project_name: Name of the project
source_file: Path to source task file
Returns:
Dict with task breakdown result
"""
self.logger.info(f"MCP: Breaking down task file for project {project_name}")
try:
project_dir, created_files = self.task_manager.break_down_task(project_name, source_file)
result: TaskBreakdownResult = {
"success": True,
"project_name": project_name,
"project_dir": str(project_dir),
"source_file": source_file,
"created_files": created_files,
"file_count": len(created_files),
"next_step_command": f"claude-tasks run-project {project_name}"
}
return result
except Exception as e:
self.logger.error(f"Error breaking down task: {e}")
return {
"success": False,
"error": str(e),
"context": {
"project_name": project_name,
"source_file": source_file
}
}
def run_task(self, project_name: str, task_filename: str) -> Dict[str, Any]:
"""
Run a single task with context isolation.
Args:
project_name: Name of the project
task_filename: Name of the task file
Returns:
Dict with task execution result
"""
self.logger.info(f"MCP: Running task {task_filename} in project {project_name}")
try:
result_file = self.task_manager.run_task(project_name, task_filename)
# Read result file content
with open(result_file, 'r') as f:
content = f.read()
result: Dict[str, Any] = {
"success": True,
"project_name": project_name,
"task_filename": task_filename,
"result_file": result_file,
"content": content
}
return result
except Exception as e:
self.logger.error(f"Error running task: {e}")
return {
"success": False,
"error": str(e),
"context": {
"project_name": project_name,
"task_filename": task_filename
}
}
def run_project(self, project_name: str, skip_confirmation: bool = True) -> Dict[str, Any]:
"""
Run all tasks in a project with context isolation.
Args:
project_name: Name of the project
skip_confirmation: Skip confirmation prompt and run all tasks
Returns:
Dict with project execution result
"""
self.logger.info(f"MCP: Running all tasks in project {project_name}")
try:
# In MCP, we always skip confirmation
results = self.task_manager.run_project(project_name)
# Get task status
status = self.task_manager.check_project_status(project_name)
# Collect result contents
task_results = []
for result_file in results:
task_file = os.path.basename(result_file).replace('.result', '.md')
try:
with open(result_file, 'r') as f:
content = f.read()
task_results.append({
"task_file": task_file,
"result_file": result_file,
"status": "success",
"content": content
})
except Exception as e:
task_results.append({
"task_file": task_file,
"result_file": result_file,
"status": "error",
"error": str(e)
})
result: Dict[str, Any] = {
"success": True,
"project_name": project_name,
"total_tasks": status["total_tasks"],
"completed_tasks": status["completed_tasks"],
"pending_tasks": status["pending_tasks"],
"completion_percentage": status["completion_percentage"],
"results": task_results
}
return result
except Exception as e:
self.logger.error(f"Error running project: {e}")
return {
"success": False,
"error": str(e),
"context": {"project_name": project_name}
}
def list_projects(self) -> Dict[str, Any]:
"""
List all projects.
Returns:
Dict with project list result
"""
self.logger.info("MCP: Listing all projects")
try:
projects = self.task_manager.list_projects()
result: ProjectListResult = {
"success": True,
"base_dir": self.base_dir,
"projects": projects,
"count": len(projects)
}
return result
except Exception as e:
self.logger.error(f"Error listing projects: {e}")
return {
"success": False,
"error": str(e),
"context": {"base_dir": self.base_dir}
}
def list_tasks(self, project_name: str) -> Dict[str, Any]:
"""
List all tasks in a project.
Args:
project_name: Name of the project
Returns:
Dict with task list result
"""
self.logger.info(f"MCP: Listing tasks in project {project_name}")
try:
tasks = self.task_manager.list_tasks(project_name)
result: TaskListResult = {
"success": True,
"project_name": project_name,
"tasks": tasks,
"count": len(tasks)
}
return result
except Exception as e:
self.logger.error(f"Error listing tasks: {e}")
return {
"success": False,
"error": str(e),
"context": {"project_name": project_name}
}
def check_status(self, project_name: str) -> Dict[str, Any]:
"""
Check the status of a project.
Args:
project_name: Name of the project
Returns:
Dict with project status
"""
self.logger.info(f"MCP: Checking status of project {project_name}")
try:
status = self.task_manager.check_project_status(project_name)
if not status["exists"]:
return {
"success": False,
"error": f"Project {project_name} not found",
"context": {"project_name": project_name}
}
# Get the tasks and results for more detailed status
tasks = self.task_manager.list_tasks(project_name)
results = self.task_manager.get_task_results(project_name)
# Create a detailed task status list
task_status = []
for task in tasks:
if task in results:
task_status.append({
"task_file": task,
"status": "completed",
"result_file": results[task]
})
else:
task_status.append({
"task_file": task,
"status": "pending",
"result_file": None
})
# Prepare comprehensive status response
result = {
"success": True,
"project_name": project_name,
"total_tasks": status["total_tasks"],
"completed_tasks": status["completed_tasks"],
"pending_tasks": status["pending_tasks"],
"completion_percentage": status["completion_percentage"],
"tasks": task_status,
"base_dir": self.base_dir,
"project_dir": str(Path(self.base_dir) / project_name)
}
return result
except Exception as e:
self.logger.error(f"Error checking project status: {e}")
return {
"success": False,
"error": str(e),
"error_type": "unexpected_error",
"context": {"project_name": project_name}
}
def run(self, host: str = "localhost", port: int = 3000) -> None:
"""
Run the FastMCP server.
Args:
host: Host to listen on (default: localhost)
port: Port to listen on (default: 3000)
"""
self.logger.info(f"Starting FastMCP server on {host}:{port}")
self.app.run(host=host, port=port)
# Default instance for simple import
default_mcp = TaskManagerMCP()
if __name__ == "__main__":
# If run directly, start the server
default_mcp.run()
#!/usr/bin/env python
"""
MCP diagnostic tool for Claude Task Manager.
This script provides utilities to diagnose MCP-related issues with the Task Manager.
"""
import os
import sys
import json
import logging
import argparse
import subprocess
from pathlib import Path
import socket
from typing import Dict, List, Any, Optional, Tuple
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("MCP-Diagnostic")
def check_mcp_config() -> Tuple[bool, str]:
"""
Check if MCP configuration exists and contains task_manager entry.
Returns:
Tuple with success status and config path
"""
# Get MCP config path
mcp_config_path = os.environ.get('MCP_CONFIG_PATH')
if mcp_config_path:
config_path = Path(mcp_config_path)
else:
config_path = Path.home() / '.mcp.json'
# Check if config exists
if not config_path.exists():
logger.error(f"MCP config not found at {config_path}")
return False, str(config_path)
# Load config
try:
with open(config_path, 'r') as f:
config = json.load(f)
except json.JSONDecodeError:
logger.error(f"MCP config at {config_path} is not valid JSON")
return False, str(config_path)
# Check if task_manager is configured
if 'mcpServers' not in config:
logger.error("No mcpServers section in MCP config")
return False, str(config_path)
if 'task_manager' not in config['mcpServers']:
logger.error("No task_manager entry in MCP config")
return False, str(config_path)
tm_config = config['mcpServers']['task_manager']
if 'command' not in tm_config or 'args' not in tm_config:
logger.error("task_manager config is incomplete")
return False, str(config_path)
logger.info(f"MCP config found at {config_path} with task_manager entry")
return True, str(config_path)
def check_claude_code() -> bool:
"""
Check if Claude Code is available.
Returns:
True if Claude Code is available, False otherwise
"""
try:
result = subprocess.run(
["claude", "--version"],
capture_output=True,
text=True,
check=False
)
if result.returncode != 0:
logger.error("Claude Code is installed but returned an error")
logger.error(f"Error: {result.stderr}")
return False
logger.info(f"Claude Code found: {result.stdout.strip()}")
return True
except FileNotFoundError:
logger.error("Claude Code not found in PATH")
return False
def check_desktop_commander() -> bool:
"""
Check if Desktop Commander is installed and running.
Returns:
True if Desktop Commander is detected, False otherwise
"""
# This is a simplified check - there's no direct way to check for Desktop Commander
# Instead, we'll try to check if the directory exists where it would typically be installed
# Common installation paths
paths = [
Path.home() / '.config' / 'desktop-commander',
Path.home() / 'Library' / 'Application Support' / 'desktop-commander'
]
# Check if any of the paths exist
found = any(path.exists() for path in paths)
if found:
logger.info("Desktop Commander installation detected")
else:
logger.error("Desktop Commander installation not detected")
logger.error("Please install Desktop Commander and ensure it's running")
return found
def check_dependencies() -> bool:
"""
Check if all required dependencies are installed.
Returns:
True if all dependencies are available, False otherwise
"""
try:
import fastmcp
logger.info("FastMCP is installed")
fastmcp_ok = True
except ImportError:
logger.error("FastMCP is not installed")
logger.error("Please install it with: pip install fastmcp")
fastmcp_ok = False
try:
import typer
logger.info("Typer is installed")
typer_ok = True
except ImportError:
logger.error("Typer is not installed")
logger.error("Please install it with: pip install typer")
typer_ok = False
try:
import rich
logger.info("Rich is installed")
rich_ok = True
except ImportError:
logger.error("Rich is not installed")
logger.error("Please install it with: pip install rich")
rich_ok = False
return fastmcp_ok and typer_ok and rich_ok
def check_port(port: int) -> bool:
"""
Check if a port is available.
Args:
port: Port number to check
Returns:
True if port is available, False otherwise
"""
try:
# Try to bind to the port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("localhost", port))
logger.info(f"Port {port} is available")
return True
except socket.error:
logger.error(f"Port {port} is already in use")
return False
def check_server(host: str, port: int) -> bool:
"""
Check if the server is running at the specified host and port.
Args:
host: Host to check
port: Port to check
Returns:
True if server is running, False otherwise
"""
try:
# Try to connect to the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(1)
s.connect((host, port))
logger.info(f"Server is running at {host}:{port}")
return True
except (socket.error, socket.timeout):
logger.error(f"Server is not running at {host}:{port}")
return False
def run_diagnostics(run_server_check: bool = False, host: str = "localhost", port: int = 3000) -> Dict[str, Any]:
"""
Run all diagnostics and return results.
Args:
run_server_check: Whether to check if server is running
host: Host to check
port: Port to check
Returns:
Dictionary with diagnostic results
"""
results = {
"mcp_config": {
"status": False,
"path": None
},
"claude_code": {
"status": False
},
"desktop_commander": {
"status": False
},
"dependencies": {
"status": False
}
}
# Check MCP config
mcp_config_ok, config_path = check_mcp_config()
results["mcp_config"]["status"] = mcp_config_ok
results["mcp_config"]["path"] = config_path
# Check Claude Code
claude_code_ok = check_claude_code()
results["claude_code"]["status"] = claude_code_ok
# Check Desktop Commander
desktop_commander_ok = check_desktop_commander()
results["desktop_commander"]["status"] = desktop_commander_ok
# Check dependencies
dependencies_ok = check_dependencies()
results["dependencies"]["status"] = dependencies_ok
# Check server if requested
if run_server_check:
results["server"] = {
"status": check_server(host, port),
"host": host,
"port": port
}
# Check port if not checking server
else:
results["port"] = {
"status": check_port(port),
"port": port
}
# Overall status
results["overall"] = {
"status": all([
mcp_config_ok,
claude_code_ok,
desktop_commander_ok,
dependencies_ok
])
}
return results
def format_results(results: Dict[str, Any]) -> None:
"""
Format and print diagnostic results.
Args:
results: Diagnostic results
"""
print("\n===== Claude Task Manager MCP Diagnostics =====\n")
# MCP Config
if results["mcp_config"]["status"]:
print("✅ MCP Config: Found")
print(f" Path: {results['mcp_config']['path']}")
else:
print("❌ MCP Config: Not found or invalid")
print(f" Expected path: {results['mcp_config']['path']}")
# Claude Code
if results["claude_code"]["status"]:
print("✅ Claude Code: Installed and accessible")
else:
print("❌ Claude Code: Not installed or not in PATH")
# Desktop Commander
if results["desktop_commander"]["status"]:
print("✅ Desktop Commander: Detected")
else:
print("❌ Desktop Commander: Not detected")
# Dependencies
if results["dependencies"]["status"]:
print("✅ Dependencies: All installed")
else:
print("❌ Dependencies: Some missing")
# Server or port
if "server" in results:
if results["server"]["status"]:
print(f"✅ Server: Running at {results['server']['host']}:{results['server']['port']}")
else:
print(f"❌ Server: Not running at {results['server']['host']}:{results['server']['port']}")
elif "port" in results:
if results["port"]["status"]:
print(f"✅ Port {results['port']['port']}: Available")
else:
print(f"❌ Port {results['port']['port']}: Already in use")
# Overall status
print("\n===== Overall Status =====")
if results["overall"]["status"]:
print("✅ All checks passed. MCP integration should work correctly.")
else:
print("❌ Some checks failed. MCP integration may not work correctly.")
# Print troubleshooting tips if there are failures
if not results["overall"]["status"]:
print("\n===== Troubleshooting Tips =====")
if not results["mcp_config"]["status"]:
print("• Create a .mcp.json file in your home directory with the task_manager entry")
print(" See README.md for the correct format")
if not results["claude_code"]["status"]:
print("• Ensure Claude Code is installed and in your PATH")
print(" Try running 'claude --version' to verify")
if not results["desktop_commander"]["status"]:
print("• Install Desktop Commander using the instructions in README.md")
print("• Ensure Claude Desktop is running with the hammer icon visible")
if not results["dependencies"]["status"]:
print("• Install missing dependencies with pip:")
print(" pip install fastmcp typer rich")
if "server" in results and not results["server"]["status"]:
print(f"• Start the server with: python run_task_manager_server.py --host {results['server']['host']} --port {results['server']['port']}")
elif "port" in results and not results["port"]["status"]:
print(f"• Port {results['port']['port']} is already in use. Choose a different port")
print(f" python run_task_manager_server.py --port <different-port>")
def main():
"""Command-line interface for MCP diagnostics."""
parser = argparse.ArgumentParser(description="MCP diagnostic tool for Task Manager")
parser.add_argument(
"--check-server",
action="store_true",
help="Check if server is running (default: check if port is available)"
)
parser.add_argument(
"--host",
default="localhost",
help="Host to check (default: localhost)"
)
parser.add_argument(
"--port",
type=int,
default=3000,
help="Port to check (default: 3000)"
)
parser.add_argument(
"--json",
action="store_true",
help="Output results as JSON"
)
args = parser.parse_args()
# Run diagnostics
results = run_diagnostics(args.check_server, args.host, args.port)
# Output results
if args.json:
print(json.dumps(results, indent=2))
else:
format_results(results)
if __name__ == "__main__":
main()
"""
MCP schema generator for Claude Task Manager.
This module provides utilities to generate MCP-compatible schema
for the Task Manager functions.
"""
import json
import sys
import argparse
from typing import Dict, List, Any, Optional
from claude_task_manager.types import MCPSchema, MCPFunction, MCPParameter
def generate_mcp_schema() -> MCPSchema:
"""
Generate a complete MCP schema for all Task Manager functions.
Returns:
MCPSchema with all function definitions
"""
# Define the schema metadata
schema: MCPSchema = {
"functions": [],
"metadata": {
"name": "claude-task-manager",
"version": "1.0.0",
"description": "Claude Task Manager - Manage complex tasks with context isolation",
"requires_desktop_commander": True,
"requires_claude_code": True,
"mcp_compatible": True,
"documentation_url": "https://github.com/your-repo/claude_task_manager",
"author": "Anthropic"
}
}
# Add create_project function
schema["functions"].append({
"name": "create_project",
"description": "Create a new project structure",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True,
"default": None
},
"source_file": {
"type": "string",
"description": "Path to source task file (optional)",
"required": False,
"default": None
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the created project"
},
"project_dir": {
"type": "string",
"description": "Path to the created project directory"
}
}
},
"examples": [
{
"description": "Create a new empty project",
"input": {"project_name": "my_project"},
"output": {
"success": True,
"project_name": "my_project",
"project_dir": "/home/user/claude_tasks/my_project"
}
},
{
"description": "Create a project with a source task file",
"input": {
"project_name": "my_project",
"source_file": "/path/to/task_list.md"
},
"output": {
"success": True,
"project_name": "my_project",
"project_dir": "/home/user/claude_tasks/my_project",
"source_file": "/path/to/task_list.md"
}
}
]
})
# Add break_task function
schema["functions"].append({
"name": "break_task",
"description": "Break down a task into individual task files",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True,
"default": None
},
"source_file": {
"type": "string",
"description": "Path to source task file",
"required": True,
"default": None
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"project_dir": {
"type": "string",
"description": "Path to the project directory"
},
"created_files": {
"type": "array",
"description": "List of created task files",
"items": {
"type": "string"
}
},
"file_count": {
"type": "integer",
"description": "Number of files created"
}
}
},
"examples": [
{
"description": "Break down a task file",
"input": {
"project_name": "my_project",
"source_file": "/path/to/task_list.md"
},
"output": {
"success": True,
"project_name": "my_project",
"project_dir": "/home/user/claude_tasks/my_project",
"created_files": [
"000_project_overview.md",
"001_first_task.md",
"002_second_task.md"
],
"file_count": 3
}
}
]
})
# Add run_task function
schema["functions"].append({
"name": "run_task",
"description": "Run a single task with context isolation",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True,
"default": None
},
"task_filename": {
"type": "string",
"description": "Name of the task file",
"required": True,
"default": None
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"task_filename": {
"type": "string",
"description": "Name of the task file"
},
"result_file": {
"type": "string",
"description": "Path to the result file"
},
"content": {
"type": "string",
"description": "Content of the result file"
}
}
},
"examples": [
{
"description": "Run a single task",
"input": {
"project_name": "my_project",
"task_filename": "001_first_task.md"
},
"output": {
"success": True,
"project_name": "my_project",
"task_filename": "001_first_task.md",
"result_file": "/home/user/claude_tasks/my_project/results/001_first_task.result",
"content": "Task execution output..."
}
}
]
})
# Add run_project function
schema["functions"].append({
"name": "run_project",
"description": "Run all tasks in a project with context isolation",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True,
"default": None
},
"skip_confirmation": {
"type": "boolean",
"description": "Skip confirmation prompt and run all tasks",
"required": False,
"default": True
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"total_tasks": {
"type": "integer",
"description": "Total number of tasks"
},
"completed_tasks": {
"type": "integer",
"description": "Number of tasks completed"
},
"pending_tasks": {
"type": "integer",
"description": "Number of tasks pending"
},
"results": {
"type": "array",
"description": "Task execution results",
"items": {
"type": "object"
}
}
}
},
"examples": [
{
"description": "Run all tasks in a project",
"input": {"project_name": "my_project"},
"output": {
"success": True,
"project_name": "my_project",
"total_tasks": 3,
"completed_tasks": 3,
"pending_tasks": 0,
"results": [
{
"task_file": "001_first_task.md",
"result_file": "/path/to/result1.result",
"status": "success",
"content": "Task execution output..."
},
{
"task_file": "002_second_task.md",
"result_file": "/path/to/result2.result",
"status": "success",
"content": "Task execution output..."
}
]
}
}
]
})
# Add list_projects function
schema["functions"].append({
"name": "list_projects",
"description": "List all projects",
"parameters": {},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"base_dir": {
"type": "string",
"description": "Base directory for task management"
},
"projects": {
"type": "array",
"description": "List of project names",
"items": {
"type": "string"
}
},
"count": {
"type": "integer",
"description": "Number of projects found"
}
}
},
"examples": [
{
"description": "List all projects",
"input": {},
"output": {
"success": True,
"base_dir": "/home/user/claude_tasks",
"projects": ["project1", "project2", "project3"],
"count": 3
}
}
]
})
# Add list_tasks function
schema["functions"].append({
"name": "list_tasks",
"description": "List all tasks in a project",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True,
"default": None
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"tasks": {
"type": "array",
"description": "List of task filenames",
"items": {
"type": "string"
}
},
"count": {
"type": "integer",
"description": "Number of tasks found"
}
}
},
"examples": [
{
"description": "List all tasks in a project",
"input": {"project_name": "my_project"},
"output": {
"success": True,
"project_name": "my_project",
"tasks": [
"000_project_overview.md",
"001_first_task.md",
"002_second_task.md"
],
"count": 3
}
}
]
})
# Add check_status function
schema["functions"].append({
"name": "check_status",
"description": "Check the status of a project",
"parameters": {
"project_name": {
"type": "string",
"description": "Name of the project",
"required": True,
"default": None
}
},
"returns": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the operation was successful"
},
"project_name": {
"type": "string",
"description": "Name of the project"
},
"total_tasks": {
"type": "integer",
"description": "Total number of tasks"
},
"completed_tasks": {
"type": "integer",
"description": "Number of tasks completed"
},
"pending_tasks": {
"type": "integer",
"description": "Number of tasks pending"
},
"completion_percentage": {
"type": "number",
"description": "Percentage of completion"
},
"tasks": {
"type": "array",
"description": "Status of each task",
"items": {
"type": "object"
}
}
}
},
"examples": [
{
"description": "Check the status of a project",
"input": {"project_name": "my_project"},
"output": {
"success": True,
"project_name": "my_project",
"total_tasks": 3,
"completed_tasks": 1,
"pending_tasks": 2,
"completion_percentage": 33.33,
"tasks": [
{
"task_file": "000_project_overview.md",
"status": "completed",
"result_file": "/path/to/result.result"
},
{
"task_file": "001_first_task.md",
"status": "pending",
"result_file": None
}
]
}
}
]
})
return schema
def save_schema_to_file(schema: MCPSchema, output_file: str) -> None:
"""
Save the schema to a file in JSON format.
Args:
schema: The MCP schema to save
output_file: Path to the output file
"""
with open(output_file, 'w') as f:
json.dump(schema, f, indent=2)
print(f"Schema saved to {output_file}")
def main():
"""Command-line interface for schema generation."""
parser = argparse.ArgumentParser(description="Generate MCP schema for Task Manager")
parser.add_argument(
"--output", "-o",
default="task_manager_mcp_schema.json",
help="Output file path (default: task_manager_mcp_schema.json)"
)
args = parser.parse_args()
# Generate and save the schema
schema = generate_mcp_schema()
save_schema_to_file(schema, args.output)
if __name__ == "__main__":
main()
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "claude-task-manager"
version = "1.0.0"
description = "Task management system for Claude Code with context isolation"
readme = "README.md"
authors = [
{name = "Graham Anderson", email = "[email protected]"}
]
license = {text = "MIT License"}
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
dependencies = [
"typer>=0.9.0",
"rich>=13.5.0",
"mcp>=0.9.0",
"python-dotenv>=1.1.0"
]
[project.scripts]
claude-tasks = "claude_task_manager.cli:main"
[project.urls]
Repository = "https://github.com/grahama1970/claude-task-manager"
[tool.setuptools]
packages = ["claude_task_manager"]
"""
Core task manager module - provides the main TaskManager class that handles projects, tasks, and execution.
This module requires Desktop Commander to be installed and enabled in Claude Desktop for file system access.
See: https://desktopcommander.app/#installation
"""
import os
import sys
import subprocess
import shutil
import re
import json
import logging
import tempfile
import time
from pathlib import Path
from typing import List, Dict, Any, Optional, Union, Tuple, cast
from claude_task_manager.types import (
ProjectInfo, ProjectResult, TaskBreakdownResult, TaskRunResult,
ProjectRunResult, ProjectListResult, TaskListResult, ErrorResult,
LogLevel as LogLevelType
)
class TaskManager:
"""
Main class for managing Claude Code task execution with context isolation.
This class handles the entire workflow:
1. Creating project structures
2. Breaking down large tasks into individual task files
3. Executing tasks with context isolation
4. Managing results
"""
def __init__(self, base_dir: str, log_level: int = logging.INFO):
"""
Initialize the TaskManager with a base directory.
Args:
base_dir: Base directory for all projects
log_level: Logging level to use
"""
self.base_dir = Path(base_dir)
self.setup_logging(log_level)
# Create base directory if it doesn't exist
self.base_dir.mkdir(exist_ok=True, parents=True)
self.logger.info(f"Task Manager initialized at {self.base_dir}")
def setup_logging(self, log_level: int) -> None:
"""
Set up logging configuration.
Args:
log_level: Logging level to use
"""
logging.basicConfig(
level=log_level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger("TaskManager")
self.logger.setLevel(log_level)
def create_project(self, project_name: str, source_file: Optional[str] = None) -> Path:
"""
Create a new project structure.
Args:
project_name: Name of the project
source_file: Optional path to a source task file
Returns:
Path to the created project directory
Raises:
ValueError: If the project name is invalid
FileNotFoundError: If the source file doesn't exist
"""
self.logger.info(f"Creating project: {project_name}")
# Validate project name
if not project_name or not project_name.strip():
raise ValueError("Project name cannot be empty")
if os.path.sep in project_name or project_name.startswith('.'):
raise ValueError(f"Invalid project name: {project_name}")
# Create project directory structure
project_dir = self.base_dir / project_name
tasks_dir = project_dir / "tasks"
results_dir = project_dir / "results"
temp_dir = project_dir / "temp"
for dir_path in [project_dir, tasks_dir, results_dir, temp_dir]:
dir_path.mkdir(exist_ok=True, parents=True)
# Initialize sequence file
sequence_file = project_dir / "task_sequence.txt"
with open(sequence_file, 'w') as f:
f.write(f"# Task sequence for project: {project_name}\n")
f.write("# Format: one task filename per line (relative to tasks directory)\n")
f.write("# Lines starting with # are comments and will be ignored\n")
f.write("# Empty lines are also ignored\n\n")
# Initialize project metadata
project_info: ProjectInfo = {
"name": project_name,
"created_at": str(Path.ctime(Path.home())),
"tasks_dir": str(tasks_dir),
"results_dir": str(results_dir),
"source_file": None
}
with open(project_dir / "project.json", 'w') as f:
json.dump(project_info, f, indent=2)
# Copy source file if provided
if source_file:
source_path = Path(source_file)
if not source_path.exists():
raise FileNotFoundError(f"Source file not found: {source_file}")
self.logger.info(f"Copying source file: {source_file}")
target_path = project_dir / "task_list.md"
shutil.copy2(source_file, target_path)
project_info["source_file"] = str(target_path)
# Update project metadata
with open(project_dir / "project.json", 'w') as f:
json.dump(project_info, f, indent=2)
self.logger.info(f"Project created at {project_dir}")
return project_dir
def break_down_task(self, project_name: str, source_file: str) -> Tuple[Path, List[str]]:
"""
Break down a large task file into individual task files using Claude Code.
Args:
project_name: Name of the project
source_file: Path to the source task file
Returns:
Tuple containing:
- Path to the project directory
- List of created task files
Raises:
FileNotFoundError: If the source file doesn't exist
RuntimeError: If the task breakdown fails
"""
self.logger.info(f"Breaking down task file: {source_file} for project: {project_name}")
# Validate source file
source_path = Path(source_file)
if not source_path.exists():
raise FileNotFoundError(f"Source file not found: {source_file}")
# Ensure project exists or create it
project_dir = self.base_dir / project_name
if not project_dir.exists():
project_dir = self.create_project(project_name, source_file)
tasks_dir = project_dir / "tasks"
temp_dir = project_dir / "temp"
temp_dir.mkdir(exist_ok=True)
# Create instructions for Claude
instructions_file = temp_dir / "breakdown_instructions.md"
result_file = temp_dir / "breakdown_result.md"
with open(instructions_file, 'w') as f:
f.write("# Task Breakdown Instructions\n\n")
f.write("I need you to analyze the following task list and break it down into individual task files that can be executed independently.\n\n")
f.write("## Source Task List\n\n")
# Read and include the source file content
with open(source_file, 'r') as src:
f.write(src.read())
f.write("\n\n## Instructions\n\n")
f.write("1. Analyze the task list and identify logical divisions for independent tasks\n")
f.write("2. For each task, create a separate markdown file named with a numerical prefix (e.g., 001_task_name.md)\n")
f.write("3. Ensure each task file is self-contained with all necessary context\n")
f.write("4. The first file should be 000_project_overview.md with a summary of the entire project\n")
f.write("5. For each task file, include:\n")
f.write(" - Clear title and objective\n")
f.write(" - Required context or background\n")
f.write(" - Implementation steps\n")
f.write(" - Verification methods\n")
f.write(" - Acceptance criteria\n")
f.write("6. Generate a task_sequence.txt file listing the tasks in execution order\n\n")
f.write("## Output Format\n\n")
f.write("For each task file, provide the filename and content in this format:\n\n")
f.write("### [FILENAME: 000_project_overview.md]\n")
f.write("# Project Overview\n")
f.write("...content...\n\n")
f.write("### [FILENAME: 001_first_task.md]\n")
f.write("# Task 1: First Task\n")
f.write("...content...\n\n")
f.write("And so on. After listing all task files, provide a task_sequence.txt file with all filenames in execution order.\n\n")
f.write("This breakdown should ensure each task can be executed in isolation without requiring context from other tasks.\n")
# Run Claude Code to perform the breakdown
self.logger.info("Running Claude Code to analyze and break down the task list...")
cmd = ["claude", "code", "--input", str(instructions_file), "--output", str(result_file)]
try:
result = subprocess.run(cmd, check=True, text=True, capture_output=True)
self.logger.debug(f"Claude Code output: {result.stdout}")
except subprocess.CalledProcessError as e:
self.logger.error(f"Claude Code failed with error: {e}")
self.logger.error(f"STDOUT: {e.stdout}")
self.logger.error(f"STDERR: {e.stderr}")
raise RuntimeError("Task breakdown failed") from e
self.logger.info("Task breakdown complete. Extracting task files...")
# Process the breakdown result
created_files = self._extract_task_files(result_file, tasks_dir, project_dir)
self.logger.info(f"Created {len(created_files)} task files")
return project_dir, created_files
def _extract_task_files(self, result_file: Path, tasks_dir: Path, project_dir: Path) -> List[str]:
"""
Extract task files from Claude's breakdown result.
Args:
result_file: Path to the breakdown result file
tasks_dir: Directory to write task files to
project_dir: Root project directory
Returns:
List of created task filenames
Raises:
FileNotFoundError: If the result file doesn't exist
"""
if not result_file.exists():
raise FileNotFoundError(f"Result file not found: {result_file}")
created_files: List[str] = []
# Process the breakdown result
with open(result_file, 'r') as f:
content = f.read()
# Extract task files using regex
file_pattern = r'### \[FILENAME: ([^\]]+)\](.*?)(?=### \[FILENAME:|$)'
matches = re.findall(file_pattern, content, re.DOTALL)
for filename, file_content in matches:
filename = filename.strip()
if filename == "task_sequence.txt":
# Handle task sequence file separately
with open(project_dir / "task_sequence.txt", 'w') as f:
f.write(file_content.strip())
else:
# Handle regular task files
file_path = tasks_dir / filename
with open(file_path, 'w') as f:
f.write(file_content.strip())
created_files.append(filename)
# If no task sequence file was provided, generate one
if not any(f == "task_sequence.txt" for f, _ in matches):
self.logger.info("Generating task_sequence.txt based on filenames...")
with open(project_dir / "task_sequence.txt", 'a') as f:
f.write("\n# Generated task sequence\n")
for filename in sorted(created_files):
f.write(f"{filename}\n")
return created_files
def run_task(self, project_name: str, task_filename: str) -> str:
"""
Run a single task with context isolation.
Args:
project_name: Name of the project
task_filename: Name of the task file
Returns:
Path to the result file
Raises:
FileNotFoundError: If the project or task file doesn't exist
RuntimeError: If the task execution fails
"""
self.logger.info(f"Running task: {task_filename} in project: {project_name}")
project_dir = self.base_dir / project_name
tasks_dir = project_dir / "tasks"
results_dir = project_dir / "results"
# Ensure directories exist
for dir_path in [project_dir, tasks_dir, results_dir]:
if not dir_path.exists():
self.logger.error(f"Directory not found: {dir_path}")
raise FileNotFoundError(f"Directory not found: {dir_path}")
# Construct task file path
task_file = tasks_dir / task_filename
if not task_file.exists():
self.logger.error(f"Task file not found: {task_file}")
raise FileNotFoundError(f"Task file not found: {task_file}")
# Prepare result file
result_filename = task_filename.replace('.md', '.result')
result_file = results_dir / result_filename
# Create temporary file for Claude input with clear command
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as tmp:
tmp.write("/clear\n")
tmp.write("# Focus on this task only\n")
tmp.write("Please focus exclusively on the task described in this file.\n")
tmp.write("Do not attempt to access previous context or tasks.\n\n")
# Append task content
with open(task_file, 'r') as f:
tmp.write(f.read())
tmp_path = tmp.name
# Run Claude Code
self.logger.info(f"Starting Claude Code for task: {task_filename}")
cmd = ["claude", "code", "--input", tmp_path, "--output", str(result_file)]
try:
result = subprocess.run(cmd, check=True, text=True, capture_output=True)
self.logger.debug(f"Claude Code output: {result.stdout}")
except subprocess.CalledProcessError as e:
self.logger.error(f"Claude Code failed with error: {e}")
self.logger.error(f"STDOUT: {e.stdout}")
self.logger.error(f"STDERR: {e.stderr}")
raise RuntimeError(f"Task execution failed for {task_filename}") from e
finally:
# Clean up temporary file
os.unlink(tmp_path)
self.logger.info(f"Task completed: {task_filename}")
self.logger.info(f"Result saved to: {result_file}")
return str(result_file)
def run_project(self, project_name: str) -> List[str]:
"""
Run all tasks in a project in sequence.
Args:
project_name: Name of the project
Returns:
List of result file paths
Raises:
FileNotFoundError: If the project directory doesn't exist
"""
self.logger.info(f"Running all tasks for project: {project_name}")
project_dir = self.base_dir / project_name
tasks_dir = project_dir / "tasks"
sequence_file = project_dir / "task_sequence.txt"
# Ensure project exists
if not project_dir.exists():
self.logger.error(f"Project directory not found: {project_dir}")
raise FileNotFoundError(f"Project directory not found: {project_dir}")
# Get task sequence
task_files: List[str] = []
if sequence_file.exists():
self.logger.info(f"Using task sequence from: {sequence_file}")
with open(sequence_file, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
task_files.append(line)
else:
self.logger.warning(f"No task sequence file found at {sequence_file}")
self.logger.info("Using alphabetical order for task execution")
task_files = sorted([f.name for f in tasks_dir.glob('*.md')])
# Display execution plan
self.logger.info(f"Task execution plan for {project_name}:")
for i, task in enumerate(task_files, 1):
self.logger.info(f" {i}. {task}")
# Run each task
results: List[str] = []
for task_file in task_files:
try:
result = self.run_task(project_name, task_file)
results.append(result)
# Add a small delay between tasks
time.sleep(2)
except Exception as e:
self.logger.error(f"Error running task {task_file}: {e}")
# Continue with next task
self.logger.info(f"All tasks for project {project_name} completed")
return results
def list_projects(self) -> List[str]:
"""
List all projects in the base directory.
Returns:
List of project names
"""
projects: List[str] = []
for path in self.base_dir.iterdir():
if path.is_dir() and (path / "project.json").exists():
projects.append(path.name)
return sorted(projects)
def list_tasks(self, project_name: str) -> List[str]:
"""
List all tasks in a project.
Args:
project_name: Name of the project
Returns:
List of task filenames
"""
project_dir = self.base_dir / project_name
tasks_dir = project_dir / "tasks"
if not tasks_dir.exists():
return []
return sorted([f.name for f in tasks_dir.glob('*.md')])
def get_task_results(self, project_name: str) -> Dict[str, str]:
"""
Get all task results for a project.
Args:
project_name: Name of the project
Returns:
Dictionary mapping task filenames to result file paths
"""
project_dir = self.base_dir / project_name
results_dir = project_dir / "results"
if not results_dir.exists():
return {}
results: Dict[str, str] = {}
for result_file in results_dir.glob('*.result'):
task_filename = result_file.name.replace('.result', '.md')
results[task_filename] = str(result_file)
return results
def check_project_status(self, project_name: str) -> Dict[str, Any]:
"""
Check the status of a project.
Args:
project_name: Name of the project
Returns:
Dictionary with project status information
"""
project_dir = self.base_dir / project_name
if not project_dir.exists():
return {"exists": False}
tasks = self.list_tasks(project_name)
results = self.get_task_results(project_name)
completed_tasks = [task for task in tasks if task in results]
pending_tasks = [task for task in tasks if task not in results]
return {
"exists": True,
"total_tasks": len(tasks),
"completed_tasks": len(completed_tasks),
"pending_tasks": len(pending_tasks),
"completion_percentage": 100 * len(completed_tasks) / len(tasks) if tasks else 0
}
"""
Type definitions for Claude Task Manager.
This module defines TypedDict classes and other type annotations used throughout the package
to provide more specific typing information.
"""
from typing import TypedDict, List, Dict, Any, Optional, Union, Literal
class ProjectInfo(TypedDict):
"""Information about a project."""
name: str
created_at: str
tasks_dir: str
results_dir: str
source_file: Optional[str]
class ProjectResult(TypedDict):
"""Result of a project creation operation."""
success: bool
project_name: str
project_dir: str
source_file: Optional[str]
class TaskBreakdownResult(TypedDict):
"""Result of a task breakdown operation."""
success: bool
project_name: str
project_dir: str
source_file: str
created_files: List[str]
file_count: int
next_step_command: str
class TaskRunResult(TypedDict):
"""Result of a task execution operation."""
success: bool
project_name: str
task_filename: str
result_file: str
class ProjectRunResult(TypedDict):
"""Result of a project execution operation."""
success: bool
project_name: str
total_tasks: int
completed_tasks: int
failed_tasks: int
tasks: List[Dict[str, Any]]
results: List[str]
class ProjectListResult(TypedDict):
"""Result of a project listing operation."""
success: bool
base_dir: str
projects: List[str]
count: int
class TaskListResult(TypedDict):
"""Result of a task listing operation."""
success: bool
project_name: str
tasks: List[str]
count: int
class ErrorResult(TypedDict):
"""Error result with details."""
success: bool
error: str
context: Dict[str, Any]
class SchemaCommandResult(TypedDict):
"""Schema command return type."""
name: str
version: str
description: str
commands: Dict[str, Dict[str, Any]]
options: Dict[str, Dict[str, Any]]
metadata: Dict[str, Any]
# Define literal types for common parameters
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
OutputFormat = Literal["json", "yaml"]
MergeMethod = Literal["merge", "squash", "rebase"]
# MCP-specific types
class MCPParameter(TypedDict):
"""MCP parameter description."""
type: str
description: str
required: bool
default: Optional[Any]
examples: Optional[List[Any]]
class MCPFunctionReturns(TypedDict):
"""MCP function return type description."""
type: str
description: str
properties: Dict[str, Dict[str, Any]]
class MCPFunctionExample(TypedDict):
"""MCP function example."""
description: str
input: Dict[str, Any]
output: Dict[str, Any]
class MCPFunction(TypedDict):
"""MCP function description."""
name: str
description: str
parameters: Dict[str, MCPParameter]
returns: Dict[str, Any]
examples: List[MCPFunctionExample]
class MCPMetadata(TypedDict):
"""MCP metadata."""
name: str
version: str
description: str
requires_desktop_commander: bool
requires_claude_code: bool
mcp_compatible: bool
documentation_url: Optional[str]
author: Optional[str]
class MCPSchema(TypedDict):
"""MCP schema output."""
functions: List[MCPFunction]
metadata: MCPMetadata
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment