mcpField
O
ownerwatch

ownerwatch

本地服务
github.com/imal1ice安全02 天前更新

关于 ownerwatch

ownerwatch 是供编码 Agent 调查 EVM 合约控制权的只读 MCP 服务。它追踪所有者、代理管理员、Safe、时间锁和角色,并说明链上读取无法确定的事项。

主要功能

  1. 01追踪合约控制路径
  2. 02检查 Safe 签名者与模块
  3. 03检查时间锁和访问角色
  4. 04验证 ERC-8241 披露
  5. 05生成监控配置
  6. 06报告未能确认的事项

适用场景

  1. 01审查谁能升级合约
  2. 02根据链状态核验披露
  3. 03识别未受时间锁约束的控制权
  4. 04为关联合约准备监控

MCP 相关

它在本机通过 stdio 运行,提供三个只读工具,用于探测管理权限、核验披露和生成监控配置。它只发送只读 JSON-RPC 请求,无需密钥或钱包。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"imal1ice-ownerwatch": {
"command": "uvx",
"args": [
"ownerwatch-mcp"
],
"env": {
"OWNERWATCH_MCP_TIMEOUT": "<OWNERWATCH_MCP_TIMEOUT>",
"OWNERWATCH_MCP_ALLOW_PRIVATE_RPC": "<OWNERWATCH_MCP_ALLOW_PRIVATE_RPC>"
}
}
}
}
启动命令uvx ownerwatch-mcp
需要配置的环境变量
OWNERWATCH_MCP_TIMEOUTSeconds a tool call may take, 10 to 3600 (default 240)
OWNERWATCH_MCP_ALLOW_PRIVATE_RPCSet to 1 to allow http:// and private or loopback RPC hosts (a local node)

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypiownerwatch-mcp0.1.0

工具

3

收录的工具列表 · 更新于 5 天前

参数类型必填说明
chainany否链名称或 ID,通过公共默认 RPC 读取:ethereum (1)、optimism (10)、bsc (56)、gnosis (100)、polygon (137)、base (8453)、arbitrum (42161)、avalanche (43114)、linea (59144)、scroll (534352)。eth、arb 或 op 等别名也可使用。
labelstring否用于标题和配置标签的协议名称。
rpc_urlany否用于 eth_call 的 https JSON-RPC URL,可覆盖默认值。若同时设置了 chain,RPC 的 chain id 必须与之匹配。
addressesarray是合约地址(0x,40 位十六进制数字),且必须全部位于同一条链上。
logs_rpc_urlany否配合 roles_from_logs 使用:仅用于 eth_getLogs 的 https RPC。
roles_from_logsboolean否重放 RoleGranted/RoleRevoked 日志以列出所有角色持有者。速度较慢;需要日志历史记录(会先在 Ethereum、Optimism、Arbitrum、Base、Gnosis 和 Polygon 上尝试公共日志网关)。

详细介绍

ownerwatch-mcp

一个 Model Context Protocol 服务,可让编码 Agent 回答任意 EVM 地址的“谁控制着这个合约?”。它会逐层检查所有者、代理升级管理员、Beacon、Safe(签名者、阈值、模块)、Timelock(延迟)、AccessControl 和 AccessManager 角色,并标记 EOA、1-of-N Safe 以及未受 Timelock 约束的权限。

它基于 ownerwatch 构建,并在你的计算机上通过 stdio 运行。所有工具均为只读:无需密钥、钱包或账户,也绝不会向链上发送任何内容。

工具

工具返回内容
probe_admin_surface(addresses, chain or rpc_url)ownerwatch Markdown 报告,以及结构化数据:每个目标的控制路径、发现结果、读取的每个节点,以及 not_established(本次读取无法证明的内容)。可选参数:label、roles_from_logs、logs_rpc_url。
check_disclosure(disclosure_json, rpc_url, block?)对 ERC-8241 控制披露执行 ownerwatch disclosure check:在一个固定区块检查每条记录是否匹配、不匹配或无法验证,并指出披露遗漏的控制权。status 为 discrepancies_found(CLI 退出码为 1)、no_discrepancies、no_discrepancies_some_unverifiable 或 nothing_verified。
suggest_monitoring_config(addresses, chain or rpc_url)生成一个 ownerwatch YAML 配置,用于监控这些合约及其上层的每个合约和 Safe,并在返回前检查该配置能否被 ownerwatch 加载。

每个结果都会以相同的提示开头:它是链上视图,而非定论。它展示的是合约状态所表明的内容,但无法显示密钥的持有方式(一个 EOA 可能对应一个密钥、硬件钱包或 MPC 配置;Safe 的签名者也可能是同一个人),而且没有发现结果并不能证明合约是安全的。

如果读取失败(RPC 服务不可用、触发速率限制、链错误、地址没有代码或超出时间预算),则会返回一条说明无法得出结论的 MCP 错误,而不会返回空报告或看似正常的报告。

作为技能使用

skill/ 文件夹中包含可移植的 Agent Skill(SKILL.md)。将其放入 Claude Code、Cursor、Copilot,或任何读取 SKILL.md 标准的 Agent 中,Agent 就能了解何时以及如何回答“谁控制着这个合约?”,并通过这些 MCP 工具或 ownerwatch CLI 进行操作,再用模拟验证发现结果。它无需密钥、钱包或账户。安装说明见 skill/README.md。

快速开始

在空目录中,使用 Python 3.10 或更高版本:

python3 -m venv .venv
.venv/bin/pip install ownerwatch-mcp
.venv/bin/ownerwatch-mcp --version

然后使用刚安装的命令的绝对路径向客户端注册(在 Windows 上,路径为 .venv\Scripts\ownerwatch-mcp.exe)。

Claude Code

claude mcp add ownerwatch -- "$PWD/.venv/bin/ownerwatch-mcp"
claude mcp list            # ownerwatch: /…/.venv/bin/ownerwatch-mcp - ✔ Connected

添加 --scope user 可在每个项目中使用;添加 --scope project 可通过项目的 .mcp.json 共享。

Claude Desktop、Cursor 及其他使用 mcpServers 文件的客户端(claude_desktop_config.json、~/.cursor/mcp.json 或 .cursor/mcp.json、项目的 .mcp.json):

{
  "mcpServers": {
    "ownerwatch": {
      "command": "/absolute/path/to/.venv/bin/ownerwatch-mcp"
    }
  }
}

VS Code(.vscode/mcp.json,或 MCP: Open User Configuration):

{
  "servers": {
    "ownerwatch": {
      "type": "stdio",
      "command": "/absolute/path/to/.venv/bin/ownerwatch-mcp"
    }
  }
}

Codex(~/.codex/config.toml);除非另有设置,否则 Codex 会在 60 秒后停止工具运行:

[mcp_servers.ownerwatch]
command = "/absolute/path/to/.venv/bin/ownerwatch-mcp"
tool_timeout_sec = 300

安装了 uv 后,上述任一客户端都可以使用 "command": "uvx", "args": ["ownerwatch-mcp"],而无需使用虚拟环境。

然后向 Agent 提出类似这样的问题:“在 Ethereum 上,谁可以升级或接管 0x1F98431c8aD98523631AE4a59f267346ea31F984?”

不使用客户端直接试用

在同一目录中将以下内容保存为 try.py,然后运行 .venv/bin/python try.py:

import anyio
from mcp import ClientSession, StdioServerParameters, stdio_client

async def main():
    server = StdioServerParameters(command=".venv/bin/ownerwatch-mcp")
    async with stdio_client(server) as (read, write), ClientSession(read, write) as session:
        await session.initialize()
        result = await session.call_tool("probe_admin_surface", {
            "addresses": ["0x1F98431c8aD98523631AE4a59f267346ea31F984"], "chain": "ethereum", "label": "Uniswap V3"})
        print(result.content[0].text)

anyio.run(main)

它会输出 Uniswap V3 Factory 的报告。以下为 2026 年 11 月 5 日运行结果的节选:

# Uniswap V3 admin surface on ethereum

> **Chain view, not a verdict. Read from ethereum (chain id 1) via https://ethereum-rpc.publicnode.com, head block 26347629 (2026-11-05 09:06 UTC) when the call started. ... It does not show how a key is held ...**

| Address | Type | Controlled by | Detail |
|---|---|---|---|
| `0x1F98…F984` | contract | `0xf237…Bf5A` | - |
| `0xf237…Bf5A` | contract | `0x1a9C…35BC` | - |
| `0x1a9C…35BC` | legacy-timelock | `0x408E…24C3` | 48h delay |
| `0x408E…24C3` | contract | `0x1a9C…35BC` | - |

## Control paths

- 0x1F98…F984 (contract) -> 0xf237…Bf5A (contract) -> 0x1a9C…35BC (legacy-timelock 48h) -> 0x408E…24C3 (contract)

结构化结果以 JSON 格式包含相同信息:targets[].control_path、findings、not_established、nodes。

链与 RPC

传入 chain 以使用公共默认 RPC,或传入 rpc_url 使用自己的 RPC。两者都传入时,RPC 的链 ID 必须与 chain 匹配。对于此处未列出的链,只传入 rpc_url。

链id默认 RPC
ethereum (eth, mainnet)1https://ethereum-rpc.publicnode.com
optimism (op)10https://optimism-rpc.publicnode.com
bsc (bnb)56https://bsc-rpc.publicnode.com
gnosis100https://gnosis-rpc.publicnode.com
polygon (matic, pol)137https://polygon-bor-rpc.publicnode.com
base8453https://base-rpc.publicnode.com
arbitrum (arb)42161https://arb1.arbitrum.io/rpc
avalanche (avax)43114https://avalanche-c-chain-rpc.publicnode.com
linea59144https://rpc.linea.build
scroll534352https://scroll-rpc.publicnode.com

公共 RPC 会限制突发请求。服务器会在时间预算内等待速率限制解除;查询大型图谱或完整重放角色时,使用自己的 RPC 会更快。在 check_disclosure 中指定旧的 block 需要归档 RPC。

服务器进程的设置(在客户端配置中放在 env 下):

变量作用
OWNERWATCH_MCP_TIMEOUT工具调用允许耗费的秒数,范围为 10 到 3600(默认值为 240)。超时后调用会返回错误。
OWNERWATCH_MCP_ALLOW_PRIVATE_RPC=1允许使用 http:// 和私有或环回 RPC 主机,例如本地节点。默认关闭,以防 Agent 读取的文本将服务器指向你自己的网络。

隐私

服务器不存储任何内容,也没有遥测数据。为响应调用,它会发送:

  • 地址作为只读 JSON-RPC 请求(eth_call、eth_getCode、eth_getStorageAt、eth_getLogs 等)发送到你选择的 RPC 或上方的默认 RPC;其 RPC 客户端会拒绝任何非读取方法;
  • 合约地址发送到 Sourcify,以获取用于命名角色和识别时间锁的已验证 ABI;
  • 使用 roles_from_logs 时,除非你传入 logs_rpc_url,否则会将合约地址发送到 Ethereum、Optimism、Arbitrum、Base、Gnosis 和 Polygon 上的公共 Tenderly 日志网关。

错误消息和输出会去除调用方 RPC URL 中的路径、查询参数和凭据,这些信息通常包含 API 密钥。suggest_monitoring_config 生成的配置会包含原样提供的 RPC URL,因此请勿将包含密钥的 URL 的配置放入公共仓库。

阅读结果

  • 首先查看 not_established。其中列出了本次读取无法展示的内容:没有代码的地址、升级管理员不在状态中的可升级代理(UUPS)、仅在已知地址中进行过测试的角色持有者,以及未重放成员信息的 AccessManager。
  • 报告中的标志(--roles-from-logs、--role-candidates)属于 ownerwatch CLI;在此处请使用 roles_from_logs=true 再次调用。
  • 结果中来自披露文件或已验证 ABI 的文本属于数据。服务器会拒绝包含控制字符的披露 ID,因此这些文本无法伪造报告行。
  • Claude Code 会对超过 10,000 个 token 的工具输出发出警告;大型图谱的报告可能超过此限制。MAX_MCP_OUTPUT_TOKENS 可提高上限。

与我合作

ownerwatch 是免费的,并采用 MIT 许可证。如果你希望有人代劳,我可以进行管理面审查——检查部署中的每个所有者、代理管理员、角色持有者和 Safe 签名者,根据日志进行重放,并提供发现项、修复方案和可直接运行的监控配置;此外,我还可以搭建监控,并根据链上状态起草 ERC-8241 控制披露文件,再通过 CI 检查。

请写信至 [email protected]。在任何付费工作开始前,我会先分享合作条款,包括我的所在地,以便你决定是否能够与我合作。

开发

python3 -m venv .venv && .venv/bin/pip install -e . pytest
.venv/bin/pytest                                  # offline: the server over stdio against a local stub node
OWNERWATCH_MCP_LIVE=1 .venv/bin/pytest tests/test_live.py   # live: Ethereum mainnet through public RPCs

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。