hydra-ops-mcp
Provides tools for operating a Hydra head on the Cardano blockchain, including lifecycle management (init, commit, decommit, close, fanout), in-head transactions, L1 wallet queries, and diagnosis of node logs and error codes.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hydra-ops-mcpWhat state is the head in, and what does alice hold on L1?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
hydra-ops-mcp
Operate a Hydra head by talking to it. An MCP server that exposes a
running head — lifecycle, ledger, L1 wallets, node logs, and on-chain error
codes — as tools an LLM client can call, so you can drive and debug a head
in plain language instead of switching between a TUI, curl, cardano-cli,
and docker logs.
It covers the full operational surface: init, deposits, in-head
transactions, decommit, close, fanout, partial fanout, and deposit
recovery, plus read-only views of head state and the L1. Every operation that
changes state describes what it would do and waits for your explicit
confirmation before doing it.
Contents
Why
Operating a head means holding several tools at once. The TUI shows you head
state but not why a transaction was rejected. The WebSocket API gives you
events but you're parsing JSON by hand. When something goes wrong the answer
is usually in docker compose logs, correlated against head state, and
decoded against error codes that live in the Plutus source.
This server puts all of that behind one conversational interface:
"The head won't fan out. What's wrong?"
Claude can check head state, pull the failing transaction from the node logs,
decode the H39 abort code to FanoutUTxOHashMismatch, and tell you the two
things that actually cause it — in one turn, because it has the head API, the
container logs, and the error tables all in reach.
It's also useful for the routine parts: opening and funding a head, moving
funds, and settling out, with each step explained and confirmed before it
runs. And unlike a TUI session bound to a single node, every tool takes a
node argument, so you can compare what alice, bob and carol each believe
about the same head.
Currently targets the hydra demo devnet (three nodes, three parties). The API layer is not devnet-specific; the L1 helpers and key handling are (see Limitations).
Quick start
Prerequisites — Docker, Python 3.10+, and a checkout of cardano-scaling/hydra (for the demo devnet and the Plutus error tables).
git clone https://github.com/skoniog/hydra-ops-mcp && cd hydra-ops-mcp
python3 -m venv .venv # or: uv venv .venv
.venv/bin/pip install -r requirements.txt
./reset_devnet.sh # cardano-node + 3 hydra-nodes, seededRegister the server with your MCP client. Claude Code:
claude mcp add hydra-ops -- /absolute/path/to/hydra-ops-mcp/.venv/bin/python \
/absolute/path/to/hydra-ops-mcp/server.pyClaude Desktop — add to claude_desktop_config.json:
{
"mcpServers": {
"hydra-ops": {
"command": "/absolute/path/to/hydra-ops-mcp/.venv/bin/python",
"args": ["/absolute/path/to/hydra-ops-mcp/server.py"]
}
}
}MCP launchers spawn the server with a stripped environment, so pass any
overrides (HYDRA_DEMO_DIR, HYDRA_REPO) in an "env" block rather than
exporting them in your shell.
Then ask:
"What state is the head in, and what does alice hold on L1?" "Open a head and commit alice's funds." "Send 5 ADA from alice to bob, then show me the head UTXO set."
New to operating a head this way? RUNBOOK.md walks the whole lifecycle — open, fund, transact, decommit, close, settle, and break it on purpose — as a series of guided sessions.
Architecture
MCP client (Claude Code / Claude Desktop / anything speaking MCP)
│ stdio
▼
server.py FastMCP registration; thin wrappers only
│
tools/ one module per domain, plain functions
├── observe.py head state, UTXOs, L1 funds, params, events
├── lifecycle.py init, commit, decommit, close, fanout, recover
├── transact.py in-head transfers
├── diagnose.py node logs, error-code decoding
└── types.py ok() / err() / needs_confirmation()
│
├──▶ hydra_client.py WebSocket + HTTP to hydra-node
│ async core, sync facade, event buffer
├──▶ tx_builder.py PyCardano: build + sign in-head txs
├──▶ cardano.py cardano-cli in the node container (L1)
└──▶ errors.py parses hydra-plutus for abort codeshydra_client.py holds a WebSocket connection per node, running an async
event loop on a daemon thread behind a synchronous facade — so tool functions
stay simple while still awaiting protocol events. It buffers every server
output for recent_events, tracks head status, and correlates confirmed
transactions. Commands wait for their specific outcome event
(Decommit → DecommitFinalized, Fanout → HeadIsFinalized) rather than
returning optimistically, so a tool call that succeeds means the protocol
step actually completed.
tx_builder.py builds and signs transactions with PyCardano — no
cardano-cli round-trip per transaction. cardano.py handles the L1 side
(address derivation, UTXO queries, signing and submitting deposit
transactions) by exec'ing cardano-cli inside the running cardano-node
container, which is also where the keys live.
errors.py parses HeadError.hs, DepositError.hs, HeadTokensError.hs
and friends out of your local hydra checkout at call time, so decoded codes
always match the version you're running rather than a table that drifts.
The confirmation model
Every tool that changes state takes confirm: bool = False. Called without
it, the tool validates everything it can, resolves what it would actually do,
and returns a description — having changed nothing:
{
"status": "requires_confirmation",
"action": "deposit alice's UTXO 4a3f…#0 (100,000,000,000 lovelace) into the head via node 1",
"message": "This would deposit… Nothing has been done. Retry with confirm=True to execute.",
"party": "alice", "utxo_ref": "4a3f…#0", "lovelace": 100000000000
}In practice this means Claude proposes, you approve, and only then does
anything happen on-chain. It matters most for the operations that are
unilateral and irreversible: close_head affects every participant in the
head, and fanout settles the head's final state.
The preview is resolved, not hypothetical — commit_funds names the exact
UTXO it selected, decommit names the owner and amount it derived from the
head's UTXO set, send_tx reports the transaction id it built. Validation
runs before the gate, so you're never asked to confirm something that would
have failed anyway. Read-only tools have no gate and run immediately.
Tool reference
All tools return {status, error, ...}; failures are
{"status": "error", "error": "<message>", ...} rather than exceptions. Every
tool accepts node: int = 1 (1 = alice, 2 = bob, 3 = carol) except
l1_funds and explain_error.
Observability (read-only)
Tool | Signature | Returns |
|
| Head tag, WS-observed status, UTXO count, total lovelace, snapshot number, head version, contestation deadline |
|
| The head's UTxO set grouped by address, each with ref and value |
|
| A party's L1 address, UTXO count, total lovelace, and per-UTXO values |
|
| The head's ledger parameters — full set plus a summary of the ones that bite (fees, min-UTXO, sizes) |
|
| Deposits observed but not yet absorbed — the recovery candidates |
|
| Server outputs seen on this connection, optionally filtered by tag |
recent_events covers events since the server connected — the WS connection
requests no history, so it's a live tail rather than the full log. For
anything older, use node_logs.
Lifecycle (confirmation-gated)
Tool | Signature | Notes |
|
| Refuses unless the head is |
|
| Drafts the deposit via |
|
| Withdraws one head UTxO to L1 with the head still open. Derives the owner from the UTxO's address and builds a full-value self-transfer as the decommit tx |
|
| Posts the latest confirmed snapshot and starts the contestation period. Affects all participants |
|
| Waits for |
|
| Settles a chosen subset; reports what was distributed and what remains. See Limitations — needs a node newer than 2.3.0 |
|
|
|
commit_funds deliberately deposits a single UTXO per call: multi-UTXO
deposits are what wedge fanout with H39 on 2.3.0
(see Operational notes).
Transactions (confirmation-gated)
Tool | Signature | Notes |
|
| In-head transfer. |
Amounts below 1 ADA are refused. The head zeroes min-UTXO, so such an output is valid on L2 and then impossible to recreate on L1 — it would wedge fanout permanently. The transaction is rebuilt against the current UTxO set at confirmation time, so a preview that sat around doesn't spend stale inputs. The call returns once the transaction appears in a confirmed snapshot, not merely when it's accepted.
Diagnosis (read-only)
Tool | Signature | Notes |
|
| Container logs, optionally regex-filtered. Returns how many lines matched and the last |
|
| Decodes an abort code ( |
Compared with hydra-tui
The tool surface deliberately matches what hydra-tui exposes, so anything
you can do in the TUI you can do here:
hydra-tui | here |
|
|
commit dialog |
|
|
|
|
|
|
|
|
|
|
|
|
|
main tab |
|
funds tab |
|
event history tab |
|
— |
|
Like the TUI, this doesn't expose Contest, SafeClose or
SideLoadSnapshot. Those are protocol responses to specific on-chain
conditions where one action is correct and timing is load-bearing; they
belong in deterministic tooling with alerting, not behind a prompt.
Where this goes further:
Diagnosis.
node_logsandexplain_errorhave no TUI equivalent. This is the biggest practical gain — a wedged head goes from "the TUI says it failed" to a decoded abort code and the matching log lines.Cross-node. A TUI session attaches to one node. Here every tool takes
node, so you can ask what alice, bob and carol each believe about the same head — the fastest way to spot a node that has fallen behind.L1 and L2 together.
l1_fundsqueries the chain directly, so "did that decommit actually land?" is one question rather than a context switch tocardano-cli.Guardrails. Sub-min-UTXO outputs and multi-UTXO deposits are refused by construction, because both silently wedge fanout later.
Composition. Multi-step operations happen in one request: "close the head, wait out contestation, fan out, and show me everyone's final L1 balances" is a single ask.
Where the TUI still wins: it's a live dashboard. MCP is request/response, so you get snapshots rather than a continuously updating view — for watching a head over time, keep the TUI open. Keystrokes also beat a model round-trip for repetitive work, and the TUI's UTxO pickers are visual where here you list then select.
Configuration
Everything is in config.py, with environment overrides:
Setting | Default | Meaning |
|
| Node index → WS/HTTP endpoints and party name |
|
| Demo devnet: docker compose project and credentials |
|
| Hydra checkout, for decoding abort codes |
|
| Devnet magic |
|
| Refusal threshold for in-head outputs |
Signing keys are the demo's {alice,bob,carol}-funds pairs. Container-side
paths are used for cardano-cli (signing and submitting on L1); host-side
copies of the same keys are read by PyCardano for in-head transactions.
Pointing at a different deployment with the same layout is a config change;
pointing at a different topology is not (see Limitations).
Testing
.venv/bin/python test_ops.py # offline — no devnet needed
.venv/bin/python test_ops_devnet.py # live — needs a devnet with the head Idletest_ops.py asserts that every state-changing tool returns
requires_confirmation and reaches no client without confirm=True (the
stub client raises if a command escapes the gate), that request payloads match
the API, that the min-UTXO refusal and UTxO validation fire, that the error
table parses and decodes, and that all 16 tools register with the server.
test_ops_devnet.py drives a real head through the whole lifecycle and
asserts observability at each stage: gate check → init → commit →
six read tools → two in-head payments → decommit, verified by the funds
appearing on L1 while the head stays open → close → fanout → back to
Idle → logs and error decoding. It skips with a clear message if the devnet
isn't up or the head isn't Idle.
Operational notes
Things worth knowing before they cost you a head.
H39 / FanoutUTxOHashMismatch wedges a head permanently. Fanout can't
reproduce what the closed head committed to, so the head cannot settle and its
funds are stuck. Two causes, both preventable and both guarded against here:
multi-UTXO deposits on 2.3.0, and any head output below the L1 min-UTXO. Ask
explain_error("H39") for the details.
The head zeroes min-UTXO; L1 does not. A 0.5 ADA output transacts happily
on L2 and then cannot be recreated on L1. send_tx refuses below 1 ADA for
this reason.
Deposits are absorbed after a deposit period, not instantly.
commit_funds waits and reports if absorption doesn't happen; a deposit that
never lands shows up in pending_deposits and comes back with
recover_deposit.
Close is unilateral and affects everyone. Any participant can close, and the whole head must then settle. The gate exists mostly for this.
A head needs every participant online. If a payment hangs, check
docker compose ps before suspecting the tooling.
The demo devnet's block producer can stall after long idle periods —
cardano-cli query tip returns the same slot twice and everything hangs.
./reset_devnet.sh fixes it; the devnet is disposable by design.
Unparseable WebSocket input returns no tag. A command a node doesn't
recognize comes back as a bare {"input", "reason"} object rather than a
tagged event — worth knowing if you script against the API directly, since a
client waiting on tagged events will hang. The client here handles it.
Limitations
partial_fanout needs a node newer than 2.3.0. The command postdates the
release (hydra PR #2750, commit a271cced2), and the pinned demo image
rejects it — the node lists the commands it knows and PartialFanout isn't
among them. The tool detects this precisely and reports the version gap. The
code path is ready for a node built from master but has only been exercised up
to that rejection.
Fees are zero. tx_builder.py hardcodes fee=0, which is correct for the
demo's protocol parameters and wrong everywhere else. Real fee estimation and
coin selection are needed before this points at preview/preprod or mainnet.
Devnet-shaped assumptions. Three parties with known key names, keys
readable inside the cardano-node container, docker compose available for L1
queries and logs. The head API layer is general; the L1 helpers are not.
ADA only. Transaction building handles pure-lovelace UTXOs — no native tokens, scripts, datums, or minting.
recover_deposit is untested against a genuinely stuck deposit. It
follows the API, but the demo devnet absorbs deposits too reliably to produce
one on demand.
No auth. Anyone who can reach the server can operate the head. That's appropriate for a local operator tool and would not be for anything exposed.
Extending
Adding a tool: write a plain function in the relevant tools/ module
returning ok() / err() / needs_confirmation(), then register a thin
wrapper in server.py. Tool modules don't import FastMCP, so they're directly
callable from tests — which is how both suites drive them.
Adding a protocol command: add a method to HydraClient using
_command_and_wait(command, ok_tags), which sends and waits for the outcome
event while treating both CommandFailed and untagged parse rejections as
errors.
Targeting another deployment: point NODES at the endpoints and
HYDRA_DEMO_DIR / HYDRA_REPO at the right paths. Anything beyond the demo's
three-party layout means revisiting key handling in cardano.py and
tx_builder.py, and fees.
Further reading
The Hydra documentation for the protocol itself, and RUNBOOK.md for the guided tour of operating a head with these tools.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP server exposing the Backtest360 engine API as tools for AI agents.
Hosted MCP server for live Bittensor chain reads and self-custodial on-chain writes.
MCP server providing access to the Scorecard API to evaluate and optimize LLM systems.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/skoniog/hydra-ops-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server