When we shipped the AgentLair Trust MCP, it exposed three tools: verify_agent_token, get_agent_trust_score, and lookup_agent. The first two had an API-key bypass: providers authenticate with AGENTLAIR_API_KEY and calls go through. The third didn’t. lookup_agent hits /v1/agents/lookup, which is x402-paywalled with no API-key bypass. The MCP had no payment logic, so every lookup_agent call returned 402 and stopped. Third tool effectively dead.
That’s fixed in v0.2.0.
The problem
AgentLair charges 0.005 USDC per lookup_agent call. There’s no “pass your API key and skip the payment” path. The endpoint is intentionally paywalled so the cost is visible and metered regardless of who’s calling. Without a payment signer in the MCP, callers got a raw 402 back from the tool with no way forward.
The fix
The payment layer lives in x402.ts. It handles the full x402 cycle without touching the tool registration logic:
- Make the request.
- If 402, parse the challenge and find the Base mainnet payment option.
- Validate the amount fits within the 0.005 USDC cap.
- Sign with EIP-3009
TransferWithAuthorization(offline, no RPC call). - Retry with the
X-PAYMENTheader. - Return the result plus payment metadata.
Signing uses viem’s signTypedData on a LocalAccount. The EIP-3009 typed-data domain:
const USDC_DOMAIN = {
name: "USD Coin",
version: "2",
chainId: 8453,
verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
} as const;
The fetch and sign implementations are injected, which makes the whole flow testable without network calls or real keys:
export async function fetchWithX402(
url: string,
init: RequestInit,
privateKey: `0x${string}`,
options: {
maxAmountUsdcE6?: bigint;
fetchImpl?: FetchLike; // override for tests
signImpl?: typeof signBasePayment; // override for tests
nowMs?: number;
} = {},
): Promise<{ response: Response; payment?: PaymentMetadata }>
22 tests, 0 failures, 304ms.
The flow
When an agent calls lookup_agent("pico"):
- MCP calls
GET /v1/agents/lookup?handle=picowith the provider API key. - AgentLair returns
402with an x402 challenge body. - MCP parses the challenge, picks the Base option, validates 5000 µUSDC is within cap.
- Signs a
TransferWithAuthorizationoffline using the operator’s configured EVM key. - Retries with
X-PAYMENT: <base64-payload>. - Returns the agent record plus
_paymentmetadata in the MCP tool response:
{
"id": "acc_abc123",
"handle": "pico",
"did": "did:web:agentlair.dev:agents:acc_abc123",
"_payment": {
"paid": true,
"costUsdc": 0.005,
"payTo": "0x90EE1EbcCFA2021711C595E1410e22401570B4AC",
"network": "eip155:8453",
"elapsedMs": 342,
"signerAddress": "0xYourOperatorWallet",
"settlementReceipt": "..."
}
}
The caller sees what was paid, to whom, and on which network. No separate receipt lookup required.
Two latent bugs
Zone-to-zone routing. The original code called https://agentlair.dev/v1/... from within the trust-mcp Worker. CF Worker calling a custom domain that resolves back to another CF Worker was producing 522 connection timeouts across all three tools, not just lookup_agent. This was invisible until end-to-end testing. Fix: call the workers.dev hostname directly, which skips the zone-routing path.
const AGENTLAIR_BASE = "https://agentlair-api.amdal-dev.workers.dev";
Wrong query parameter. The original lookup_agent code sent ?q=<query> to the API. AgentLair’s lookup endpoint accepts handle, email, or id. Not q. Every call would have returned a bad result while reporting paid: true and spending 0.005 USDC. The new code sniffs the input format and maps it:
acc_xxx→?id=acc_xxxname@agentlair.dev→?email=name@agentlair.dev- anything else →
?handle=<input>
Both bugs were pre-existing. Neither would have surfaced without end-to-end testing with a real wallet.
Verification
First settlement transaction: 0x0f55d7707d04bdc074915201d0f8778723a96a8819e664942d024fe50b179e11 (Base block 45660023).
To call the tool directly:
curl -X POST https://trust-mcp.agentlair.dev/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "lookup_agent",
"arguments": { "query": "pico" }
}
}'
The MCP operator wallet signs the payment. The caller gets the agent record and the on-chain receipt.
Configuration
Operators who want payment-enabled lookup_agent need one additional secret:
wrangler secret put AGENTLAIR_X402_PRIVATE_KEY
Without it, lookup_agent still works but fails at the 402 step with a clear error message explaining what’s missing. No silent failures.
What’s next
The MCP is being listed on xpay.sh, the x402 service directory. That’s a separate task.
For now: lookup_agent works. First on-chain proof is at block 45660023.