simple-task-tracker-mcp

Ravenight13/simple-task-tracker-mcp

3.2

If you are the rightful owner of simple-task-tracker-mcp and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.

The Task MCP Server is a lightweight Model Context Protocol server designed for efficient task and subtask tracking during agentic AI development.

Tools
5
Resources
0
Prompts
0

Task MCP Server

A lightweight Model Context Protocol (MCP) server for task and subtask tracking during agentic AI development. Provides isolated SQLite databases per project workspace for Claude Code and Claude Desktop integration.

What's New in v0.4.0 🎉

Released: 2025-11-02

  • 🛡️ Workspace Metadata Tracking: Automatic capture of workspace context on task creation
  • 🔍 Workspace Validation: validate_task_workspace tool to detect cross-project contamination
  • 📋 Comprehensive Audit: audit_workspace_integrity tool for full workspace health checks
  • 📖 Audit Guide: Complete documentation in
  • 🔐 Contamination Prevention: Early detection and cleanup of misplaced tasks

All changes are backward compatible with v0.3.0. See for full details.

Previous Releases

v0.3.0 (2025-10-29)

  • 🏢 Entity System: Track files, vendors, and other entities with many-to-many task relationships
  • 🔗 Entity Links: 7 new MCP tools for entity CRUD and linking to tasks
  • 📊 Generic Metadata: Flexible JSON storage for entity-specific data
  • 🗂️ Tag-based Discovery: Filter entities by type and tags for quick discovery

v0.2.0 (2025-10-27)

  • ✨ Auto-capture conversation ID in created_by field
  • 🕐 ISO 8601 timestamp format
  • 🔧 True partial updates for tasks

Features

🎯 Core Capabilities

  • Project Isolation: Separate SQLite database per workspace (auto-detected)
  • Task Hierarchy: Parent/child task relationships with unlimited nesting
  • Dependencies: Explicit task dependencies with validation
  • Status Tracking: State machine workflow (todo → in_progress → blocked → done → cancelled)
  • Soft Delete: 30-day retention before permanent deletion
  • Full-Text Search: Search tasks by title and description
  • Tag Organization: Space-separated tags with normalization
  • Concurrent Access: SQLite WAL mode for simultaneous Claude Code + Desktop reads
  • Entity Tracking: Track files, vendors, and other entities with many-to-many task relationships
  • Generic Metadata: Flexible JSON storage for entity-specific data
  • Entity Links: Many-to-many relationships between tasks and entities with soft delete cascade
  • Workspace Validation: Automatic metadata capture and cross-project contamination detection
  • Integrity Audits: Comprehensive workspace health checks with actionable recommendations

🔒 Data Validation

  • Description length limit (10,000 characters)
  • Status/Priority enum validation
  • Blocker reason requirement when status='blocked'
  • Dependency resolution before task completion
  • Tag normalization (lowercase, single spaces)

📊 Database Architecture

  • Per-project databases: ~/.task-mcp/databases/project_{hash}.db
  • Master registry: ~/.task-mcp/master.db (cross-client discovery)
  • Workspace detection: Explicit parameter → TASK_MCP_WORKSPACE env → current directory
  • Path hashing: SHA256 truncated to 8 characters for safe filenames

Installation

Prerequisites

  • Python 3.9 or higher
  • uv package manager (recommended)

Install from Source

# Clone repository
git clone https://github.com/Ravenight13/simple-task-tracker-mcp.git
cd simple-task-tracker-mcp

# Install dependencies
uv sync --dev

# Verify installation
uv run task-mcp --help

Install as Package

# Install with uv
uv pip install git+https://github.com/Ravenight13/simple-task-tracker-mcp.git

# Or with pip
pip install git+https://github.com/Ravenight13/simple-task-tracker-mcp.git

Configuration

Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "task-mcp": {
      "command": "uv",
      "args": ["run", "task-mcp"],
      "env": {
        "TASK_MCP_WORKSPACE": "/path/to/your/project"
      }
    }
  }
}

Location:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

Claude Code

For Claude Code, the server auto-detects the workspace from the current working directory. Add to .claude/config.json:

