Chips

把 Cloudflare API 接进 Agent 工具链

· 约 5 分钟 · 1828 字

通过 MCP 把云平台 API 暴露给 Agent 之后,真正需要设计的不是连接本身,而是权限边界与回滚路径:token 按最小范围发放、写操作前先导出快照、下发脚本保持幂等,并警惕 PUT 整表语义。

问题:Agent 需要直接读写远端资源

在把云平台 API 接给 Agent 之前,操作路径是人肉的:打开控制台点几下,或者手敲 curl 拼 JSON。改一条 DNS 记录、调一条隧道 ingress、看一眼对象存储桶——每一步都要人读返回值判断成败。

麻烦的不只是慢。面板操作不可复现:点完之后没有能重放的命令,改错了只能凭记忆再点回去。

MCP(Model Context Protocol)把这类 API 封装成结构化工具:Agent 按 schema 调用,拿到的是结构化数据而不是一段 HTML。代价是凭据交给了进程——tools 里任何一个能改状态的函数,都让「误改」的成本降到接近于零。

tools 与 resources:只有一类会产生副作用

MCP 服务器对外暴露两类能力:

类型 语义 是否改变远端状态 权限给法
tools 可调用的函数 按最小集,写权限尤其收紧
resources 可读取的数据 不会 可以放宽

接上之后先列一遍:

/mcp list
<api 服务器>   http   https://mcp.example.com/mcp   tools: docs, search, execute
<docs 服务器>  http   https://docs.example.com/mcp  tools: search

这里有个容易误判的点:工具数量少不等于能力小。上面那个 API 服务器采用 Code Mode,只暴露三个工具——文档、检索、执行,而「执行」背后是两千多个 API 端点。也就是说单个 execute 的能力面,等于 token 的全部权限范围。

结论很直接:工具清单不是权限边界,token 的 scope 才是。

凭据:最小权限,以及本机的多处副本

配置本身很朴素,token 直接写在 header 里:

{
  "mcpServers": {
    "<api 服务器>": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer <API 令牌>" }
    }
  }
}

两件事要同时做。

一是文件权限。 token 是明文,配置文件必须收到 root-only:

chmod 600 ~/.config/<agent>/mcp.json
stat -c '%a %n' ~/.config/<agent>/mcp.json
# 600 /home/user/.config/<agent>/mcp.json

二是 token 自身的范围。 给到能完成任务的最小集合,能限定到单个区域就别给账户级。最容易犯的错是图省事给「全域读写」:只想改几个域名的解析记录时,限定到 DNS 编辑权限就够,多出来的权限只是把 execute 的爆炸半径放大。

然后是被忽略的一面:token 在本机不止一份。除了配置文件,Agent 的会话记录和历史数据库通常会把工具调用原文存下来(包括 Authorization 头),备份目录又会把配置文件再复制一遍。按前缀搜一遍:

sudo grep -rlE '<前缀>_[A-Za-z0-9]{20,}' ~/.config ~/.local/share /srv /var/backups 2>/dev/null

实测常见结果是四条:配置文件、sessions/ 下的会话文件、history.db、以及一个时间戳命名的备份目录。这说明「轮换 token」不是改一个文件就完事;反过来也说明,先摸清副本清单,再谈要不要轮换。

「配置正确」与「实际接通」是两回事

配置里 URL 写对,不代表服务器认。两个坑:

  1. 端点 URL 会记错。 曾经把端点写成 https://api.mcp.<平台域>/mcp,而实际存在的是 https://mcp.<平台域>/mcp。多一个 api. 前缀,HTTP 层直接连不上——这种错只有真发一次请求才暴露,读配置看不出来。不同平台的 MCP 端点形态还不一样,抄文档时尤其容易混。
  2. 连得上、有权限,是两层检查。 Bearer 头格式正确(TCP/TLS 与协议层通过)、token 有效(不返回 401)、token 有权做这件事(不返回 403),三者互相独立。

验证顺序应当是先只读、后写

curl -fsS "https://api.example.com/v4/zones/<区域 ID>/dns_records?per_page=5" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  | jq -r '.result[] | [.type, .name, .content] | @tsv'
A   www.example.com 203.0.113.10
CNAME   wiki.example.com    <隧道 ID>.<平台隧道域>

只读调用通了,才谈写操作。而且写操作的首次验证应落在一个无副作用或可回退的对象上,不要拿生产记录试。

写操作:先 dump 再改

改云端配置的风险不在「改」,而在改之前有没有一份能回去的状态。有些配置只存在于云端:隧道采用 token 模式时,ingress 规则由平台侧下发,本地没有对应文件。这类资源必须先导出:

#!/bin/bash
set -euo pipefail
zone='<区域 ID>'
stamp=$(date -u +%Y%m%dT%H%M%SZ)
out="/srv/backups/dns-${stamp}.json"

curl -fsS "https://api.example.com/v4/zones/${zone}/dns_records?per_page=200" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" > "$out"

jq -r '.result[] | [.type, .name, .content, .proxied] | @tsv' "$out" \
  | sort > "${out%.json}.tsv"
wc -l < "${out%.json}.tsv"

三个要点:dump 必须在改之前执行;要落成可读文本(用 jq 打平的 TSV 比原始 JSON 好 diff);脚本同时保留 --dump(只读)与下发两个模式,让还原不需要重写代码。改完之后用同一脚本再 dump 一次,比对两次快照:

diff before.tsv after.tsv

非空的行就是这次改动的全部影响——这是把「Agent 说它改了什么」变成「磁盘上只剩这一处差异」的最低成本做法。

双刃:恢复变容易,误改也变容易

脚本化之后,恢复配置从「回忆着点面板」变成「跑一条命令」,这是收益;但同一条命令也让误改变成一次调用,而 Agent 执行时不会犹豫。两个必须内建的约束:

幂等。 同一份期望状态下发两次,结果必须一致。判据很具体:连续执行两次下发,再 diff 两次 dump 的快照,应当为空。做不到幂等就要先想清重复执行的后果——「追加」语义的接口会重复创建记录。

整表替换的警觉。 有些接口是 PUT 整表语义:提交的数组就是改完之后的完整集合,没列进去的条目会被静默删除。在这类接口上「只改一条记录」是危险的——你以为在做局部修改,实际在做全量覆盖。判断方法有两个:看返回体里缺不缺你没提交的条目;或看改后快照里有没有你没打算删的行。

在脚本里加硬约束比事后发现便宜得多,例如:清单条目数少于预期时直接中止;兜底项(如 http_status:404 这类结尾规则)不存在时拒绝下发。注意顺序语义——具体条目必须排在兜底项之前,否则全部请求落到兜底。

可操作要点

← 全部文章