Skip to main content
Glama
skoniog

hydra-ops-mcp

by skoniog

hydra-ops-mcp

通过对话操作 Hydra 头。 一个 MCP 服务器,将运行中的 Hydra 头——生命周期、账本、L1 钱包、节点日志和链上错误码——暴露为 LLM 客户端可调用的工具,让您可以用自然语言驱动和调试 Hydra 头,而无需在 TUI、curl、cardano-cli 和 docker logs 之间切换。

它涵盖了完整的操作面:init、存款、头内交易、decommit、close、fanout、部分 fanout 和存款恢复,以及头状态和 L1 的只读视图。每个改变状态的操作都会描述它将做什么,并在执行之前等待您的明确确认。


目录


Related MCP server: mcp-cli-catalog

为什么

操作一个头意味着同时掌握多个工具。TUI 显示头状态,但不会告诉你交易为何被拒绝。WebSocket API 提供事件,但你得手动解析 JSON。当出现问题时,答案通常藏在 docker compose logs 中,需要与头状态关联,并根据 Plutus 源码中的错误码进行解码。

这个服务器将所有内容置于一个对话式接口之后:

“头无法 fanout。出了什么问题?”

Claude 可以检查头状态,从节点日志中提取失败交易,将 H39 中止码解码为 FanoutUTxOHashMismatch,并告诉你实际导致它的两个原因——一轮对话即可完成,因为它能同时访问头 API、容器日志和错误表。

对于常规操作也很有用:打开并注资一个头,转移资金,以及结算,每一步在运行前都会得到解释和确认。与绑定到单个节点的 TUI 会话不同,每个工具都接受一个 node 参数,因此你可以比较 alice、bob 和 carol 各自对同一个头的看法。

目前针对 hydra 演示 devnet(三个节点,三方)。API 层并非 devnet 专用;L1 辅助函数和密钥处理是 devnet 特定的(参见限制)。


快速开始

先决条件 — Docker、Python 3.10+,以及 cardano-scaling/hydra 的克隆(用于演示 devnet 和 Plutus 错误表)。

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, seeded

向 MCP 客户端注册服务器。Claude Code:

claude mcp add hydra-ops -- /absolute/path/to/hydra-ops-mcp/.venv/bin/python \
    /absolute/path/to/hydra-ops-mcp/server.py

Claude Desktop — 添加到 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 启动器会以精简环境启动服务器,因此请将任何覆盖项(HYDRA_DEMO_DIR、HYDRA_REPO)放在 "env" 块中传递,而不是在 shell 中导出。

然后询问:

“头处于什么状态,alice 在 L1 上持有什么?” “打开一个头并提交 alice 的资金。” “从 alice 向 bob 发送 5 ADA,然后显示头 UTXO 集。”

第一次以这种方式操作头?RUNBOOK.md 以一系列引导式会话的方式,贯穿整个生命周期——打开、注资、交易、decommit、关闭、结算,以及故意破坏。


架构

  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 codes

hydra_client.py 为每个节点持有一个 WebSocket 连接,在守护线程上运行异步事件循环,背后是同步接口——这样工具函数保持简单,同时仍能等待协议事件。它为 recent_events 缓冲每个服务器输出,跟踪头状态,并关联已确认的交易。命令会等待其特定结果事件(Decommit → DecommitFinalized,Fanout → HeadIsFinalized),而不是乐观返回,因此成功的工具调用意味着协议步骤实际完成。

tx_builder.py 使用 PyCardano 构建和签署交易——无需每次交易都经过 cardano-cli 往返。cardano.py 通过在执行 cardano-cli 的容器内执行 cardano-cli 处理 L1 端(地址派生、UTXO 查询、签署和提交存款交易),密钥也存放在那里。

errors.py 在调用时从本地 hydra 检出中解析 HeadError.hs、DepositError.hs、HeadTokensError.hs 等文件,因此解码的代码始终与你运行的版本匹配,而不是一个可能过时的表。


确认模型