{
  "mcpServers": {
    "task-mcp": {
      "command": "uv",
      "args": ["run", "task-mcp"]
    }
  }
}

The TASK_MCP_WORKSPACE environment variable is automatically set by Claude Code based on the active project.

Quick Start

Create a Task

# Via MCP tool
create_task(
    title="Implement authentication API",
    description="Add JWT-based authentication to the REST API",
    status="todo",
    priority="high",
    tags="backend api authentication"
)

List Tasks

# Get all tasks
list_tasks()

# Filter by status
list_tasks(status="in_progress")

# Filter by priority and tags
list_tasks(priority="high", tags="backend")

Search Tasks

# Full-text search
search_tasks("authentication")

Update Task Status

update_task(
    task_id=1,
    status="in_progress"
)

Create Subtasks

# Create parent task
parent = create_task(title="Build Authentication System")

# Create subtasks
create_task(
    title="Design database schema",
    parent_task_id=parent['id']
)

create_task(
    title="Implement JWT generation",
    parent_task_id=parent['id']
)

# Get full task tree
get_task_tree(parent['id'])

Task Dependencies

# Create tasks with dependencies
task1 = create_task(title="Set up database")
task2 = create_task(title="Create API endpoints", depends_on=[task1['id']])

# Get next actionable tasks (no unresolved dependencies)
get_next_tasks()

MCP Tools Reference

Core CRUD Operations

create_task

Create a new task with validation.

Parameters:

  • title (str, required): Task title
  • workspace_path (str | None): Optional workspace path (auto-detected)
  • description (str | None): Task description (max 10k chars)
  • status (str): Task status (default: "todo")
  • priority (str): Priority level (default: "medium")
  • parent_task_id (int | None): Parent task ID for subtasks
  • depends_on (list[int] | None): List of task IDs this depends on
  • tags (str | None): Space-separated tags
  • file_references (list[str] | None): List of file paths
  • created_by (str | None): Conversation ID

Returns: Task object with all fields

Example:

task = create_task(
    title="Implement user registration",
    description="Create registration endpoint with email validation",
    priority="high",
    tags="backend api user-management",
    file_references=["src/api/auth.py", "tests/test_auth.py"]
)
get_task

Fetch a single task by ID.

Parameters:

  • task_id (int, required): Task ID
  • workspace_path (str | None): Optional workspace path

Returns: Task object

Example:

task = get_task(task_id=1)
print(task['title'], task['status'])
update_task

Update an existing task with validation.

Parameters:

  • task_id (int, required): Task ID
  • workspace_path (str | None): Optional workspace path
  • title (str | None): Updated title
  • description (str | None): Updated description
  • status (str | None): Updated status
  • priority (str | None): Updated priority
  • blocker_reason (str | None): Required when status='blocked'
  • Other fields as needed

Returns: Updated task object

Example:

# Mark task as blocked
update_task(
    task_id=1,
    status="blocked",
    blocker_reason="Waiting for API key from infrastructure team"
)

# Complete task
update_task(task_id=1, status="done")
list_tasks

List tasks with optional filters.

Parameters:

  • workspace_path (str | None): Optional workspace path
  • status (str | None): Filter by status
  • priority (str | None): Filter by priority
  • parent_task_id (int | None): Filter by parent task
  • tags (str | None): Filter by tags (partial match)

Returns: List of task objects

Example:

# Get all high-priority in-progress tasks
tasks = list_tasks(status="in_progress", priority="high")

# Get all subtasks of a parent
subtasks = list_tasks(parent_task_id=5)

# Get tasks by tag
backend_tasks = list_tasks(tags="backend")
search_tasks

Full-text search on task title and description.

Parameters:

  • search_term (str, required): Search term
  • workspace_path (str | None): Optional workspace path

Returns: List of matching tasks

Example:

results = search_tasks("authentication")
delete_task

Soft delete a task (sets deleted_at timestamp).

Parameters:

  • task_id (int, required): Task ID
  • workspace_path (str | None): Optional workspace path
  • cascade (bool): If True, also delete all subtasks (default: False)

Returns: Success confirmation with deleted count

Example:

# Delete single task
delete_task(task_id=1)

# Delete task and all subtasks
delete_task(task_id=1, cascade=True)

