agentpay-mcp 4.1.1 → 4.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -2
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/otel-budget.d.ts +265 -0
- package/dist/tools/otel-budget.d.ts.map +1 -0
- package/dist/tools/otel-budget.js +399 -0
- package/dist/tools/otel-budget.js.map +1 -0
- package/dist/tools/session.d.ts.map +1 -1
- package/dist/tools/session.js +2 -0
- package/dist/tools/session.js.map +1 -1
- package/dist/tools/x402.d.ts.map +1 -1
- package/dist/tools/x402.js +58 -0
- package/dist/tools/x402.js.map +1 -1
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +8 -1
- package/dist/utils/client.js.map +1 -1
- package/dist/utils/x402-networks.d.ts +12 -0
- package/dist/utils/x402-networks.d.ts.map +1 -0
- package/dist/utils/x402-networks.js +30 -0
- package/dist/utils/x402-networks.js.map +1 -0
- package/docs/channel-agent-affiliate-controls.md +142 -0
- package/docs/hitl-reference-architecture.md +140 -0
- package/docs/security-posture.md +74 -0
- package/docs/trust-architecture.md +127 -0
- package/docs/vercel-deployment-hardening.md +115 -0
- package/docs/whatsapp-smb-agent-controls.md +130 -0
- package/docs/x402-batch-settlement-channels.md +199 -0
- package/docs/x402-bazaar-observability.md +209 -0
- package/docs/x402-chain-drift-compatibility.md +63 -0
- package/docs/x402-mcp-funding-ux-benchmark.md +36 -0
- package/docs/x402-multi-sdk-batch-settlement-parity.md +167 -0
- package/docs/x402-scanner-readiness.md +110 -0
- package/docs/x402-tvm-readiness.md +53 -0
- package/package.json +6 -5
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Vercel deployment hardening for AgentPay MCP
|
|
2
|
+
|
|
3
|
+
Vercel's April 2026 security bulletin changed the threat model for agent teams. The incident started through a compromised third-party AI tool OAuth grant, then moved into Google Workspace and Vercel systems. Vercel reported that a limited set of non-sensitive environment variables were enumerated and decrypted, and it told affected customers to rotate readable env vars, review activity and deployments, and move secrets into sensitive environment variables.
|
|
4
|
+
|
|
5
|
+
AgentPay MCP should be deployed as if the host can leak readable configuration. Keep payment authority isolated, make every paid tool fail closed, and treat OAuth app grants as production access.
|
|
6
|
+
|
|
7
|
+
## 10-minute incident response checklist
|
|
8
|
+
|
|
9
|
+
Use this first if you deployed AgentPay MCP or any x402-capable agent on Vercel before April 24, 2026.
|
|
10
|
+
|
|
11
|
+
1. Rotate every readable Vercel environment variable that can reach money, data, or infrastructure. Include `AGENT_PRIVATE_KEY`, `AGENT_WALLET_ADDRESS`, `RPC_URL`, model provider keys, database URLs, OAuth client secrets, webhook secrets, and any internal API tokens.
|
|
12
|
+
2. Move secrets into Vercel sensitive environment variables. Anything that signs, pays, authenticates, deploys, or reads private data should not be readable from the dashboard or API after creation.
|
|
13
|
+
3. Audit Google Workspace OAuth grants for AI tools. Remove any app you don't recognize or can't tie to an owner. Vercel published the OAuth app ID `110671459871-30f1spbu0hptbs60cb4vsmv79i7bbvqj.apps.googleusercontent.com` as an indicator of compromise.
|
|
14
|
+
4. Review Vercel activity logs for environment variable reads, token changes, project setting changes, team membership changes, and unusual API access.
|
|
15
|
+
5. Review recent deployments. Delete deployments you didn't initiate, don't recognize, or can't explain from a linked commit and CI run.
|
|
16
|
+
6. Rotate Deployment Protection bypass tokens if they exist. Treat them like exposed credentials.
|
|
17
|
+
7. Verify Deployment Protection is at least Standard for every production project.
|
|
18
|
+
8. Re-deploy from a known clean commit after rotation. Don't trust a deployment that was built with stale credentials.
|
|
19
|
+
9. Re-run AgentPay MCP approval validation before reconnecting agents to paid tools.
|
|
20
|
+
10. Keep the old credentials disabled. Don't keep them as fallback values in preview or development projects.
|
|
21
|
+
|
|
22
|
+
## Vercel environment variable policy
|
|
23
|
+
|
|
24
|
+
Classify AgentPay MCP configuration by blast radius:
|
|
25
|
+
|
|
26
|
+
| Variable | Classification | Vercel setting | Rotation trigger |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| `AGENT_PRIVATE_KEY` | x402 signing key | Sensitive env var only | Any host, OAuth, CI, or dashboard compromise |
|
|
29
|
+
| `AGENT_WALLET_ADDRESS` | Public identifier | Readable allowed | Wallet migration or key rotation |
|
|
30
|
+
| `RPC_URL` with provider key | Paid infrastructure credential | Sensitive env var only | Any provider, Vercel, or CI exposure |
|
|
31
|
+
| `AGENTPAY_MCP_TOKEN` | MCP transport auth | Sensitive env var only | Any agent, app, or host compromise |
|
|
32
|
+
| Model provider API keys | Paid tool and data access | Sensitive env var only | Any env var read or OAuth compromise |
|
|
33
|
+
| Database URLs | Production data access | Sensitive env var only | Any env var read, deployment anomaly, or DB alert |
|
|
34
|
+
|
|
35
|
+
Rules:
|
|
36
|
+
|
|
37
|
+
- Don't store owner keys in Vercel. AgentPay MCP should use an agent hot wallet with on-chain spend limits, not the owner's treasury key.
|
|
38
|
+
- Don't put signing keys in client-side env vars. In Vercel, avoid any `NEXT_PUBLIC_` prefix for payment, API, OAuth, or database secrets.
|
|
39
|
+
- Don't copy production secrets into preview projects unless the preview deployment is protected and tied to the same incident response process.
|
|
40
|
+
- Use separate keys for development, preview, and production. A preview leak shouldn't become a production payment incident.
|
|
41
|
+
|
|
42
|
+
## x402 signing key isolation
|
|
43
|
+
|
|
44
|
+
AgentPay MCP is safest when the Vercel app does not hold unrestricted signing authority.
|
|
45
|
+
|
|
46
|
+
Recommended pattern:
|
|
47
|
+
|
|
48
|
+
1. Deploy AgentPay MCP as a separate service behind HTTPS with an auth token or mTLS-equivalent gateway.
|
|
49
|
+
2. Give the Vercel AI app only an `AGENTPAY_MCP_TOKEN` and MCP endpoint URL.
|
|
50
|
+
3. Keep `AGENT_PRIVATE_KEY` in the AgentPay MCP service, a KMS, or an HSM-backed signer. If it must run on Vercel, mark it as a sensitive environment variable and bind it to the smallest possible project scope.
|
|
51
|
+
4. Use an agent hot wallet with on-chain spend caps, recipient allowlists, and daily limits.
|
|
52
|
+
5. Keep owner or treasury keys outside the Vercel runtime entirely.
|
|
53
|
+
6. Log every signing attempt with `agent_id`, `task_id`, `tool_call_id`, amount, recipient, policy version, approval ID, and x402 receipt ID.
|
|
54
|
+
|
|
55
|
+
The invariant is simple: a leaked model key can burn credits; a leaked signing key can move funds. Treat them differently.
|
|
56
|
+
|
|
57
|
+
## Fail-closed paid tools with Vercel AI SDK approvals
|
|
58
|
+
|
|
59
|
+
The Vercel AI SDK approval flow is the correct place to stop paid tools before x402 signing. Use `needsApproval: true` or a dynamic `needsApproval` function for any tool that can spend money, change billing state, or call a paid API.
|
|
60
|
+
|
|
61
|
+
AgentPay MCP v4.1.3 already documents the native AI SDK approval bridge. Preserve this signing rule in production:
|
|
62
|
+
|
|
63
|
+
- `tool-approval-response` with `approved: true`: allow policy checks, then signing
|
|
64
|
+
- denied response: block signing
|
|
65
|
+
- missing response: block signing
|
|
66
|
+
- approval engine error: block signing
|
|
67
|
+
- policy engine error: block signing
|
|
68
|
+
- amount, recipient, chain, or payment header mismatch: block signing
|
|
69
|
+
|
|
70
|
+
Never let a model retry a denied paid tool until a human creates a new approval. Add a system instruction such as: `When a tool execution is not approved, do not retry the same paid call.`
|
|
71
|
+
|
|
72
|
+
Run the existing approval-validation test before deploy:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install
|
|
76
|
+
npm test -- tests/payments.test.ts
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This test suite covers approval queue behavior and must remain fail-closed: decline, cancel, missing approval, and incomplete approval paths must not reach signing.
|
|
80
|
+
|
|
81
|
+
## Activity and deployment review
|
|
82
|
+
|
|
83
|
+
After a Vercel security event, review these records before turning agents back on:
|
|
84
|
+
|
|
85
|
+
- Vercel account activity log for env var reads, project changes, team changes, token changes, and unusual API access
|
|
86
|
+
- deployment list for unexpected deploys, build sources, commit SHAs, domains, and aliases
|
|
87
|
+
- GitHub repository audit log for OAuth app grants, webhooks, deploy keys, and branch protection edits
|
|
88
|
+
- Google Workspace connected apps and OAuth grants for AI tools
|
|
89
|
+
- npm publish history if the deployment consumes internal packages
|
|
90
|
+
- AgentPay MCP transaction history for unexpected x402 attempts, blocked approvals, and policy denials
|
|
91
|
+
|
|
92
|
+
If the app paid anything during the exposure window, reconcile the x402 receipt IDs against approval IDs and policy versions. Any payment without an approval record should be treated as an incident until disproven.
|
|
93
|
+
|
|
94
|
+
## Pre-deploy gate
|
|
95
|
+
|
|
96
|
+
Do not ship a Vercel-hosted agent with paid tools until all of these pass:
|
|
97
|
+
|
|
98
|
+
- every secret value is sensitive, scoped by environment, and rotated after any exposure
|
|
99
|
+
- owner keys are absent from Vercel
|
|
100
|
+
- agent signing key is isolated to AgentPay MCP or a dedicated signer
|
|
101
|
+
- Vercel Deployment Protection is enabled for production
|
|
102
|
+
- Google Workspace OAuth grants have an owner and a business reason
|
|
103
|
+
- activity logs and deployment logs were reviewed after the last rotation
|
|
104
|
+
- AI SDK paid tools require approval before execution
|
|
105
|
+
- AgentPay MCP validation proves x402 signing is blocked until approval
|
|
106
|
+
- spend caps, recipient allowlists, daily limits, and kill switches are active
|
|
107
|
+
- audit records join approval ID, tool call ID, policy version, and x402 receipt ID
|
|
108
|
+
|
|
109
|
+
## References
|
|
110
|
+
|
|
111
|
+
- Vercel April 2026 security incident bulletin: https://vercel.com/kb/bulletin/vercel-april-2026-security-incident
|
|
112
|
+
- Vercel sensitive environment variables: https://vercel.com/docs/environment-variables/sensitive-environment-variables
|
|
113
|
+
- Vercel activity logs: https://vercel.com/docs/cli/activity
|
|
114
|
+
- Vercel AI SDK tool execution approval: https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#tool-execution-approval
|
|
115
|
+
- AgentPay MCP approval validation tests: ../tests/payments.test.ts
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# WhatsApp and SMB paid-agent controls with AgentPay MCP
|
|
2
|
+
|
|
3
|
+
WhatsApp agent management changes the buyer question. The problem is no longer only "can this gateway call a paid API?" It is "can a shop owner approve spend from the same channel where the agent works?"
|
|
4
|
+
|
|
5
|
+
AgentPay MCP should sit between the channel agent and the x402 or USDC endpoint.
|
|
6
|
+
|
|
7
|
+
## Reference flow
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Customer or operator in WhatsApp
|
|
11
|
+
-> WhatsApp agent runtime
|
|
12
|
+
-> paid action requested
|
|
13
|
+
-> AgentPay MCP policy check
|
|
14
|
+
-> approval card or decline path
|
|
15
|
+
-> x402 or USDC payment only after approval
|
|
16
|
+
-> audit row returned to the operator
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Control points
|
|
20
|
+
|
|
21
|
+
1. The WhatsApp agent asks to call a paid API, book a service, buy data, or trigger a USDC payment.
|
|
22
|
+
2. AgentPay MCP computes the spend decision before any wallet signature exists.
|
|
23
|
+
3. If the request needs approval, the channel shows a concise card:
|
|
24
|
+
- merchant or endpoint,
|
|
25
|
+
- amount,
|
|
26
|
+
- token and chain,
|
|
27
|
+
- reason the agent gave,
|
|
28
|
+
- daily cap remaining,
|
|
29
|
+
- approve, deny, or lower cap.
|
|
30
|
+
4. AgentPay MCP signs only after an approved decision.
|
|
31
|
+
5. The operator gets a receipt with transaction hash, URL, amount, policy version, and agent task ID.
|
|
32
|
+
|
|
33
|
+
## Policy shape
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"agent_id": "whatsapp-agent-42",
|
|
38
|
+
"channel": "whatsapp",
|
|
39
|
+
"daily_cap_usdc": "25.00",
|
|
40
|
+
"per_call_cap_usdc": "2.00",
|
|
41
|
+
"approval_required_above_usdc": "0.50",
|
|
42
|
+
"allowed_recipients": ["api.vendor.example"],
|
|
43
|
+
"allowed_chains": ["base", "base-sepolia"],
|
|
44
|
+
"audit_tags": ["smb", "whatsapp", "x402"]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Example approval card text
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
Approve paid agent action?
|
|
52
|
+
|
|
53
|
+
Agent: Inventory assistant
|
|
54
|
+
Endpoint: api.supplier.example/restock-check
|
|
55
|
+
Cost: 0.18 USDC on Base
|
|
56
|
+
Daily cap left: 14.72 USDC
|
|
57
|
+
Reason: Check supplier stock before replying to customer
|
|
58
|
+
|
|
59
|
+
[Approve once] [Deny] [Lower cap]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Failure behavior
|
|
63
|
+
|
|
64
|
+
- If the user denies, return a normal agent message and do not sign.
|
|
65
|
+
- If the cap is exceeded, fail closed and ask for a cap update.
|
|
66
|
+
- If the payment metadata is missing or malformed, do not guess.
|
|
67
|
+
- If the chain is unknown, route to the chain-drift watchlist before enabling payment.
|
|
68
|
+
- If the WhatsApp session expires, require a fresh approval.
|
|
69
|
+
|
|
70
|
+
## Audit row
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"timestamp": "2026-04-28T05:20:00Z",
|
|
75
|
+
"agent_id": "whatsapp-agent-42",
|
|
76
|
+
"task_id": "restock-check-1902",
|
|
77
|
+
"channel": "whatsapp",
|
|
78
|
+
"resource": "https://api.supplier.example/restock-check",
|
|
79
|
+
"amount_usdc": "0.18",
|
|
80
|
+
"chain": "base",
|
|
81
|
+
"approval_decision": "approved_once",
|
|
82
|
+
"policy_version": "2026-04-28.whatsapp-smb.v1",
|
|
83
|
+
"settlement_reference": "0x..."
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Acceptance criteria
|
|
88
|
+
|
|
89
|
+
- No channel agent can produce a wallet signature before AgentPay MCP returns an approved decision.
|
|
90
|
+
- Operators can set daily and per-call caps per WhatsApp agent.
|
|
91
|
+
- Every paid call joins channel context, payment metadata, policy version, and settlement reference.
|
|
92
|
+
- Deny, cancel, expired session, cap exceeded, and unknown chain paths all fail closed.
|
|
93
|
+
|
|
94
|
+
## Persona creation approval gate
|
|
95
|
+
|
|
96
|
+
Axon-style vertical personas make the first spend decision happen before the first paid API call. When a shop owner creates an inventory assistant, booking assistant, legal intake assistant, or support persona, AgentPay MCP should attach payment authority at creation time instead of waiting for the first x402 challenge.
|
|
97
|
+
|
|
98
|
+
Recommended creation flow:
|
|
99
|
+
|
|
100
|
+
1. Operator creates or edits a persona in the channel agent dashboard.
|
|
101
|
+
2. The dashboard asks AgentPay MCP for a spend profile before enabling paid tools.
|
|
102
|
+
3. AgentPay MCP returns default caps, chain allowlist, recipient allowlist, and approval threshold.
|
|
103
|
+
4. The operator approves the persona's payment authority in WhatsApp or the admin UI.
|
|
104
|
+
5. The runtime stores the persona ID, policy version, cap, approver, and allowed tool set.
|
|
105
|
+
6. Every later x402 or USDC call must reference the persona policy before signing.
|
|
106
|
+
|
|
107
|
+
Minimal policy attachment:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"persona_id": "inventory-assistant-ptbr",
|
|
112
|
+
"persona_vertical": "retail_inventory",
|
|
113
|
+
"created_by": "operator:shop-owner-17",
|
|
114
|
+
"approval_surface": "whatsapp",
|
|
115
|
+
"policy_version": "2026-04-29.persona-controls.v1",
|
|
116
|
+
"daily_cap_usdc": "10.00",
|
|
117
|
+
"per_call_cap_usdc": "0.25",
|
|
118
|
+
"approval_required_above_usdc": "0.05",
|
|
119
|
+
"allowed_tools": ["supplier_stock_lookup", "delivery_quote"],
|
|
120
|
+
"allowed_recipients": ["api.supplier.example", "api.delivery.example"],
|
|
121
|
+
"allowed_chains": ["base", "base-sepolia"]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Acceptance additions:
|
|
126
|
+
|
|
127
|
+
- Persona creation cannot enable paid tools without an attached AgentPay MCP policy.
|
|
128
|
+
- Persona edits that raise spend caps, add recipients, add chains, or add paid tools require fresh approval.
|
|
129
|
+
- WhatsApp receipts include both `agent_id` and `persona_id` so the owner can see which persona spent money.
|
|
130
|
+
- Revoking a persona immediately disables its payment authority, even if the agent runtime still has an active session.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# x402 batch-settlement channel compatibility for AgentPay MCP
|
|
2
|
+
|
|
3
|
+
x402 Foundation PR #2061 moves the TypeScript SDK toward batch-settlement channels for repeat paid calls. Instead of one paid request mapping to one immediate facilitator settle call, the client deposits once, signs cumulative vouchers off-chain, and lets the server claim or refund later.
|
|
4
|
+
|
|
5
|
+
That is good for low-latency paid MCP tools. It also changes the control surface. AgentPay MCP integrations must audit channel state, voucher caps, storage atomicity, corrective 402 recovery, and delayed settlement evidence before relying on batch-settlement for production tool calls.
|
|
6
|
+
|
|
7
|
+
## Compatibility goal
|
|
8
|
+
|
|
9
|
+
AgentPay MCP should treat batch-settlement as a channel lifecycle, not a single payment event.
|
|
10
|
+
|
|
11
|
+
A production integration is compatible only when it records and enforces these checkpoints:
|
|
12
|
+
|
|
13
|
+
1. Deposit created or topped up before the first paid call in the channel.
|
|
14
|
+
2. Voucher signed for each paid request with a cumulative maximum claimable amount.
|
|
15
|
+
3. Server verify path checks the voucher without settling every request.
|
|
16
|
+
4. Refund path closes unused channel balance with outstanding vouchers accounted for.
|
|
17
|
+
5. Claim path proves which cumulative voucher amount was claimed before final settle.
|
|
18
|
+
6. Storage uses an atomic compare-and-set or transaction boundary per `channelId`.
|
|
19
|
+
7. Corrective 402 recovery resyncs the client when cumulative state diverges.
|
|
20
|
+
8. Audit logs preserve off-chain voucher state and eventual on-chain settlement proof.
|
|
21
|
+
|
|
22
|
+
## Channel state AgentPay must persist
|
|
23
|
+
|
|
24
|
+
Batch-settlement adds durable per-channel state on both sides of the request.
|
|
25
|
+
|
|
26
|
+
AgentPay MCP should store a sanitized record keyed by `channelId`, `payer`, `receiver`, `token`, and `network`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"event_type": "x402_batch_channel_state",
|
|
31
|
+
"agent_id": "agent_123",
|
|
32
|
+
"task_id": "task_456",
|
|
33
|
+
"mcp_tool": "agentpay.x402_pay",
|
|
34
|
+
"channel_id": "0xchannel",
|
|
35
|
+
"network": "eip155:84532",
|
|
36
|
+
"token": "USDC",
|
|
37
|
+
"payer_hash": "sha256:...",
|
|
38
|
+
"payer_authorizer_hash": "sha256:...",
|
|
39
|
+
"receiver_hash": "sha256:...",
|
|
40
|
+
"receiver_authorizer_hash": "sha256:...",
|
|
41
|
+
"deposit_amount": "500000",
|
|
42
|
+
"charged_cumulative_amount": "200000",
|
|
43
|
+
"signed_max_claimable": "250000",
|
|
44
|
+
"total_claimed": "0",
|
|
45
|
+
"refund_nonce": "0",
|
|
46
|
+
"storage_version": "42",
|
|
47
|
+
"policy_version": "agentpay-policy-2026-04-30",
|
|
48
|
+
"created_at": "2026-04-30T13:20:00Z"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Public logs should hash payer, receiver, payer authorizer, and receiver authorizer addresses. Internal reconciliation can retain raw addresses in encrypted storage.
|
|
53
|
+
|
|
54
|
+
## Deposit gate
|
|
55
|
+
|
|
56
|
+
A deposit is not a policy approval. It is a funding action that creates channel capacity.
|
|
57
|
+
|
|
58
|
+
Before any deposit or top-up, AgentPay MCP should check:
|
|
59
|
+
|
|
60
|
+
- the deposit amount is under the agent's per-channel deposit cap,
|
|
61
|
+
- the token and network are allowlisted,
|
|
62
|
+
- the receiver and server-owned `receiverAuthorizer` are expected for the paid MCP provider,
|
|
63
|
+
- the deposit multiplier or custom deposit strategy cannot exceed the daily budget,
|
|
64
|
+
- human approval is present when policy requires it.
|
|
65
|
+
|
|
66
|
+
The policy row should say whether the deposit was approved, declined, or skipped. Never infer approval from the x402 SDK choosing a default deposit amount.
|
|
67
|
+
|
|
68
|
+
## Voucher cap checks
|
|
69
|
+
|
|
70
|
+
Batch-settlement vouchers are cumulative. The important value is not only the current request price. It is the new `signedMaxClaimable` amount compared with policy.
|
|
71
|
+
|
|
72
|
+
Before signing a voucher, AgentPay MCP should compute:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const nextSignedMaxClaimable = currentSignedMaxClaimable + requestCharge;
|
|
76
|
+
|
|
77
|
+
if (nextSignedMaxClaimable > policy.perChannelVoucherCap) {
|
|
78
|
+
throw new Error("voucher cap exceeded");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (nextSignedMaxClaimable > approvedChannelBudget) {
|
|
82
|
+
throw new Error("approved channel budget exceeded");
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Required checks:
|
|
87
|
+
|
|
88
|
+
- per-request price is under `maxAmountRequired`,
|
|
89
|
+
- cumulative voucher amount is under the channel voucher cap,
|
|
90
|
+
- cumulative voucher amount is under the human-approved channel budget,
|
|
91
|
+
- channel balance covers the voucher or a policy-approved top-up is required,
|
|
92
|
+
- voucher signer delegation is recorded when `payerAuthorizer` differs from the payer.
|
|
93
|
+
|
|
94
|
+
If any check fails, signing must fail closed. A skipped facilitator settle call must never skip AgentPay policy.
|
|
95
|
+
|
|
96
|
+
## Atomic storage requirement
|
|
97
|
+
|
|
98
|
+
A batch channel can receive overlapping paid calls. Application-level `get` then `set` is not enough because two requests can sign from the same base cumulative amount.
|
|
99
|
+
|
|
100
|
+
AgentPay MCP should require compare-and-set semantics per channel:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
await storage.updateChannel(channelId, current => {
|
|
104
|
+
if (!current) return createInitialChannel();
|
|
105
|
+
if (current.storageVersion !== expectedVersion) return current;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
...current,
|
|
109
|
+
chargedCumulativeAmount: nextChargedCumulativeAmount,
|
|
110
|
+
signedMaxClaimable: nextSignedMaxClaimable,
|
|
111
|
+
storageVersion: current.storageVersion + 1,
|
|
112
|
+
lastRequestTimestamp: Date.now()
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Acceptable production backends:
|
|
118
|
+
|
|
119
|
+
- Redis or Valkey with Lua or `WATCH` / `MULTI` / `EXEC`,
|
|
120
|
+
- SQL transaction with `SELECT ... FOR UPDATE` or optimistic version checks,
|
|
121
|
+
- Cloudflare Durable Objects when a single object owns one channel,
|
|
122
|
+
- any backend that gives atomic conditional mutation for all app instances sharing the channel.
|
|
123
|
+
|
|
124
|
+
In-memory storage is acceptable only for a single-process demo.
|
|
125
|
+
|
|
126
|
+
## Corrective 402 recovery
|
|
127
|
+
|
|
128
|
+
PR #2061 adds corrective 402 recovery for cumulative mismatches. AgentPay MCP should log recovery as a first-class payment event because it means client and server channel state diverged.
|
|
129
|
+
|
|
130
|
+
Recovery handling should verify and record:
|
|
131
|
+
|
|
132
|
+
- corrective error code, such as `batch_settlement_cumulative_amount_mismatch`,
|
|
133
|
+
- server-provided `chargedCumulativeAmount`,
|
|
134
|
+
- server-provided `signedMaxClaimable`,
|
|
135
|
+
- voucher signature used to prove the server snapshot,
|
|
136
|
+
- whether recovery came from server signature or on-chain state,
|
|
137
|
+
- local storage version before and after resync,
|
|
138
|
+
- whether the original request was retried.
|
|
139
|
+
|
|
140
|
+
If recovery cannot verify the signature, channel config, or on-chain state, AgentPay MCP should mark the channel `recovery_failed` and block more voucher signing until a human or operator reconciles it.
|
|
141
|
+
|
|
142
|
+
## Refund and claim audit path
|
|
143
|
+
|
|
144
|
+
Refund and claim are delayed settlement operations. They need different audit rows from a normal one-shot x402 payment.
|
|
145
|
+
|
|
146
|
+
For refunds, record:
|
|
147
|
+
|
|
148
|
+
- requested refund amount or full refund flag,
|
|
149
|
+
- outstanding signed max claimable amount,
|
|
150
|
+
- server claim action before refund if any,
|
|
151
|
+
- refund transaction hash or pending status,
|
|
152
|
+
- remaining channel balance after refund.
|
|
153
|
+
|
|
154
|
+
For claims, record:
|
|
155
|
+
|
|
156
|
+
- claimed channel IDs,
|
|
157
|
+
- claimed cumulative amount per channel,
|
|
158
|
+
- claim transaction hash,
|
|
159
|
+
- settle transaction hash if funds are swept separately,
|
|
160
|
+
- receiver and receiver authorizer hashes,
|
|
161
|
+
- failed, partial, or retried claims.
|
|
162
|
+
|
|
163
|
+
A paid MCP provider should be able to answer: which tool call increased the voucher, which cumulative voucher got claimed, and which on-chain transaction settled it.
|
|
164
|
+
|
|
165
|
+
## Server-owned receiver authorizer
|
|
166
|
+
|
|
167
|
+
The batch-settlement scheme includes `receiverAuthorizer` in channel config. For AgentPay MCP, this is part of provider identity.
|
|
168
|
+
|
|
169
|
+
AgentPay MCP should pin or allowlist expected receiver authorizers per paid MCP provider. If the receiver authorizer changes, the integration should require one of these outcomes:
|
|
170
|
+
|
|
171
|
+
- a signed provider rotation notice,
|
|
172
|
+
- a fresh human approval,
|
|
173
|
+
- or fail-closed rejection.
|
|
174
|
+
|
|
175
|
+
Do not treat a matching receiver address as enough. The receiver authorizer can become the operational key that approves server-side settlement behavior.
|
|
176
|
+
|
|
177
|
+
## Off-chain settlement audit checklist
|
|
178
|
+
|
|
179
|
+
Before enabling batch-settlement for a paid MCP tool, require:
|
|
180
|
+
|
|
181
|
+
- `channel_id` in every payment attempt row,
|
|
182
|
+
- `deposit_amount`, `charged_cumulative_amount`, and `signed_max_claimable` in channel rows,
|
|
183
|
+
- policy approval ID attached to deposit and voucher signing,
|
|
184
|
+
- CAS or transaction proof for channel storage mutations,
|
|
185
|
+
- corrective 402 recovery rows,
|
|
186
|
+
- refund and claim rows with transaction hashes when available,
|
|
187
|
+
- hashed payer, receiver, payer authorizer, and receiver authorizer fields,
|
|
188
|
+
- failure rows for skipped deposits, voucher cap rejection, recovery failure, refund failure, and claim failure.
|
|
189
|
+
|
|
190
|
+
## Acceptance checklist
|
|
191
|
+
|
|
192
|
+
- [ ] Deposit policy enforces per-channel and daily caps before channel funding.
|
|
193
|
+
- [ ] Voucher signing checks cumulative voucher caps, not only per-request price.
|
|
194
|
+
- [ ] Server-owned `receiverAuthorizer` is pinned or routed through approval.
|
|
195
|
+
- [ ] Channel storage uses atomic compare-and-set or transaction semantics.
|
|
196
|
+
- [ ] Corrective 402 recovery is logged and fails closed when verification fails.
|
|
197
|
+
- [ ] Refund flows account for outstanding signed vouchers before returning balance.
|
|
198
|
+
- [ ] Claim flows log cumulative claimed amount and settlement transaction proof.
|
|
199
|
+
- [ ] Audit rows connect MCP tool name, policy version, channel ID, voucher state, and on-chain settlement.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# x402 Bazaar observability for paid MCP tools
|
|
2
|
+
|
|
3
|
+
x402 Bazaar is moving from passive discovery metadata to searchable catalog behavior. AgentPay MCP should treat that as a production contract: paid MCP tools need searchable metadata, shared facilitator auth, and visible extension outcomes after verify and settle.
|
|
4
|
+
|
|
5
|
+
This recipe tracks the x402 Foundation Apr 29 signal: WithBazaar SDK wrappers now support search, unified auth, and Python parity, and PR #2161 adds EXTENSION-RESPONSES header readback for verify and settle.
|
|
6
|
+
|
|
7
|
+
## Production goal
|
|
8
|
+
|
|
9
|
+
A paid MCP tool is Bazaar-ready only when all four checks pass:
|
|
10
|
+
|
|
11
|
+
1. The 402 response carries a `bazaar` extension with MCP search metadata.
|
|
12
|
+
2. The facilitator client can list and search Bazaar resources with the same auth provider used for `verify`, `settle`, and `supported`.
|
|
13
|
+
3. The client reads `EXTENSION-RESPONSES` from verify and settle responses.
|
|
14
|
+
4. AgentPay MCP writes sanitized audit rows for catalog status, policy approval, and settlement.
|
|
15
|
+
|
|
16
|
+
If any check fails, AgentPay MCP should still fail closed for payment signing. Discovery failure must not become silent spend authority.
|
|
17
|
+
|
|
18
|
+
## Bazaar metadata for MCP tools
|
|
19
|
+
|
|
20
|
+
For each paid MCP tool, the 402 response should include a `bazaar` extension under `extensions`.
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"extensions": {
|
|
25
|
+
"bazaar": {
|
|
26
|
+
"info": {
|
|
27
|
+
"input": {
|
|
28
|
+
"type": "mcp",
|
|
29
|
+
"tool": "agentpay.x402_pay",
|
|
30
|
+
"description": "Pay an x402-protected API after AgentPay MCP policy approval",
|
|
31
|
+
"transport": "streamable-http",
|
|
32
|
+
"inputSchema": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"resource": { "type": "string" },
|
|
36
|
+
"maxAmountRequired": { "type": "string" },
|
|
37
|
+
"network": { "type": "string" },
|
|
38
|
+
"payTo": { "type": "string" }
|
|
39
|
+
},
|
|
40
|
+
"required": ["resource", "maxAmountRequired", "network", "payTo"]
|
|
41
|
+
},
|
|
42
|
+
"example": {
|
|
43
|
+
"resource": "https://api.example.com/research/report",
|
|
44
|
+
"maxAmountRequired": "100000",
|
|
45
|
+
"network": "eip155:8453",
|
|
46
|
+
"payTo": "0x0000000000000000000000000000000000000000"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"output": {
|
|
50
|
+
"type": "json",
|
|
51
|
+
"example": {
|
|
52
|
+
"approved": true,
|
|
53
|
+
"transaction": "0x...",
|
|
54
|
+
"policy_version": "agentpay-policy-2026-04-30"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"schema": {
|
|
59
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
60
|
+
"type": "object",
|
|
61
|
+
"required": ["input"],
|
|
62
|
+
"properties": {
|
|
63
|
+
"input": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"required": ["type", "tool", "inputSchema"],
|
|
66
|
+
"properties": {
|
|
67
|
+
"type": { "const": "mcp" },
|
|
68
|
+
"tool": { "type": "string" },
|
|
69
|
+
"description": { "type": "string" },
|
|
70
|
+
"transport": { "enum": ["streamable-http", "sse"] },
|
|
71
|
+
"inputSchema": { "type": "object" },
|
|
72
|
+
"example": { "type": "object" }
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": false
|
|
75
|
+
},
|
|
76
|
+
"output": { "type": "object" }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Required AgentPay fields:
|
|
85
|
+
|
|
86
|
+
- `input.type`: `mcp`
|
|
87
|
+
- `input.tool`: MCP tool name passed to `tools/call`
|
|
88
|
+
- `input.description`: one sentence with the paid action and policy boundary
|
|
89
|
+
- `input.transport`: `streamable-http` by default, `sse` only when the server uses SSE
|
|
90
|
+
- `input.inputSchema`: the same schema the MCP tool advertises
|
|
91
|
+
- `input.example`: a safe example that does not include a real private key, API token, wallet secret, or customer identifier
|
|
92
|
+
- `output.example.policy_version`: the policy revision attached to the approval decision
|
|
93
|
+
|
|
94
|
+
For MCP resources, the catalog key is the tuple `resource.url` plus `input.tool`. Do not assume one MCP endpoint maps to one paid capability.
|
|
95
|
+
|
|
96
|
+
## Search checks with WithBazaar
|
|
97
|
+
|
|
98
|
+
AgentPay MCP should be discoverable by a Bazaar search query, not only by a raw list call.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { HTTPFacilitatorClient } from "@x402/core/http";
|
|
102
|
+
import { withBazaar } from "@x402/extensions";
|
|
103
|
+
|
|
104
|
+
const facilitator = new HTTPFacilitatorClient({
|
|
105
|
+
url: process.env.X402_FACILITATOR_URL,
|
|
106
|
+
createAuthHeaders: async () => ({
|
|
107
|
+
verify: { Authorization: `Bearer ${process.env.X402_FACILITATOR_TOKEN}` },
|
|
108
|
+
settle: { Authorization: `Bearer ${process.env.X402_FACILITATOR_TOKEN}` },
|
|
109
|
+
supported: { Authorization: `Bearer ${process.env.X402_FACILITATOR_TOKEN}` },
|
|
110
|
+
bazaar: { Authorization: `Bearer ${process.env.X402_FACILITATOR_TOKEN}` }
|
|
111
|
+
})
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const client = withBazaar(facilitator);
|
|
115
|
+
|
|
116
|
+
const matches = await client.extensions.bazaar.search({
|
|
117
|
+
query: "approval gated x402 MCP payment tool",
|
|
118
|
+
type: "mcp",
|
|
119
|
+
network: "eip155:8453",
|
|
120
|
+
scheme: "exact",
|
|
121
|
+
extensions: "bazaar",
|
|
122
|
+
limit: 10
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The `bazaar` auth header must use the same auth source as `verify`, `settle`, and `supported`. Divergent auth is a bad production signal because the agent may be able to pay but not prove catalog status.
|
|
127
|
+
|
|
128
|
+
## EXTENSION-RESPONSES readback
|
|
129
|
+
|
|
130
|
+
Facilitators may return an `EXTENSION-RESPONSES` header from `verify` or `settle`. The value is base64-encoded JSON keyed by extension name.
|
|
131
|
+
|
|
132
|
+
Example decoded value:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"bazaar": {
|
|
137
|
+
"status": "success"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Rejected example:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"bazaar": {
|
|
147
|
+
"status": "rejected",
|
|
148
|
+
"rejectedReason": "info failed schema validation"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
AgentPay MCP should parse the header, keep only allowlisted fields, and write the result into the payment audit row. Never log full request bodies, auth headers, private keys, payment signatures, or customer contact data.
|
|
154
|
+
|
|
155
|
+
Allowlisted fields:
|
|
156
|
+
|
|
157
|
+
- `status`
|
|
158
|
+
- `rejectedReason`
|
|
159
|
+
- `reason`
|
|
160
|
+
- `code`
|
|
161
|
+
|
|
162
|
+
## Audit row shape
|
|
163
|
+
|
|
164
|
+
Paid MCP tool audit rows should include enough state to debug Bazaar cataloging without leaking secrets.
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"event_type": "x402_paid_mcp_tool_settled",
|
|
169
|
+
"agent_id": "agent_123",
|
|
170
|
+
"task_id": "task_456",
|
|
171
|
+
"mcp_tool": "agentpay.x402_pay",
|
|
172
|
+
"resource": "https://api.example.com/research/report",
|
|
173
|
+
"network": "eip155:8453",
|
|
174
|
+
"asset": "USDC",
|
|
175
|
+
"max_amount_required": "100000",
|
|
176
|
+
"pay_to_hash": "sha256:...",
|
|
177
|
+
"policy_version": "agentpay-policy-2026-04-30",
|
|
178
|
+
"approval_id": "approval_789",
|
|
179
|
+
"verify_extension_responses": {
|
|
180
|
+
"bazaar": { "status": "processing" }
|
|
181
|
+
},
|
|
182
|
+
"settle_extension_responses": {
|
|
183
|
+
"bazaar": { "status": "success" }
|
|
184
|
+
},
|
|
185
|
+
"settlement_tx": "0x...",
|
|
186
|
+
"created_at": "2026-04-30T05:14:00Z"
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`pay_to_hash` is preferred for public logs. Internal systems can keep the raw address in encrypted storage when reconciliation needs it.
|
|
191
|
+
|
|
192
|
+
## Failure handling
|
|
193
|
+
|
|
194
|
+
- Missing Bazaar search result: payment can proceed only if the normal AgentPay policy approves it, but write `bazaar_catalog_status: missing`.
|
|
195
|
+
- Missing `EXTENSION-RESPONSES`: write `extension_response_status: absent`, not `success`.
|
|
196
|
+
- Malformed `EXTENSION-RESPONSES`: ignore the raw header, write `extension_response_status: malformed`, and keep the payment state separate.
|
|
197
|
+
- `bazaar.status: rejected`: surface the rejection reason to the operator and flag the MCP metadata for repair.
|
|
198
|
+
- Auth failure on Bazaar search: do not downgrade verify or settle auth. Fix the `bazaar` auth header path.
|
|
199
|
+
|
|
200
|
+
## Acceptance checklist
|
|
201
|
+
|
|
202
|
+
- [ ] 402 response includes `extensions.bazaar.info.input.type = "mcp"`.
|
|
203
|
+
- [ ] `input.tool` matches the MCP `tools/call` name.
|
|
204
|
+
- [ ] `input.inputSchema` matches the MCP tool schema.
|
|
205
|
+
- [ ] `withBazaar(...).search({ type: "mcp", extensions: "bazaar" })` returns the paid tool or a clear miss.
|
|
206
|
+
- [ ] `createAuthHeaders` includes `verify`, `settle`, `supported`, and `bazaar` entries.
|
|
207
|
+
- [ ] Verify and settle read `EXTENSION-RESPONSES` when present.
|
|
208
|
+
- [ ] Logs keep only `status`, `rejectedReason`, `reason`, and `code` from extension response payloads.
|
|
209
|
+
- [ ] Payment signing remains gated by AgentPay policy approval even when Bazaar metadata is valid.
|