Fallback
Return a degraded result instead of forcing every unpaid call into a hard failure.
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.
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
Toolgate wraps the call boundary where payment, quota, retries, execution, and recovery meet.
Return a degraded result instead of forcing every unpaid call into a hard failure.
Replay completed results for duplicate request keys without charging twice.
Use minor-unit accounting through `Money`, `usd()`, and ledger adapters.
Record usage and optionally price after execution based on actual metrics.
Credit back prepaid calls when the handler fails after charge.
Inspect decisions, charge status, fallback usage, handler status, and recovery actions.
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);
Use `createMcpAdapter()` to expose a paid tool through an MCP server. `_meta.toolgate` carries receipt, fallback, and payment-required metadata.
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}`] }),
});
Payment rails are optional adapters. Multi-instance production requires durable idempotency, which is future work.
| Area | Status |
|---|---|
| Core runtime | Developer preview |
| In-memory ledger and idempotency | Local development and single-process prototypes |
| SQLite / D1 ledger | Local and single-process paths |
| Stripe test mode | Validated with configured test credentials |
| Stripe production | Beta; validate your webhook and deployment path |
| x402 | Experimental; mainnet not tested |
| MPP | Mocked / spec-path unless verified with real mppx integration |