# Task Queue Mcp

> A FastMCP server that exposes the agent orchestration task queue as an MCP tool interface. Agents submit tasks, check status, and record completions through typed, validated tools instead of raw YAML file writes.

- **Type:** MCP server
- **Install:** `agentstack add mcp-tadmstr-task-queue-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [TadMSTR](https://agentstack.voostack.com/s/tadmstr)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [TadMSTR](https://github.com/TadMSTR)
- **Source:** https://github.com/TadMSTR/task-queue-mcp

## Install

```sh
agentstack add mcp-tadmstr-task-queue-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# task-queue-mcp

[](https://claude.ai/code)
[](https://opensource.org/licenses/MIT)

A [FastMCP](https://github.com/jlowin/fastmcp) server that exposes the agent orchestration task queue as an MCP tool interface. Agents submit tasks, check status, and record completions through typed, validated tools instead of raw YAML file writes.

Runs as a Docker container on port 8485. Wired globally into `~/.claude.json` so all Claude Code agent sessions have access.

## Tools

| Tool | Description |
|------|-------------|
| `submit_task` | Create a new task with `status: submitted` |
| `list_tasks` | List tasks with optional filters; TTL-expired tasks excluded |
| `get_task` | Retrieve a single task by UUID (resolves archived + quarantined) |
| `update_task` | Agent-facing status transition (strict); appends a history entry |
| `set_task_status` | Operator status change — approve, cancel, or advance a missed task (audited override) |
| `cancel_task` | Graceful terminal `cancelled` state for stale tasks (record kept, never deleted) |
| `quarantine_task` | Isolate a task to `quarantine/` (recoverable); drops from `list_tasks` |
| `restore_task` | Restore a quarantined task to the active queue |

Agents use the strict `update_task` path; operators (via the HTTP control API) use
`set_task_status` / `cancel_task` / `quarantine_task` / `restore_task`. Agents cannot
cancel — `cancelled` is operator-only.

### submit_task

```python
submit_task(
    source_agent="research",
    target_agent="claudebox",       # agent name or "auto" for dispatcher routing
    task_type="build",              # build | deploy | fix | research | review | audit | notify
    summary="Deploy qmd update",
    description="Apply the qmd stack update from build plan...",
    risk_level="low",               # low | medium | high (default: low)
    requires_approval=False,        # explicit override of approval gate
    priority="normal",              # normal | high | urgent (default: normal)
    context_refs=["/home/ted/.claude/projects/research/build-plans/qmd/plan.md"],
    ttl_days=30,
    workflow_mode="semi-auto",          # semi-auto | auto (default: semi-auto)
)
# → {"ok": true, "task_id": "", "filename": "-.yml"}
```

`context_refs` must be absolute paths. `risk_level` and `priority` are validated against allowlists. `workflow_mode` controls dispatcher behavior: `semi-auto` (default) queues the task for operator pickup with a Matrix notification, while `auto` triggers the dispatcher to launch the target agent headlessly. The server generates the UUID, sets `created`, and initializes `alert_state` and `retry_policy` stubs.

### list_tasks

```python
list_tasks(
    target_agent="claudebox",       # optional
    source_agent="research",        # optional
    status="approved,in-progress",  # comma-separated, optional
    task_type="build",              # optional
    include_archived=False,         # include archive/ subdirectory
    limit=20,                       # max 200
)
# → list of task dicts, sorted by created descending
```

Tasks past their `ttl_days` are excluded. The dispatcher is authoritative for TTL archiving, but `list_tasks` filters them out proactively so agents don't act on stale items.

### get_task

```python
get_task(task_id="a7f3d2c1-1234-5678-abcd-000000000000")
# → full task dict, or {"ok": false, "error": "not found"}
```

Searches the main queue first, then `archive/`. Requires a full UUID — no prefix matching.

### update_task

```python
update_task(
    task_id="a7f3d2c1-1234-5678-abcd-000000000000",
    status="in-progress",           # see transition table below
    actor="claudebox",
    note="Claimed task, starting build.",
    output=None,                    # written to result.output on completed/failed
)
# → {"ok": true, "task_id": ""} or {"ok": false, "error": "..."}
```

**Valid transitions:**

| From | To |
|------|----|
| `approved` | `in-progress` |
| `in-progress` | `completed` |
| Any non-terminal | `failed` |

Non-terminal: `submitted`, `pending-approval`, `approved`, `in-progress`.
Terminal: `completed`, `failed`, `cancelled`.

`alert_state` and `retry_policy` are dispatcher-owned — `update_task` never touches them.

### Operator transitions (`set_task_status`)

Broader than `update_task` but still audited and bounded:

| From | To | Notes |
|------|----|-------|
| `submitted` / `pending-approval` | `approved` | standard |
| Any non-terminal | `cancelled` | standard (also via `cancel_task`) |
| Any non-terminal | Any non-terminal | requires `allow_override=True` + a non-empty note (the "advance a missed task" override) |

Terminal tasks are immutable even for operators. Every operator change appends a history entry with `actor` + `note`.

## Status Lifecycle

```
submitted → [pending-approval] → approved → in-progress → completed
                                                 ↓
                                              failed

Any non-terminal ──(operator)──> cancelled        # graceful dismissal, record kept
Any task ──(operator quarantine)──> quarantine/    # isolate (recoverable via restore)
```

The dispatcher owns the `submitted → approved/pending-approval` transitions. Agents own `approved → in-progress → completed` (or `failed`). Operators own `cancelled`, quarantine/restore, and audited status overrides. Approval gating is controlled by agent manifests and the `requires_approval` field.

## HTTP Control API

Non-MCP clients (the CloudCLI plugin and Matrix bot) can't import the Python core, so all their mutations go through a thin HTTP control API mounted as FastMCP custom routes on the **same port 8485**. Each endpoint delegates to the tool handlers above, inheriting transition validation, `fcntl` locking, and atomic writes — so there is exactly one validated write path for the whole system.

| Method | Path | Delegates to |
|--------|------|--------------|
| `POST` | `/tasks/{id}/approve` | `set_task_status(approved)` |
| `POST` | `/tasks/{id}/cancel` | `cancel_task` |
| `POST` | `/tasks/{id}/status` | `set_task_status` (body: `status`, `note`, `allow_override`) |
| `POST` | `/tasks/{id}/quarantine` | `quarantine_task` |
| `POST` | `/tasks/{id}/restore` | `restore_task` |

Body fields: `actor` (default `operator`), `note`, plus `status` / `allow_override` for the status route. Responses map the canonical result: `200` ok, `404` not found, `400` validation/transition error.

**Auth:** custom routes bypass the MCP auth middleware, so a shared-secret header is the gate (defense in depth on top of the loopback-published port):

- Send `X-Task-Queue-Secret: $TASK_QUEUE_API_SECRET` on every mutation.
- The server compares it in constant time (`hmac.compare_digest`) and **fails closed** (401) when the secret is missing, wrong, or unconfigured.
- The secret lives in `~/.secrets/forge.env` and is injected via env into the container, bot, and plugin — never committed to source.

## Deployment

### Docker (production)

```yaml
services:
  task-queue-mcp:
    image: task-queue-mcp:latest
    container_name: task-queue-mcp
    ports:
      - "8485:8485"
    volumes:
      - /home/ted/.claude/task-queue:/task-queue
    environment:
      - TASK_QUEUE_DIR=/task-queue
      - MCP_HOST=0.0.0.0
      - MCP_PORT=8485
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    read_only: true
    tmpfs: [/tmp]
    user: "1000:1000"
    restart: unless-stopped
    networks:
      - claudebox-net
```

The container mounts only the task-queue directory read-write. The rest of the filesystem is read-only. `/tmp` is a tmpfs for transient scratch space.

### Claude Code settings.json

```json
{
  "mcpServers": {
    "task-queue-mcp": {
      "type": "url",
      "url": "http://localhost:8485/mcp"
    }
  }
}
```

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `TASK_QUEUE_DIR` | `/task-queue` | Path to the task queue directory inside the container |
| `MCP_HOST` | `0.0.0.0` | Bind host for the HTTP server |
| `MCP_PORT` | `8485` | Port for the HTTP server |
| `TASK_QUEUE_API_SECRET` | — | Shared secret for the HTTP control API. **Required** for any control-API mutation — fails closed (401) if unset. The MCP tools themselves do not use it. |

## Building

```bash
docker build -t task-queue-mcp:latest .
```

## Development

```bash
pip install -r requirements.txt

# Run tests
pytest

# Run server locally (against a local task-queue directory)
TASK_QUEUE_DIR=/home/ted/.claude/task-queue python -m src.server
```

```bash
pip install -r requirements.txt pytest pytest-cov ruff

# Lint + format (Baseline gate)
ruff check .
ruff format --check .

# Tests with coverage (gate: >=80%)
python -m pytest --cov=src --cov-report=term-missing
```

The test suite covers all eight tools and the HTTP control API — validation edge cases, adversarial YAML strings, illegal transitions, the quarantine/restore round-trip, operator-override auditing, and the shared-secret gate (missing/wrong secret → 401). All writes use `yaml.dump` — never string interpolation — to prevent YAML injection.

## Security

The MCP tool endpoint on port 8485 is unauthenticated and limited to LAN/loopback — the port is not proxied externally via SWAG and the host firewall blocks external access. The **HTTP control API** mutation routes additionally require a shared-secret header (`X-Task-Queue-Secret`, constant-time compare, fail-closed) — see [HTTP Control API](#http-control-api). The container runs as UID 1000 with `cap_drop: ALL`, `no-new-privileges`, and a read-only rootfs (only `/task-queue` is writable).

### Trust model

**Loopback is the trust boundary.** The shared secret gates only the cross-process HTTP control routes (`/tasks/...`) — it is *not* the sole barrier to mutation. All MCP tools, including the operator-mutating `set_task_status` / `cancel_task` / `quarantine_task` / `restore_task`, are reachable via the unauthenticated `/mcp/` JSON-RPC endpoint, so **any process with loopback access to port 8485 can mutate the queue without the secret.** This is intentional: the queue is internal agent-coordination state, the port is loopback-only, and the MCP transport has always been unauthenticated. The secret exists to authenticate the *specific* cross-process clients (the CloudCLI plugin and Matrix bot) over plain HTTP, not to harden the loopback boundary. If loopback trust ever becomes insufficient, gate the MCP transport with a FastMCP auth provider rather than relying on the control-route secret alone.

## Task File Schema

Tasks are YAML files in `~/.claude/task-queue/`, named `YYYYMMDD-HHMMSS-.yml`. All writes are atomic (write to `.tmp`, then `os.rename()`). Per-task file locks via `fcntl.flock` prevent races between concurrent MCP calls and the dispatcher.

For the full schema and lifecycle documentation, see the [homelab-agent component doc](https://github.com/TadMSTR/homelab-agent/blob/main/docs/components/task-queue-mcp.md).

## Related

- [homelab-agent](https://github.com/TadMSTR/homelab-agent) — agent orchestration documentation
- [task-dispatcher](https://github.com/TadMSTR/homelab-agent/blob/main/docs/components/task-dispatcher.md) — the dispatcher that routes and gates tasks

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [TadMSTR](https://github.com/TadMSTR)
- **Source:** [TadMSTR/task-queue-mcp](https://github.com/TadMSTR/task-queue-mcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-tadmstr-task-queue-mcp
- Seller: https://agentstack.voostack.com/s/tadmstr
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
