MCP 和 A2A 协议

LibreFang 支持 MCP (Model Context Protocol) 和 A2A (Agent to Agent) 协议。


MCP (Model Context Protocol)

MCP 是一个标准化协议,用于连接 LLM 与外部工具和服务。

概述

┌─────────────┐      MCP       ┌─────────────┐
   LibreFang ◄─────────────►  MCP Server
   (Client)  │   JSON-RPC 2.0  │  (Server)   │
└─────────────┘                  └─────────────┘

MCP 服务器配置

[[mcp_servers]]
name = "filesystem"
transport = { type = "stdio", command = "npx", args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] }

[[mcp_servers]]
name = "github"
transport = { type = "stdio", command = "npx", args = ["-y", "@modelcontextprotocol/server-github"] }
env = ["GITHUB_TOKEN"]

启动参数位于 transport 表内,而不是条目的顶层。 env字符串列表而非表:裸写 "GITHUB_TOKEN" 表示从守护进程自身环境转发该变量,"GITHUB_TOKEN=ghp_..." 则直接内联赋值。 McpServerConfigEntrydeny_unknown_fields,所以顶层的 command 键或 env = { … } 表会直接解析失败,而不是被忽略。

接入 EveryAPI MCP 服务器

EveryAPI CLI 在自身二进制里内置了一个 MCP 服务器,通过 everyapi mcp 启动。 LibreFang 本身就是 stdio MCP 客户端,因此两边都不需要新增任何代码 —— 两段配置就是全部集成工作。

1. 在 ~/.librefang/config.toml 中注册服务器:

[[mcp_servers]]
name = "everyapi"
transport = { type = "stdio", command = "everyapi", args = ["mcp"] }
timeout_secs = 30
env = []

env = [] 是正确写法,不是遗漏。 stdio 传输会先清空子进程环境,再转发一组固定的安全变量,其中包含 HOMEXDG_CONFIG_HOMEUSERPATH(Windows 上还有 USERPROFILE / APPDATA)。 这恰好够 everyapi mcp 自行找到 ~/.config/everyapi/credentials.json,所以不需要把任何凭据复制进 LibreFang 的配置或密钥库。

2. 在 agent 的 agent.toml 中授予该服务器:

mcp_servers = ["everyapi"]

与 provider 接线相互独立

这条桥接与 librefang models connect everyapi 是两回事。 那条命令把 EveryAPI 接成 LLM provider,让 agent 的补全请求经网关路由。 而 MCP 服务器给的是工具,用来检视和操作网关账户本身。 everyapi mcp 直接读取网关自己的凭据文件,所以无论是否跑过 models connect everyapi,这条桥接都能工作;反过来,接好 provider 也不会让这些工具出现。 两种情况下都请先执行 everyapi login:服务器进程在未登录时也能正常启动,但在凭据出现之前,每个工具都会返回未登录错误。

可选:添加目录条目

上面那段配置已经足够。 如果你还想用 librefang mcp add everyapi 并让它出现在 dashboard 目录里,把下面这个文件放到 ~/.librefang/mcp/catalog/everyapi.toml

id = "everyapi"
name = "EveryAPI"
description = "Inspect and operate an EveryAPI gateway account — quota, seller channels, edge nodes — through the everyapi CLI's built-in MCP server"
category = "cloud"
icon = "lucide:gauge"
tags = ["everyapi", "gateway", "quota", "billing", "marketplace"]

[transport]
type = "stdio"
command = "everyapi"
args = ["mcp"]

[health_check]
interval_secs = 60
unhealthy_threshold = 3

setup_instructions = """
1. Install the EveryAPI CLI and run `everyapi login`, which writes ~/.config/everyapi/credentials.json.
2. Run `librefang mcp add everyapi`.
3. Add "everyapi" to the agent's mcp_servers list in its agent.toml.

No credential needs to be entered into LibreFang. The MCP server reads the gateway credentials file itself.
"""

[i18n.zh]
name = "EveryAPI"
description = "通过 everyapi CLI 内置的 MCP 服务器检视与操作 EveryAPI 网关账户 —— 配额、卖家渠道、边缘节点。"