Advanced Query Tools

get_task_tree

Get task with all descendant subtasks (recursive).

Parameters:

  • task_id (int, required): Root task ID
  • workspace_path (str | None): Optional workspace path

Returns: Task object with nested 'subtasks' field

Example:

tree = get_task_tree(task_id=1)
print(tree['title'])
for subtask in tree['subtasks']:
    print(f"  - {subtask['title']}")
get_blocked_tasks

Get all tasks with status='blocked'.

Parameters:

  • workspace_path (str | None): Optional workspace path

Returns: List of blocked tasks with blocker_reason

Example:

blocked = get_blocked_tasks()
for task in blocked:
    print(f"{task['title']}: {task['blocker_reason']}")
get_next_tasks

Get actionable tasks (status='todo', no unresolved dependencies).

Parameters:

  • workspace_path (str | None): Optional workspace path

Returns: List of actionable tasks sorted by priority DESC, created_at ASC

Example:

next_tasks = get_next_tasks()
if next_tasks:
    print(f"Next task to work on: {next_tasks[0]['title']}")

Maintenance Tools

cleanup_deleted_tasks

Permanently delete tasks soft-deleted more than N days ago.

Parameters:

  • workspace_path (str | None): Optional workspace path
  • days (int): Retention days (default: 30)

Returns: Count of purged tasks

Example:

# Purge tasks deleted >30 days ago
result = cleanup_deleted_tasks()
print(f"Purged {result['purged_count']} tasks")

# Custom retention period
cleanup_deleted_tasks(days=60)

Project Management Tools

list_projects

List all known projects from master database.

Returns: List of projects sorted by last_accessed DESC

Example:

projects = list_projects()
for project in projects:
    print(f"{project['friendly_name']}: {project['workspace_path']}")
get_project_info

Get project metadata and task statistics.

Parameters:

  • workspace_path (str, required): Project workspace path

Returns: Project info with task counts by status and priority

Example:

info = get_project_info("/path/to/project")
print(f"Total tasks: {info['total_tasks']}")
print(f"By status: {info['by_status']}")
print(f"Blocked: {info['blocked_count']}")
set_project_name

Set friendly name for a project.

Parameters:

  • workspace_path (str, required): Project workspace path
  • friendly_name (str, required): Human-readable project name

Returns: Success confirmation

Example:

set_project_name("/path/to/project", "My Awesome Project")

Entity Tools

create_entity

Create a new entity (file, vendor, or other type).

Parameters:

  • entity_type (str, required): Entity type ("file" or "other")
  • name (str, required): Human-readable entity name
  • identifier (str | None): Unique identifier (e.g., file path, vendor code)
  • workspace_path (str | None): Optional workspace path (auto-detected)
  • description (str | None): Entity description
  • metadata (dict | str | None): Generic JSON metadata
  • tags (str | None): Space-separated tags
  • created_by (str | None): Conversation ID (auto-captured)

Returns: Entity object with all fields

Example:

# Create vendor entity
vendor = create_entity(
    entity_type="other",
    name="ABC Insurance Company",
    identifier="ABC-INS",
    metadata={"phase": "active", "formats": ["xlsx", "pdf"]},
    tags="vendor insurance active"
)

# Create file entity
file_entity = create_entity(
    entity_type="file",
    name="Authentication Controller",
    identifier="/src/auth/login.py",
    metadata={"language": "python", "line_count": 250},
    tags="backend auth"
)
update_entity

Update an existing entity with partial updates.

Parameters:

  • entity_id (int, required): Entity ID
  • workspace_path (str | None): Optional workspace path
  • name (str | None): Updated name
  • identifier (str | None): Updated identifier
  • description (str | None): Updated description
  • metadata (dict | str | None): Updated metadata
  • tags (str | None): Updated tags

Returns: Updated entity object

Example:

# Update vendor metadata
update_entity(
    entity_id=1,
    metadata={"phase": "inactive", "formats": ["xlsx"]},
    tags="vendor insurance inactive"
)
get_entity

Retrieve a single entity by ID.

Parameters:

  • entity_id (int, required): Entity ID
  • workspace_path (str | None): Optional workspace path

