Releases: DanMeon/hatchet-mcp
Releases · DanMeon/hatchet-mcp
Release list
v0.3.0 — operational toolkit expansion
Added
- New domain
tools/webhooks.py— V1 inbound-webhook read surface:list_webhooks
(filter by name/source),get_webhook(by name). Read-only on purpose — create/update
/delete carry credential material (BasicAuth/APIKeyAuth/HMACAuth) kept out of MCP. - New domain
tools/meta.py— server-side aggregation over existing reads:
top_failing_workflows(rank workflows by FAILED count in a window),
list_stuck_runs(RUNNING runs older than a minute threshold),describe_run_failure
(one-call diagnostic that runsget_run→ logs/events/timings in parallel via
asyncio.gatherand assembles a single structured payload). - New SDK-gap read tools in existing domains:
get_workflow_version,
get_workflow_metrics,get_task_stats,get_scheduled(closes the
get_cronasymmetry),list_dag_tasks. - New SDK-gap mutating tools:
cancel_run/replay_run(single-ID, no
dry-run/cap dance — for many IDs use the bulk variants),replay_events(500-event
cap),bulk_delete_scheduled(dry-run default, mutex IDs/filter, 500-cap on
explicit IDs). HatchetAPIErrortyped exception —RuntimeErrorsubclass withstatus: int | None
andkind: Literal["validation_error" | "unauthorized" | "not_found" | "conflict" | "rate_limited" | "server_error" | "unknown"]. Message prefix"Hatchet API error"is
preserved so existingpytest.raises(RuntimeError, match=...)callers still match
(src/hatchet_mcp/_shared.py).- MCP read-tool annotations — every read tool now advertises
ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True)so clients (Cursor, Claude Code in trusted-server mode) can skip
per-call approval prompts for reads. Every registered tool (read + mutating) also carries
the MCP 2025-06-18titlefield, derived from the snake_case name
(src/hatchet_mcp/server.py,src/hatchet_mcp/_shared.py). tool.ok/tool.errorstderr records now include the MCP JSON-RPCrequest_id
(present, may benulloutside a request scope);tool.erroralso carrieserror_status
(int) anderror_kind(str) fromHatchetAPIError. Concurrent stdio tool calls become
attributable in stderr (src/hatchet_mcp/_shared.py,src/hatchet_mcp/_logging.py).- Progress notifications on bulk ops —
cancel_runs/replay_runs/
bulk_delete_scheduledaccept an injectedctx: Context | None, emittingctx.infoand
ctx.report_progressat resolve / submit / done milestones; existing callers (noctx)
see no behavior change (src/hatchet_mcp/tools/runs.py,
src/hatchet_mcp/tools/schedules.py).
Changed (breaking)
get_run_statusreturn-key flips from{"workflow_run_id": "..."}to
{"workflowRunId": "..."}, restoring CLAUDE.md invariant 4 ("tool output uses
the Hatchet REST shape — camelCase viaby_alias=True"). The resource at
hatchet://runs/{workflow_run_id}/statusfollows by pass-through. Path-parameter names
are unchanged. Callers parsing the response key by name must switch toworkflowRunId
(src/hatchet_mcp/tools/runs.py).list_runsnow defaults to a 9-field projection (minimal_output=True):
taskExternalId,workflowRunExternalId,status,workflowName,startedAt,
finishedAt,errorMessage,parentTaskExternalId,numSpawnedChildren—
typically ~5-7x smaller per response (a 124 KB / 100-row scan drops to ~20 KB).
Setminimal_output=Falseto get every field, or useget_runfor one run's full
record. Follows the GitHub MCP convention of defaulting list tools to a compact
shape (src/hatchet_mcp/tools/runs.py).list_eventsnow defaults tominimal_output=True, which drops each event's
payload,triggeredRuns, andadditionalMetadatafields. Set
minimal_output=Falsefor the full record, or useget_eventfor one event's full
payload. Same convention aslist_runs(src/hatchet_mcp/tools/events.py).
Callers that depended on the full row from list_runs / list_events, or on the
snake_case get_run_status key, must update.
Changed
- HTTP connection reuse —
_rest_callnow invokes a process-wide cachedApiClient
(client.get_api_client(), guarded bythreading.Lockfor the double-checked init under
asyncio.to_threadworkers) instead of constructing a freshApiClient→RESTClientObject
→urllib3.PoolManageron every call. Keep-alive now amortizes TCP+TLS across the 11
REST-direct sites (events, observability, tasks, runs filter-resolve, etc.). The SDK's own
aio_*feature-client methods retain the per-call construction pattern upstream
(src/hatchet_mcp/client.py,src/hatchet_mcp/_shared.py). describe_run_failureruns its three independent sub-reads (logs / events / timings)
viaasyncio.gather(return_exceptions=True). The timings branch is best-effort — an
ApiException/HatchetAPIError/asyncio.TimeoutErrordemotes it totimings=null
while logs + events still succeed (src/hatchet_mcp/tools/meta.py).get_run_timingsdescription now explicitly calls out that it is the cheapest way
to expand a parent run's child task tree in a single call (usedepth=1for direct
children) — closes a discovery gap that previously forced callers to page through
list_runsto find children of a known parent
(src/hatchet_mcp/tools/observability.py).
Fixed
- MCP resources bypassed the reliability wrapper (latent since v0.2.0). Resource handlers
inresources.pyimported raw tool functions and awaited them directly, skipping the 30s
per-call deadline, retry on 5xx/429/transport, andtool.ok/tool.errorlog records — a
hung Hatchet response could hang the MCP session with no audit trail. Resources now go
through pre-wrapped handlers built once at module import time
(src/hatchet_mcp/resources.py). - Concurrency race on
get_api_client()init closed viathreading.Lock+ double-check
(twoasyncio.to_threadworkers could otherwise both see_api_client is Noneand build
twoApiClientinstances) (src/hatchet_mcp/client.py).
Docs
- New spec
v0.3.0/operational-toolkit-expansionand paired ADR — 7 decisions, AC-1
through AC-7 (docs/roadmap/v0.3.0/,docs/design/v0.3.0/). docs/roadmap/README.mdactive-spec index updated with the v0.3.0 row.
v0.2.1 list filter parity
Every list_* MCP read tool now passes through the full hatchet-sdk (or REST) filter signature. Closes the parity gaps surfaced by an audit; one underlying refactor (a shared _parse_enum single-value validator).
Added
list_runsnow forwards two SDK filters that were previously hidden from MCP clients:parent_task_external_id(expand a sub-workflow tree from a parent run) andtriggering_event_external_id(trace which runs an event caused) (src/hatchet_mcp/tools/runs.py).list_schedulednow forwards three SDK filters:parent_workflow_run_id,order_by_field(triggerAt/createdAt), andorder_by_direction(ASC/DESC) (src/hatchet_mcp/tools/schedules.py). Invalidorder_by_*values raise with the allowed-values list rather than reaching the SDK.list_cronsnow forwardsorder_by_field(name/createdAt) andorder_by_direction(ASC/DESC) (src/hatchet_mcp/tools/schedules.py).list_eventsnow forwardsevent_ids(specific events by UUID) andscopes(filter by event scope strings) (src/hatchet_mcp/tools/events.py).list_rate_limitsnow forwardsorder_by_field(key/value/limitValue) andorder_by_direction(lowercaseasc/desc, matching the SDK'sRateLimitOrderByDirectionenum — distinct fromWorkflowRunOrderByDirection's uppercase) (src/hatchet_mcp/tools/observability.py).- New shared helper
_parse_enumfor validating a single (str) enum value, mirroring_parse_enum_list(src/hatchet_mcp/_shared.py).
v0.2.0 reliability
Added
- New read tool
get_server_infoand matching resourcehatchet://server/info, both
delegating to a single_build_server_infohelper and returning a byte-identical JSON
payload (read_only,read_tool_count,mutating_tool_count,server_url_source—
"token"or"override"—,hatchet_sdk_version,python_version). Neither surface
carries the Hatchet token. Read tool count is now 25 (src/hatchet_mcp/tools/server_info.py,
src/hatchet_mcp/resources.py). - Per-call 30s deadline on every registered tool via
asyncio.wait_for, so a hung Hatchet
can no longer lock the stdio session (src/hatchet_mcp/_shared.py). - Idempotent-only retry on transient
5xx,429, and connection-class
(RestTransportError) failures: 3 attempts, exponential backoff (1s / 2s / 4s with ±25%
jitter), andRetry-Afterhonored on429clamped to 10s. Non-idempotent mutations
(trigger_workflow,push_event,cancel_runs,replay_runs,restore_task, every
create_*) keep the deadline but skip the retry layer, gated at registration time by
ToolAnnotations.idempotentHint(src/hatchet_mcp/_shared.py,src/hatchet_mcp/server.py). - Structured stderr logging: one JSON-line record per tool invocation
(event=tool.ok/tool.error, withtool,duration_ms, and a redactedredacted_error
on failure) and per server lifecycle event (event=server.start/server.error). Records
include input-validation failures that raise before any Hatchet call, because the timer
starts at wrapper entry (src/hatchet_mcp/_logging.py).
Changed
- Startup banner is now an
event=server.startJSON record on stderr instead of the
unstructuredhatchet-mcp: starting…print; theConfigErrorexit emits a matching
event=server.errorrecord (src/hatchet_mcp/server.py). server.start/get_server_infouse the same origin label"token"/"override"for
the server URL source so a single grep covers both channels.- Tool handlers no longer wrap each Hatchet call in
try / except ApiException; the
reliability wrapper owns the SDK-exception-to-RuntimeErrortranslation centrally, and the
raw exception flows through the retry layer first.
Security
- The stderr structured-log channel is independently redacted at format time — every string
field, at any nesting depth, runs throughredact()before emit. The existing MCP JSON-RPC
error channel redaction via_api_erroris unchanged; the two surfaces own independent
redaction with neither acting as the other's gate (src/hatchet_mcp/_logging.py,
src/hatchet_mcp/_shared.py).
Docs
- Freeze the
v0.2.0/reliabilityspec and its paired ADR (Draft → Frozen,target → ga),
and bumpdocs/roadmap/README.md's Status to v0.2.0.
v0.1.1
Security
redact()now strips both the fullHATCHET_CLIENT_TOKENand its 16-char prefix from external-facing strings, catching truncated log lines and partial header echoes that the exact-substring match in 0.1.0 missed.ConfigErrorfrom_parse_boolruns the offending env value throughredact()before echoing — a token mis-pasted intoHATCHET_MCP_READ_ONLYno longer surfaces in the startup banner.- New
muzzle_dependency_loggers()forceshatchet_sdk,aiohttp,httpx,httpcore,grpc, andurllib3loggers toWARNINGat server boot, closing the only realistic path by which a downstreamLOG_LEVEL=DEBUGcould echoAuthorization: Bearer <token>headers to stderr.
Docs
- README: add a 4-step Quick start (token → install → MCP-client wiring → first call) and remove the pre-publish placeholders.
- Scaffold the v0.2.0/reliability spec (idempotent retry + 30s deadline + structured stderr logs + `get_server_info` diagnostics) with its paired ADR.
Full changelog: v0.1.0...v0.1.1
v0.1.0
Initial public release.
- 24 read-only tools (always registered) + 17 opt-in mutating tools, gated by
HATCHET_MCP_READ_ONLY - 5 MCP resources and 3 operator prompts
- Read-only by default with layered safety: mutation gate, dry-run + 500-run cap on bulk cancel/replay, full token redaction
- Environment-only configuration; fail-fast startup when the token is missing
See CHANGELOG.md for details.