目录会从上游 registry 同步,但同步只会清理它自己安装过的文件,因此手动放置的条目不会被删除。 该条目没有声明 required_env,所以 librefang mcp add everyapi 会直接报告 Ready,不会索要密钥。

工具清单

服务器共暴露 15 个工具。 7 个只读,8 个写入。

只读工具返回内容
everyapi_status账户名、剩余与已用配额(美元)、请求数、充值 URL
everyapi_topup购买额度的钱包 URL —— 仅返回字符串,不涉及任何资金操作
everyapi_seller_list调用者已挂载的卖家渠道
everyapi_seller_eligibility账户已通过哪些市场卖家准入条件
everyapi_edge_list已注册的自带 GPU 边缘节点
everyapi_edge_status单个边缘节点的健康与服务状态
everyapi_admin_marketplace_status部署级市场设置(管理员)
写入工具影响与摩擦机制
everyapi_seller_add_key以参数明文传入的上游 API 密钥挂载卖家渠道。没有确认关卡。
everyapi_seller_withdraw从卖家余额转出资金。需要 confirm: "yes"
everyapi_admin_marketplace_set修改部署级市场设置。需要 confirm: "yes"
everyapi_edge_remove销毁边缘节点注册。需要 confirm: "yes"
everyapi_seller_add_oauth_codex_start发起 OAuth 渠道挂载,需要人工回贴授权码。
everyapi_seller_add_oauth_codex_poll轮询该流程直至完成。
everyapi_seller_add_oauth_claude_start发起 Claude OAuth 渠道挂载。
everyapi_seller_add_oauth_claude_complete用回贴的授权码完成挂载。

工具名带双重前缀

LibreFang 会把每个 MCP 工具命名为 mcp_{server}_{tool},而 EveryAPI 的工具本身就叫 everyapi_*。 两者按字面拼接,不做任何去重:

everyapi_status  mcp_everyapi_everyapi_status

第一个 everyapi 来自你 [[mcp_servers]] 里的服务器名,第二个属于上游工具本身。 这一点很关键,因为审批模式匹配的是完整的命名空间名。

把 agent 限制在只读工具上

require_approval 接受 glob 模式,且匹配的是命名空间名,因此四条模式即可覆盖全部 8 个写工具、且不误伤 7 个读工具:

# ~/.librefang/config.toml
[approval]
require_approval = [
  "mcp_everyapi_everyapi_seller_add_*",        # add_key + 4 个 OAuth 工具
  "mcp_everyapi_everyapi_seller_withdraw",
  "mcp_everyapi_everyapi_admin_marketplace_set",
  "mcp_everyapi_everyapi_edge_remove",
]

seller_listseller_eligibility 刻意不在其中:通配符锚定在 seller_add_ 上,那是一个互不重叠的前缀。

关于这套配置能买到什么,有四点必须说清楚。 后三点都是绕过路径:config.toml 中别处的配置会让这些 glob 静默失效。

require_approval 是暂停,不是拒绝。 命中的调用会挂起当前轮次并发起审批请求,再由人来批准或驳回。 对无人值守的 cron agent 而言,这意味着写工具是卡住而不是被拒绝,超时后的结果取决于 approval.timeout_fallback(默认 deny,也可选 skipescalate)。 如果你要的是硬阻断而非暂停,请改用 channel deny 规则,或干脆不把带写工具的服务器放进该 agent 的 mcp_servers 列表。

trusted_senders 不会对 MCP 工具绕过它。 只有当工具不属于高风险类别时,审批管理器才为可信发送者放行,而 classify_risk 把每个 mcp_* 名字都判为高风险 —— 正因为它无法枚举第三方服务器代码会做什么。 所以即便是可信发送者,mcp_everyapi_everyapi_seller_withdraw 依然受门禁约束。 需要说明的是此前并非如此:分类器只匹配一份内置名字的封闭列表,MCP 工具因此落进 Low,绕过分支在 require_approval 被读到之前就返回了。

channel allowed_tools 规则仍然是一条绕过路径。 命中的 allowed_tools 条目会在 require_approval 列表被查阅之前直接返回"无需审批",所以 channel 白名单不是安放 EveryAPI 写工具的安全位置。

