Operational MCP server for Hatchet — read-only by default, with opt-in run control over the Hatchet REST API.
Project description
hatchet-mcp
Unofficial, community project — not affiliated with, sponsored by, or endorsed by Hatchet. It is an independent integration that talks to Hatchet only through the public REST API via the official
hatchet-sdk.
An operational MCP (Model Context Protocol) server for Hatchet,
the durable orchestration engine. It exposes Hatchet's live state — and, optionally, its
control surface — to an LLM over the Hatchet REST API (via the official hatchet-sdk
feature clients) using the stdio transport.
It is a control tower for a Hatchet tenant: inspect workflow definitions, runs, tasks, logs, workers, events, cron/scheduled triggers, rate limits, filters, queue/task metrics, run timings, and OpenTelemetry traces — and, when explicitly enabled, trigger/cancel/replay runs, push events, pause/resume workflows and workers, and manage cron/scheduled/filters.
Read-only by default. Out of the box it never triggers, cancels, replays, or mutates
anything, so it is safe to point at a production tenant. Mutating tools are unregistered
until you opt in with HATCHET_MCP_READ_ONLY=false.
Design background lives in
docs/init/. Seedocs/init/mcp-server-design.mdfor the full tool surface and security model.
Install & run
# From PyPI (once published)
uvx hatchet-mcp
# Directly from a git checkout, no install
uvx --from git+https://github.com/DanMeon/hatchet-mcp hatchet-mcp
# From a local clone
uvx --from . hatchet-mcp
# Local development
uv run hatchet-mcp
All launch the same stdio MCP server. Configuration is environment-only because uvx
makes passing CLI flags awkward. With no HATCHET_CLIENT_TOKEN the server fails fast
and exits non-zero — it never starts a half-configured server.
Use it from an MCP client
The repo ships a .mcp.json.example for project-scoped setup. Copy it to
.mcp.json (gitignored, so your token stays local) and fill in the token:
cp .mcp.json.example .mcp.json
# then edit .mcp.json → set HATCHET_CLIENT_TOKEN
Before the package is published to PyPI, run it from git instead — keep "command": "uvx" and
set "args": ["--from", "git+https://github.com/DanMeon/hatchet-mcp", "hatchet-mcp"].
Or register it directly with the CLI:
claude mcp add hatchet -e HATCHET_CLIENT_TOKEN=<your-token> -- uvx hatchet-mcp
Or the JSON config block (Claude Desktop / Claude Code). This example opts into the mutating tools:
{
"mcpServers": {
"hatchet": {
"command": "uvx",
"args": ["hatchet-mcp"],
"env": {
"HATCHET_CLIENT_TOKEN": "<your-token>",
"HATCHET_CLIENT_SERVER_URL": "https://<self-host-url>",
"HATCHET_MCP_READ_ONLY": "false"
}
}
}
}
Omit HATCHET_MCP_READ_ONLY (or set it to true) to keep the safe, read-only posture.
Multiple tenants
A Hatchet token is scoped to exactly one tenant, so to operate several tenants you run one server instance per tenant — each with its own token — and register them as distinct MCP servers. No special configuration is needed; the client picks the server by name.
{
"mcpServers": {
"hatchet-prod": {
"command": "uvx",
"args": ["hatchet-mcp"],
"env": { "HATCHET_CLIENT_TOKEN": "<prod-token>" }
},
"hatchet-staging": {
"command": "uvx",
"args": ["hatchet-mcp"],
"env": { "HATCHET_CLIENT_TOKEN": "<staging-token>", "HATCHET_MCP_READ_ONLY": "false" }
}
}
}
This keeps tenant tokens in separate processes (no token mixing). See
docs/init/multitenancy-and-dependencies.md §1 for the rationale and the
alternative (a single server with a tenant argument), which was considered and not adopted.
Configuration (environment only)
| Env var | Required | Default | Meaning |
|---|---|---|---|
HATCHET_CLIENT_TOKEN |
yes | — | Hatchet API token (a JWT). Server URL and tenant are decoded from it. |
HATCHET_CLIENT_SERVER_URL |
no | from token | REST base URL override (for a self-host whose token embeds an unreachable internal URL). |
HATCHET_MCP_READ_ONLY |
no | true |
The mutation gate. true registers only the 24 read tools; false additionally registers the 17 mutating tools. Accepts true/false/1/0/yes/no/on/off; an unrecognized value fails fast. |
If HATCHET_CLIENT_TOKEN is missing the server fails fast at startup and exits — no
silent fallback. The token is never logged, echoed, or included in any error message
(even when the SDK rejects it, only the exception type is surfaced).
Safety model
This server can drive a production orchestrator, so safety is layered:
- Read-only by default.
HATCHET_MCP_READ_ONLYdefaults totrue. In that mode the 17 mutating tools are not registered at all — an MCP client cannot see or call them. - Defense in depth. Even if a mutating handler is somehow reached in read-only mode, it re-checks the gate and refuses.
- Destructive annotations. Every mutating tool is advertised with
destructiveHint=true(and an accurateidempotentHint) so MCP clients can require human approval before each call. - Bulk guardrails.
cancel_runs/replay_runsdefault to a dry run — they return the matching run IDs without acting. You must re-call withdry_run=falseto mutate. They also refuse to act on more than 500 matching runs, forcing a narrower filter. - Token confidentiality. The token is never written to stdout (the JSON-RPC channel), stderr, tool responses, or error messages.
- Single tenant. One token scopes the whole server to one Hatchet tenant (its
subclaim).
Tools
41 tools total: 24 read-only (always registered) + 17 mutating (registered only
when HATCHET_MCP_READ_ONLY=false).
Read-only (24) — always available
| Tool | Hatchet SDK / REST call | Purpose |
|---|---|---|
list_workflows |
h.workflows.aio_list |
List workflow definitions (name, paused, versions) |
get_workflow |
h.workflows.aio_get |
One workflow definition by ID |
list_runs |
h.runs.aio_list |
List workflow/task runs (time, status, workflow, worker, metadata filters). Excludes payloads by default — use get_run for inputs/outputs |
get_run |
h.runs.aio_get |
One run in detail (task tree / DAG shape, inputs, outputs) |
get_run_status |
h.runs.aio_get_status |
Status only — lightweight polling |
get_task |
h.runs.aio_get_task_run |
One task run by ID (status, attempt, worker, I/O) |
get_task_logs |
h.logs.aio_list |
Log lines for a single task run |
list_task_events |
TaskApi.v1_task_event_list |
Orchestration event timeline for a task run (state transitions) |
list_workers |
h.workers.aio_list |
List workers (status, slots, registered actions) |
get_worker |
h.workers.aio_get |
One worker by ID (slot state, recent runs, registered actions) |
list_events |
EventApi.v1_event_list |
List ingested events (key, time, workflow, run-status, metadata) |
get_event |
EventApi.v1_event_get |
One event with full payload + triggered-run summary |
list_event_keys |
EventApi.v1_event_key_list |
Distinct event keys in the tenant |
list_crons |
h.cron.aio_list |
List cron triggers |
get_cron |
h.cron.aio_get |
One cron trigger by ID |
list_scheduled |
h.scheduled.aio_list |
List scheduled (one-off, future) runs |
list_rate_limits |
h.rate_limits.aio_list |
Rate limits with current consumption |
list_filters |
h.filters.aio_list |
Event filters (CEL expressions) |
get_filter |
h.filters.aio_get |
One event filter by ID |
cel_debug |
h.cel.aio_debug |
Test a CEL expression against sample input (no filter created) |
get_queue_metrics |
h.metrics.aio_get_queue_metrics |
Native per-queue depth |
get_task_metrics |
h.metrics.aio_get_task_metrics |
Task counts grouped by status |
get_run_timings |
WorkflowRunsApi.v1_workflow_run_get_timings |
Task waterfall timings for a run |
get_trace |
ObservabilityApi.v1_observability_get_trace |
OpenTelemetry spans for a run |
Mutating (17) — only when HATCHET_MCP_READ_ONLY=false
| Tool | Hatchet SDK / REST call | Idempotent | Purpose |
|---|---|---|---|
trigger_workflow |
h.runs.aio_create |
no | Trigger a new workflow run by name |
cancel_runs |
h.runs.aio_bulk_cancel |
no | Cancel runs by IDs or filter — dry-run default, 500 cap |
replay_runs |
h.runs.aio_bulk_replay |
no | Replay runs by IDs or filter — dry-run default, 500 cap |
restore_task |
TaskApi.v1_task_restore |
no | Restore an evicted durable task |
push_event |
EventApi.event_create |
no | Push an event (may trigger event-driven workflows) |
pause_workflow |
WorkflowApi.workflow_update (isPaused=true) |
yes | Pause a workflow definition |
resume_workflow |
WorkflowApi.workflow_update (isPaused=false) |
yes | Resume a paused workflow definition |
pause_worker |
h.workers.aio_pause |
yes | Pause a worker |
resume_worker |
h.workers.aio_unpause |
yes | Resume a worker |
create_cron |
h.cron.aio_create |
no | Create a cron trigger |
delete_cron |
h.cron.aio_delete |
yes | Delete a cron trigger |
create_scheduled |
h.scheduled.aio_create |
no | Schedule a one-off future run |
delete_scheduled |
h.scheduled.aio_delete |
yes | Delete a scheduled run |
reschedule |
h.scheduled.aio_update |
yes | Change a scheduled run's trigger time |
create_filter |
h.filters.aio_create |
no | Create an event filter (CEL) |
update_filter |
h.filters.aio_update |
yes | Update an event filter |
delete_filter |
h.filters.aio_delete |
yes | Delete an event filter |
Status enums differ by engine generation and are passed through unchanged (no lossy remapping). v1 runs/tasks use
QUEUED · RUNNING · COMPLETED · CANCELLED · FAILED; legacy objects usePENDING · RUNNING · SUCCEEDED · FAILED · CANCELLED · QUEUED · BACKOFF. Seedocs/init/overview-and-concepts.md§5.
Response size and limits
A live tenant can hold thousands of runs, each with large input/output payloads, so a naive
list_runs can return several megabytes and blow past an MCP client's context window. The
read tools follow the same "keep lists small, fetch detail on demand" pattern as the GitHub,
Sentry, and Grafana MCP servers:
- Lists exclude heavy payloads by default.
list_runsreturns run metadata — id, status, workflow, timings — but not each run's input/output. Passinclude_payloads=true(ideally with a smalllimit) for the full rows, or fetch one run's payloads withget_run. - Every list tool defaults and caps its
limit. Unset resolves to 50, the hard cap is 100, and a value below 1 is rejected.get_task_logsis the exception — 1000 log lines, capped at 1000. Useoffsetto page where the tool exposes it. - Single-item
get_*tools are never truncated.get_run/get_task/get_eventreturn the whole object — the one item is the point, so they bypass the size guard below. - A ~500 KB ceiling backstops every list result. Anything larger is rejected with a
message explaining how to narrow it (smaller
limit, tighter time window or statuses) instead of silently overflowing your context. This also protects tools whose upstream call takes nolimit, such aslist_workers.
Resources & prompts
In addition to tools, the server exposes read-only MCP resources (URI-addressable views that reuse the read tools, so their JSON is identical) and operator prompts. Both are always available and add no mutating surface.
Resources:
| URI | Backed by |
|---|---|
hatchet://workflows |
list_workflows |
hatchet://workflows/{workflow_id} |
get_workflow |
hatchet://workers |
list_workers |
hatchet://runs/{workflow_run_id} |
get_run |
hatchet://runs/{workflow_run_id}/status |
get_run_status |
Prompts:
| Prompt | Arguments | Orchestrates |
|---|---|---|
triage_failed_runs |
hours (default 24) |
list_runs → get_run → get_task_logs |
debug_run |
workflow_run_id |
get_run → get_run_timings → get_task_logs → get_trace |
tenant_health |
hours (default 24) |
get_queue_metrics → get_task_metrics → list_workers |
How it talks to Hatchet
MCP client (Claude Code, …)
│ stdio (JSON-RPC)
▼
hatchet-mcp (Python, FastMCP)
│ hatchet-sdk feature clients, async aio_* methods
│ Authorization: Bearer <HATCHET_CLIENT_TOKEN>
▼
Hatchet REST API (/api/v1/stable/… + /api/v1/…)
REST only — never the gRPC worker dispatcher protocol. A single token scopes the server to
a single tenant (resolved from the token's sub claim by the SDK).
Development
uv sync # install runtime + dev deps on Python 3.10+
uv run pytest # full test suite — needs no token, performs no real mutation
uv run ruff check src tests
uv run ruff format --check src tests
uv run pyright
uv run hatchet-mcp # needs HATCHET_CLIENT_TOKEN in the environment
Contributing
Contributions are welcome. See CONTRIBUTING.md for development setup,
quality gates, project conventions, and the maintainer release process.
Security
hatchet-mcp can drive a production orchestrator and handles an API token. Please report
vulnerabilities privately — see SECURITY.md. Don't open a public issue
for security reports.
License
MIT — see LICENSE.
"Hatchet" and any related marks belong to their respective owners. This is an independent project and is not affiliated with or endorsed by Hatchet.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hatchet_mcp-0.1.0.tar.gz.
File metadata
- Download URL: hatchet_mcp-0.1.0.tar.gz
- Upload date:
- Size: 208.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80b0d137c83aa59b2b9d9fab60278619bbc626f3202a67b0327aeeb35dc977e5
|
|
| MD5 |
d31ce19319e4a21f0e43f0e7cc0a3cd3
|
|
| BLAKE2b-256 |
ba1331f7aab22c40ce04c7b38071a513f38fb15e2530c4034dfa751bc7d9a71b
|
File details
Details for the file hatchet_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hatchet_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fdfea53e7acfe1076d99bb73bd1e7a04f92115e760722f50ff4d23ef822fe51b
|
|
| MD5 |
4654de56db3a5cca3f0a6f1d8996f1de
|
|
| BLAKE2b-256 |
160db1f49e8f3e2acdcf9cc3e4fe96012da652576d2d7a10ffad8d91bc1e8556
|