mcpField
A
agent-custody

agent-custody

本地服务
agent-custody.dev安全82 天前更新

关于 agent-custody

agent-custody 将 Agent 工具调用记录为签名收据,并可通过 MCP 网关执行授权和 Cedar 策略检查。它适用于需要验证 Agent 操作记录的团队;SDK 也可在受支持的 Agent 框架内记录调用。

主要功能

  1. 01网关执行授权与策略检查
  2. 02为允许和拒绝的调用签发收据
  3. 03Merkle 透明日志
  4. 04使用公钥离线验证
  5. 05面向 Agent 框架的 SDK 拦截器
  6. 06针对具体操作的人类审批签名

适用场景

  1. 01用签名授权约束重要的 Agent 调用
  2. 02保存可验证的工具调用拒绝记录
  3. 03审核收据并测试策略变更

MCP 相关

网关作为 Agent 与上游 MCP 服务之间的代理,在转发工具调用前检查授权和策略,并为允许或拒绝的调用生成签名收据。

配置

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

mcp.json · 通用配置
{
"mcpServers": {
"svayatta-agent-custody": {
"command": "npx",
"args": [
"-y",
"@agent-custody/receipts"
]
}
}
}
启动命令npx -y @agent-custody/receipts

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

安装包
npm@agent-custody/receipts0.12.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

agent-custody

AI Agent 的监管链:每次工具调用都有一份签名回执,仅凭公钥即可验证。每项检查能证明什么,以及不能证明什么,见下表。

两个生成方,一种回执格式,一个验证器。

  • 网关是 Agent 与其能够影响的系统之间的 MCP 代理:包括 MCP 服务、作为工具描述的 REST API,以及它通过 A2A 委托的其他 Agent。对于每次工具调用,无论允许还是拒绝,它都会检查由人类主体签名的委托授权,以及对确切参数的任何必要人工审批;通过自行调用上游来收集策略所需的事实;评估一项默认拒绝的 Cedar 策略;仅在允许时转发调用;并生成签名回执,将其追加到 Merkle 透明日志中。
  • SDK是 Agent 自身进程中的拦截器,挂接到框架的工具调用回调:Claude Code、Claude Agent SDK、OpenAI Agents SDK、Vercel AI SDK、OpenClaw、DeepSeek Harness、Hermes、Google ADK、LangChain,或任何你封装的函数。它能接触网关无法看到的一切,并生成相同的回执,标记为自报告。

任何持有公钥的人都可以离线验证回执。Agent 不受信任。它周围的层是可信的,而回执会准确说明这种信任的范围,从签发者是谁开始。回执文件如何载明一次调用的信息,由每项工具自行决定:字段可以作为承诺予以隐藏,也可以使用你的密钥加密;验证器会在获知这些值后重新计算摘要。

  • 参考文档:每个函数、端点、MCP 工具和命令及其请求与响应
  • 教程:34 个可运行示例,每个示例对应代码的一个方面,并由测试套件全部执行
  • 金融操作:持久化操作 ID、提供方幂等键、原子每日预算预留,以及用于处理结果不确定情况的对账回执
  • 网关控制台:供网关运营者使用的管理服务,包含配置、工具、每份回执及其判定、可在保存前重放已记录调用的策略编辑器、授权撤销和审计跟踪
  • 配置网关:哪些人可以使用哪些工具,以及工具所在位置;包含一个涉及两个 Agent 的完整示例、Docker 镜像,以及用于在启动前验证配置的 agent-custody check
  • 使用指南:网关设置、接入 Claude Desktop、Claude Code 或自有 Agent 循环,以及将网关作为 A2A Agent,让相互委派任务的 Agent 之间进行交互;私有证据:回执文件记录的每次调用内容;以及使用状态包的审查控制台搜索回执并导出离线案例
  • Codex 实时恢复演示:真实支持 Agent、错误的 CRM 记忆、由操作员审查的补偿操作,以及经验证的回执;可选择运行的实时演示,并由离线回归测试覆盖
  • 人工审批:针对确切操作的短期签名、双人审批门槛、单次使用强制执行,以及离线验证
  • 拦截器 SDK:Claude Code 钩子、Claude Agent SDK、适用于 OpenAI Agents SDK、Vercel AI SDK、LangChain、OpenClaw 和 DeepSeek Harness 的适配器、通过 Python 软件包提供的 Hermes Agent 插件和 Google ADK 回调,以及在其他任何工具中封装工具函数
  • 编写策略:工具调用如何转化为 Cedar 请求,附带经过测试的示例;以及使用 policy simulate 根据拟定策略重放历史记录
  • 验证回执:每项检查的含义,以及已验证回执能证明和不能证明的内容
  • 证据满足哪些要求:将回执、证据包和证书对应到 SOC 2、ISO 27001、《欧盟 AI 法案》和 UK GDPR,并说明它们都不作出哪些声明
  • 将日志锚定到链上:从公开地址将日志检查点提交到 Ethereum 主网,或安装选择的其他 EVM 链;说明这能证明和不能证明什么、如何运行锚定,以及如何使用 log-check 检查链上记录
  • 支付授权和 Agent 身份:使授权和回执符合 Google 的 AP2 mandates、Visa 的 Trusted Agent Protocol 和 Mastercard Agent Pay 的设计;尚未实现
  • 威胁模型:所有可能伪造回执的参与方、其手段、阻止方法,以及这属于证据本身还是部署特性;并说明未防御的内容
  • 与 IETF SCITT 保持一致:将回执作为 SCITT 签名声明、将日志作为透明度服务、将我们的 RFC 9162 证明作为 COSE Receipts(RFC 9942);说明差异及各项选择、Agent 操作配置文件,以及迈向工作组的步骤

