Stipendex

Transport bindings: MCP and HTTP

How the envelope (envelope.md) travels over two transports. The bindings are semantically equivalent; a receiver MAY support both.


MCP binding

Capability and prices

A server declares support in initialize:

{ "capabilities": { "experimental": { "stipendex/payments": { "version": "0.1" } } } }

Tools carry a price hint in their _meta (level L0 — useful even with zero payment code; _meta is MCP's extension point and survives SDK validation, unlike custom annotation keys):

{
  "name": "web_search",
  "_meta": {
    "stipendex/pricing": { "model": "per_call", "estimate": "0.005 USD" }
  }
}

stipendex/pricing is a planning hint, not an offer. The only obligation is a signed Quote.

Payment cycle

  1. The agent calls a tool. Without payment the server returns a JSON-RPC error:
{
  "error": {
    "code": -32402,
    "message": "Payment required",
    "data": { "quote": { "stipendex": "quote/0.1", "…": "…" } }
  }
}
  1. The host intercepts -32402 (the LLM never sees the error) and hands the Quote to the wallet.
  2. The wallet pays → Proof; the host retries the call:
{
  "method": "tools/call",
  "params": {
    "name": "web_search",
    "arguments": { "query": "…" },
    "_meta": { "stipendex/payment": { "stipendex": "proof/0.1", "…": "…" } }
  }
}
  1. The server verifies the Proof (itself or via a facilitator), executes, returns the result; the Receipt rides in the response _meta:
{ "result": { "content": [ … ], "_meta": { "stipendex/receipt": { "…": "…" } } } }

Until stipendex/payments is accepted into the MCP specification (SEP), all of the above lives in experimental/_meta and requires no protocol changes.

HITL via elicitation

In interactive mode the wallet requests confirmation through standard elicitation (MCP hosts already have the UI): text = description + total + remaining limits. The answer is recorded in a Confirmation.

Hosts without Stipendex support

The wallet runs in proxy mode: the host's MCP client connects not to the server directly but to the wallet's local proxy, which handles -32402 transparently. Adoption is not blocked on host vendors (though native integration is better — see roadmap).

HTTP binding

Compatible in spirit with x402 (deliberately close in mechanics):

  1. Unpaid request:
HTTP/1.1 402 Payment Required
Content-Type: application/json

{ "quote": { "stipendex": "quote/0.1", "…": "…" } }
  1. Retry with payment:
GET /api/search?q=…
Stipendex-Payment: <base64url(Proof JWS)>
  1. Response:
HTTP/1.1 200 OK
Stipendex-Receipt: <base64url(Receipt JWS)>

Discoverability

GET /.well-known/stipendex.json — the receiver descriptor: id, key, rails, trust registry link, supported profiles. The format matches the registry descriptor (trust-registry.md) — the self-hosted variant of the same document.

Requirements common to both transports