Skip to main content
Glama
TavenYin

hatchet-ops-mcp

by TavenYin
README.md
# hatchet-ops-mcp

A small, read-only [MCP](https://modelcontextprotocol.io/) server that exposes
[Hatchet](https://hatchet.run) **client / REST observability APIs** to coding
agents — across multiple environments from one process.

Hatchet itself does **not** ship an official ops MCP. This project is a thin
wrapper around the official Python [`hatchet-sdk`](https://pypi.org/project/hatchet-sdk/):
each tool call picks an `env`, resolves that environment's Hatchet JWT from
`envs.yaml`, uses a cached `Hatchet` client, and returns the SDK response as
structured MCP output.

The default transport is local stdio (no host / API key). For a shared process
on a trusted network, streamable HTTP (and legacy SSE) are also available.

## Why this exists

Hatchet's official agent integrations today are mainly **docs MCP**, **CLI
skills**, and SDK helpers that turn *your* workflows/tasks into agent tools.
None of those is a multi-tenant ops console for listing runs, workers, and
queue metrics.

This server fills that gap by mapping read-only `hatchet-sdk` feature clients
(`workflows`, `runs`, `workers`, `metrics`, logs, …) to MCP tools, with one
extra capability: explicit `env` routing so one MCP process can talk to several
Hatchet tenants/instances.

## Requirements

- Python 3.12 or newer
- [uv](https://docs.astral.sh/uv/)
- A Hatchet API token (JWT) for each environment you want to query
- Docker and Docker Compose (optional, for HTTP deployment)

## Setup

```bash
git clone <repository-url>
cd hatchet-ops-mcp
cp envs.example.yaml envs.yaml
uv sync
```

Edit `envs.yaml`. Each environment has its own Hatchet JWT (`token`). Entries
left as `TODO` remain visible in `list_envs`, but cannot be queried until a
token is provided.

```yaml
envs:
  development:
    description: "Development"
    token: "eyJ..."   # Hatchet API JWT for this env
    server_url: "http://localhost:8080"
    tls_strategy: "none"

  production:
    description: "Production"
    token: "TODO"
    server_url: "https://hatchet.example.com"
```

`server_url` and `tls_strategy` are optional. When `server_url` is omitted,
Hatchet resolves it from the JWT.

> **Note:** `envs.yaml` `token` is the **Hatchet** credential. The MCP HTTP
> Bearer key is separate: set `HATCHET_OPS_MCP_API_KEY` (only needed for
> streamable-http / sse).

## Local use (stdio)

This is the usual path for Cursor / Claude Code on your laptop — no bind
address, no API key:

```bash
uv run hatchet-ops-mcp
```

Set `HATCHET_OPS_MCP_ENVS` when the config is outside the current checkout:

```bash
HATCHET_OPS_MCP_ENVS=/absolute/path/to/envs.yaml uv run hatchet-ops-mcp
```

MCP client configuration:

```json
{
  "mcpServers": {
    "hatchet-ops": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/hatchet-ops-mcp",
        "run",
        "hatchet-ops-mcp"
      ],
      "env": {
        "HATCHET_OPS_MCP_ENVS": "/absolute/path/to/hatchet-ops-mcp/envs.yaml"
      }
    }
  }
}
```

## Shared HTTP (trusted network)

For one process that several clients hit on a LAN / VPN. Auth is a single
shared Bearer API key checked by middleware — not OAuth.

HTTP refuses to start without an API key unless you pass `--allow-insecure`
(trusted network only). Default bind is `127.0.0.1`; only override `--host`
when something other than this machine must connect (Docker Compose already
sets `0.0.0.0`).

```bash
export HATCHET_OPS_MCP_API_KEY='replace-with-a-strong-random-value'

uv run hatchet-ops-mcp --transport streamable-http
# optional: --port 8765
# Docker / LAN only: --host 0.0.0.0
```

Endpoints: `/mcp` (MCP), `/health` (no auth).

Docker Compose mounts local `envs.yaml` and binds `0.0.0.0` for you:

```bash
export HATCHET_OPS_MCP_API_KEY='replace-with-a-strong-random-value'
docker compose up --build
```

Remote MCP client example:

```json
{
  "mcpServers": {
    "hatchet-ops": {
      "url": "http://hatchet-ops.internal:8765/mcp",
      "headers": {
        "Authorization": "Bearer replace-with-the-shared-api-key"
      }
    }
  }
}
```

| Environment variable | Purpose |
| --- | --- |
| `HATCHET_OPS_MCP_ENVS` | Path to `envs.yaml` |
| `HATCHET_OPS_MCP_TRANSPORT` | `stdio` (default), `streamable-http`, or `sse` |
| `HATCHET_OPS_MCP_PORT` | HTTP port (default `8765`) |
| `HATCHET_OPS_MCP_HOST` | HTTP bind address (default `127.0.0.1`; Docker uses `0.0.0.0`) |
| `HATCHET_OPS_MCP_API_KEY` | Shared Bearer key for MCP HTTP (not a Hatchet JWT) |
| `HATCHET_OPS_MCP_ALLOW_INSECURE` | Allow HTTP with no API key |

## Tools

| Tool | Purpose |
| --- | --- |
| `list_envs` | List environment names and configuration status without secrets |
| `list_workflows` | List workflow definitions (optional `workflow_name` filter) |
| `get_workflow` | Get one workflow definition by ID |
| `list_runs` | List recent workflow or task runs (status / name / metadata / worker filters) |
| `get_run` | Get one workflow run (status, tasks; payloads opt-in) |
| `get_run_events` | Get lifecycle events for one workflow run |
| `get_run_logs` | Get logs for one task **or** merged logs for a whole workflow run |
| `list_workers` | List workers and their status |
| `get_worker` | Get one worker by ID |
| `get_queue_metrics` | Inspect current queue backlog depth |
| `get_task_metrics` | Task counts by status over a time window |
| `get_task_stats` | Per-task-name statistics for the tenant |

All tools except `list_envs` require `env`.

### Debug playbook

Typical failed / stuck run investigation:

1. `list_runs` with `statuses`, `workflow_name` (exact name), and/or `additional_metadata`
2. `get_run` for status and per-task summaries (`include_payloads=true` for I/O)
3. `get_run_events` for QUEUED / STARTED / FAILED / CANCELLED timeline
4. `get_run_logs` with `workflow_run_id` for merged application logs (newest lines kept)
5. If stuck QUEUED: `list_workers` / `get_worker`
6. For environment health: `get_queue_metrics`, `get_task_metrics`, `get_task_stats`

`list_runs` / `get_run` omit payloads by default. `include_payloads=true` on
`list_runs` also disables `minimal_output`.

## Security

- `envs.yaml` is ignored by Git. Never commit real Hatchet tokens.
- Prefer stdio on developer machines; treat shared HTTP as an internal service.
- HTTP refuses to start without a Bearer API key unless `--allow-insecure` is set.
- Prefer read-only Hatchet tokens when the deployment supports scoped tokens.
- Tool results may contain workflow inputs, outputs, errors, or logs. Treat MCP
  output according to the sensitivity of the underlying environment.
- The server intentionally exposes no mutating Hatchet operations.

## Development

```bash
uv sync
uv run pytest
uv build
```

## License

[MIT](LICENSE)

Maintenance

ActivitySlowing
ResponsivenessNo issues