快速入门

npm install @agent-custody/receipts        # or bun add, pnpm add

已发布到 npm,包名为 @agent-custody/receipts:编译后的 JavaScript 和类型声明,需要 Node 22 或更高版本,采用 Apache-2.0 许可证。

无需网关,即可在自己的 Agent 内记录回执。生成签名密钥,为其指定配置,然后包装 Agent 调用的函数:

npx agent-custody keygen --dir keys --name app
{ "agentId": "billing-bot", "identity": { "keyFile": "keys/app.key" }, "receiptsDir": "receipts", "logFile": "log.jsonl" }
import { createSdkIssuer, loadSdkConfig, loadPublicKey, verifyBundle } from "@agent-custody/receipts";

const issuer = createSdkIssuer(loadSdkConfig("./sdk.json"));
const refund = issuer.wrap("stripe.refund", async (args: { amount: number }) => stripe.refund(args));
await refund({ amount: 5000 });                 // one signed receipt in receipts/, one leaf in log.jsonl

const bundle = JSON.parse(readFileSync("receipts/<id>.json", "utf8"));
verifyBundle(bundle, { issuerKeys: [loadPublicKey("keys/app.pub")], logFile: "log.jsonl" }).ok;   // true

框架钩子和适配器包括 Claude Code、OpenAI Agents SDK、Vercel AI SDK、LangChain、OpenClaw、DeepSeek Harness、Hermes 和 Google ADK,详见 docs/sdk.md。如需强制执行而非仅记录,可在 Agent 与其工具之间部署网关:npx agent-custody gateway --config gateway.json,配置方法见 docs/usage.md;如需在 Agent 与其通过 A2A 委派的 Agent 之间部署网关,--a2a 可将网关作为 A2A Agent 提供服务。该网关已在 MCP Registry 中登记,名称为 io.github.svayatta/agent-custody;其条目见 server.json,每次 npm 发布后,可通过 Actions 选项卡运行 mcp-registry 工作流来发布(使用 GitHub OIDC,无需令牌)。

工作原理

架构指南补充介绍了客户侧与托管侧的划分、数据模型,以及入驻、撤销和验证的时序图。

flowchart LR
    P["Principal<br/>(human or org, holds a signing key)"]
    A["Agent host<br/>Claude Desktop, Claude Code,<br/>LangGraph, custom loop"]
    S["SDK interceptor<br/>inside the agent process:<br/>hooks or wrapped tools"]
    G["agent-custody gateway<br/>scope check → fact lookups → Cedar policy"]
    U["Upstream MCP server<br/>Stripe, database, GitHub, ..."]
    R[("receipt bundles<br/>receipts/*.json")]
    L[("Merkle log<br/>local file, or a remote log<br/>run by someone else")]
    V["Verifier<br/>auditor, counterparty, CI job"]
    O["Observability<br/>OTel, Splunk, LangSmith, Arize"]

    P -- "signed delegation grant" --> G
    A -- "MCP tools/call" --> G
    G -- "only on allow" --> U
    U -- "result" --> G
    G -- "result + receipt id, or denial + receipt id" --> A
    G -- "signed receipt (issuer: gateway)" --> R
    G -- "leaf hash" --> L
    A -. "in-process tool calls" .-> S
    S -- "signed receipt (issuer: sdk)" --> R
    S -- "leaf hash" --> L
    R --> V
    L -. "copy of the log (optional)" .-> V
    P -. "public key" .-> V
    G -. "public key" .-> V
    A -. "traces (unchanged)" .-> O
    G -. "one span per receipt, OTLP" .-> O
    S -. "one span per receipt, OTLP" .-> O

三方持有密钥。主体签署授权,说明哪个 Agent 可以使用哪些工具,以及授权何时到期。签发方(网关或 SDK)签署每条回执和每个树头。验证方只持有公钥,无需访问签发方、Agent 或上游系统。

两种生成方,一种回执