Returns: Entity object with all fields

Example:

entity = get_entity(entity_id=1)
print(f"{entity['name']}: {entity['entity_type']}")
list_entities

List entities with optional filtering.

Parameters:

  • workspace_path (str | None): Optional workspace path
  • entity_type (str | None): Filter by entity type ("file" or "other")
  • tags (str | None): Filter by tags (partial match)

Returns: List of entity objects

Example:

# Get all vendor entities
vendors = list_entities(entity_type="other", tags="vendor")

# Get all file entities with auth tag
auth_files = list_entities(entity_type="file", tags="auth")
delete_entity

Soft delete an entity (cascades to links).

Parameters:

  • entity_id (int, required): Entity ID
  • workspace_path (str | None): Optional workspace path

Returns: Success confirmation

Example:

delete_entity(entity_id=1)
link_entity_to_task

Create a many-to-many relationship between a task and an entity.

Parameters:

  • task_id (int, required): Task ID
  • entity_id (int, required): Entity ID
  • workspace_path (str | None): Optional workspace path
  • created_by (str | None): Conversation ID (auto-captured)

Returns: Link object with link_id, task_id, entity_id, created_at

Example:

# Link vendor to commission processing task
link_entity_to_task(task_id=5, entity_id=1)
get_task_entities

Get all entities linked to a task.

Parameters:

  • task_id (int, required): Task ID
  • workspace_path (str | None): Optional workspace path

Returns: List of entity objects with link metadata (link_created_at, link_created_by)

Example:

# Get all vendors for a task
entities = get_task_entities(task_id=5)
for entity in entities:
    print(f"{entity['name']} ({entity['entity_type']})")
    print(f"  Linked at: {entity['link_created_at']}")
get_entity_tasks

Get all tasks linked to an entity (reverse query).

Parameters:

  • entity_id (int, required): Entity ID
  • workspace_path (str | None): Optional workspace path
  • status (str | None): Filter by task status (todo, in_progress, done, etc.)
  • priority (str | None): Filter by task priority (low, medium, high)

Returns: List of task objects with link metadata (link_created_at, link_created_by)

Example:

# Get all tasks for ABC Insurance vendor
tasks = get_entity_tasks(entity_id=7)
for task in tasks:
    print(f"{task['title']} - {task['status']}")
    print(f"  Linked at: {task['link_created_at']}")

# Get only high-priority tasks for a vendor
high_priority_tasks = get_entity_tasks(
    entity_id=7,
    priority="high"
)

# Get only in-progress tasks for a vendor
in_progress_tasks = get_entity_tasks(
    entity_id=7,
    status="in_progress"
)

Example: Vendor Entity Management

Track insurance vendors and link them to commission processing tasks:

# Create vendor entity
vendor = create_entity(
    entity_type="other",
    name="ABC Insurance Company",
    identifier="ABC-INS",
    metadata={"phase": "active", "formats": ["xlsx", "pdf"]},
    tags="vendor insurance active"
)

# Create commission processing task
task = create_task(
    title="Process ABC Insurance Q4 commissions",
    priority="high",
    tags="commission q4 vendor"
)

# Link vendor to task
link_entity_to_task(task_id=task["id"], entity_id=vendor["id"])

# Get all vendors for task (forward query)
vendors = get_task_entities(task_id=task["id"])
for v in vendors:
    metadata = json.loads(v['metadata'])
    print(f"{v['name']}: Phase {metadata['phase']}, Formats: {metadata['formats']}")

# Get all tasks for ABC Insurance vendor (reverse query)
vendor_tasks = get_entity_tasks(entity_id=vendor["id"])
for t in vendor_tasks:
    print(f"Task: {t['title']} ({t['status']}) - Priority: {t['priority']}")

Development

Running Tests

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=task_mcp --cov-report=html

# Run specific test file
uv run pytest tests/test_database.py -v

# Run specific test
uv run pytest tests/test_database.py::TestDatabase::test_get_connection_creates_database

Quality Gates

# Linting
uv run ruff check src/ tests/

# Type checking
uv run mypy src/

# Format code
uv run black src/ tests/

Project Structure

