Skip to main content
Glama
Task.md Utilities MCP Server

Task.md Utilities MCP Server

by DrSkippy

Task.md Utilities (HENDRICKSON KANBAN)

A REST API, MCP server, and interactive CLI for managing tasks organized in lanes, where each task is a markdown file. The CLI includes a full-screen Textual TUI (kanban board with keyboard navigation) as well as non-interactive subcommands for scripting.

About Tasks.md

This project builds on and extends the Tasks.md project. For comprehensive information about the Tasks.md system, including task visualization, board views, file format specifications, and VSCode integration, visit the Tasks.md repository.

This package adds a REST API, a network-capable interactive CLI, and an MCP server for programmatic task management.

Related MCP server: task-manager-mcp

Overview

Tasks are stored as markdown files organized in lane directories:

  • Task title = filename (without .md)

  • Tags: [tag:tagname] — one per line at the top of the file

  • Due dates: [due:YYYY-MM-DD]

  • Tasks can be split into subtasks using the [[split]] marker

Architecture

┌──────────────────┐   HTTP   ┌───────────────────┐
│ tasks CLI / TUI  │ ────────▶│  Flask REST API   │  :2999 (container) / :3101 (host)
│ (bin/tasks)      │          │  (task_api/)      │──┐
└──────────────────┘          └───────────────────┘  │   ┌──────────────┐   ┌───────────────────┐
                                                     ├──▶│  task_lib/   │──▶│  Markdown files   │
┌──────────────────┐   MCP    ┌───────────────────┐  │   │ (in-process) │   │  /data/tasks/     │
│ AI assistant     │ ────────▶│  MCP server       │──┘   └──────────────┘   └───────────────────┘
│ (Claude etc.)    │          │ (mcp_task_service)│  :3003                     ▲
└──────────────────┘          └───────────────────┘                             │ same folder
                                                     Tasks.md web app ──────────┘

The REST API and the MCP server each import task_lib and read and write the card files directly; neither calls the other. The CLI and TUI only talk to the REST API. The Tasks.md web app reads the same folder.

Project Structure

task.md-utilities/
├── bin/
│   ├── tasks               # CLI — TUI (no args) or subcommands (scripting)
│   └── tag-utility.py      # One-time tag format migration utility
├── docs/
│   └── screenshot.svg      # TUI kanban board screenshot
├── task_lib/
│   ├── api_client.py       # Shared HTTP helpers (used by CLI and TUI)
│   ├── cli_config.py       # CLI settings (~/.config/tasks/config.yaml)
│   ├── config.py           # Configuration (YAML)
│   ├── lane_order.py       # Per-machine lane (column) order for CLI and TUI
│   ├── errors.py           # Typed errors: not_found, exists, invalid, conflict
│   ├── sections.py         # Partial edits: append, upsert section, check items
│   ├── task.py             # Task model and file I/O
│   └── task_manager.py     # Lane, task and trash operations
├── task_tui/               # Textual TUI package
│   ├── app.py              # KanbanApp — main board, keybindings, workers
│   ├── api.py              # Sync API wrappers for use in workers
│   ├── screens.py          # Detail, form, confirm, filter, move screens
│   └── widgets.py          # LaneColumn, TaskItem, FunctionKeyBar
├── task_api/               # Flask REST API service
│   ├── app.py
│   ├── config.py
│   ├── models.py           # Pydantic request/response schemas
│   ├── routes/             # tasks, trash, lanes, operations, health
│   ├── gunicorn.conf.py
│   ├── Dockerfile
│   ├── docker-compose.yml
│   └── README.md
├── mcp_task_service/       # FastMCP server for AI assistants
│   ├── server.py
│   ├── Dockerfile
│   ├── docker-compose.yml
│   └── README.md
├── tests/                  # pytest suite (conftest.py isolates $HOME)
├── config.yaml             # Task data location (baked into Docker images)
├── tasks.config.example.yaml  # Every CLI setting with its default
├── docker-compose.yml      # Deploys both services together
├── Makefile                # build / push / deploy / test / lint
└── .mcp.json               # Connects Claude Code in this repo to the MCP server

Deployment

Both services are deployed as Docker containers. Build from the repo root (build context must include both task_lib/ and the service directory).

Build images

make push      # build and push both images (make build to only build)
make release   # run the tests first, then build and push
make deploy    # recreate the containers in /opt/stacks/mcp-task

# Single service, or another registry/tag
make push-mcp
make push-api REGISTRY=registry.example:5000 TAG=0.3.0

make help lists every target.

Deploy

The running services are the Dockge stack in /opt/stacks/mcp-task, whose compose file matches the root docker-compose.yml. After make push:

make deploy    # docker compose pull && up -d --force-recreate in /opt/stacks/mcp-task

Restarting the MCP server drops connected MCP clients; reconnect them (in Claude Code: /mcp reconnect). To run the stack from the repo instead, use docker compose up -d in the repo root.

The compose file starts both services on a shared network:

Service

Internal port

Host port

REST API

2999

3101

MCP server

3003

3003

Both containers mount the task data directory:

volumes:
  - /mnt/raid1/lib/tasks.md/tasks:/data/tasks

Configuration

config.yaml (committed to the repo) sets the task data location inside the container. It is copied into the image at build time — no volume mount needed:

base_dir: /data/tasks

To change the data path, edit config.yaml and rebuild the images.

Running services individually

Each service has its own docker-compose.yml for standalone use:

cd task_api && docker-compose up -d       # REST API only
cd mcp_task_service && docker-compose up -d  # MCP only

CLI (bin/tasks)

The tasks binary has two modes:

  • No arguments — launches the full-screen interactive TUI (kanban board)

  • With a subcommand — runs non-interactively for scripting

Installation

poetry install
poetry run bin/tasks --help

poetry install does not put tasks on your PATH; the examples below write tasks for poetry run bin/tasks. To type tasks directly, add an alias:

alias tasks='poetry -C ~/Working/task.md-utilities run ~/Working/task.md-utilities/bin/tasks'

Configuration

The CLI reads its settings from a YAML file, the first of:

  1. --config PATH flag

  2. TASKS_CONFIG environment variable

  3. ~/.config/tasks/config.yaml

Every key is optional and a missing default file is fine. Copy tasks.config.example.yaml, which lists every key with its default: api_url, request timeout, the board's app_title, default_sort for tasks show, the empty_cell placeholder, and the table styles. Unknown keys and badly typed values are reported as errors.

The board's column order is per machine: Shift+←/→ in the board or tasks lanes order saves it to ~/.local/state/tasks/lane_order.yaml ($XDG_STATE_HOME, or the lane_order_file config key). tasks lanes list and tasks show follow it too. The server, the MCP tools and other machines still list lanes alphabetically.

The API URL comes from the first of:

  1. --api-url URL flag

  2. TASKS_API_URL environment variable

  3. api_url in the config file

  4. Default: http://localhost:3101

api_url: http://your-server:3101

Interactive TUI

tasks

Launches a full-screen kanban board. Lanes are displayed as side-by-side columns; tasks are listed under each lane.

Kanban Board

Keyboard navigation

Key

Action

← / →

Move between lanes

Shift+← / Shift+→

Move the focused lane (column) left / right; the order is saved

↑ / ↓

Move between tasks within a lane

Enter

Open task detail view

Esc

Go back / close dialog

F1

Show keyboard shortcut help

F2

New task (in the focused lane)

F3

Edit selected task

F4

Delete selected task (confirm prompt)

F5

Filter tasks (by lane, tag, or title substring)

F6

Move selected task to another lane

F7

Open / close task detail view

F8

Refresh board from the API

F9

Quit

Active filters are shown in a status bar at the top; press F5 again to change or clear them.


Subcommands

tasks [--api-url URL] [--config PATH] COMMAND

Commands:
  show         List tasks (--lane, --tag, --string, --query, --due-before,
               --due-after, --sort lane|title|due|modified)
  find         Search titles and bodies, showing the matching text
  get          Show a single task in full
  add          Create a new task (lane must exist unless --create-lane)
  update       Update task fields
  append       Append text to the end of a task
  section      Replace (or add) one "## Heading" section of a task
  check        Tick "- [ ]" checklist items (--uncheck to untick)
  delete       Move a task to Trash
  move         Change a task's lane (--create-lane for a new lane)
  trash        Manage the Trash
    list       List trashed cards, newest first
    restore    Restore a card into a lane
    empty      Permanently delete Trash (--dry-run, --yes)
  empty-trash  Same as `trash empty`
  split        Split tasks containing [[split]] marker
  lanes        Manage lanes
    list       List lanes with task counts
    order      Show or set the column order (--reset)
    add        Create a new lane
  stats        Show statistics summary

Adding, renaming, moving and restoring never overwrite a card: reusing an existing title fails, and so does naming a lane that doesn't exist (unless you pass --create-lane). split also checks first: a card whose 1-<title>, 2-<title>, … names are already taken is left unsplit and reported.

Examples

# List all tasks
tasks show

# Filter by lane, tag, or title substring (all case-insensitive)
tasks show --lane "In Progress"
tasks show --tag urgent
tasks show --string "login"

# Show full task detail
tasks get "Implement login"

# Create a task (prompts for missing fields)
tasks add --title "Fix bug" --content "Reproduce and fix" --lane Backlog --tags "bug,urgent"

# Update fields (only provided fields change)
tasks update "Fix bug" --tags "bug,urgent,p1" --due-date 2026-06-01

# Search titles and bodies; sort by due date
tasks find "books db"
tasks show --sort due --due-before 2026-06-30

# Edit part of a card without resending the whole body
tasks section "Fix bug" Progress "Reproduced on staging"
tasks append --no-blank-line "Fix bug" -- "- [ ] write regression test"
tasks check "Fix bug" "regression test"
echo "Patch the null check" | tasks section "Fix bug" "Next action" -

# Move between lanes
tasks move "Fix bug" "In Progress"

# Delete (moves to Trash, prompts for confirmation)
tasks delete "Fix bug"

# Trash: list, restore, preview, then permanently delete (prompts for confirmation)
tasks trash list
tasks trash restore "Fix bug" Backlog
tasks trash empty --dry-run
tasks trash empty

# Lane management
tasks lanes list
tasks lanes add "Sprint 3"

# Column order: named lanes first, the rest keep their order after them
tasks lanes order                 # show it
tasks lanes order Doing Backlog   # set it
tasks lanes order --reset         # back to alphabetical

# Split a task (divides on [[split]] marker)
tasks split

# Statistics
tasks stats

MCP Server

The MCP server exposes kanban operations to AI assistants (Claude, Cursor, etc.) via the Model Context Protocol.

Connecting a client

Add to your Claude Desktop / Claude Code config (~/.claude/claude_desktop_config.json or .mcp.json):

{
  "mcpServers": {
    "task-manager": {
      "type": "http",
      "url": "http://localhost:3003/mcp"
    }
  }
}

Available tools

Every tool returns {"ok": true, ...} or {"ok": false, "error": {"code", "message"}}.

Tool

Description

info

Server version, data folder, counts

list_lanes

Lanes with task counts, plus trash_count

list_tasks

Task summaries; lane/tag/due filters, sort, limit, optional bodies

find_tasks

Search titles and bodies

get_task

One task in full

get_statistics

Lane, tag, due-date, overdue and due-this-week counts

add_task

Create a task (refuses existing titles and unknown lanes)

update_task

Update task fields

append_to_task / upsert_section / check_items

Edit part of a card

move_task_to_lane

Move a task to another lane

delete_task

Move a task to Trash

list_trash / restore_task

See and undo deletions

empty_trash

Permanently delete Trash contents (dry_run supported)

add_lane

Create a new lane

split_tasks

Split tasks with [[split]] marker; skips (and reports) cards whose subtask titles are taken

Title, lane and tag matching is case-insensitive across all tools. Writes accept expected_version (from any task result) to guard against concurrent edits, and return the card's summary, or the full card with include_content=true.

See mcp_task_service/README.md for full details.

REST API

The REST API is documented in task_api/README.md.

Quick reference:

# Health check
curl http://localhost:3101/health

# List tasks
curl "http://localhost:3101/tasks"
curl "http://localhost:3101/tasks?lane=Backlog&tag=urgent"
curl "http://localhost:3101/tasks?search=login"          # title substring
curl "http://localhost:3101/tasks?query=books%20db&sort=due"  # title or body

# Create a task
curl -X POST http://localhost:3101/tasks \
  -H 'Content-Type: application/json' \
  -d '{"title":"My Task","content":"Details","lane":"Backlog","tags":["urgent"]}'

# Move a task
curl -X POST "http://localhost:3101/tasks/My%20Task/move" \
  -H 'Content-Type: application/json' \
  -d '{"lane":"In Progress"}'

# Trash: list, preview emptying
curl http://localhost:3101/trash
curl -X DELETE "http://localhost:3101/operations/trash?dry_run=true"

# Statistics
curl http://localhost:3101/operations/statistics

Task File Format

[tag:frontend]
[tag:urgent]
[due:2026-06-01]

Task body text goes here.

Only lines that contain nothing but [tag:…] tokens (or a single [due:…]) are metadata; a tag mentioned inside a sentence is ordinary text.

Data Directory Structure

/data/tasks/
├── Backlog/
│   └── Implement login.md
├── In Progress/
│   └── Fix auth bug.md
├── Done/
│   └── Setup CI.md
└── Trash/
    └── old-task~20261014-120102.md   # timestamp suffix keeps same-named cards apart

Development

Set up

poetry install --extras dev   # adds black, mypy and type stubs

Run tests and checks

make test   # pytest with coverage for task_lib, task_api, mcp_task_service
make lint   # black --check and mypy on the modules kept clean so far

make test doesn't measure task_tui; for that, run poetry run pytest --cov=task_tui --cov-report=term-missing tests/. Much of the older code doesn't pass black or mypy yet, so make lint covers only the files that do.

tests/conftest.py points Path.home() at an empty temporary directory and clears TASKS_CONFIG, TASKS_API_URL and XDG_STATE_HOME, so your own ~/.config/tasks/config.yaml and saved lane order never affect a test run.

Run the services locally

The committed config.yaml points at /data/tasks, the path inside the containers. Locally, point TASK_CONFIG_PATH at a config for a scratch folder:

mkdir -p /tmp/tasks-dev && echo "base_dir: /tmp/tasks-dev" > /tmp/tasks-dev.yaml

TASK_CONFIG_PATH=/tmp/tasks-dev.yaml poetry run gunicorn --config task_api/gunicorn.conf.py "task_api.app:create_app()"
TASK_CONFIG_PATH=/tmp/tasks-dev.yaml poetry run python mcp_task_service/server.py

Utilities

Tag Format Migration

bin/tag-utility.py converts old-format tags (tags: tag1, tag2) to the current format ([tag:tagname]). Edit the hardcoded directory path in the script before running. Supports dry-run mode.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Enables AI agents to manage kanban boards with tasks, including creating boards, items, moving between columns, and searching.
    8
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to manage Kanban tasks, boards, teams, and checklists via natural language, with full CRUD operations and live updates.
    231 npm
    MIT