带 hand 标签的 agent 会自动批准一切。 经由 activate_hand() 拉起的 agent 带有 hand: 标签,被视为经过策展的可信包:它们提交的审批直接返回 AutoApproved,从不抵达人类,执行照常继续。 这同样覆盖 mcp_everyapi_everyapi_seller_add_keymcp_everyapi_everyapi_seller_withdraw。 如果一个 hand agent 确实需要接触 EveryAPI 工具,请把上面的 glob 当作文档而非强制手段,并让带写工具的服务器远离该 agent。

审批默认按会话缓存。 approval.cache_approvals_per_session 默认为 true,因此某个工具名在一个会话中第一次被人工批准后,该会话内后续对同一工具的每次调用都会自动放行。 对 seller_add_key —— EveryAPI 自身唯一没有加 confirm 令牌的写工具 —— 这意味着一次"同意"会覆盖此后无限次的密钥上传,直到会话结束或守护进程重启。 如果你要的是逐次决策,请设置 cache_approvals_per_session = false

按用户的 RBAC 是上述任何一条都无法豁免的关卡。 用户策略给出的 NeedsApproval 会置上 force_human:它在可信发送者检查之前被求值,会抑制 hand agent 的自动批准豁免,也会跳过按会话的缓存。 它是上面三条绕过路径都必须遵守的唯一机制,所以当发送者可信、agent 带 hand 标签、或会话长期存活时,应该用它。

旗舰场景:在昂贵任务前查余额

预期用法是让 cron agent 调用 mcp_everyapi_everyapi_status,在网关额度快耗尽时提前中止,而不是跑到一半才撞上一整墙 402。 读工具全都可以放心不加关卡,所以这个场景完全不需要任何审批交互。

需要坦白的一点是:everyapi_status 返回的是渲染后的文本,而非 JSON。 agent 实际收到的大致是这样:

Account: alice (alice@example.com)
Quota:    $12.34 remaining   $87.66 used
Requests: 1420
Top-up:   https://api.everyapi.ai/wallet

其中没有任何结构化字段可读。 想据此判断剩余余额的 agent,必须从这段散文里解析出一个浮点数,而这正是模型在边界情况上容易出错的动作 —— 一个 $1,234.00 的千位分隔符,或未来一次措辞调整,都会让它悄无声息地失效。 相比依赖精确解析的算术,更建议用粗粒度阈值("看起来低于十美元就停下并通知"),并在无法自信读出数字时,让 agent 把原始那一行交给人来看。

MCP 工具命名

MCP 工具命名格式:

mcp_{server}_{tool}

例如:

  • mcp_filesystem_read_file
  • mcp_github_create_issue

内置 MCP 服务器

LibreFang 包含内置 MCP 服务器:

服务器工具
filesystemread_file, write_file, list_directory
githubcreate_issue, get_pr, search_repos
postgresquery, execute, list_tables

MCP 客户端

作为 MCP 客户端连接到外部服务器:

[[mcp_servers]]
name = "custom"
transport = { type = "stdio", command = "python", args = ["./mcp_server.py"] }

开发 MCP 服务器

from mcp.server import Server
from mcp.types import Tool, TextContent

app = Server("my-server")

@app.list_tools()
async def list_tools():
    return [
        Tool(
            name="my_tool",
            description="My custom tool",
            inputSchema={"type": "object", "properties": {}}
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    return [TextContent(type="text", text="result")]

MCP 污点策略

发往 MCP tool 的出站载荷会被出站污点检测器扫描敏感数据(token、API key、知名 secret 前缀、PII)。默认每条规则都触发;可以按 tool按路径 mute 特定规则,给确实需要接收该类数据的 MCP server 开口子。

# config.toml —— 按 tool 名模式全局 mute
[[mcp_taint_policy]]
tool = "mcp__github__create_pr"
skip_rules = ["AuthorizationLiteral"]   # 允许 GitHub MCP 转发 bearer token

可用规则 idTaintRuleId enum):

规则检测
AuthorizationLiteral载荷中的 Authorization: Bearer ... / Authorization: Basic ...
KeyValueSecretpassword=...token=...api_key=... 这类模式
WellKnownPrefixsk-ghp_xoxb- 等已知 token 前缀
OpaqueToken没有已知前缀的长高熵字符串
PiiEmail / PiiPhone / PiiCreditCard / PiiSsn个人信息类
SensitiveKeyName名为 passwordsecretprivate_key 等的 JSON 字段名

API:调用方也可以直接 check_outbound_text_violation_with_skip(payload, sink, skip_rules) 按调用 skip 规则。原 check_outbound_text_violation 用空 skip 集合委托过去,所以现有调用方行为不变。


A2A (Agent to Agent)

A2A 协议支持 LibreFang agents 之间的通信。

概述

┌─────────────┐      A2A       ┌─────────────┐
   Agent A ◄─────────────►   Agent B
└─────────────┘   JSON over    └─────────────┘
                  HTTP/WebSocket

Agent Card

每个 Agent 发布 Agent Card:

{
  "name": "researcher",
  "description": "Deep research agent",
  "url": "http://localhost:4545/api/a2a",
  "version": "1.0.0",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false
  },
  "skills": [
    { "id": "research", "name": "Research" }
  ]
}

客户端端点(向外部 A2A Agent 发送任务)

这些端点用于让 LibreFang 作为客户端,主动向外部 A2A agent 委派任务:

端点方法说明
/api/a2a/agentsGET列出已发现的外部 A2A agents
/api/a2a/discoverPOST发现指定 URL 处的外部 A2A agent
/api/a2a/sendPOST向外部 A2A agent 发送任务
/api/a2a/tasks/{id}/statusGET查询已发送任务的状态

服务端端点(接受外部任务)

这些端点由 LibreFang 对外暴露,供其他 A2A agent 向本实例发送任务:

端点方法说明
/.well-known/agent.jsonGETAgent Card(能力声明)
/api/a2a/tasksPOST接受来自外部的任务
/api/a2a/tasks/{id}GET获取任务状态
/api/a2a/tasks/{id}/messagesGET获取任务消息历史

发现并发送任务到外部 Agent

# 发现外部 agent
curl -X POST http://localhost:4545/api/a2a/discover \
  -H "Content-Type: application/json" \
  -d '{"url": "http://other-agent:4545"}'

# 向外部 agent 发送任务
curl -X POST http://localhost:4545/api/a2a/send \
  -H "Content-Type: application/json" \
  -d '{
    "agent_url": "http://other-agent:4545",
    "message": "Research AI trends and summarize"
  }'

# 查询任务状态
curl http://localhost:4545/api/a2a/tasks/task-123/status

接受来自外部的任务

# 外部 agent 向本实例发送任务
curl -X POST http://localhost:4545/api/a2a/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "id": "task-456",
    "message": {
      "role": "user",
      "parts": [{ "type": "text", "text": "Research AI trends" }]
    }
  }'

# 轮询结果
curl http://localhost:4545/api/a2a/tasks/task-456

# 或使用 SSE 流式接收
curl -N http://localhost:4545/api/a2a/tasks/task-456/events

对比

特性MCPA2A
用途LLM → 工具Agent ↔ Agent
协议JSON-RPC 2.0HTTP/WebSocket
方向单向双向
示例文件系统、GitHubAgent 协作

使用场景

MCP 场景

  • 文件系统操作
  • 数据库查询
  • GitHub API 调用
  • 自定义工具集成

A2A 场景

  • 多 Agent 协作
  • 任务委派
  • 跨实例通信

配置示例

完整 MCP 配置

# MCP 服务器
[[mcp_servers]]
name = "filesystem"
transport = { type = "stdio", command = "npx", args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"] }

[[mcp_servers]]
name = "github"
transport = { type = "stdio", command = "npx", args = ["-y", "@modelcontextprotocol/server-github"] }

# A2A 配置
[a2a]
enabled = true
listen_path = "/a2a"

CLI 命令

# 列出 MCP 服务器
librefang mcp list

# 测试 MCP 服务器
librefang mcp test filesystem

# 启动 MCP 模式
librefang mcp