Metadata-Version: 2.4
Name: hermes-kanban-mcp-vn
Version: 0.1.1
Summary: Restricted MCP bridge for durable Hermes Kanban tasks
Author-email: NamHT <namht.dev@gmail.com>
License-Expression: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: fastapi<1,>=0.115
Requires-Dist: httpx<1,>=0.27
Requires-Dist: mcp<2,>=1.2
Requires-Dist: pydantic<3,>=2.8
Requires-Dist: uvicorn<1,>=0.30
Provides-Extra: dev
Requires-Dist: pytest<9,>=8; extra == "dev"
Requires-Dist: pytest-asyncio<1,>=0.23; extra == "dev"
Requires-Dist: ruff<1,>=0.5; extra == "dev"
Requires-Dist: mypy<2,>=1.10; extra == "dev"

# Hermes Kanban MCP (`hermes-kanban-mcp-vn`)

Restricted stdio MCP bridge that submits durable work to an existing Hermes Kanban board. It does
not create another task database and does not expose arbitrary shell, workspace, profile, model,
parent dependency, or runtime selection.

## Architecture and security

```text
MCP Hub --stdio/uvx--> this MCP --HTTPS bearer--> restricted backend --fixed argv--> Hermes Kanban
```

Hermes v0.21.0 has authenticated dashboard plugin routes, but its dashboard credential grants a
broader surface than this integration needs. This package therefore includes a companion backend
that exposes only four operations and invokes the installed Hermes CLI with an argv list and
`shell=False`. It always creates a `scratch` workspace, records `created-by=external-mcp`, and
force-loads `kanban-team-workflow`. Both MCP client and backend enforce board and assignee
allowlists.

The backend defaults to loopback. Put TLS and source-IP/network policy at the reverse proxy or use a
private VPN path; never expose the Hermes dashboard or this backend directly to the public Internet.
Plain HTTP is rejected by the MCP client except for loopback tests. Keep bearer tokens only in the
service/MCP Hub secret store, never in args, source, logs, or committed config.

`ready` is conservative: a profile must both exist on disk according to `hermes kanban assignees`
and be explicitly listed in `HERMES_KANBAN_READY_ASSIGNEES` after Linus verifies its gateway,
provider, and runtime. A created task remains asynchronous. Creating one does not authorize
production deployment, deletion, external communication, or spending; normal task approval gates
still apply.

## MCP tools

- `list_agents`: list only allowlisted profiles and their conservative readiness.
- `create_task`: create a durable asynchronous task and return its real `t_........` ID. Callers must
  reuse a stable `idempotency_key` for retries; Hermes stores the key durably.
- `get_task`: return task status, comments, events, runs, results, and evidence metadata.
- `add_task_comment`: append a durable comment as `external-mcp`.

All public inputs have descriptions, examples, length/pattern constraints, and read/write/
idempotency annotations in the generated MCP schema.

## MCP Hub install (after PM code gate and PyPI publish)

Use a slug-safe server key and pin the reviewed version:

```json
{
  "mcpServers": {
    "hermes-kanban": {
      "command": "uvx",
      "args": [
        "--from",
        "hermes-kanban-mcp-vn==0.1.1",
        "hermes-kanban-mcp"
      ],
      "env": {
        "HERMES_KANBAN_URL": "https://hermes-kanban.internal.example",
        "HERMES_KANBAN_TOKEN": "<secret-from-hub-secret-store>",
        "HERMES_KANBAN_ALLOWED_ASSIGNEES": "developer,project-manager",
        "HERMES_KANBAN_ALLOWED_BOARDS": "default",
        "HERMES_KANBAN_TIMEOUT": "20"
      }
    }
  }
}
```

## Restricted backend contract

Run on the Hermes host with the same `HERMES_HOME`/board configuration as the installed CLI:

```bash
HERMES_KANBAN_BACKEND_TOKEN='<secret-from-service-store>' \
HERMES_KANBAN_CLI=/opt/hermes/bin/hermes \
HERMES_KANBAN_ALLOWED_ASSIGNEES=developer,project-manager \
HERMES_KANBAN_READY_ASSIGNEES=developer,project-manager \
HERMES_KANBAN_ALLOWED_BOARDS=default \
HERMES_KANBAN_COMMAND_TIMEOUT=20 \
uvx --from hermes-kanban-mcp-vn==0.1.1 hermes-kanban-backend
```

Optional listener variables are `HERMES_KANBAN_BACKEND_HOST` (default `127.0.0.1`) and
`HERMES_KANBAN_BACKEND_PORT` (default `8766`). The reverse proxy must terminate TLS, require the
private route, restrict MCP Hub source addresses, and avoid logging `Authorization`.

Authenticated endpoints:

- `GET /v1/agents?board=<slug>`
- `POST /v1/tasks`
- `GET /v1/tasks/{task_id}?board=<slug>`
- `POST /v1/tasks/{task_id}/comments?board=<slug>`

`GET /healthz` is unauthenticated and returns only `{"ok": true}`. The backend has no arbitrary
command endpoint and disables OpenAPI/Redoc routes.

## Development and verification

```bash
uv sync --extra dev
uv run pytest -q
uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run mypy src
uv build
```

Protocol smoke through source or a clean wheel:

```bash
uv run python scripts/mcp_smoke.py
HERMES_KANBAN_SMOKE_WHEEL=dist/hermes_kanban_mcp_vn-0.1.1-py3-none-any.whl \
  uv run python scripts/mcp_smoke.py
```

That smoke starts a loopback fake backend, launches the stdio server, performs MCP initialize,
tools/list, and calls `list_agents`. Unit/backend tests mock network and Hermes CLI process results.
They cover valid create/read/comment, duplicate submission, denied/non-allowlisted input, timeout,
backend unavailable, secret-redacted errors, generated schemas, and tool annotations.

A harmless real-board E2E was also run against an isolated board: it initialized the MCP via `uvx`,
listed/called tools, created task `t_9beed776`, retried with the same idempotency key and received the
same task ID, read the task, and appended a comment. The fixture contains no deployment or
destructive action. The fixed read-only evaluation is
`evaluations/hermes_kanban_read_only.xml`.

## Publish, rollout, and rollback (Linus; only after PM approval)

Publish destination: PyPI package `hermes-kanban-mcp-vn`, matching the repository's existing uvx
package convention.

```bash
cd uvx/hermes-kanban
UV_PUBLISH_TOKEN='<from-approved-secret-store>' ./publish.sh
```

The script runs tests, Ruff, format check, mypy, build, and then `uv publish`. It contains no token.
After publishing, verify the exact version on PyPI and clean-install it with the pinned `uvx --from`
command before changing MCP Hub.

Rollout:

1. Start the backend on loopback/private networking with an independently generated token.
2. Verify `/healthz`, then verify missing/invalid bearer access is denied.
3. Configure TLS/private routing and the pinned MCP Hub entry using its secret store.
4. Reload MCP Hub, refresh client actions, call `list_agents`, then use an isolated harmless board to
   create/read/comment and retry the same idempotency key.
5. Confirm the real Hermes task ID and evidence in the existing board before enabling more agents.

Rollback: restore the previous pinned package version (or remove the MCP Hub entry), reload Hub, and
stop/disable the companion backend. Existing tasks remain durable in Hermes and are not deleted.