task-mcp/
├── src/task_mcp/
│   ├── __init__.py
│   ├── server.py          # FastMCP server + tool registration
│   ├── database.py        # Project database operations
│   ├── master.py          # Master database (project registry)
│   ├── models.py          # Pydantic data models
│   └── utils.py           # Utilities (workspace detection, hashing)
├── tests/
│   ├── test_database.py   # Database integration tests
│   ├── test_models.py     # Model validation tests
│   └── test_mcp_tools.py  # MCP tools integration tests
├── pyproject.toml         # Project configuration
└── README.md

Database Schema

Tasks Table (tasks):

CREATE TABLE tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    description TEXT,
    status TEXT NOT NULL CHECK(status IN ('todo', 'in_progress', 'blocked', 'done', 'cancelled')),
    priority TEXT DEFAULT 'medium' CHECK(priority IN ('low', 'medium', 'high')),
    parent_task_id INTEGER,
    depends_on TEXT,  -- JSON array of task IDs
    tags TEXT,  -- Space-separated tags
    blocker_reason TEXT,
    file_references TEXT,  -- JSON array of file paths
    created_by TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    completed_at TIMESTAMP,
    deleted_at TIMESTAMP,
    FOREIGN KEY (parent_task_id) REFERENCES tasks(id)
);

-- Indexes
CREATE INDEX idx_status ON tasks(status);
CREATE INDEX idx_parent ON tasks(parent_task_id);
CREATE INDEX idx_deleted ON tasks(deleted_at);
CREATE INDEX idx_tags ON tasks(tags);

Entities Table (entities):

CREATE TABLE entities (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    entity_type TEXT NOT NULL CHECK(entity_type IN ('file', 'other')),
    name TEXT NOT NULL,
    identifier TEXT,
    description TEXT,
    metadata TEXT,  -- JSON storage for flexible data
    tags TEXT,  -- Space-separated tags
    created_by TEXT,  -- Audit: who created
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_by TEXT,  -- Audit: who last updated
    deleted_at TIMESTAMP
);

-- Indexes
CREATE INDEX idx_entity_type ON entities(entity_type);
CREATE INDEX idx_entity_deleted ON entities(deleted_at);
CREATE INDEX idx_entity_tags ON entities(tags);
CREATE UNIQUE INDEX idx_entity_identifier ON entities(identifier, entity_type)
    WHERE deleted_at IS NULL;  -- Partial unique index

Task-Entity Links Table (task_entity_links):

CREATE TABLE task_entity_links (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    task_id INTEGER NOT NULL,
    entity_id INTEGER NOT NULL,
    created_by TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (task_id) REFERENCES tasks(id),
    FOREIGN KEY (entity_id) REFERENCES entities(id),
    UNIQUE(task_id, entity_id)
);

-- Indexes for bidirectional queries
CREATE INDEX idx_link_task ON task_entity_links(task_id);
CREATE INDEX idx_link_entity ON task_entity_links(entity_id);

Projects Table (master.db):

CREATE TABLE projects (
    id TEXT PRIMARY KEY,  -- 8-char hash
    workspace_path TEXT UNIQUE NOT NULL,
    friendly_name TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_last_accessed ON projects(last_accessed);

Troubleshooting

Database Location

Databases are stored at:

  • Project databases: ~/.task-mcp/databases/project_{hash}.db
  • Master database: ~/.task-mcp/master.db

To inspect manually:

sqlite3 ~/.task-mcp/master.db "SELECT * FROM projects;"

Workspace Detection Issues

Check workspace resolution priority:

  1. Explicit workspace_path parameter (highest priority)
  2. TASK_MCP_WORKSPACE environment variable
  3. Current working directory (fallback)

Verify workspace detection:

from task_mcp.utils import resolve_workspace
print(resolve_workspace())

Reset Project Database

# Find project hash
python -c "from task_mcp.utils import hash_workspace_path; print(hash_workspace_path('/path/to/project'))"

# Remove database
rm ~/.task-mcp/databases/project_{hash}.db

License

MIT License - see LICENSE file

Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all quality gates pass (ruff, mypy, pytest)
  5. Submit a pull request

Acknowledgments

Built with: