Skip to content

fix(mcp): use scheme="exact" in X-Payment envelope so the facilitator actually accepts it#18

Merged
Germey merged 1 commit into
mainfrom
fix/x402-envelope-v2
May 10, 2026
Merged

fix(mcp): use scheme="exact" in X-Payment envelope so the facilitator actually accepts it#18
Germey merged 1 commit into
mainfrom
fix/x402-envelope-v2

Conversation

@acedatacloud-dev
Copy link
Copy Markdown
Member

Summary

User reported their aceguard_pay_for_api calls showing "MCP could not be loaded" / payment failures. Tracking it down, there's a real wire-format bug independent of any client setup: the X-Payment envelope built in _tool_pay_for_api declares scheme: "solana", but the upstream facilitator (FacilitatorX402, what api.acedata.cloud routes 402 retries to) is x402-spec compliant and matches on scheme: "exact" + network: "solana" — not on scheme: "<chain>".

Live in x402f/views_multichain.py:178:

{"x402Version": 2, "scheme": "exact", "network": network}

So today every aceguard_pay_for_api invocation:

  1. Hits the upstream → 402 (correct)
  2. Parses accepts → picks network: "solana" (correct)
  3. Calls execute_spend() → on-chain agent_vault.spend() succeeds, real USDC moves, real tx signature returned (correct)
  4. Builds X-Payment with scheme: "solana" and retries
  5. Facilitator returns 402 again — does not recognise scheme: "solana"
  6. Agent sees a confusing failure, even though USDC has already moved on-chain

Wallet pays, agent sees nothing. That's the worst possible failure mode for this product.

Fix

scheme: "solana"scheme: "exact". One field. payload.signature (the on-chain tx hash) stays identical — facilitator solana_chain._extract_signature reads it from payload.signature exactly.

Also rewrites the comment to point at the actual facilitator source rather than describing what we thought it accepted.

Verification

  • uvx ruff check api/routes/mcp.py — clean
  • python -c "import ast; ast.parse(...)" — parses
  • Wire-format alignment cross-checked against:
  • 2026-04-25 settlement matrix in X402Client README — all six verified mainnet payments use scheme: "exact"

End-to-end re-verification (live spend + facilitator round-trip) is covered by the demo script in the follow-up PR — needs a funded vault, can't run in CI.

What this does NOT change

  • On-chain Anchor program — untouched
  • aceguard_balance / aceguard_history / aceguard_spend tools — they don't build envelopes, only _tool_pay_for_api does
  • MCP transport / tool definitions — same
  • Backwards compatibility — there is no "before" since the only consumers are agents that have been getting 402-loops since day one of this tool

Risk

Negligible. The current value is provably broken (facilitator does not match the scheme); the new value is what every other production payment in this org uses.

The aceguard_pay_for_api MCP tool was constructing the X-Payment envelope
with scheme="solana", but FacilitatorX402 (the upstream that
api.acedata.cloud routes to) is x402-spec compliant: its multichain view
matches on (scheme="exact", network=<chain>), not on scheme=<chain>.

In practice this means every aceguard_pay_for_api call would loop on 402
even after a successful on-chain agent_vault.spend() — the policy check,
SPL transfer, and tx signature were all real, but the facilitator
rejected the envelope as an unrecognized scheme and returned 402 again,
making the agent see the call as "payment failed" with no way to debug.

Fix: scheme: "solana" -> scheme: "exact". Wire-format alignment with
@acedatacloud/x402-client (typescript/scripts/test-solana-e2e.ts:172) and
the AceDataCloud Python SDK payment_handler that downstream demos will
plug into. payload.signature stays the same — facilitator
solana_chain.py:_extract_signature accepts {payload: {signature: <tx>}}
exactly.