网关SDK
运行位置Agent 与工具之间的独立进程Agent 进程内部
可见内容MCP 工具调用框架钩子所能提供的内容
强制执行可以,已拒绝的调用不会到达上游仅当钩子可以阻止调用时才行
字段来源attested 和 observed全部为 claimed
验证方能获知什么Agent 无法跳过或伪造此记录Agent 进程报告了此记录,且此后未被更改
安装方式在宿主的 MCP 配置中添加一行添加钩子条目或包装函数

每条回执都会标明其签发方,验证方会先说明该签发方类型的可信程度。如果仪表板上满是 sdk 记录,就应该将关键调用路由至网关。

支持的宿主和框架
宿主或框架提供方强制执行 + 记录仅记录测试依据
任意 MCP 宿主:Claude Desktop、Claude Code、Cursor、自定义宿主网关是真实的 MCP 客户端,以及通过 stdio 连接的上游
Codex CLI(无头模式)网关是手动运行 codex exec 和离线 MCP 恢复检查;指南
Claude CodeSDKhook 命令,PreToolUse 拒绝PostToolUse通过 stdin 验证文档所述的钩子契约
Claude Agent SDKSDKclaudeAgentHooks同左同一个处理器
OpenAI Agents SDK (JS)SDKwrapToolsobserveRunner使用脚本化模型运行真实的 Runner
Vercel AI SDKSDKwrapTools使用 SDK 的模拟模型运行真实的 generateText 循环
LangChain / LangGraph (JS)SDKtool(issuer.wrap(fn))ReceiptCallbackHandler调用真实的 StructuredTool
OpenClawSDKregisterOpenClaw / openclawHooks:before_tool_call 拒绝after_tool_call在插件进程中验证 OpenClaw 文档所述的钩子契约
DeepSeek HarnessSDK此模块即插件:tools/pre-execute 拒绝tools/post-execute按照 harness 的调用方式驱动工具瀑布流类型
Hermes Agentsidecar + Python 包agent_custody.hermes:pre_tool_call 阻止post_tool_call针对运行中的 sidecar 验证文档所述的钩子契约
Google ADKsidecar + Python 包adk_callbacks:before_tool_callback 跳过after_tool_callback使用真实的包和 ADK 的 runner,配合脚本化模型
其他任何框架SDKissuer.wrap(name, fn)issuer.record普通函数
Python:LangChain、OpenAI Agents SDK、CrewAI、Claude Agent SDKsidecar + Python 包wrap_tools(OpenAI Agents、CrewAI)、claude_hook PreToolUse 拒绝、client.wrapReceiptCallbackHandler使用真实的 Python 包,收据由此验证器检查
Go、Java、Rust,以及任何支持 HTTP 的语言sidecar决策后记录记录语言示例,每个示例都针对运行中的 sidecar 执行
任意语言中的任意 MCP 宿主:Claude Agent SDK Python、OpenAI Agents Python网关是网关本身是 MCP 服务;usage.md
任意 REST API,作为 Agent 通过网关访问的工具网关,rest 上游是模拟 HTTP API;usage.md
通过 A2A 接入的任意 Agent(Google ADK RemoteA2aAgent,或任意 A2A 客户端)网关,a2a 上游和 gateway --a2a是使用两种线格式的模拟 A2A Agent;Python 测试套件中的 ADK 自带 to_a2a 和 RemoteA2aAgent;usage.md

框架包、pg 和 @node-saml/node-saml(用于登录门户的 SAML)都是可选的对等依赖项。每个适配器仅从其自身的包中导入内容。

一次工具调用,从头到尾

sequenceDiagram
    participant Agent
    participant Gateway
    participant Upstream as Upstream MCP server
    participant Log as Merkle log

    Agent->>Gateway: tools/call stripe.refund {customer_id, amount}
    Gateway->>Gateway: tool in signed grant's scopes?
    Gateway->>Upstream: tools/call customer.lookup {customer_id}
    Upstream-->>Gateway: {verified: true}
    Note over Gateway: facts.customer = that result, labelled "observed"
    Gateway->>Gateway: Cedar: permit if amount ≤ limit and facts.customer.verified
    alt allow
        Gateway->>Upstream: tools/call stripe.refund {customer_id, amount}
        Upstream-->>Gateway: {refund_id, status}
    else deny
        Note over Gateway: no upstream call is made
    end
    Gateway->>Gateway: build in-toto statement, sign (DSSE, Ed25519)
    Gateway->>Log: append canonical envelope
    Log-->>Gateway: leaf index, inclusion proof, root
    Gateway->>Gateway: sign tree head, write bundle to receipts/
    Gateway-->>Agent: result or denial, _meta["agent-custody/receipt"] = id

被拒绝的调用也会生成收据。“Agent 曾尝试支付资金,但遭到拒绝”是值得留存的证据。

收据包的组成

flowchart TB
    B["receipt bundle (one JSON file)"]
    B --> E["envelope: DSSE, signed by gateway key"]
    B --> T["treeHead: DSSE, signed by gateway key<br/>{treeSize, rootHash, timestamp}"]
    B --> I["inclusion: {leafIndex, treeSize, hashes[]}"]
    E --> S["in-toto Statement v1"]
    S --> SU["subject: tool-call:&lt;tool&gt;:&lt;id&gt;<br/>digest = sha256(args)"]
    S --> PR["predicate"]
    PR --> P0["issuer: gateway or sdk, keyid, framework"]
    PR --> P1["principal, agent, delegation<br/><b>attested</b>: signed by principal key (gateway)"]
    PR --> P2["tool, facts, policy decision, execution<br/><b>observed</b>: gateway obtained it (gateway)"]
    PR --> P3["args, model id, session<br/><b>claimed</b>: agent-supplied, unchecked (both)<br/>every field, when issued by the sdk"]

每个字段都带有来源标签。这是最重要的设计决策,也是验证器读取的内容。

来源含义当前示例
attested由非签发方的密钥签名主体 ID、Agent ID、委托授权(网关收据)
observed签发方在 Agent 无法控制的情况下,以确定性方式获取上游工具结果、事实查询、策略决策、执行状态(网关收据)
claimed源自 Agent、模型或 Agent 自身进程,未经独立核验工具参数、模型 ID、会话 ID,以及 SDK 收据中的所有字段

快速开始

bun install                              # from the repository root, once for the workspace
cd packages/receipts
node scripts/demo.ts                     # gateway: keys, grant, policy, four tool calls, verification, a tampering attempt; then the SDK wrapping the same tool
node examples/01-keys-and-signing.ts     # first of thirty-four step-by-step examples, see docs/tutorials.md
bun run test                             # this package; `bun run test` at the root runs every package

所有内容都可直接在 Node 22 或更高版本上运行。无需构建步骤,也无需在命令行中使用 npx 或 tsx。

演示会将所有内容(包括两个生产方生成的收据)放在 demo-out/ 中。手动验证收据:

node src/cli.ts verify demo-out/receipts/<id>.json \
  --issuer-key demo-out/keys/gateway.pub \
  --principal-key demo-out/keys/principal.pub \
  --log demo-out/log.jsonl

退出代码为 0 表示所有检查均已通过。报告含义请参阅 docs/verification.md。

无需网关,即可从自己的 Agent 代码签发收据:

import { loadSdkConfig } from "./src/config.ts";
import { createSdkIssuer } from "./src/sdk/index.ts";

const issuer = createSdkIssuer(loadSdkConfig("./sdk.json"));
const refund = issuer.wrap("stripe.refund", async (args: { amount: number }) => stripe.refund(args));

docs/sdk.md 包含配置文件和各框架的适配器。

收据能证明什么,不能证明什么

声明验证者对手机制状态
收据签发后未被篡改任何人后续编辑者对规范化 JSON 进行 DSSE 签名已完成
收据按顺序签发,且没有被删除持有日志副本的审计员篡改历史记录的操作员Merkle 包含性证明 + 重新计算根哈希已完成
主体确实将此范围委托给了此 Agent持有主体密钥的任何人操作员、Agent由主体密钥签名的授权;检查范围和有效时间窗口已完成
策略决策依据的是这一确切策略任何人替换策略的操作员收据中策略文本的 sha256已完成
策略所依据的事实并非由 Agent 声明阅读收据的任何人Agent由网关获取事实,记录各自的摘要,并标记为 observed已完成
副作用发生前已提交到日志持有日志副本的任何人操作员、日志中断对于 precommit 中的工具,或 strict 下的所有工具,会先记录已签名的授权,再将其及其叶子位置嵌入收据,并检查其是否早于收据已完成
上游系统确实执行了操作持有上游密钥或提供商密钥的任何人操作员上游为收据签署其结果,或嵌入并验证提供商自己的交付签名(Stripe、GitHub)已完成,适用于会签名的上游
操作员本身无法伪造收据监管机构、交易对手操作员需要采用经证明构建的 TEE 网关(#179),并依靠提供商自己的记录来发现网关之外的操作(#178)尚未完成
哪个模型生成了调用任何人操作员没有托管式提供商会对模型身份签名目前无法做到,标记为 claimed
SDK 收据是否反映了工具实际执行的操作任何人Agent 自身进程无;SDK 与 Agent 共享进程有意不作此声明;签发者类型 sdk 表明了这一点

如果供应商告诉你,他们的收据能证明的内容超过前五行,请问他们是哪把密钥签署了收据。

目录结构

src/config.ts      gateway and SDK config schemas, path resolution
src/crypto.ts      canonical JSON, sha256, Ed25519 keys, DSSE sign/verify
src/log.ts         Merkle log: append, root, inclusion and consistency proofs, verify, JSONL persistence
src/log-check.ts   the outside monitor: verifies the head, checkpoints, witness, and anchor of a running log
src/portal.ts      the tenant portal: register, first key, usage against plan, keys, Stripe billing, export, member roles, TOTP MFA, single sign-on, on the log's Postgres
src/sso.ts         enterprise sign-in for the portal: OIDC (code and PKCE, id_token checked against the IdP's key set) and SAML 2.0 (signed assertions only, through @node-saml/node-saml)
src/log-export.ts  a tenant's export of their own log, self-checked, as a log file the verifier reads
src/witness.ts     the witness: countersigns the log's checkpoints from another operator's machine, or refuses with an alarm
src/anchor.ts      the anchor: commits a Merkle root over new checkpoints to a chain, or refuses with an alarm; checks a record against JSON-RPC
src/anchor-evm.ts  the anchor's transactions on Ethereum or another EVM chain, through the optional dependency viem
src/signer.ts      the signer: the log's key in its own process, the key document verifiers fetch
src/checkpoints.ts signed heads published on a schedule, to files and to Postgres
src/log-store.ts   the log server's backends: the file, and Postgres with tenants, hashed tokens, one writer per tenant, rate limits
src/log-sink.ts    where leaves go: the local file, or a remote log over HTTP; plus the reference log server
src/policy.ts      Cedar evaluation wrapper, fail-closed
src/simulate.ts    replay recorded policy decisions against a proposed Cedar policy, grouped changes and run digest
src/delegation.ts  signed delegation grants
src/approval.ts    exact-action human approvals, distinct trusted signatures, persistent single-use consumption
src/receipt.ts     receipt and authorization statement types and provenance labels
src/issue.ts       sign, log, and write a receipt, or commit an authorization first; shared by both producers
src/gateway.ts     the MCP proxy: a host (key, policy, upstreams, log) and a session per grant; scope check, facts, policy, forward, receipt
src/operations.ts  SQLite operation claims and daily budget reservations, retained across retries and process crashes
src/gateway-http.ts the gateway over Streamable HTTP: one process, a session per connection, each under the grant it presents
src/sdk/index.ts   the interceptor: policy decision, record, wrap(tool fn)
src/sdk/claude.ts  Claude Code command hook and Claude Agent SDK in-process hooks
src/sdk/openai-agents.ts, vercel-ai.ts, langchain.ts   framework adapters, tested against the real packages
src/sidecar.ts     the SDK issuer behind a local HTTP API, for agents in other languages
src/otel.ts        OpenTelemetry export: one OTLP/HTTP span per receipt, after the receipt, no SDK dependency
src/splunk.ts      Splunk export: one HTTP Event Collector event per receipt, token from the environment, beside or instead of otel
src/rest.ts        the REST connector: an HTTP API described as tools, standing where an MCP upstream stands
src/a2a.ts         agents delegating to agents: a remote A2A agent as an upstream, and the gateway serving A2A itself in front of it
src/upstream.ts    attested execution: an upstream signs its result for the receipt; the verifier checks it with the upstream key
src/capture.ts     private evidence: per-tool capture rules applied by the issuer to the whole statement, withheld values as salted commitments or encrypted under the customer's key, restore and reveal
vectors/           conformance vectors: receipts, keys, logs, proofs, and expected verdicts; `bun run vectors` regenerates them
src/verify.ts      offline verification, the human-readable report, and the audit that a later log extends an earlier one
src/cli.ts         keygen, grant, gateway, hook, serve, log, prune, verify, reveal, audit, policy simulate
src/retention.ts   pruning the log: leaves become their hashes, bundles are removed, proofs survive
src/index.ts       the package's public surface; adapters are exported on ./sdk/<framework> subpaths
tsconfig.build.json  emits dist/ (JavaScript plus declarations) for consumers; the repo itself runs the .ts directly
scripts/           fake Stripe upstream (signs its results with --key), a second fake upstream, fixture builders for gateway and SDK, the replay builder (`replay.ts`: example 27 and state example 07 as a folder of receipts and public keys anyone can verify with the published packages), demo
examples/          thirty-four runnable tutorials, plus examples/languages/: Python, Go, Java, and Rust clients of the sidecar, run by the test suite, one per aspect; each is run by the test suite
test/              unit tests per module, end-to-end gateway test, SDK and hook tests,
                   adapter tests against the real packages, and a test that runs every policy in docs/policies.md
docs/              tutorials, usage (gateway), codex-recovery (live demo), approvals, sdk, policies, verification

计划

设计由两个生产方为一个验证器提供数据。SDK 位于漏斗顶端:安装成本低、覆盖面广,并如实说明内容为自我报告。网关则是安全或合规负责人对重大操作的强制要求。两者都已存在;接下来的工作是分别扩大它们的应用范围。

已完成

  • 严格模式:"strict": true 会拒绝本地日志、非仅哈希日志以及未固定密钥的日志;在观察模式下也会预先提交每个工具;并关闭发件箱。在授权记录写入运营方无法运行的日志之前,Agent 发出的调用不会被转发;每份回执都会注明 issuer.strict,且 verify --require-strict 会要求启用严格模式(用法,教程 34)。Issue #177。
  • 策略重新加载:网关会在 policy.cedar 发生变化时重新读取,并从下一次调用起应用新策略,无需重启,也不会中断会话;无法解析的策略会被拒绝,当前生效的策略保持不变;每次变更或拒绝都会记录在 receipts/policy-reloads.jsonl 中(用法)。
  • Codex 实时支持恢复演示:由模型选择 MCP 调用、CRM 记忆有误、经过审核的恢复计划、模拟信用补偿以及已验证的证据;参见指南,并提供离线回归测试覆盖。
  • 与负载绑定的人工审批:对主体、Agent、工具、完整参数、金额、货币和受益方进行短时签名;可配置单密钥或双密钥门槛,即使在观察模式下也会强制执行;可配置有效期上限;转发前会持久化并以原子方式完成一次性消费;并支持独立验证审批密钥。指南,教程 30。
  • 网关:MCP 代理、签名委派、由网关获取事实、Cedar 策略、拒绝回执、Merkle 日志、离线验证器。
  • 回执架构包含签发方类型,因此验证器会先判断是网关还是 SDK 签发。
  • SDK 核心:策略决策、记录,以及适用于任何以函数作为工具的框架的通用 wrap(tool, fn)。
  • Claude Code 命令钩子,支持 PreToolUse、PostToolUse 和 PostToolUseFailure,并会在拒绝时阻止调用。
  • Claude Agent SDK 进程内钩子,使用同一处理程序。
  • 提供方原生交付:包装 Stripe 或 GitHub 的上游服务会将已签名的 webhook 或交付内容附加到调用中;持有共享密钥的验证器会检查 HMAC、时间戳以及与结果的绑定关系,并报告该执行已通过共享密钥证明。
  • 对数级追加:Merkle 日志会缓存完整子树,因此在第百万个叶节点签发回执,与在第一个叶节点签发的成本相同;每份回执的实测耗时为 0.15 ms;包括策略、事实查询和上游签名在内,每次网关调用约耗时半毫秒。远程日志每次调用会额外增加一次网络往返和约 5 ms 的服务器处理时间;预先提交的调用则增加 2 ms;部署指南提供了基于实时日志的测量数据。
  • 日志保留:prune 会将早于截止时间的叶节点替换为其哈希,并删除对应的包,因此证明仍可验证,而内容已被移除。
  • 一个网关和一份授权下可接入多个上游;每个工具恰好由一个上游负责,回执会注明处理该调用的上游;已消费的事实会在这些上游之间流转。
  • 经证明的执行:持有密钥的上游会为其结果签名,网关将签名嵌入回执;向验证器提供上游密钥后,验证器会将执行报告为已证明,而非仅观察到。记忆服务和演示上游都会签名。
  • HTTP 上游:网关可通过 Streamable HTTP 使用环境变量中的 bearer token 连接已运行的 MCP 服务,也可通过 stdio 启动 MCP 服务。
  • 可选事实查询:如果查询引用的调用参数并未包含在调用中,则会跳过该查询,而不是拒绝调用;这样,策略可以看到某次写入即将替代的事实,同时不会拒绝那些没有替代任何内容的写入。
  • 已消费的事实:上游会在结果的 _meta 中声明其提供的事实;会话中之后的每份网关回执都会以 consumed 标出这些 ID,并将其记录下来,因此 Agent 在每次调用前已看到的内容都有据可查。
  • 转发的调用会在 _meta 中携带回执 ID,以及已证明的 Agent 和主体,因此有状态的上游可以引用该回执;@agent-custody/state 中的记忆服务便以这种方式运行。
  • 一致性测试向量由测试套件生成并随规范发布;agent-custody.dev 上的浏览器验证器可通过所有测试向量。
  • Sidecar:通过本地 HTTP API(serve)提供 SDK 签发方,并配有已具备发布至 PyPI 条件的 Python 包,以及 Go、Java 和 Rust 客户端,因此任何语言的 Agent 都能通过同一套签名实现获得相同的回执。
  • 树头之间的连续性证明(RFC 9162)由日志提供,并由 audit 命令检查;因此,持有旧树头的审计人员可以证明此前没有内容被改写。
  • 远程日志:签发方可通过 HTTP 向他人运行的日志追加记录,由该日志的密钥为树头签名,因此验证器可以确认回执位于运营方无法改写的日志中。包括参考日志服务、bearer token 身份验证以及供审计人员使用的根端点。
  • 框架适配器均使用脚本化模型进行测试,针对真实软件包运行且不访问网络:OpenAI Agents SDK(wrapTools 强制执行,observeRunner 根据生命周期事件记录)、Vercel AI SDK(wrapTools 用于真实的 generateText 循环)、LangChain(ReceiptCallbackHandler 负责记录,issuer.wrap 负责强制执行)。
  • 计划与租户门户:每个租户都属于某个计划(免费计划,每月 1 万次追加;团队计划,每月 100 万次;企业计划,不限量),追加时会强制执行,并清晰返回 429;运营方的 PORTAL_HOST 上的门户允许团队注册、获取租户及首个密钥、查看相对于计划的用量、创建和撤销密钥、通过 Stripe 购买团队计划,以及复制导出命令;每项操作都会记录在审计追踪中。
  • 管理操作审计追踪:每个租户的创建或停用,以及每个令牌的创建或撤销,都会记录执行者;无论操作来自管理页面还是命令行,记录都会显示在页面上,并随租户导出一同提供。
  • 租户导出:log-export 使用租户自己的令牌获取每个叶子哈希、已签名的树头、已发布的密钥、检查点及其用量,验证这些数据彼此吻合,并写入一份供验证器离线读取的日志副本;这些证据永远不依赖运营方继续经营。
  • 监控与计量:log-check 是一个外部探测程序,用于验证树头、检查点和见证方,遇到问题时以状态码 1 退出;它由 monitor 工作流每十分钟运行一次;另有 GET /health,以及管理页面和 CSV 中按租户、按月显示的用量。
  • 见证方:在日志运营方无法控制的另一台机器上运行第二个签名方;它先证明每个检查点都接续了自己上次签署的检查点,然后才进行联署;若历史记录被改写或分叉,则拒绝签署并发出警报;同时发布自己的密钥;audit --witness-url 要求提供见证方。参见 issue #6 的阶段 6。
  • 签名方、密钥与检查点:让密钥运行在独立进程中(signer、--signer-url);密钥文档位于 /.well-known/agent-custody-log.json,由 verify --log-url 和 audit --log-url 获取并固定;每个日志的已签名检查点会发布到目录和表中,供未持续监看的验证器使用。参见 issue #6 的阶段 3。
  • 基于 Postgres 的日志:使用 log --db-env;叶子哈希存储在按租户键控的一张表中;每个租户通过 advisory lock 确保只有一个写入方;租户和哈希后的令牌存储在由 log-admin 管理的表中;支持速率限制和正文大小上限、接收端重试,以及用于导入现有文件日志的 import。参见 issue #6 的阶段 2。
  • 为他人提供的日志:hashOnly 仅发送叶子哈希,因此日志永远不会持有收据;参考服务在 /t/<tenant>/ 下运行多个租户日志,各自使用独立的令牌和 ID;树头会标明所属日志,验证器通过 --log-id 检查。参见托管日志阶段 1,issue #6。
  • OpenTelemetry 导出:在任一配置中启用 otel 后,每张收据也会作为一个 span 发送到团队已在使用的收集器;trace ID 等于收据 ID,并包含工具、Agent、主体、状态、决策和日志位置等属性;在收据生成后尽力发送,绝不进入证据路径。协调收据会被标记且不包含策略决策,因此仪表板不会将其计作第二次已允许的调用。
  • 子 Agent 的委派链:一项授权会指定 Agent 的密钥,使其能够委派范围更窄的授权,并嵌入父级授权,最多三层;验证器和网关会沿链追溯至主体,拒绝任何扩大范围、放宽时间窗口、更改主体或由错误密钥签署的链接;收据会标明子 Agent 和主体,并包含委派链。由 gateway-chain-* 测试向量固定,并在浏览器验证器中保持一致。
  • 一个网关服务多个 Agent:gateway --http 通过 Streamable HTTP 提供网关服务;每个连接在其提交的授权范围内拥有一个会话;会话共享上游和策略,仅此而已;平台团队只需在工具前运行一个网关,而不必为每个 Agent 单独运行一个进程。
  • Splunk 导出:在任一配置中启用 splunk 后,每张收据也会作为一个事件发送到 HTTP Event Collector;收据 ID、工具、Agent、主体、状态、决策和日志位置会作为可搜索字段,并从环境中读取令牌;同样遵循尽力而为原则。
  • REST 连接器:在网关配置中将普通 HTTP API 描述为工具,并从环境中读取凭据,使 Agent 对 API 的直接调用转变为通过网关、带有收据且经过策略检查的工具调用。
  • 对重要工具进行预先提交授权:在 precommit 中指定后,调用会先签名并写入日志,再转发;如果日志不接受,则不会转发;其收据包含已提交的授权及证明,表明授权先于执行。
  • 每次调用都检查授权(issue #97):在所有模式下,通过 MCP、HTTP 和 A2A 接口进行的每次调用都会检查授权是否过期或已撤销,并在转发前再次检查;agent-custody revoke 会撤销一项授权及从其委派的所有授权。预先提交的调用转至上游前,会先根据固定的密钥验证远程日志的响应(issue #99)。门户只有在用户自己的账户会话中验证地址后,才允许提供方登录与该地址关联(issue #103)。验证器会重新计算执行结果摘要(issue #104)。Stripe webhook 会忽略重复或乱序事件(issue #105)。
  • 在托管下进行 Agent 间委派:支持 A2A 上游和网关的 A2A 接口;每次委派都会生成一张收据,并将受托方的卡片作为事实记录。Google ADK 的 RemoteA2aAgent 或任何 A2A 客户端都可以指向网关;若委派被拒绝,返回的任务会被标记为已拒绝,受托方不会看到该任务。受托方目前不会对其答复联署;收据证明网关发送和观察到的内容,而非受托方对其采取的行动。
  • 私有证据:按工具设置捕获规则,由签发方对完整声明应用规则,确保私有规则下只有固定结构列表之外的内容不会以明文形式出现;被隐去的值会以加盐承诺表示,盐值绝不进入收据;内容使用客户密钥加密,并由验证器揭示和重新计算;这些承诺无法确认对被隐去值的猜测。人工审批信封包含操作字段和未加盐的参数摘要,因此私有规则会将其隐去并作为承诺;适用隐私限制。
  • 托管门户的企业身份:成员角色(owner、admin、reviewer、readonly)支持邀请;支持使用一次性代码的 TOTP MFA 和运营方重置(log-admin user mfa-reset);每个租户可通过自己的 IdP 使用单点登录,支持 OIDC 或 SAML 2.0,由运营方设置(log-admin sso set);启用强制模式后,成员将无法通过密码、GitHub 和 Google 路径登录。现已支持在会话期间撤销 Agent 的权限(agent-custody revoke,示例 27);示例 31 展示了企业登录和各类角色。
  • 状态审查控制台:按交易、客户、Agent 或时间搜索收据,查看十项答复,并导出可移植的 HTML/JSON 案例;其中的签名、授权和包含证明均可离线验证。
  • 普通调用后日志中断(issue #149):已签名的收据会暂存于 receipts/outbox/,日志恢复后由网关追加;Agent 会收到实际结果,并标记为 log-pending;GET /health 会统计待处理收据;requirePrecommit 会拒绝没有 precommit 的远程日志。故障表列出了各种情况。
  • 锚定(issue #176):agent-custody anchor 会证明每个新检查点都接续了上次锚定的检查点,然后在单笔交易中从自己的地址向自身提交一个覆盖这些检查点的 Merkle 根;默认使用 Ethereum,也可使用 Base、Arbitrum、Optimism、Polygon,或通过链 ID 指定任意 EVM 链(--chain),并按各链设置费用上限。它会发布每个日志的记录和包含证明;闲置日志不会产生交易;超过费用上限时会跳过该次运行;不会同时发起多笔交易。每笔交易都涵盖所有受监控的日志;交易只有在其区块最终确定后才算完成,并会处理链重组。log-check --anchor-url 会通过一个或多个 JSON-RPC 端点检查记录,要求日志 ID 与验证器固定的 ID 相同,且地址的交易必须是最新交易;还会将日志树头与已锚定的检查点进行核对,因此仅凭链上数据就能发现被改写或回滚的树(锚定,示例 33)。 接下来,按投入产出排序

前三项旨在弥合两者之间的差距:日志无法由操作员改写,而记录无法由操作员塑造。

  1. 在主网上运行 log.agent-custody.dev 的锚定服务:由锚定服务独自在主机上持有一把已注资密钥,其记录与检查点一同提供,其地址和每个租户的日志 ID 发布在欢迎页和安全页面上,并由监控系统检查。代码已经完成。Issue #176。
  2. 从服务提供商一侧验证完整性:审计员的只读密钥列出 Stripe 的退款,然后列出 GitHub 的操作,并将每一项与收据匹配。这能发现任何网关都无法看到的情况:在网关之外执行的操作。Issue #178。
  3. TEE 网关:一个可复现且经过证明的机密容器;在 enclave 中生成并保管签名密钥;TLS 在容器内部终止;两个验证器都检查从收据密钥、经由证明、直到发布版本的完整路径。它将取代此前计划的托管在 TEE 中的签名器。Issue #179。
  4. 在不属于我们的机器上、使用不属于我们的账户运行 log.agent-custody.dev 的见证服务,并要求将其列入欢迎页。代码已经完成;现在需要的是第二位操作员。Issue #116。
  5. 后量子签名:在同一个 DSSE 信封中同时使用 ML-DSA 和 Ed25519;存在 PQ 密钥时默认采用混合模式,并用于每个签名工件和浏览器验证器。Issue #11。
  6. Agent 间调用的接收方证明:委托方为收据签署其答复,就像上游签名方现在所做的那样。
  7. SD-JWT 脱敏,然后是策略合规性的 ZK 证明。暂不推进。

TypeScript 适配器稳定后,将按照相同的模式推出 Python SDK。

评论

支持 Markdown

第一次接触 MCP?

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

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