Quickstart
From zero to a paid tool. Under ten minutes if your Moove account is ready.
What you need
- Node 20 or newer.
- A Moove account with a handle and a default wallet set. Without both, link creation fails with a
409and no retry helps. See Moove setup. - An API key from the Moove dashboard. It is shown once.
Run the reference server
git clone https://github.com/Jagadeeshftw/tollbooth cd tollbooth npm install npm run build export MOOVE_API_KEY=mk_live_... # Only if your key names a different host. Do not guess it. # export MOOVE_API_BASE_URL=https://api.moove.xyz node examples/research-tools/dist/stdio.js
You should see [research-tools] ready on stdio. That is a complete paid MCP server: three research tools behind a credit pack, settling to your wallet.
Point a client at it
For a local stdio server, add it to your client’s MCP config with command: node and the path to dist/stdio.js. To try the deployed one instead:
{
"mcpServers": {
"tollbooth-research": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://tollbooth-server-production.up.railway.app/mcp"]
}
}
}Then ask for something that needs a tool — Read https://example.org and tell me what it says. The first call returns a payment challenge. Open the link, pay, tell the agent you have paid, and it retries with the handle.
Make your own tool paid
Wrap the server once:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { definePrice } from '@tollbooth/core';
import { withPaywall } from '@tollbooth/mcp';
import { MooveClient, MooveProvider } from '@tollbooth/moove';
import { SqliteEntitlementStore } from '@tollbooth/store-sqlite';
const store = new SqliteEntitlementStore({ path: './tollbooth.sqlite' });
const provider = new MooveProvider({
client: new MooveClient({ apiKey: process.env.MOOVE_API_KEY! }),
store,
prices: [
definePrice({
sku: 'research',
unit: 'credit_pack',
amount: '10.00', // decimal string, never a number
credits: 250,
label: 'Research tools — 250 credits',
}),
],
});
const server = withPaywall(
new McpServer({ name: 'my-server', version: '0.1.0' }),
{ provider, store }
);Then paidTool takes the same arguments as registerTool, plus a sku and a per-call cost. The argument order mirrors Cloudflare Agents’ paidTool so the shape is familiar.
server.paidTool(
'fetch_readable',
'Fetch a web page and return its readable text.',
{ sku: 'research', cost: 1 }, // an expensive tool can cost more
{ url: z.string() },
{ readOnlyHint: true },
async (args) => readable(String(args.url))
);Tollbooth adds the tollboothTokenargument to your tool’s schema, gates the call, and hands your handler the arguments with the token already stripped. Unpaid calls never reach your code.
Where to next
- The full reference server, with its hardening notes: examples/research-tools.
- Choosing a store for deployment: stores.
- Every option: configuration reference.