@tab/tab-mcp
MCP server that adds billing and settlement to any MCP server, enabling paid tool calls with configurable pricing, spending limits, and automatic invoicing through the AllScale CLI.
README
AllScale Tab
Credit and settlement for the AI agent economy. Put one middleware in front of any MCP server and the agents calling it can run a tab: consume now, settle on terms.
The buyer sets a spending fence once — per-transaction cap, total cap, expiry. After that, every paid tool call is a local ledger entry: no payment round-trip, no per-call latency, no money moving. When the tab hits a threshold or the billing period closes, Tab invoices, the buyer's kit pays, and the tab clears. Miss the settlement and service stops.
agent ──MCP──▶ [ tab middleware ] ──▶ your tools
│
├─ charge locally (no network, no money moves)
├─ over the limit → 402 PAYMENT_REQUIRED
└─ threshold hit → invoice ──▶ buyer kit ──▶ payout ──▶ tab cleared
│
every money move goes through one
SettlementAdapter → AllScale CLI
Quickstart
npm install
npm test # unit + end-to-end tests (108)
npm run self-test # live checklist: accumulate → invoice → fund → claim → 402
./demo/run-demo.sh
With the AllScale CLI installed and logged in, these check it directly:
npm install -g @allscale/cli # then: allscale device-login
npm run capture:help # capture the CLI surface locally (git-ignored)
npm run verify:chains # re-derive the chain id table, fail on drift
node scripts/cli-probe.mjs # per-method: live-ok / argv-only / blocked
npm run test:cli # the suite with TAB_ADAPTER=cli
The demo builds everything, starts the live ledger page on http://127.0.0.1:4790, starts the
buyer's settlement daemon, then runs an agent that makes 60 paid calls. You will see the balance
climb, the cycle close itself with an allscale invoice send, the buyer answer with an
allscale payout send + allscale claim-link claim, the balance drop to zero, and — once the
tab is cut off — calls come back as 402 PAYMENT_REQUIRED before service resumes.
Everything runs on the stub adapter by default: no credentials, no network, no money.
Make your own MCP server paid
Three lines:
import { createRuntime, attachBilling } from '@tab/middleware';
import { startLedgerServer } from '@tab/core';
const { ledger, config } = createRuntime(); // reads tab.config.json, picks the adapter
attachBilling(server, { ledger, config }); // every tool on `server` is now billed
await startLedgerServer({ ledger }); // optional: GET /ledger for the UI
Or let an agent do it — Tab ships its own MCP server (@tab/tab-mcp) with add_billing,
set_pricing and tab_status:
claude mcp add tab -- node packages/tab-mcp/dist/src/bin/server.js
# then: "add billing to ./packages/example-server, one cent per call"
tab.config.json
{
"pricing": { "default": "0.01", "tools": { "fx_rate": "0.01", "fx_convert": "0.02" } },
"settleThreshold": "0.50",
"billingCycle": "threshold",
"creditLimit": "0.60"
}
| Field | Meaning |
|---|---|
pricing.default |
Charged for any tool without an override. "0.00" makes a tool free. |
pricing.tools |
Per-tool prices. |
settleThreshold |
Balance that closes a billing cycle. |
billingCycle |
threshold invoices automatically; manual waits for POST /settle. |
creditLimit |
How far a buyer may run up before calls are refused with a 402. |
All amounts are fixed-point decimal strings and are computed on as integer cents. No floats touch money.
The 402
A call that cannot be served is answered in an x402-shaped envelope, both as text and in the
result's _meta['tab/payment_required']:
{
"code": 402,
"error": "PAYMENT_REQUIRED",
"tab": {
"balance": "0.50",
"creditLimit": "0.50",
"settleUrl": "http://127.0.0.1:4788/settle",
"status": "settling",
"reason": "CREDIT_EXCEEDED",
"referenceId": "tab-claude-2026w33"
}
}
This is the response shape only — protocol-level x402 signature verification is not implemented (see Roadmap).
Packages
| Package | What it is |
|---|---|
@tab/core |
Ledger engine, SettlementAdapter contract, stub + CLI adapters, read-only HTTP API |
@tab/middleware |
attachBilling(server, …) — makes any MCP server's tools paid |
@tab/example-server |
A paid MCP server (fx_rate, fx_convert) plus the demo consumption driver |
@tab/tab-mcp |
Tab's own MCP server: add_billing, set_pricing, tab_status |
@tab/buyer-kit |
Buyer daemon: validates invoices against the fence, pays them, echoes the real CLI command |
@tab/ledger-ui |
Single-page live ledger (vanilla JS, polls GET /ledger) |
Ledger API
Owned by the MCP server process — the single writer.
| Route | Purpose |
|---|---|
GET /ledger |
Full snapshot: tabs, entries, invoices, CLI echo log |
GET /invoices?status=sent |
Bills awaiting payment |
POST /payments |
Report a funded and claimed payout (idempotent on referenceId) |
POST /settle |
Close the current cycle manually |
POST /cutoff · POST /reopen |
Suspend or restore service |
Settlement
Every money movement — and only these — goes through SettlementAdapter:
| Adapter call | Real command |
|---|---|
enableFence |
(no CLI command — granted in the dashboard), confirmed with allscale payout status --json |
fenceStatus |
allscale payout status --json |
sendPayout |
allscale payout send --amount … --chain … --stable-coin … --reference-id … [--receiver-email …] --json |
claimPayout |
allscale claim-link claim --claim-token … --to-wallet --json (a link can also be claimed by --claim-url …) |
sendInvoice |
allscale invoice send --to-email … --amount … --line "desc|qty|total" --wallet-id … --payment-type 1 --memo … --json |
listTransactions |
allscale transaction list --json |
Settlement is two steps, not one. payout send does not pay the seller: it funds a
Claim Link which the receiver must then claim, and the link expires within minutes
(~21 observed) — an unclaimed link is refunded to the buyer and the bill stays unpaid. So the
buyer kit funds and claims as one serialised step, and a bill is only marked paid once the
claim is proven. Authorization is granted per chain × token pair in the dashboard
(Store Settings → Payout Authorization); an undelegated pair is refused with
FENCE_NOT_AUTHORIZED.
If a claim is lost mid-flight — the process dies after funding, or the claim races the
on-chain deposit and exits pending_deposit — the link is still recoverable by id for as long
as it has not expired, because claim-link get will hand back the claim_url:
allscale claim-link get <claim-link-id> --select 'id status amount claim_url' --json
allscale claim-link status --claim-url <url> --json # wait for is_claimable: true
allscale claim-link claim --claim-url <url> --to-wallet --json
Defaults are sepolia / USDT, set with TAB_CHAIN and TAB_STABLE_COIN.
TAB_ADAPTER=stub (default) runs the local stub. TAB_ADAPTER=cli targets the real CLI.
The CLI adapter requires the AllScale CLI — npm install -g @allscale/cli. It is
implemented against the real command surface: every flag was read off --help, and every
difference from the original design assumptions is itemised in docs/DIFF.md.
Every adapter method has now been exercised against the live API: payout send funded real
claim links on Sepolia and one was claimed on chain. The per-method evidence — and the one
failure mode still open, an unattended claim that can time out and let the link refund — is in
docs/cli-mode-report.md. Without store credentials the payout leg
fails closed (CLI_AUTH_MISSING) before any network call.
Ambiguous outcomes are never treated as failures: on an ambiguous exit or a timeout, Tab
checks payout status first, then retries the same --reference-id, which the backend
deduplicates.
Stub output follows the same discipline: fields whose names are not documented are not
invented — they live under raw with a TODO, and stub-generated values are prefixed stub_
so no demo output can be mistaken for a real settlement. The stub also models the two-step
claim flow, per-pair authorization, claim-link expiry and in-flight funding, so the failure
modes are reachable without touching money.
The claim token — and the claim URL, which embeds it — are bearer credentials. Neither is logged, persisted to the ledger, or shown in the UI.
Credentials
Read from the environment only, never from code or a config file, never logged:
| Variable | Used by |
|---|---|
ALLSCALE_STORE_API_KEY |
payout send (store-key HMAC — not your CLI login) |
ALLSCALE_STORE_API_SECRET |
payout send (prefer the env var; a flag value leaks via shell history) |
The other commands use the CLI's own session (allscale device-login), which needs the
invoice:all, claim_link:all, wallet:read_only and transaction:read_only scopes.
See .env.example for the full list of settings (names only, no values).
.env, credentials, and *.key are git-ignored.
Roadmap (not implemented)
Deliberately out of scope for this build:
- Usage-tier / volume pricing
- Multi-currency tabs (one stablecoin per tab today)
- Credit scoring and dynamic limits
- Dispute and arbitration flow
- Protocol-level x402 signature verification (only the response shape is implemented)
Requirements
Node.js ≥ 20.10, npm. TypeScript is compiled to dist/ — no runtime type-stripping needed.
License
MIT — see LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.