No test added: this code path requires a real on-chain spend + live
facilitator round-trip; covered by the manual demo in scripts/ (next PR).
@Germey Germey merged commit 6c96c97 into main May 10, 2026
2 checks passed
acedatacloud-dev added a commit that referenced this pull request May 10, 2026
…Claude (#19)

User reported "MCP could not be loaded" on Claude Desktop. Two real
issues underneath the symptom:

1. The README's Step 4 config (`{"mcpServers": {"aceguard": {"url": "..."}}}`)
   is the Claude.ai web Custom Connectors schema, not the Claude Desktop
   one. Claude Desktop only speaks stdio MCP — to load an HTTP MCP
   endpoint it needs the `mcp-remote` stdio<->HTTP bridge.
2. There was no way to test the MCP endpoint without going through a
   client at all, so any failure mode (network, token, server, client
   config) all surfaced as the same opaque "could not be loaded".

Two scripts to cover (2):

- scripts/demo.py    — Python (httpx + stdlib). Speaks JSON-RPC to the
                       MCP endpoint, runs the full sequence:
                         tools/list → balance → pay_for_api → balance
                         → history (with Solscan deep-link).
                       Distinguishes 401-bad-token, RPC-error,
                       isError-true (policy rejection), upstream non-200,
                       and "endpoint healthy" cleanly.
- scripts/mcp-curl.sh — bash + curl + jq variant. Same script for users
                        without a Python toolchain.

README rewrite for Step 4: now documents three paths in order of "needs
an LLM in the loop":

  4a. Verify it works at all      → run the bundled demo
  4b. Claude Desktop              → mcp-remote bridge config (correct schema)
  4c. Cursor / Cline / etc.       → URL straight in
  4d. Skip MCP                    → @acedatacloud/x402-client SDK direct call

DEMO.md updated to the same mcp-remote config + a new troubleshooting
row pointing at the demo script as a self-diagnostic.

Smoke-tested live against https://x402guard.acedata.cloud/mcp/<bad-token>:
proper 401 detection + remediation hint.

ruff check clean. shellcheck clean (`bash -n`).

Pairs with #18 (envelope `scheme: "exact"` fix) — together those two
PRs make `aceguard_pay_for_api` actually work end-to-end and let the
user prove that *without* depending on a specific LLM client.

Co-authored-by: acedata-bot <bot@acedata.cloud>
acedatacloud-dev added a commit that referenced this pull request May 10, 2026
…pay_for_api caveat (#22)

Adds a "Live on devnet" badge + a quoted callout near the top with the
real 2026-05-10 verification result (3 spends, vault 4.00 -> 3.97 USDC,
finalized tx 249u8Pion...3y3D on Solscan). The customer who reported
"MCP could not be loaded" can now skim the top of the README, click the
Solscan link to confirm the on-chain side is live, and run the curl /
demo recipe to confirm their own MCP URL is healthy without any Claude
/ Cursor / SDK plumbing.

Concrete changes:
- "60-second verification" section near the top: 3 steps, all `curl` +
  `python scripts/demo.py`. End-state explicitly: "If steps 1-2 work,
  any `MCP could not be loaded` you see in Claude Desktop is a
  client-side problem".
- Spelled out the `aceguard_spend` request/response shape with a real
  finalized tx as the canonical example. Added the
  `recipient ATA must exist on devnet` pre-req inline (Anchor 3012),
  with the one-line `spl-token create-account` command to satisfy it.
- Pivoted Step 5 of the walkthrough from `pay_for_api` to
  `aceguard_spend`. Reason: api.acedata.cloud issues mainnet x402
  quotes (`EPjFWdd5...` mint, `5iVXFr...` payTo); the production
  x402guard deploy is on devnet, so the recipient ATA the on-chain
  program expects does not exist on this cluster. This is *expected*
  per .plans/X402GUARD.md and called out clearly so customers do not
  burn an afternoon trying to make that path work pre-mainnet flip.
- Updated Step 6 (boundary-in-action prompts) to use `aceguard_spend`
  invocations that map to actual Anchor errors today, instead of the
  pre-existing `pay_for_api` examples that no longer fire.

Pairs with #18 / #19 / #20 / #21. The mainnet flip stays the V2 step
.plans/X402GUARD.md already calls out (#11 / "Why devnet, not mainnet").

Co-authored-by: acedata-bot <bot@acedata.cloud>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants