npm packages are not installable yet: [email protected] can be published after 2026-09-16 10:30 UTC. Details
Tollstile

Proxy (any language)

Charge for a FastAPI, Python MCP, Go, or Rails service without changing its code — put a Tollstile gateway in front of it.

Public Beta · early access

@tollstile/proxy is a paid gateway. It prices HTTP routes and MCP tools before they reach your service, forwards paid requests unchanged, and settles on your service's answer. The service needs no Tollstile code, so it can be written in any language.

agent ──► tollstile-proxy ──► your service (private address)
          402 · verify · settle · receipt

Configure

tollstile.proxy.mjs
import { defineProxyConfig } from "@tollstile/proxy";
import { createTollstile, memoryLedger, testRail, upTo } from "tollstile";

export default defineProxyConfig({
  toll: createTollstile({ rails: [testRail()], ledger: memoryLedger() }),
  upstream: "http://127.0.0.1:8000",
  mcp: { path: "/mcp" },
  routes: [
    { method: "GET", path: "/weather", price: "$0.01" },
    { method: "POST", path: "/summarize", price: upTo("$0.50") },
    { tool: "generate_image", price: "$0.04" },
  ],
});
npm install tollstile @tollstile/proxy
npx tollstile-proxy --config tollstile.proxy.mjs --port 8402

Routes take the same options as toll.price(): access, require, flow, commit, resource. Paths support :param segments and a trailing *. Requests no route prices are forwarded free, or refused with unmatched: "deny".

Your service

A FastAPI example with no payment code except two optional headers:

app/main.py
@app.get("/weather")
def weather(request: Request):
    return {"forecast": "clear", "paid_by": request.headers.get("tollstile-payer")}

@app.post("/summarize")
async def summarize(request: Request, response: Response):
    words = len((await request.body()).split())
    response.headers["tollstile-fulfill-amount"] = f"${words / 1000:.3f}"  # upTo: what this call used
    return {"words": words}

MCP servers built with the MCP Python SDK work as they are: the proxy reads tools/call on the MCP endpoint, and every other message passes through.

HeaderDirectionMeaning
Tollstile-Payment-Via, Tollstile-Payer, Tollstile-Charge-Id, Tollstile-Amountto your serviceWho paid, the charge, and the amount. Client-sent tollstile-* headers are removed first
Tollstile-Fulfill-Amountfrom your serviceOn upTo() routes, the amount to charge (at most the maximum). Stripped before the client sees the response
Tollstile-Result-Reffrom your serviceWhere the result is stored, returned to idempotent retries

How it decides

Upstream answerChargeClient gets
HTTP status below 400 (tools: a result without isError)settled before the response is sentthe response, with the rail's receipt
400 or above, isError, or an invalid Tollstile-Fulfill-Amountreleasedthe response, no receipt
No answer within upstreamTimeoutMsreleased502 upstream_unavailable
Settlement rejected by the providerfaileda fresh 402, the upstream response withheld

Unpaid MCP tool calls get a payment-required tool result — x402's PaymentRequired when an x402 rail is configured, otherwise Tollstile's denial in _meta["tollstile/payment-required"]. Paid results carry the receipt in _meta, in JSON and SSE responses.

Security

Only the proxy may reach your service

Bind the service to 127.0.0.1 or a private network. Anything that reaches it directly skips payment and can forge the Tollstile-* headers.

  • Paths are matched and forwarded in canonical form, so percent-encoding, repeated slashes, or trailing slashes cannot route around a price.
  • Priced tools inside JSON-RPC batches, and MCP bodies over maxMcpBodyBytes, are refused.
  • Idempotency-Key and _meta["tollstile/idempotency-key"] work as everywhere: a retried paid request is never paid or forwarded twice.
  • MCP tool responses are buffered to add the receipt, so progress notifications arrive with the result. HTTP bodies stream after settlement.

Run anywhere

createProxy() returns a Web-standard handler, so the gateway also runs on Cloudflare Workers, Deno, and Bun. On Node, @tollstile/proxy/node exports serve(handler, { port }).

Verification status

Tested with unit tests and end to end in front of a real FastAPI app with the MCP Python SDK 2.2. Not yet run in production or on Workers. Full example: examples/proxy-python.

On this page