关于 x402 Merchant Check
x402 Merchant Check 是供 Agent 在付款前评估陌生 x402 商家的 MCP 服务。它依据可观测的链上行为返回机器可读信号,作为付费决策参考,而非安全保证。
主要功能
- 01链上商家信号检查
- 02可靠性与价格参考
- 03覆盖 Base 和 Solana
- 04通过 Base x402 付费检查
适用场景
- 01Agent 付款前核查商家
- 02依据可用数据比较商家价格
MCP 相关
通过 `https://mcp.gradientdecisions.com/mcp` 向 MCP 客户端提供付费工具 `check_merchant`。每次检查通过 Base 上的 x402 支付 $0.01。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://mcp.gradientdecisions.com/mcpclaude mcp add --transport http com-gradientdecisions-merchant-check https://mcp.gradientdecisions.com/mcp{"mcpServers":{"com-gradientdecisions-merchant-check":{"type":"http","url":"https://mcp.gradientdecisions.com/mcp"}}}在 Cursor 中打开{"servers":{"com-gradientdecisions-merchant-check":{"type":"http","url":"https://mcp.gradientdecisions.com/mcp"}}}在 VS Code 中打开{"mcpServers":{"com-gradientdecisions-merchant-check":{"type":"http","url":"https://mcp.gradientdecisions.com/mcp"}}}{"mcpServers":{"com-gradientdecisions-merchant-check":{"serverUrl":"https://mcp.gradientdecisions.com/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
1| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| asset | string | 否 | 可选。402 响应的 accepts[].asset 中的支付资产/代币。评分不需要此项。 |
| price | number | 否 | 待核查公平性的美元等值报价 |
| amount | string | 否 | 可选。如果优先于 `price`,可填写 402 响应的 accepts[].amount 中的原子单位支付金额。评分不需要此项。 |
| network | string | 否 | 可选。402 响应的 accepts[].network 中的 CAIP-2 网络 ID(例如 "eip155:8453");如果已知则可填写。评分不需要此项。 |
| service_url | string | 否 | 可选。Agent 尝试访问的资源/服务 URL。评分不需要此项。 |
| resource_type | string | 否 | 已弃用,接受但不使用。现在,价格公平性会自动与商家自身从链上数据推导出的类别进行比较,无需提供此项。 |
| merchant_wallet_address | string | 是 | 商家的收款钱包地址:Base(0x...)或 Solana(base58)地址。通常可直接从 402 Payment Required 响应的 accepts[].payTo 中提取。 |
详细介绍
merchant-check-mcp
Gradient Decisions 为自主商务提供商家情报。 x402 Merchant Check 会评估可观测的链上支付行为,帮助 Agent 在向不熟悉的 x402 商家付款前做出更明智的决策。面向 Agent 原生设计、机器可读、原生支持 x402——每次检查收费 $0.01,通过 x402 支付。这是一项付款前决策基础功能,并非认证、安全保证,也不能替代 Agent 自身的支付策略。完整的发现 → 检查 → 决策 → 付款流程、从 402 响应到输入的映射,以及准确的响应格式,请参阅 INTEGRATION.md。有关数据处理方式,请参阅隐私政策。本文记录了已实际构建的内容、尚未实现的内容,以及在继续推进前需要你完成的事项。
隐私政策
现已上线于 gradientdecisions.com/privacy(src/privacy.ts),并在首页页脚和本文中提供链接。由 Claude 于 2026-08-13 应直接请求起草,内容依据本系统实际的数据处理实践(query_log 和 merchant_signals 实际存储的内容——见上文“内部调用方跟踪仪表板”),而非通用套话。未经律师审核——页面本身已注明这一点,在完成审核前应保留该说明。联系邮箱为 [email protected](于 2026-08-19 从开发者的个人地址切换而来——原因和做法见下文“数据访问政策”)。如果之后需要再次更改,src/privacy.ts 中的 CONTACT_EMAIL 常量是权威来源。
DISCOVER → IDENTIFY PAYMENT DESTINATION → GRADIENT MERCHANT CHECK → AGENT PAYMENT POLICY → X402 PAYMENT
状态:两个在线入口,一款产品
- Agent:
https://mcp.gradientdecisions.com/mcp— 付费的check_merchantMCP 工具。已在 Base 主网上线,使用真实 USDC (已从 Base Sepolia 测试网切换——见“转向主网”;query_log中已确认真实结算的付款,Base 上也有真实交易哈希)。商家覆盖范围包括 Base 和 Solana(PayAI/Helius)——见“Solana 数据源”。已端到端验证支付流程:免费发现tools/list→tools/call正确返回 402 → Agent 构建并签署 x402 支付、提交付款 → Facilitator 完成结算 → 返回真实等级。参见下文“亲自试用”。 - 人类用户:
https://gradientdecisions.com— 截至 2026-08-19,是一个极简静态占位页(src/homePlaceholder.ts),并非在线仪表板。它过去会直接展示每个已评分商家的建议、信号和定价,还会在/api/wallets提供同一数据集的原始 JSON——为何要锁定这些内容,见下文“数据访问政策”。重新设计的正式首页/仪表板是另一个后续任务,将根据 Figma 设计稿构建,而非使用此占位页。
注意:上述双链商家信号数据与此端点自身的支付通道相互独立——check_merchant 本身仍仅通过 Base x402 付费(见上文“支付流程”);这一点没有变化。
数据访问政策(2026-08-19)
结构化商户数据——recommendation、signals、pricing、
reasons、platforms,以及 check_merchant 返回的任何内容——
只能通过付费的 check_merchant MCP 工具获取,按次通过
x402 付费。这并非一直如此,值得明确记录这一变更:
/和/dashboard过去会呈现完整的实时仪表板:每个经过评分的 商户及其推荐、信号、原因和定价,直接来自 D1。现在改为静态占位页面(src/homePlaceholder.ts), 完全不查询 D1。/api/wallets过去会以原始 JSON 提供完全相同的数据集——无需 渲染、没有限流,而且很容易编写脚本调用。现在,无论何种方法和子路径 (/api/wallets/、?query查询字符串、HEAD、OPTIONS、POST,等等), 都会返回404,并附带Cache-Control: no-store,且不包含 CORS 标头。/merchant/<address>(逐个商户的资料页,于 2026-08-18 添加—— 参见 git 历史记录)也以相同方式退役,理由相同:它会返回某个钱包的 相同字段,而调用check_merchant的 Agent 已经有了该地址,本可免费读取 该页面,而不必付费。代码仍保留在src/merchantProfile.ts中,未使用也未删除, 以便未来的付费版或精选版可以复用。
这三项功能之所以存在,是因为让用户/Agent 免费读取相同的数据,而
check_merchant 对每次查询收费 $0.01,直接损害了产品的业务。
check_merchant 本身(src/tool.ts)从未受到影响——它始终通过
src/db/queries.ts(getMerchantSignals、getComparablePrices、
getOwnPrices)直接读取 D1,从不经过上述任何路由,因此这些变更无需触及
付费工具的逻辑或架构。
联系邮箱。 隐私政策中的 CONTACT_EMAIL(src/privacy.ts)
是 [email protected],取代了过去出现在该文件和其他位置的开发者个人
Gmail 地址。它已设置为指向同一个人收件箱的 Cloudflare Email Routing 转发——
发送至 info@ 的邮件会抵达现有 Gmail,但个人邮箱地址本身不再出现在任何公开页面
或已提交的代码中。Email Routing 仅支持接收和转发:它无法以
[email protected] 的身份发送邮件。如果将来需要以该地址发送邮件,
那是另一项需要手动完成的工作(Google Workspace 或自定义 SMTP),不是 Email Routing
所能实现的。
要启用此功能(需在 Cloudflare 控制面板中操作,因为本次会话没有 Cloudflare API 令牌,无法通过 API 执行——参见本节随附的 PR/提交中的“手动步骤”说明):
- Cloudflare 控制面板 →
gradientdecisions.com区域 → Email → Email Routing。 - 为该区域启用 Email Routing。Cloudflare 会自动添加所需的
MX记录(指向其自身的邮件服务器)和TXT记录 (v=spf1 include:_spf.mx.cloudflare.net ~all;如果已有 SPF 记录,则会合并到其中)——对于 DNS 已由 Cloudflare 管理的区域,无需手动添加 DNS 记录(本区域符合这一情况,因为wrangler.toml中的custom_domain = true已将该区域配置为使用 Cloudflare DNS)。 - 添加一条路由规则:
[email protected]→ Destination address → 现有的个人 Gmail 地址。 - 首次将该 Gmail 地址添加为目标地址时,Cloudflare 会向其发送验证邮件——
需要有人点击其中的链接,无法通过程序完成。在点击链接之前,发送至
info@的邮件不会实际转发。
其他支付协议(调研于 2026-08-19)
Cloudflare 的 Agent Readiness「Commerce」清单(ACP、AP2、 MPP、UCP、x402)促使我们展开调研——用户明确表示不在乎使用哪种协议, 只要 Agent 确实能够付款即可,因此我们在做出决定前,均根据实际规范 逐一调研,而非想当然地判断。
已发布:GET /check(src/httpCheckEndpoint.ts)——一个纯 HTTP 的 x402
接口,镜像 check_merchant,并复用与 /mcp 完全相同的 facilitator/结算
路径。完整理由请参阅该文件的模块注释;简而言之,MCP 路径的 402 挑战位于
JSON-RPC 响应正文中,任何只发送裸 GET 的纯 HTTP 客户端(包括 Cloudflare 自己的
扫描器)都看不到。
已调研,未实现:
-
ACP(Agentic Commerce Protocol — OpenAI/Stripe)和 UCP (Universal Commerce Protocol — Google/Shopify/Etsy/Walmart):两者都是完整的零售结账标准,涵盖购物车构建、委托支付 令牌、税费/配送、Google Pay 等凭据提供方。 它们用于从商品目录中购买商品,而非按计量 API 调用付费。我们通过实际规范(agenticcommerce.dev、 developers.openai.com/commerce、orkes.io/blog/universal-commerce-protocol-ucp-explained)确认了这一点: 形态不匹配是结构性的,并非多投入一些实现工作就能解决。
-
AP2(Agent Payments Protocol — Google/PayPal):它确实支持 MCP 作为传输方式,并非 Cloudflare 扫描器所暗示的仅支持 A2A。其 核心机制是经过加密签名的「mandates」——用于验证用户是否已委托 Agent 代其购买的凭证。这项协议真实且有意思,但解决的是另一个问题(用户委托 Agent 代购时的授权同意),而不是本服务采用的模式(Agent 直接付款,无需用户委托层)。 这本身就是一个完整项目,不适合在同一会话中顺手添加。
-
MPP(Machine Payments Protocol — Tempo/Stripe,mpp.dev):概念上最接近我们的需求——其文档称可用于「为 API 调用付费…… 购买数据访问权限」,其
usdc计费方式使用 EIP-3009 transferWithAuthorization,与 x402 的exact方案使用相同的签名原语。阻碍在于:MPP 规范没有 facilitator 模型——不同于 x402(Coinbase 的 CDP facilitator 代我们提交/结算,服务端不会接触私钥),MPP 要求 商家自己的服务端在链上提交交易,并直接支付 Gas。实际实现需要:- 一个专用 EVM 钱包,并为其充值 ETH 以支付 Gas;该钱包需作为新密钥保管 ——这会带来全新的运维责任(保持钱包余额充足),而 x402 无需我们托管签名密钥。
- 服务端中继代码:解析
Authorization: Payment凭据,通过 RPC 提供商提交transferWithAuthorization调用,轮询确认状态,并处理重试/失败。 - 发布前进行正式安全审查——这是直接托管资金、转移资金的代码,与 x402 经 facilitator 处理的模式截然不同。
- 在现有 x402 请求头之外,处理
WWW-Authenticate: Payment/Authorization: Payment请求头 (RFC draft-ryan-httpauth-payment)。
这是一个需要多天完成的基础设施项目(钱包充值、中继、监控、安全审查),而非配置变更。建议: 等
GET /check或/mcp流量显示出真实的自然需求后再重新评估——query_log目前显示自然产生的付费使用量接近于零(参见调用方分析),因此暂时没有理由增加第二条支付通道。此项已在此列为正式路线图事项,并未放弃。
分类
这是对信任等级评分的补充,不会触及 src/scoring.ts。每个商家都会从固定的六个值中获得一个 category(src/categorize/types.ts:data_api、compute、content_generation、financial_data、storage、other)——绝不会使用集合之外的值;流水线无法有把握地归类的内容会归入 other,并记录到 category_review_log 以便抽查,而不是猜测。
分两轮处理,每个钱包首次摄取时运行一次(不遵循每 2 小时一次的信任信号周期——另有每月 cron 强制重新处理所有钱包,以防商品描述发生变化,见 wrangler.toml):
- 规则(
src/categorize/rules.ts)——根据 Bazaar 商品文本进行关键词匹配。仅当恰好匹配一个类别时才视为有把握;匹配零个或多个类别(有歧义)时,会进入第 2 轮。 - 模型(
src/categorize/model.ts)——向 Claude Haiku(claude-haiku-4-5-20251001)提供固定类别列表和描述,并要求返回恰好一个值。使用前会根据固定集合验证响应——绝不盲目信任;任何无法解析的结果都会归入other并记录。
ANTHROPIC_API_KEY(wrangler secret put ANTHROPIC_API_KEY)用于第 2 轮——没有此密钥时,所有未被规则匹配的描述都会直接归入 other(记录为 other_model_unavailable,不会默默猜测)。该密钥已设置,并于 2026-08-11 确认可正常工作。POST /categorize(由 admin-token 保护,模式与 /refresh 相同)会处理积压数据——?force=true 会重新分类所有数据,?limit=N 会限制每次调用处理的数据量(默认 200),因为对数百个钱包进行完整强制处理可能会超出单次 Worker 调用的执行时间(已通过手动分 4 批、每批 100 个确认)。
在生产环境实际运行后发现了两个真实问题,并非通过检查发现——两者均已修复并重新部署:
- 规则匹配使用普通子字符串检查,会在
"computer vision"中误匹配"compute",也会在"research"中匹配独立的"search"。两个真实商品(“Tavily Search”和“Exa /search endpoint”)此前因进入第 2 轮而被模型归类为content_generation,但本应通过规则明确归类为data_api。现已改为使用单词边界正则匹配,取代.includes()。 runCategorization的remaining计数在force=true时有误:由于该模式的 WHERE 子句不会排除已处理的行,简单重新计数每次都会报告总数——实际运行 4 批后,观察到它始终报告相同数量,而这 4 批实际上分别处理了不同的钱包(通过category_updated_at分布确认)。现已改为在每次运行前记录时间戳,并统计时间戳仍早于该值的行。
当前线上分布(2026-08-11,共 375 个真实商家):data_api 145 个,other 93 个(现在确实存在歧义,而非“模型不可用”),financial_data 59 个,content_generation 45 个,compute 21 个,storage 4 个,另有 8 个商家合理地未分类(数据已过时,自上次刷新后已从 Bazaar 下架——按设计不作处理,并非错误)。
亲自试试看
npm run demo
**截至 2026-08-11 已过时 — 部署的服务现在只接受 Base 主网
**(X402_NETWORK = eip155:8453,参见“切换到主网”)。此脚本
仍会在 Base Sepolia 上付款,因此无法用于线上部署;
保留此脚本供参考,也供在本地测试采用测试网配置的分支时使用。
如需进行真实且可用的线上测试,请参见下方的“主网实时付款测试”。
使用 .env.demo 中的一次性密钥对(已加入 gitignore,仅用于测试网,不具备
任何实际价值)。如需解决“余额不足”的问题,并在测试网部署上看到实际付费后
返回的 trusted/avoid 响应:
- 获取付款方地址:
.env.demo中的DEMO_PAYER_ADDRESS(0x9AaF5bB90307bacb9cB60f54c1be2B65B0771282)。 - 为其充值 Base Sepolia 测试 USDC: faucet.circle.com(选择 Base Sepolia)。
- 重新运行
npm run demo。两个预置钱包 (0x1111...11d1/0x2222...22d2,为本次演示直接插入 D1 —— 参见下方的“演示数据”)应分别返回trusted和avoid, 并附带真实的结算交易哈希。
主网实时付款测试
真实资金。 scripts/mainnet-live-test.ts 会针对线上主网部署进行一笔真实的 0.01 美元 x402
付款 ——
这是结算确实端到端正常工作的实际证明,而不仅仅是证明 facilitator 配置解析正确(这点此前已单独验证,未花费
任何资金)。该脚本不会接入 npm run demo 或其他任何默认命令 ——
只能通过显式运行 npm run mainnet-test 来执行,而且只能使用你通过本地、已加入 gitignore 的
.env.mainnet-test 文件提供的私钥。该文件绝不会离开你的机器(与本项目中
所有其他密钥的处理方式相同 — 我不会生成、持有或接触它)。
cd "/Users/colincleven/Documents/merchant-check-mcp"
# 1. Generate a fresh throwaway keypair (runs locally, nothing sent anywhere)
PATH="/Users/colincleven/.nvm/versions/node/v24.15.0/bin:$PATH" node --input-type=module -e "
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
const k = generatePrivateKey();
const a = privateKeyToAccount(k);
console.log('address:', a.address);
console.log('private key:', k);
"
# 2. Save the private key locally (paste the value the command above printed)
cat > .env.mainnet-test << 'EOF'
MAINNET_PAYER_PRIVATE_KEY=paste_the_private_key_here
EOF
# 3. Send a small amount of real USDC on Base mainnet (e.g. $0.05) to the
# "address:" printed in step 1, from your own wallet/exchange.
# 4. Run the real test
PATH="/Users/colincleven/.nvm/versions/node/v24.15.0/bin:$PATH" npm run mainnet-test
默认会检查实时数据集中的一个可信等级商家
(0xffc458db291b4abce020fe3de4f91f2770e537b1)— 可通过
TEST_MERCHANT_WALLET=0x... npm run mainnet-test 覆盖。成功后会输出真实的
交易哈希和 BaseScan 链接。
演示数据
merchant_signals 中有两条合成记录,是我通过 wrangler d1 execute --remote
直接插入的 — 地址明显为伪造
(0x11111111111111111111111111111111111111d1、
0x22222222222222222222222222222222222222d2),并非真实商家;由于目前仍没有真实的 avoid 示例(参见下方的“仍需完成”),因此保留了这些记录。它们标记为
is_demo = 1,并且标记为 network = 'eip155:84532'
(Base Sepolia — 演示/测试付款流程实际运行的网络,
而每条真实 BazaarDataSource 记录使用的都是 eip155:8453,即 Base 主网)。特意使用两个
独立列:is_demo 标记伪造记录,network 标记真实记录来自哪个链 ——
它们要防范的是不同的故障模式(合成数据与真实测试网数据泄漏到主网数据集),因此都明确保留,而不是合并成一个标记。目前两个筛选条件(WHERE is_demo = 0 AND network = 'eip155:8453')会产生
完全相同的结果集,但只要有任何真实数据源能够观测到测试网活动,情况就会改变。这些记录不会出现在公共仪表板和
/api/wallets 中(src/dashboard.ts)— gradientdecisions.com 只会显示真实主网数据 ——
但 check_merchant
仍能看到它们,因此 npm run demo 会继续覆盖全部三个等级。
(早期版本中,这两个地址的十六进制字符数是 38,而不是
40 — isValidWalletAddress 自带的正则表达式会拒绝它们,因此每次演示调用都会返回“不是有效的 EVM 地址”,尽管付款已正常结算。请用程序验证
地址长度,不要凭肉眼判断 — 参见 git 历史记录。)
部署期间发现并修复的已知问题
src/index.ts 中 createPaymentWrapper 配置的 resource.serviceName
必须仅包含可打印的 ASCII 字符(不能使用 em dash),且长度不得超过 32 个字符 —
@x402/core 的 ResourceInfoSchema 会对其他内容返回 ZodError,
这会悄无声息地导致客户端无法检测到需要付款的情况(格式错误的响应看起来只是一个不起作用的错误结果,而不是值得自动付款的内容)。这是通过针对线上部署进行测试发现的,并非来自任何文档 — 如果你要在其他地方添加更多资源元数据,值得留意这一点。
剩余的一次性账户设置(已完成)
过时的标题,为保留历史记录而留。 本次会话中每次部署都会失败的 workers.dev 子域名阻碍问题(wrangler 报告账户需要先设置子域名,才能关联 cron 触发器)已于 2026-08-18 解决 — 访问一次 Cloudflare 控制面板的 Workers 部分后,系统便为账户配置了子域名。已通过线上执行 wrangler deploy 确认:
schedule: 0 */4 * * *
schedule: 0 0 1 * *
现在两个 cron 触发器都能正常关联。上述部署输出所示,当时刷新 Worker 每 4 小时运行一次;2026-08-19 已改为每 2 小时一次(当前为 schedule: 0 */2 * * *,见 wrangler.toml)。
每月强制重新分类会在每月 1 日运行。在发现并修复此问题之前,数据已过期 6 天以上 — 即使 cron 正常运行,也值得定期检查主页上的 lastRefreshedAt(/api/wallets 已停用,见“数据访问策略”),与检查任何定时任务一样。
手动触发刷新
现在 cron 已关联,不再需要手动触发,但仍可用于在每 2 小时的计划间隔之外按需刷新(共享密钥请求头 X-Admin-Token):
curl -X POST https://mcp.gradientdecisions.com/refresh \
-H "X-Admin-Token: <your ADMIN_TOKEN>"
内部调用方追踪控制面板
于 2026-08-13 添加,与公开的商家评分控制面板分开。
GET /admin/callers(与 /refresh//metrics 使用相同的 X-Admin-Token 门控;附加 ?format=json 获取原始 JSON,附加 ?window=<seconds> 更改标题统计的时间窗口,默认 7 天)追踪 check_merchant 本身的使用情况 — 谁在调用它、调用频率如何、他们在检查什么 — 而非哪些商家得分较高。此页面未链接到公开网站。
用途(摘自添加此功能的请求原文):“这些调用方数据是已确认意向的真实依据(为检查商家而付款的钱包,正积极评估一笔真实交易),最终用于辅助买家侧行为细分,而不仅仅是商家评分。” 此处未实现这种细分 — 本次仅提供追踪和聚合基础设施(随时间变化的独立调用方数量、按钱包统计的查询频率,以区分一次性脚本与重复/活跃 Agent、所检查内容的类别分布、30 天保留期)。细分逻辑明确列为未来工作。
与本项目其他部分一致,不解析身份信息:此处的所有视图均以 payer_address(完成链上付款的钱包)为索引 — 不会将其解析为链下身份。完整设计说明见 src/callerDashboard.ts。
实现说明:此处主要是扩展现有的 query_log(/metrics 已使用),而非新建表 — 新增了两列 queried_category(从响应中获取的已检查商家类别)和 caller_supplied_price_atomic(调用方实际提供的值,来自 input.price),而非采用并行日志记录路径。
Phase 0 方案确认(技术栈兼容性)
已通过 Cloudflare 官方文档确认,并实际安装相关软件包并针对真实软件包进行类型检查(而非仅阅读相关资料):
- MCP 传输:
@modelcontextprotocol/sdk的WebStandardStreamableHTTPServerTransport— 基于 fetch()/Request/Response 的传输,其自身的 JSDoc 中包含一个 Cloudflare Workers 使用示例。可在 Workers 上原生运行,无需 polyfill。 - 支付:
@x402/core+@x402/evm+@x402/mcp— x402-foundation/Coinbase 官方 软件包(与协议规范本身的发布者相同)。不是x402-hono,也不是 Cloudflare 自家的agents软件包 — 详见下文“为何不使用 x402-hono”和“为何不使用 Cloudflare 的agents软件包”。 - 数据存储:D1(按需求说明)— 付款方钱包与价格观测数据之间需要关系型联接,KV 的键值模型无法满足这一需求。
最终结果:无需回退到 Vercel/Railway。Cloudflare Workers 可用于这两个部分。
为何不使用 x402-hono
x402-hono(以及通常的框架中间件系列)按 HTTP
路由进行拦截。MCP 会将每个 JSON-RPC 方法 — initialize、tools/list、
tools/call — 放在同一个 POST 端点上。拦截整个路由也会让 tools/list
受到付费墙限制,从而破坏 Agent 发现功能(需求说明中的 GTM 计划本身就依赖 Agent 能在决定付费前读取工具描述)。@x402/mcp 的 createPaymentWrapper 则会封装
特定工具处理程序,因此发现功能仍然免费,只有 check_merchant 的
实际执行才会计量收费。
为何不使用 Cloudflare 的 agents 软件包
最初的计划(核查之前)是使用 Cloudflare 的 agents 软件包
(createMcpHandler,基于 @modelcontextprotocol/server 构建)。
实际安装后发现,@x402/mcp 直接依赖于
@modelcontextprotocol/sdk(一个不同但相关的软件包),而且
没有关于 @x402/mcp 与 agents 的 MCP 服务封装器集成的文档。与其以无人记录的方式组合两个与支付相关的 SDK,不如直接使用 @modelcontextprotocol/sdk,这与
@x402/mcp 自身 README 中的示例完全一致。这里完全不依赖 agents。
平台 / 网站信息
新增于 2026-08-12。现在,check_merchant 和控制面板会显示商家钱包所对应的实际
资源 URL(以及发现信息流中提供的服务名称),例如 https://api.example.com/v1/weather,而不只是
原始钱包地址。这并非新增数据收集:Bazaar 和
PayAI 都已为每个列表返回一个 resource URL 和 serviceName,此前这些信息只会被合并到 bazaar_description 的自由文本内容中
(用于分类),除此之外都会被丢弃。MerchantSignalRow. platforms_json({url, serviceName} 的 JSON 数组,一个钱包可以对应多个
资源)现在会以结构化形式存储这些信息;CheckMerchantOutput.platforms
会将其提供给调用方;控制面板中的 Platform 列会直接链接到该信息。
在钱包至少被导入一次之前,该字段为 NULL/空 — 状态与 category 相同,且不会作为评分输入。
已构建的内容
src/index.ts— Worker 入口点,按路径名路由(而非主机名,因此在 workers.dev 回退 URL 上也能正常工作):/mcp通过WebStandardStreamableHTTPServerTransport,为check_merchant接入x402ResourceServer、ExactEvmScheme和createPaymentWrapper;/和/dashboard提供面向用户的仪表板;/api/wallets以 JSON 格式提供相同数据;/refresh提供受管理员权限保护的手动刷新触发器。src/dashboard.ts— 公开仪表板(托管于gradientdecisions.com,与位于mcp.gradientdecisions.com的 MCP 端点使用同一个 Worker)。采用独立的 HTML/CSS/JS,无外部依赖,支持浅色/深色模式,并提供客户端搜索和层级筛选。排除is_demo = 1的行——参见“演示数据”。src/tool.ts—check_merchant逻辑。仅读取 D1(merchant_signals、price_observations)——按照需求中预计算存储的要求,付费请求路径不访问链。src/scoring.ts— 基于规则的 v1 综合评分。每个reasons条目都对应一个命名信号;阈值是文件顶部的常量。src/refresh/index.ts— 定时 Worker(cron:每 2 小时运行一次,参见wrangler.toml),将原始活动汇总到merchant_signals行中。src/refresh/indexer.ts—ChainDataSource接口和BazaarDataSource,后者是针对公开 x402 Bazaar 的真实实现(并非存根)。参见下文“数据源”。src/refresh/solana-indexer.ts—PayAIDataSource,即BazaarDataSource的 Solana 对应实现:PayAI 发现 + 可选的 Helius 付款方多样性增强。参见下文“Solana 数据源”。src/chains.ts— 链检测/地址规范化逻辑,供所有处理钱包地址的模块共用。现在,存储中会在同一张表里存放 Base(0x 十六进制,不区分大小写)和 Solana(base58,区分大小写)钱包。db/schema.sql— D1 架构。已应用到在线远程merchant-signalsD1 数据库(wrangler d1 execute --remote)。scripts/backtest.ts+scripts/labeled-wallets.json— 需求要求的验证步骤,需在开始为真实查询收费之前完成。
有意保留的存根
- 信号 6(速度/脚本异常):根据你在 2026-08-10 的决定,买方钱包脚本流水线尚不存在,因此
src/refresh/index.ts中的detectVelocityAnomalyStub()始终返回“无异常”,而不会采用虚构的启发式规则,以免在无意间错误校准层级逻辑。该流水线建成后,请替换其函数体。 - 付款方聚类(信号 2 的聚类标志):同一文件中将其硬编码为 0。真正的实现需要检查付款方钱包是否共享资金来源,或是否在短时间内创建——在出现可供核查的真实数据源之前,这不在范围内。
- 钱包年龄(信号 1)和退款(信号 4):
BazaarDataSource无法获取这些信息(参见下文“数据源”)——因此,每个来自 Bazaar 的行都将wallet_age_days = null、refund_count = 0,目前scoreMerchant()不会为真实数据标记这两项。这些数据并非被伪造为“正常”,而是尚未测量。 - 价格方差(信号 5):原因相同——
BazaarDataSource从不填充price_observations,因此对于真实数据,computePriceVarianceFlag()始终返回 0。逻辑本身是真实的,且已接入;一旦有数据源能够填充该表,便可立即启用。
数据源
BazaarDataSource(src/refresh/indexer.ts)——真实数据源,并非桩实现。数据来自 x402 Bazaar,即 Coinbase 自有的服务发现目录
(GET https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources)——公开数据,无需账户或 API 密钥。已确认该服务在线:截至 2026-08-10,其中有约 14,500 个已注册资源;一次刷新运行从前 2,000 个资源中索引了 365 个独立的 Base
主网商户钱包,并根据 Coinbase 自有的商户近 30 天调用量和独立付款方指标,获取了真实的
total_tx_count / unique_payer_count。
实际范围限制(具体影响哪些信号,请参阅上文的“有意使用桩实现的部分”):仅覆盖在 Bazaar 上注册了资源的商户,不包括所有曾收到 x402 付款的钱包;没有首次活动时间戳;目录列表不提供结算完成情况或退款信息;Bazaar 的“资源”(API 端点)与 check_merchant 价格公平性检查所使用的商品/服务 resource_type 分类不对应。
要弥补这些缺口,需要使用 Coinbase 的 CDP 钱包历史记录 API(需要免费的 CDP 账户和 API 密钥,前往
portal.cdp.coinbase.com —
账户必须由你本人创建,我无法代办),或者自建链上索引器。这两者都属于后续工作,不会阻碍当前运行的任何功能。FixtureDataSource 也可用于在没有实时网络访问的情况下进行本地测试。
Solana 数据源
添加于 2026-08-12(需求:Solana 当前确实承载着真实、实时的 x402 交易量,而不只是测试网活动,因此它会像 Base 一样参与相同的信任等级评分——完整实现说明见 src/refresh/solana-indexer.ts)。包含两部分:
发现 — PayAIDataSource。 最初计划的简述是“Solana 的 x402 Agent Registry”,但这个名称并不对应真实的商家目录:solana.com/agent-registry 实际上是面向 买家 的 Agent 身份产品,而不是商家发现工具。真正功能相当的是 PayAI Network 的服务端发现信息流
(https://facilitator.payai.network/discovery/resources)——研究期间通过直接 curl 确认其处于实时运行状态:共 25,928 条项目,是 Base 与 Solana 混合目录,JSON 格式与 x402 Bazaar 的发现列表相同。免费、公开、无需密钥。与 Bazaar 不同,抽样的项目都没有 quality 字段——列表本身不提供调用量或独立付款方数量。
付款方多样性补充数据 — Helius(可选,HELIUS_API_KEY)。 用于弥补 PayAI 发现信息流留下的数据缺口。对于 PayAI 找到的每个 Solana 商家钱包,通过 Helius 的 Enhanced Transactions API 统计转入该钱包的真实 USDC(SPL)转账,并统计这些转账的唯一来源地址(mainnet.helius-rpc.com——不是会返回 401 的 api.helius.xyz;Helius 已将此端点迁移至其 RPC 主机,这是在一次实时刷新失败后才发现的,之后又对照其当前文档确认)。每次刷新最多处理 8 个钱包,每个钱包最多请求 1 页(100 笔交易)。这一上限 不是 Helius 免费套餐的限制(每月 1M 点数足以支持更多请求),而是此 Cloudflare 账户经实际确认的 Worker 子请求预算所致:每次调用约 50 次外部请求,由 BazaarDataSource(≤20 页)、PayAIDataSource 自身的发现分页(≤5 页)、Helius 补充数据,以及对首次出现的钱包调用 Anthropic 进行分类(在 src/refresh/index.ts 中单独限制为每次运行最多 8 个)共享——因为 runRefresh 会在同一次调用中依次运行所有数据源。此功能首次部署时,在这些限制尚未设置前,恰好触及了这一上限(Too many subrequests by single Worker invocation;通过 wrangler tail 在真实生产请求中捕获,并非模拟)。升级 Cloudflare Workers 套餐(Bundled/Paid 将上限提高到 1000)即可不再需要如此严格的预算——这确实是一个可选方案,但这里将其留给你作为账单决策,而没有直接执行。超出上限的钱包,或完全未设置 HELIUS_API_KEY 时,仍只保留 PayAI 的基础列表(通常为 0 次调用 / 0 个付款方)——scoreMerchant() 会将其视为数据不足,而不是任何方向的信任信号,更不会进行猜测。
为什么选择 Helius,而不是 x402scan 的付费 API。 x402scan(x402scan.com)是一个与区块链无关的 x402 浏览器,拥有自己的真实按次付费 API(通过 x402 每次调用 $0.01–0.02;已根据其 OpenAPI 规范确认),能够提供比 Helius 原始转账计数更丰富、专门针对 x402 的商家/交易数据。v1 阶段决定不采用它,理由具体明确,并非笼统地“避免付费数据”:使用它意味着这个后端需要按计划自主持有已充值的钱包并从中扣款,这是一项远超“每月 $5–15 是否合理”的托管/架构承诺。Helius 免费套餐现在就能提供真实、实时的 Solana 付款方多样性数据,无需承担这一承诺。这并非彻底排除该选项:待基于 Helius 的数据上线后,计划将其实际提供的数据与 x402scan 付费 API 能额外提供的内容进行比较,以真实数据而非猜测为依据,再重新评估——更完整的理由见 2026-08-12 的对话记录(商业模式对称性这一点:聚合器自身收取数据费用,并不意味着向上游支付象征性费用来获取更好的数据就不合理——尚待回答的问题是,相比已经免费的数据,它能带来多少边际价值,而不是原则上是否可以付费)。
价格公平性说明
引用自 src/scoring.ts 中 scorePriceFairness 的注释。2026-08-13 发现了两个实际情况;采取行动前都对照了实时生产数据进行核实,并非主观臆断:
- 经真实数据确认,原先以中位数为基准 ±25% 的「公平」区间有误。 检查了全部六个类别的实时价格分布(392 个有定价的商家):只有 19% 被判定为「公平」,「偏高」和「偏低」的比例各约为其 2 倍——并非大多数商家的定价确实有误,而是实际类别价格分布远超 ±25% 的范围。仅
data_api的价格就从 $0.001 到 $5.12(跨度超过 5000 倍);每个类别的实际四分位距(p25–p75)在中位数的上下方向各约覆盖 2–5 倍。根据观察到的四分位距而非猜测,将HIGH_PRICE_RATIO/LOW_PRICE_RATIO重新校准为 3.0 倍/0.35 倍;用同一份实时数据测试后,「公平」占比达到 52%,「偏高」和「偏低」数量也大致平衡(99/91)。其认知可靠性与上文的付款方多样性修复相同:以真实数据为依据,但并未证明这是最优方案。 - 这并未彻底解决根本原因——类别只是粗略的分类。 六个类别(
data_api、compute、content_generation、financial_data、storage、other)都包含了性质截然不同、自然价格区间也不同的资源(例如,简单的查询 API 和复杂的实时分析 API 都可能属于data_api)。扩大比例区间能减少错误标记为偏高或偏低的情况,但无法让「不论实际功能如何,都与其他所有data_api商家进行比较」成为精确的比较——在(如果将来会有的话)出现更精细的资源分类体系、以便在同类资源之间进行比较之前,应将price_fairness/pricing.fairness_vs_category视为粗略指标,而非精确估值。
Solana 信号注意事项
摘自 db/schema.sql 中 velocity_anomaly_flag 的注释,以及 src/scoring.ts 中逐信号的跨链读取逻辑,现汇总于此:
- Helius 统计的是所有 USDC 转账,并非专门统计 x402 付款。 商家通过其他渠道(直接转账或无关的支付应用)收到 USDC,也会被视为额外的 x402 交易量。Bazaar 在 Base 上存在相反的统计缺口(少计——它只能看到已注册的商品)。两种来源都不是真实基准;二者都是有文档记录的近似值。
- 对于 Solana 数据行,
firstSeenAt受时间窗口限制,并不代表钱包的真实使用年限——它只反映 90 天回溯期内、Helius 每个钱包最多 3 页数据中最早的转账。这与 Bazaar 来源的 Base 数据行在此信号上已有的「为空或近似」权衡相同(Bazaar 完全不提供首次活动时间戳)。 - 手续费支付方赞助并非错误归因风险。 Solana 的 x402「exact」方案会通过密码学方式确保手续费支付方不能成为转账来源、授权方或目的地(参见
x402-foundation/x402中的specs/schemes/exact/scheme_exact.md)——每笔被计入的转账,其来源地址始终是真正的付款方,而不是赞助交易的中继方。此结论直接根据规范验证,并非推测。 - 信号 6(速度/工具链突破)是已知即使实现后也无法迁移的唯一信号,而不只是像 Base 上那样目前尚未实现。Solana 的结算速度大约快 4 倍(约 0.5 秒,而 Base 约为 2 秒)——针对 Base 流量调校的交易频率阈值会把完全正常的 Solana 活动过度标记为异常。构建此信号的人需要使用针对 Solana 的阈值,而不是直接沿用未经调整的 Base 阈值。
- 信号 2 的多样性比率阈值(
src/scoring.ts中的LOW_PAYER_DIVERSITY_RATIO = 0.3)是按 Base 校准的,并未经过跨链验证。 比率本身(unique_payers / total_tx)是无量纲的,原则上应该可以迁移,但具体阈值仅根据 365 家真实的 Base Bazaar 商家推导得出。在积累足够多经 Helius 补充的 Solana 数据行、并据此对照真实 Solana 付款方分布进行验证之前,应将其视为一个假设。 - 各链的 USDC 小数位数一致(均为 6)——已与 x402scan 自身的 facilitator 常量及 Solana 官方 USDC 铸币地址注册表交叉核对——因此,
db/queries.ts getComparablePrices中的原子单位价格比较可以在 Base 和 Solana 之间、同一类别内直接使用,无需任何换算。这是唯一有意合并进行跨链比较、而非分别处理的地方——具体原因见该函数自身的注释。
已完成与仍需完成
2026-08-12 更正——此列表大部分内容已过时,编写时部署仍仅限测试网;下方几项「仍需完成」的事项其实已完成,但列表一直未更新。重写前已根据线上部署进行核实,并非仅凭记忆修改。 已完成:
- ✅ 已完成 Cloudflare 账户身份验证(
wrangler login,使用开发者自己的账户)。 - ✅ 已创建 D1 数据库(
merchant-signals),并已在远程应用架构。 - ✅ 已在 Base 主网运行,使用真实的
PAYOUT_ADDRESS——已通过真实的主网结算交易确认,query_log中记录了真实交易哈希(我从未看到或处理过实际地址值,这与本项目一贯的安全实践一致)。 - ✅ 已部署到
mcp.gradientdecisions.com(由于该区域已在此 Cloudflare 账户中,wrangler.toml中的custom_domain = true会自动配置 DNS 和 SSL)。 - ✅ 已向
merchant_signals写入两条合成演示数据(见“演示数据”)。 - ✅ 已接入并运行真实数据源:
BazaarDataSource从公开的 x402 Bazaar 索引真实的 Base 主网商户钱包(无需账户)。 - ✅ 回测通过真实数据验证:2/2 个案例(
trusted+caution,均为真实的 Bazaar 商户——见scripts/labeled-wallets.json)。avoid仍明确未经验证——目前不存在真实的不良行为者数据源(关于为何历史记录稀少的钱包不能作为有效替代,请参见该文件中的_avoid_bucket)——这确实仍是待解决事项,此处未作修正。 - ✅ 已将 Solana 添加为第二个数据源(2026-08-12):已将
chain列迁移到远程 D1;已在runRefresh()中将PayAIDataSource和 Helius 与BazaarDataSource接入;check_merchant输出和仪表板现在都显示chain;工具描述已重写,以便通过语义意图匹配,并提及两条链;输出和仪表板现已显示platforms(商户网站/API URL)——参见上文“Solana 数据源”和“平台/网站信息”。 - ✅ 已设置并确认
HELIUS_API_KEY正常工作——真实的 Solana 付款方多样性数据正在流入(见“Solana 数据源”)。 - ✅ 已于 2026-08-11 设置
ANTHROPIC_API_KEY,确认正常工作,并已处理分类积压数据。 - ✅ 已提交至注册表——现已上线官方 MCP 注册表(
com.gradientdecisions/merchant-check),并因此被第三方目录(例如 mcp.so)收录。值得定期检查:这些目录可能会缓存描述,无法立即反映此处对 README/工具所做的更改——如果目录中的内容看起来过时,可能是对方缓存延迟,并不一定表示此处的源内容过时,但请检查两者。 - ✅ 已于 2026-08-18 启用
workers.dev子域名——部署时现在会附加 cron 触发器(已确认上线,见上文“剩余的一次性账户设置”)。在发现此问题之前,数据已悄然过期 6 天以上;现在每 2 小时自动刷新一次,应能防止再次发生,但仍值得抽查lastRefreshedAt。
仍确实需要完成:
- 一个真实的
avoid案例用于回测——需要找到真正针对 x402 的不良行为者数据源(目前尚未公开找到——这项技术太新),或随着时间推移积累足够的真实使用数据,以便自然观察到此类案例。 - Robinhood Chain 校准数据源——已于 2026-08-13 明确延期,并未遗忘。Chain ID 4663(Arbitrum Orbit L2,自 2026-07-01 起上线)及其 Virtuals Protocol / Agentic Accounts 生态确实存在,且已确认;但在构建之前,仍缺少两项条件:(a) 用于识别 Agent 钱包的真实且经过验证的合约地址——公开搜索只找到了看似非官方的仿冒代币,它们借用了“Agentic”品牌,并非真正的 Robinhood/Virtuals 基础设施;(b) 声明的用途是针对“目前在 Base 上运行的相同逻辑”校准 harness-break 检测,但目前还没有真实的基准——信号 6 在 Base 上仍是永久存根(
detectVelocityAnomalyStub,始终为 0)。找到提供已验证地址的真实联系人,或先真正实现信号 6,即可解除这项阻碍。
转到主网(已完成)
这是过时的标题,仅为保留历史记录。 wrangler.toml 中的 X402_NETWORK
是 "eip155:8453"(Base 主网),而不是本节最初描述的 Sepolia
测试网值。PAYOUT_ADDRESS 会收到真实调用方支付的 USDC;query_log 中有真实结算的 Base 主网
交易及真实交易哈希,并已直接通过远程 D1 确认(见上文“亲自试试”)。如果将来需要为本地开发切回测试网,
要修改的仍是本节最初说明的那一行 X402_NETWORK,只是反向改回即可。
本地开发限制(此机器)
wrangler dev / wrangler d1 execute --local 需要 workerd,而 workerd 要求
macOS 13.5.0 或更高版本;此机器运行的是 12.6.0,因此这里无法运行这两条命令。Schema 语法
改为直接使用 sqlite3 验证(D1 与 SQLite 兼容)——三个表都已成功创建。npm run typecheck 全程通过。实际
运行时测试(wrangler dev,然后使用真实 MCP 客户端访问 /mcp)
需要更新版本的 macOS、Linux devcontainer,或直接对
wrangler dev --remote / 已部署的 Worker 进行测试。
已知问题
query_log(用于查看使用量/收入,不是评分输入)会将 "settled"
作为占位值写入 tier_returned,而不是实际等级——@x402/mcp 的
onAfterSettlement 钩子无法获取工具处理函数的返回值,只能获取支付/结算信息。对于 v1 来说没问题;如果将来需要记录实际等级,请参见
src/index.ts 中的注释。
category 和 price_fairness 现在确实会传达给 Agent
截至 2026-08-11,category 只存在于 D1/仪表板中——
check_merchant 的响应从未包含它;而对于每个真实商家,price_fairness 一直是
"unknown" 占位值,因为从未有任何数据写入 price_observations。这两个问题都已修复:工具的
输出现在包含 category,而 price_fairness 会将商家的价格与其所属类别中的真实
同类商家进行比较(而不是使用旧的调用方提供的 resource_type;它从未填入过数据,因此保留在输入 Schema 中以保持
兼容性,并注明未使用)。
为了让这项功能在生产环境中真正端到端运行,共发现三个实际错误(每个错误都是通过 wrangler tail 观察真实失败请求发现的,
而非代码检查发现):
- 针对价格数据按钱包逐条写入 D1(每个资源最多执行一次 INSERT,有些钱包有 65 个以上资源)在一次刷新中处理约 370 个钱包时,超出了 D1 每次调用 1000 条查询的上限。修复方法是改为批量 操作——预先查询一次类别,而不是查询 370 次;价格行先累积在内存中,最后分成约 20 个分块语句写入。
- D1 实际的绑定参数上限约为每条语句 100 个,而不是 SQLite 通常的
999 个,并且 D1 自己的错误消息中没有说明这一点。
env.DB.batch()会将调用中所有语句的参数总数 一并计入同一个上限——把多个大型插入操作放在一起批处理并不能绕过限制,只有减少每条语句的绑定参数总数才行。 getComparablePrices按observed_at DESC排序以获取最新数据,但 同一次批量刷新中的每一行时间戳完全相同——对完全相同的排序键排序,会返回任意且没有代表性的 子集。一家真实商家的价格比其类别的真实中位数低 50 到 500 倍,返回结果却仍是"high",因为它被纳入的LIMIT 200子集本身就偏低。已通过ORDER BY RANDOM()修复。
已通过线上验证:真实 data_api 商家的类别会正确出现在
check_merchant 的输出中;根据该类别实际计算的中位数(约 $0.01,基于 735 条真实
可比较的观察数据),price_fairness 会正确判定为 fair/low/high。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。