每个改变状态的工具都接受 confirm: bool = False。不传递该参数调用时,工具会尽可能验证一切,解析它实际会做什么,并返回一个描述——不改变任何东西:

{
  "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
}

实际上,这意味着 Claude 提议,你批准,然后才在链上发生任何事情。这对于单方面且不可逆的操作最为重要:close_head 影响头中的所有参与者,而 fanout 结算头的最终状态。

预览是已解析的,而非假设——commit_funds 会指定它选择的精确 UTXO,decommit 会指定它从头 UTXO 集中推导出的所有者和金额,send_tx 会报告它构建的交易 ID。验证在门控 之前 运行,因此你永远不会被要求确认本来会失败的事情。只读工具没有门控,立即运行。


工具参考

所有工具返回 {status, error, ...};失败时返回 {"status": "error", "error": "<message>", ...} 而不是异常。每个工具都接受 node: int = 1(1 = alice,2 = bob,3 = carol),l1_funds 和 explain_error 除外。

可观测性(只读)

工具

签名

返回

head_status

(node=1)

头标签、WS 观察到的状态、UTXO 数量、总 lovelace、快照编号、头版本、争议截止时间

head_utxos

(node=1)

头的 UTxO 集按地址分组,每个包含引用和值

l1_funds

(party="alice")

参与方的 L1 地址、UTXO 数量、总 lovelace,以及每个 UTXO 的值

protocol_parameters

(node=1)

头的账本参数——完整集合加上那些有影响参数的摘要(费用、最小 UTXO、大小)

pending_deposits

(node=1)

已观察到但尚未吸收的存款——即恢复候选

recent_events

(node=1, tag=None, limit=25)

在此连接上看到的服务器输出,可按标签过滤

recent_events 涵盖自服务器连接以来的事件——WS 连接不请求历史记录,因此是实时尾部而非完整日志。对于更早的内容,请使用 node_logs。

生命周期(确认门控)

工具

签名

备注

init_head

(node=1, confirm=False)

除非头处于 Idle 状态,否则拒绝。在 2.3.0 上,头立即打开并为空;资金随后通过存款注入

commit_funds

(party="alice", node=1, utxo_ref="", confirm=False)

通过 POST /commit 起草存款,用参与方的资金密钥签署,提交到 L1,然后等待吸收。每次存入一个 UTXO——除非 utxo_ref 指定了另一个,否则选择最大的那个

decommit

(utxo_ref, node=1, confirm=False)

在头仍打开的情况下,将一个头 UTxO 提取到 L1。从 UTxO 的地址推导所有者,并构建一个全额自转账作为 decommit 交易

close_head

(node=1, confirm=False)

发布最新的已确认快照并开始争议期。影响所有参与者

fanout

(node=1, confirm=False)

如果需要,等待 ReadyToFanout,然后将整个 UTxO 集分发到 L1

partial_fanout

(utxo_refs, node=1, confirm=False)

结算选定的子集;报告已分发的内容和剩余内容。参见限制 — 需要 2.3.0 以上版本的节点

recover_deposit

(tx_id, node=1, confirm=False)

DELETE /commits/{txid} — 将卡住的存款返回到 L1

commit_funds 每次故意只存入一个 UTXO:多 UTXO 存款在 2.3.0 上会导致 fanout 卡在 H39(参见操作说明)。

交易(确认门控)

工具

签名

备注

send_tx

(sender, receiver, amount_lovelace, node=1, confirm=False)

头内转账。sender 是签名密钥可用的参与方;receiver 是参与方名称或 bech32 地址

低于 1 ADA 的金额被拒绝。头将最小 UTXO 归零,因此这样的输出在 L2 上有效,但在 L1 上无法重新创建——它会永久卡住 fanout。交易在确认时根据当前 UTxO 集重新构建,因此预览不会使用过时的输入。调用在交易出现在已确认快照中后返回,而不仅仅是在被接受时。

诊断(只读)

工具

签名

备注

node_logs

(node=1, pattern="", since="10m", limit=40)

容器日志,可选择正则过滤。返回匹配的行数以及最后 limit 行

explain_error

(code)

从本地 hydra 检出中解码中止码(H39、D01、…)到其构造函数和模块,并附上实际会出现的问题的实用说明


与 hydra-tui 对比

工具表面有意匹配 hydra-tui 暴露的内容,因此你在 TUI 中能做的任何事情在这里都能做:

hydra-tui

本工具

i — init

init_head

提交对话框

commit_funds(起草、签名、提交、等待吸收)

n — 新交易

send_tx

d — decommit

decommit

c — 关闭

close_head

f — fanout

fanout

p — 部分 fanout

partial_fanout

r — 恢复存款

recover_deposit

主标签页

head_status, head_utxos

资金标签页

l1_funds

事件历史标签页

recent_events

—

protocol_parameters, pending_deposits

与 TUI 一样,这不会暴露 Contest、SafeClose 或 SideLoadSnapshot。这些是协议对特定链上条件的响应,在这种条件下只有一个操作是正确的,并且时间至关重要;它们属于带有警报的确定性工具,而不是在提示符后面。

更进一步的地方:

  • 诊断。 node_logs 和 explain_error 在 TUI 中没有对应功能。这是最大的实际收益——一个卡住的头从“TUI 说它失败了”变成了解码的终止代码和匹配的日志行。

  • 跨节点。 TUI 会话只连接一个节点。这里每个工具都接受 node 参数,因此你可以询问 alice、bob 和 carol 各自对同一个头的看法——这是发现落后节点最快的方法。

  • L1 和 L2 统一。 l1_funds 直接查询链,因此“那个 decommit 真的生效了吗?”是一个问题,而不是切换到 cardano-cli 的上下文切换。

  • 防护措施。 低于最小 UTXO 的输出和多 UTXO 存款在构建时被拒绝,因为两者都会在之后悄无声息地卡住 fanout。

  • 组合操作。 多步操作在一个请求中完成:"关闭头,等待争议期结束,执行 fanout,然后显示每个人最终的 L1 余额" 是一个单一的请求。

TUI 仍然胜出之处: 它是一个实时仪表盘。MCP 是请求/响应模式,因此你得到的是快照而不是持续更新的视图——要随时间观察一个头,请保持 TUI 打开。对于重复性工作,按键也胜过模型往返,而且 TUI 的 UTxO 选择器是可视化的,而这里你需要先列出再选择。


配置

一切都在 config.py 中,可以通过环境变量覆盖:

设置

默认值

含义

NODES

4001, 4002, 4003 位于 localhost

节点索引 → WS/HTTP 端点和参与方名称

HYDRA_DEMO_DIR

/home/dev/claudecode/hydra/demo

演示 devnet:docker compose 项目和凭据

HYDRA_REPO

/home/dev/claudecode/hydra

Hydra 仓库,用于解码终止代码

NETWORK_MAGIC

42

Devnet 魔术值

MIN_OUTPUT_LOVELACE

1_000_000

头内输出的拒绝阈值

签名密钥是演示的 {alice,bob,carol}-funds 密钥对。容器侧路径用于 cardano-cli(在 L1 上签名和提交);相同密钥的主机侧副本由 PyCardano 读取以进行头内交易。指向具有相同布局的不同部署是配置更改;指向不同的 拓扑 则不是(参见 限制)。


测试

.venv/bin/python test_ops.py           # offline — no devnet needed
.venv/bin/python test_ops_devnet.py    # live — needs a devnet with the head Idle

test_ops.py 断言每个改变状态的工具都返回 requires_confirmation,并且没有 confirm=True 就不会到达任何客户端(如果命令逃逸了门控,存根客户端会抛出异常),请求负载与 API 匹配,最小 UTXO 拒绝和 UTxO 验证触发,错误表解析和解码,以及所有 16 个工具都注册到服务器。

test_ops_devnet.py 驱动一个真实的头经历整个生命周期,并在每个阶段断言可观察性:门控检查 → init → commit → 六个读取工具 → 两笔头内支付 → decommit,通过资金出现在 L1 上而头保持打开来验证 → close → fanout → 回到 Idle → 日志和错误解码。如果 devnet 未启动或头不是 Idle 状态,它会跳过并给出明确消息。


操作说明

在它们让你损失一个头之前值得了解的事情。

H39 / FanoutUTxOHashMismatch 会永久卡住一个头。 Fanout 无法重现关闭的头所承诺的内容,因此头无法结算,资金被卡住。两个原因,都可预防并且在此处都得到了防范:2.3.0 上的多 UTXO 存款,以及任何低于 L1 最小 UTXO 的头输出。询问 explain_error("H39") 获取详细信息。

头将最小 UTXO 归零;L1 不会。 0.5 ADA 的输出在 L2 上交易愉快,然后无法在 L1 上重新创建。send_tx 出于此原因拒绝低于 1 ADA 的输出。

存款在存款期后被吸收,而非立即。commit_funds 会等待并报告如果吸收未发生;从未落地的存款会显示在 pending_deposits 中,并通过 recover_deposit 收回。

关闭是单方面的并影响所有人。 任何参与方都可以关闭,然后整个头必须结算。门控主要为此存在。

一个头需要每个参与方在线。 如果支付挂起,在怀疑工具之前先检查 docker compose ps。

演示 devnet 的区块生产者可能会在长时间空闲后停滞 —— cardano-cli query tip 返回相同的 slot 两次,一切都挂起。./reset_devnet.sh 修复它;devnet 按设计是可丢弃的。

无法解析的 WebSocket 输入不会返回 tag。 节点无法识别的命令会以裸的 {"input", "reason"} 对象返回,而不是带标签的事件——如果你直接对 API 编写脚本,值得了解,因为等待带标签事件的客户端会挂起。此处的客户端处理了这种情况。


限制

partial_fanout 需要节点版本高于 2.3.0。 该命令在发布之后(hydra PR #2750,commit a271cced2),固定的演示镜像拒绝它——节点列出它知道的命令,而 PartialFanout 不在其中。该工具精确检测到这一点并报告版本差距。代码路径已准备好用于从 master 构建的节点,但仅被测试到该拒绝为止。

费用为零。 tx_builder.py 硬编码了 fee=0,这对于演示的协议参数是正确的,但在其他地方都是错误的。在指向 preview/preprod 或主网之前,需要真实的费用估算和硬币选择。

Devnet 形状的假设。 三个参与方,已知的密钥名称,密钥在 cardano-node 容器内部可读,docker compose 可用于 L1 查询和日志。头 API 层是通用的;L1 辅助工具却不是。

仅 ADA。 交易构建处理纯 lovelace UTXO——没有原生代币、脚本、数据或铸造。

recover_deposit 未针对真正卡住的存款进行测试。 它遵循 API,但演示 devnet 吸收存款太可靠,无法按需产生一个。

无认证。 任何能到达服务器的人都可以操作头。这对于本地操作员工具是合适的,但对于任何暴露的服务则不适用。


扩展

添加工具: 在相关的 tools/ 模块中编写一个普通函数,返回 ok() / err() / needs_confirmation(),然后在 server.py 中注册一个薄包装器。工具模块不导入 FastMCP,因此可以直接从测试中调用——这正是两个测试套件驱动它们的方式。

添加协议命令: 使用 _command_and_wait(command, ok_tags) 向 HydraClient 添加方法,该方法发送并等待结果事件,同时将 CommandFailed 和未标记的解析拒绝视为错误。

定位到其他部署: 将 NODES 指向端点,将 HYDRA_DEMO_DIR / HYDRA_REPO 指向正确的路径。超出演示三方布局的任何内容都意味着需要重新审视 cardano.py 和 tx_builder.py 中的密钥处理以及费用。

进一步阅读

Hydra 文档 了解协议本身,以及 RUNBOOK.md 了解使用这些工具操作头的引导之旅。

Related MCP Connectors

Related MCP Servers