CYM for Agents
MCP Integration Guide · v1.0

Build agents that redeem gift cards paid in USDT0.

CYM Rewards exposes a native Model Context Protocol endpoint at https://cymstudio.app/api/mcp/rewards. Any LLM agent with its own wallet (Coinbase CDP, Privy server wallets, Safe, etc.) can discover brands, lock an x402 quote, sign an EIP-3009 transferWithAuthorization, and receive a real gift card voucher. Gas is paid by a shared facilitator on Conflux eSpace or Ethereum mainnet — agents only need USDT0 or USDC.

Quickstart

One curl, zero wallet, zero credentials — enough to confirm the MCP is alive and see what it can do:

bash
curl -s -X POST https://cymstudio.app/api/mcp/rewards \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
  | jq '.result.tools[].name'

Any MCP client can talk to the endpoint directly — it implements JSON-RPC 2.0 over HTTPS with the standard tools/list + tools/call handshake. See Client configs for Claude Desktop and OpenAI-compatible snippets.

Endpoints

PathMethodPurpose
/api/mcp/rewardsPOSTJSON-RPC 2.0: initialize, ping, tools/list, tools/call, resources/list.
/api/mcp/rewardsGETHuman-readable metadata (name, version, protocol, tool names).
/.well-known/gift-cards/agent-registration.jsonGETERC-8004 registration document. Pin this URL in your agent.
/api/purchasePOSTUnderlying x402 endpoint the purchase tools call. Agents may use it directly with an x-payment header.

Protocol

Every request is a JSON-RPC 2.0 POST body:

request
{
  "jsonrpc": "2.0",
  "method":  "tools/call",
  "params":  { "name": "search_giftcards", "arguments": { "brand": "Starbucks" } },
  "id":      1
}
response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{ "type": "text", "text": "Found 1 gift card.\n\n[{...}]" }],
    "isError": false
  }
}

Tool outputs are always a single content[0].text string. Structured results (product lists, quote objects, order details) are embedded as JSON inside that string — parse from the first { or [.

Tool catalogue

All 12 tools exposed at tools/list:

ToolDescription
search_giftcardsFilter 300+ brands by brand / country / currency.
get_brand_detailsDenominations, restrictions, terms, validity for one product.
list_countriesCountries with available products (US, CA, HK).
list_currenciesCurrencies with available products right now.
search_mastercardPrepaid Mastercard products (USD, CAD).
get_mastercard_detailsDetail for one Mastercard product.
check_order_statusPoll an order by order_id + email. Returns voucher when delivered.
redirect_to_checkoutBuild a pre-filled /catalogue URL for browser-wallet fallback.
verify_email_startSend a 6-digit OTP to an email address (required once per 30 days).
verify_email_completeSubmit the 6-digit OTP to mark the email verified.
get_purchase_quoteStep 1 of purchase: returns x402 payment requirements (amount, facilitator, EIP-712 domain, types, nonce).
submit_purchaseStep 2 of purchase: accepts a signed x402 envelope, settles on-chain, procures voucher.

Call tools/list at runtime for full JSON Schema (argument names, types, required fields). Nothing here is hard-coded on the agent side — the server is the source of truth.

Purchase flow

Five calls from cold start to voucher in hand:

1.  verify_email_start        { email }                       → OTP sent
2.  verify_email_complete     { email, code }                 → email verified (30 days)
3.  get_purchase_quote        { product_id, denomination,     → x402 payment requirements
                                email, network }                 + EIP-712 domain + types
                                                                 + suggested authorization
4.  [ agent wallet signs TransferWithAuthorization ]
5.  submit_purchase           { product_id, denomination,     → order_id + payment_tx
                                email, x_payment }               ( + voucher if synchronous )
6.  check_order_status        { order_id, email }             → voucher.code, voucher.pin
Why email OTP?Vouchers are delivered to the email the agent provides (and cached for audit). One-time verification per email protects against typos that would send the voucher somewhere unreachable. If an agent controls its own inbox, the OTP round-trip is programmatic; if it's purchasing on behalf of a human, the human verifies once.

EIP-3009 signing

get_purchase_quote returns everything you need to build the typed-data. Fields:

quote response (abridged)
{
  "correlation":    { "product_id": 14000003689, "denomination": 25, "email": "...", "network": "conflux" },
  "payment_requirements": {
    "scheme":            "exact",
    "x402_version":      1,
    "network":           "conflux",
    "chain_id":          1030,
    "token":             "0xaf37e8b6c9ed7f6318979f56fc287d76c30847ff",
    "pay_to":            "0xc10561c1c0d718b3d362df9d510a1b4e4331a4ee",
    "amount":            "25380000",            // 25.38 USDT0 in raw 6-decimal units (incl. 1.5% fee)
    "original_price":    "25",
    "original_currency": "USD"
  },
  "eip712_domain": {
    "name":             "USDT0",
    "version":          "1",
    "chainId":          1030,
    "verifyingContract": "0xaf37e8b6c9ed7f6318979f56fc287d76c30847ff"
  },
  "eip712_types": {
    "TransferWithAuthorization": [
      { "name": "from",        "type": "address" },
      { "name": "to",          "type": "address" },
      { "name": "value",       "type": "uint256" },
      { "name": "validAfter",  "type": "uint256" },
      { "name": "validBefore", "type": "uint256" },
      { "name": "nonce",       "type": "bytes32" }
    ]
  },
  "suggested_authorization": {
    "from": "YOUR_WALLET_ADDRESS",
    "to":   "0xc10561c1c0d718b3d362df9d510a1b4e4331a4ee",
    "value": "25380000",
    "validAfter":  0,
    "validBefore": 1800000000,
    "nonce": "0xabcd...32 random bytes..."
  }
}

Sign the TransferWithAuthorization struct with the agent's wallet key, then base64-encode the full envelope and pass it as x_payment:

envelope to encode
{
  "x402Version": 1,
  "scheme": "exact",
  "network": "conflux",
  "payload": {
    "signature": "0x...",         // 65-byte ECDSA result
    "authorization": {
      "from": "0xagent...",
      "to":   "0xc10561...",
      "value": "25380000",
      "validAfter": 0,
      "validBefore": 1800000000,
      "nonce": "0xabcd..."
    }
  }
}

Reference signing snippet with viem:

typescript / viem
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`)

const signature = await account.signTypedData({
  domain: quote.eip712_domain,
  types:  quote.eip712_types,
  primaryType: 'TransferWithAuthorization',
  message: {
    ...quote.suggested_authorization,
    from: account.address,
  },
})

const envelope = {
  x402Version: 1,
  scheme: 'exact',
  network: quote.payment_requirements.network,
  payload: {
    signature,
    authorization: { ...quote.suggested_authorization, from: account.address },
  },
}

const xPayment = Buffer.from(JSON.stringify(envelope)).toString('base64')

Client configs

Claude Desktop

Claude Desktop speaks MCP over stdio, so use mcp-remote to bridge to the HTTP endpoint. Add this to ~/Library/Application Support/Claude/claude_desktop_config.json:

claude_desktop_config.json
{
  "mcpServers": {
    "cym-rewards": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://cymstudio.app/api/mcp/rewards"]
    }
  }
}

Anthropic / OpenAI tool-use loops

Define the 12 tools manually using the schemas from tools/list, or dispatch each model-requested tool by POSTing to /api/mcp/rewards. Our own /chat page uses the latter pattern with Kimi — see app/api/chat/route.ts for a 150-line reference implementation.

Bare HTTP

Nothing is MCP-client-specific — just POST JSON-RPC:

python
import requests

def mcp_call(name, args=None):
    r = requests.post("https://cymstudio.app/api/mcp/rewards", json={
        "jsonrpc": "2.0", "id": 1,
        "method": "tools/call",
        "params": { "name": name, "arguments": args or {} },
    })
    return r.json()["result"]["content"][0]["text"]

print(mcp_call("list_countries"))

Errors & rate limits

JSON-RPC errors follow the standard codes:

  • -32700 parse error — body was not valid JSON
  • -32600 invalid request — missing jsonrpc: "2.0" or method
  • -32601 method not found — unknown JSON-RPC method
  • -32602 invalid params — unknown tool name

Tool-level errors (invalid input, Supabase failure, OTP mismatch, etc.) return HTTP 200 with result.isError: true and a text message — standard MCP pattern.

Rate limits: IP-based sliding-window on every API route (see middleware.ts). /api/purchase adds a per-wallet 10-second cooldown and $1–$5,000 order-value bounds. /api/email/* has its own envelope rate limits.

Discovery (ERC-8004)

The catalogue is registered on the ERC-8004 agent registry at 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 on Ethereum mainnet as agent ID 22628. The registration document lives at:

well-known
GET https://cymstudio.app/.well-known/gift-cards/agent-registration.json

Agents that discover via ERC-8004 read the services array from that JSON, pick the MCP entry, and connect to its endpoint.

Support

Bug reports, feature requests, and integration questions: