Reliable paid or quota-limited tool calls.

Toolgate is an open-source runtime for MCP and agent tools. It handles fallback, idempotent retries, metering, recovery, and execution traces so retries and edge cases do not double-charge or hard-fail.

Run the repo demo

Clone the repository to run the local demo. The first-run path uses the in-memory ledger and does not require Stripe, x402, MPP, wallets, webhooks, hosted APIs, or environment variables.

git clone https://github.com/tkorkmazeth/toolgate.git
cd toolgate
npm install
npm run build
npm run example:local

Runtime behavior

Toolgate wraps the call boundary where payment, quota, retries, execution, and recovery meet.

Fallback

Return a degraded result instead of forcing every unpaid call into a hard failure.

Idempotency

Replay completed results for duplicate request keys without charging twice.

Integer money

Use minor-unit accounting through `Money`, `usd()`, and ledger adapters.

Metering

Record usage and optionally price after execution based on actual metrics.

Recovery

Credit back prepaid calls when the handler fails after charge.

Traces

Inspect decisions, charge status, fallback usage, handler status, and recovery actions.

Minimal usage

Start with `ToolGate`, `InMemoryLedger`, and `usd()`; add rail adapters later.

import { ToolGate, InMemoryLedger, usd } from "@tkorkmaz/toolgate";

const ledger = new InMemoryLedger();
const gate = new ToolGate({ publisherKey: "tg_local_demo", ledger });

const search = gate.paidTool({
  name: "premium_search",
  price: usd("0.05"),
  onPaymentFailed: "fallback",
  idempotencyKey: (input) => `premium_search:${input.requestId}`,
  handler: async (input) => ({ tier: "premium", query: input.query }),
  fallback: async (input) => ({ tier: "free", query: input.query }),
});

await ledger.credit("caller-1", usd("1.00"), {
  source: "manual",
  reference: "dev-credit",
});

const result = await search({ query: "vector dbs", requestId: "r1" }, "caller-1");
console.log(result.receipt);

MCP wrapper

Use `createMcpAdapter()` to expose a paid tool through an MCP server. `_meta.toolgate` carries receipt, fallback, and payment-required metadata.

See minimal usage →

import { ToolGate, createMcpAdapter, usd } from "@tkorkmaz/toolgate";

const gate = new ToolGate({ publisherKey: "tg_local_demo" });
const mcp = createMcpAdapter(gate, {
  getCallerId: (_args, extra) => extra?.sessionId ?? "demo-user",
});

mcp.paidTool("premium_search", {
  price: usd("0.05"),
  onPaymentFailed: "fallback",
  inputSchema: { type: "object", properties: { query: { type: "string" } } },
  handler: async (args) => ({ results: [`deep results for ${args.query}`] }),
  fallback: async (args) => ({ results: [`basic result for ${args.query}`] }),
});

Status

Payment rails are optional adapters. Multi-instance production requires durable idempotency, which is future work.

AreaStatus
Core runtimeDeveloper preview
In-memory ledger and idempotencyLocal development and single-process prototypes
SQLite / D1 ledgerLocal and single-process paths
Stripe test modeValidated with configured test credentials
Stripe productionBeta; validate your webhook and deployment path
x402Experimental; mainnet not tested
MPPMocked / spec-path unless verified with real mppx integration