Skip to main content

Don't Pester Your Customer™ — Bitcoin Lightning micropayments for MCP servers

Project description

Tollbooth DPYC™

License PyPI version Python 3.12+

Milo drives the Lightning Turnpike — Don't Pester Your Customer

Don't Pester Your Customer™ — Bitcoin Lightning micropayments for MCP servers.

The metaphors in this project are drawn with admiration from The Phantom Tollbooth by Norton Juster, illustrated by Jules Feiffer (1961). Milo, Tock, the Tollbooth, Dictionopolis, and Digitopolis are creations of Mr. Juster's extraordinary imagination. We just built the payment infrastructure.


The Problem

Thousands of developers are building MCP servers — services that let AI agents like Claude interact with the world. Knowledge graphs, financial data, code repositories, medical records. Each one is a city on the map. But the turnpike between them? Wide open. No toll collectors. No sustainable economics. Just a growing network of roads that nobody's figured out how to fund.

Every MCP operator faces the same question: how do I keep the lights on?

Traditional API keys with monthly billing? You're running a SaaS company now. The L402 protocol — Lightning-native pay-per-request? Every single API call requires a payment negotiation. Milo's toy car stops at every intersection to fumble for exact change.

The Solution

Tollbooth DPYC takes a different approach — one that respects everyone's time:

Milo drives up to the tollbooth once, buys a roll of tokens with a single Lightning invoice, and drives. No stops. No negotiations. No per-request friction. The tokens quietly decrement in the background. When the roll runs low, he buys another. The turnpike stays fast.

Prepaid credits over Bitcoin's Lightning Network, gated at the tool level, settled instantly, with no subscription management and no third-party payment processor taking a cut.

Install

pip install tollbooth-dpyc

# With Nostr features (Secure Courier, audit trail, notifications, credential exchange)
pip install tollbooth-dpyc[nostr]

# With QR code rendering for credential cards
pip install tollbooth-dpyc[qr]

# Everything
pip install tollbooth-dpyc[nostr,qr]

What's in the Box

Core

Module Purpose
TollboothConfig Plain frozen dataclass — no pydantic, no env-var reading. Your host constructs it.
UserLedger Per-user credit balance with FIFO debit/credit, tranches with TTL, daily usage logs, JSON serialization.
BTCPayClient Async HTTP client for BTCPay Server's Greenfield API — invoices, payouts, health checks, sats/BTC conversion.
VaultBackend Protocol for pluggable ledger persistence — implement store_ledger, fetch_ledger, snapshot_ledger.
LedgerCache In-memory LRU cache with write-behind flush, per-user asyncio locks, dirty tracking.
ToolTier Cost tiers for tool-call metering (FREE=0, READ=1, WRITE=5, HEAVY=10 sats per call).
ToolPricing Dynamic pricing: fixed + percentage-of-parameter (ad valorem) pricing with min/max caps. compute(**tool_kwargs) returns the cost in sats.
ToolIdentity Capability-based tool identity with deterministic UUID v5. Declares category, intent, and pricing hints (pricing_hint_type, pricing_hint_value, pricing_hint_param, pricing_hint_min).
identity_proof Verifies Schnorr-signed kind-27235 Nostr events proving npub ownership. verify_proof(proof_json, expected_npub, tool_name) is the single entry point for all proof verification.

Actor Protocols

Three protocol interfaces define the DPYC ecosystem roles. Each declares the tool surface for its actor type.

Module Purpose
OperatorProtocol Operator tool surface — balance, statements, Secure Courier, delegation to Authority/Oracle.
OPERATOR_BASE_CATALOG Canonical list[ToolPathInfo] for all 19 Operator protocol tools. Single source of truth — operators inherit and extend.
AuthorityProtocol Authority tool surface — certification, operator registration, tax collection.
AUTHORITY_BASE_CATALOG Canonical list[ToolPathInfo] for all 11 Authority protocol tools.
OracleProtocol Oracle tool surface — community concierge (tax rates, membership, onboarding).
ActorRole Enum: OPERATOR, AUTHORITY, ORACLE.
ToolPath / ToolPathInfo HOT/COLD/DELEGATION path metadata for tool routing.

OperatorRuntime

OperatorRuntime is the main entry point for building a Tollbooth-powered MCP server. It wires together identity, billing, pricing, constraints, and tool registration.

Method / Feature Purpose
debit_or_deny(tool_id, npub, *, proof, tool_kwargs) Gate a tool call: identity, proof, access, pricing, constraints, billing. Returns int (cost in sats) on success, dict (error details) on denial. Every tool that takes npub requires proof.
purchase_mode="direct" Trust-root operators (Authority) read NEON_DATABASE_URL from env and skip relay bootstrap. Default "certified" uses Authority certification.
set_pricing_model(model_json, proof) Update the Neon pricing model. proof is a separate parameter (Schnorr-signed kind-27235 event). Restricted to operator.

Ready-Made Tool Implementations

The tollbooth.tools package provides functions your MCP server wraps as tools. Each takes infrastructure objects (BTCPayClient, LedgerCache) as parameters — you wire them up, Tollbooth handles the logic.

Function Purpose
purchase_credits_tool Creates a BTCPay invoice, records it as pending, returns a checkout link. Validates Authority certificate when authority_public_key is configured.
direct_purchase_tool Invoice without certificate — for Authority self-purchases (trust anchor has no upstream).
check_payment_tool Polls an invoice, credits the balance on settlement. Idempotent.
check_balance_tool Returns current balance, usage summary, tier info, and invoice history. Read-only.
restore_credits_tool Recovers credits from a paid invoice lost to cache/vault issues. Checks vault first, falls back to BTCPay.
account_statement_tool 30-day purchase and usage history for a user.
btcpay_status_tool Diagnostics: BTCPay connectivity, store name, API key permissions.
compute_low_balance_warning Pure function — returns a warning dict if balance is below threshold, None if healthy.
anchor_ledger_tool Creates a Merkle tree of all ledgers and submits root hash to OTS calendar servers.
get_anchor_proof_tool Retrieves a Merkle inclusion proof for a specific user's ledger entry.
list_anchors_tool Lists all Bitcoin-anchored ledger snapshots with timestamps and verification status.

Identity Proofs

Every tool that accepts an npub also requires a proof parameter — a JSON-serialized Schnorr-signed kind-27235 Nostr event proving the caller owns that npub. No proof, no service. This applies to both operator-restricted tools (like set_pricing_model) and patron-facing tools (like purchase_credits).

# Proof format (kind 27235, NIP-98 style)
{
    "pubkey": "<hex_pubkey>",
    "kind": 27235,
    "content": "",
    "created_at": 1713000000,
    "tags": [["u", "<tool_name>"]],
    "sig": "<schnorr_signature>"
}

Verify with identity_proof.verify_proof(proof_json, expected_npub, tool_name). The proof must be less than 60 seconds old by default.

Certificates & Verification

Module Purpose
verify_certificate_auto Detects certificate format and verifies: Nostr kind-30079 Schnorr (preferred) or Ed25519 JWT (legacy).
verify_nostr_certificate Nostr event (kind 30079) certificate verification using Schnorr/BIP-340 via pynostr.
Anti-replay JTI store Built into certificate verification — prevents double-spend of purchase certificates.

Authority Client

Module Purpose
AuthorityCertifier Server-to-server MCP client for auto-certifying credit purchases. Opens a short-lived fastmcp.Client SSE connection with Horizon OAuth auto-negotiation.
AuthorityCertifyError Raised when Authority certification fails (connection, auth, or tool error).

Oracle Client

Module Purpose
OracleClient Server-to-server MCP client for delegating Oracle tool calls. Opens a short-lived fastmcp.Client SSE connection per call. Oracle tools are free and unauthenticated.
OracleClientError Raised when an Oracle delegation call fails (connection or parse error).

DPYC Registry

Module Purpose
DPYCRegistry Cached HTTPS fetch of the dpyc-community members.json registry. Resolves membership, upstream authorities, and service URLs.
resolve_authority_service Convenience: finds an operator's upstream Authority service URL in one call.
resolve_authority_npub Finds an operator's upstream Authority npub from the registry.
resolve_oracle_service Walks the authority chain to the Prime Authority and returns the Oracle service URL.
resolve_service_by_name Finds a service by name across all active members (any role). Used for peer discovery of community services like OAuth2 collectors.

Vault Backends

Module Purpose
TheBrainVault Stores ledger state as thought notes in a TheBrain knowledge graph. Daily-child pattern with link-based member discovery.
NeonVault Neon serverless Postgres with ACID transactions, optimistic concurrency, and append-only audit journal.
NeonCredentialVault Credential storage on Neon Postgres (implements CredentialVaultBackend).
CredentialVaultBackend Protocol for pluggable credential storage (store/fetch/delete by service+npub).

Implement the VaultBackend Protocol to add your own (Redis, S3, SQLite, etc.).

Nostr Integration (optional: pip install tollbooth-dpyc[nostr])

Module Purpose
SecureCourierService High-level wrapper for 3 MCP tools: open channel, receive credentials, forget credentials. On first-time relay receipt, sends the ncred1... credential card back to the patron via Nostr DM for scan-and-paste reuse.
NostrCredentialExchange NIP-44/NIP-04 encrypted DM credential delivery with vault-first lookup. Relay copies deleted via NIP-09 after pickup.
CredentialTemplate / FieldSpec Schema and validation for credential payloads.
NostrAuditPublisher Publishes kind-30078 NIP-78 events on every vault write for tamper-evident audit trail. NIP-44v2 encrypted.
AuditedVault Wraps any VaultBackend to add the audit trail transparently.
NotificationManager Proactive NIP-44 DMs when patron balance crosses thresholds or tranches approach expiration. Fire-and-forget.
courier_health / courier_ping Relay diagnostics and outbound DM smoke tests.

Credential Cards (optional: pip install tollbooth-dpyc[qr])

Module Purpose
encode_credential_card Creates bech32-encoded ncred1... credential cards (kind 21420 Nostr events, NIP-44 encrypted).
decode_credential_card Decodes and verifies credential cards with expiration checking.
render_qr Renders credential cards as QR codes via segno.

Constraint Engine

Module Purpose
ConstraintEngine Evaluates constraint lists with configurable logic: ALL_MUST_PASS, ANY_MUST_PASS, FIRST_MATCH.
ConstraintGate Drop-in middleware integrating constraints with the _debit_or_deny pattern.
TemporalWindowConstraint Time-of-day / day-of-week access windows.
FiniteSupplyConstraint Total call quotas per patron or globally.
PeriodicRefreshConstraint ISO-8601 duration refresh windows (e.g., "10 calls per hour").
CouponConstraint Code-based discounts with expiration and redemption limits.
FreeTrialConstraint First N invocations free per patron.
LoyaltyDiscountConstraint Spend-based tiered discounts.
BulkBonusConstraint Volume bonuses on credit purchases.
HappyHourConstraint Time-based discount windows.
JsonExpressionConstraint Safe tree-based boolean logic evaluator for custom rules.

OpenTimestamps Bitcoin Anchoring

Module Purpose
MerkleTree SHA-256 Merkle tree over all (npub, ledger_json) entries.
OTSCalendarClient Submits root hash to public OTS calendar servers (no API key required).
InclusionProof Verifiable Merkle proofs for individual patron entries.

Utilities

Module Purpose
make_slug_tool Decorator for slug-prefixed MCP tool registration (e.g., brain_check_balance).
lnurl LUD-06/LUD-16 Lightning address resolution (parse → .well-known → callback → BOLT11).

Quick Start

from tollbooth import TollboothConfig, UserLedger, BTCPayClient, LedgerCache

# Configure — your host reads env vars, Tollbooth gets a plain dataclass
config = TollboothConfig(
    btcpay_host="https://your-btcpay.example.com",
    btcpay_store_id="your-store-id",
    btcpay_api_key="your-api-key",
)

# Create a BTCPay client
async with BTCPayClient(config.btcpay_host, config.btcpay_api_key, config.btcpay_store_id) as client:
    # Create an invoice for 1000 sats
    invoice = await client.create_invoice(1000, metadata={"user": "milo"})
    print(f"Pay here: {invoice['checkoutLink']}")

Configuration

TollboothConfig is a plain frozen dataclass. Your host application constructs it from its own settings (env vars, pydantic-settings, YAML — whatever you prefer). Tollbooth never reads environment variables directly.

Field Type Default Purpose
btcpay_host str | None None BTCPay Server URL for creating invoices and checking payments
btcpay_store_id str | None None BTCPay store ID — each operator runs their own store
btcpay_api_key str | None None BTCPay API key — must have invoice + payout permissions
btcpay_tier_config str | None None JSON string mapping tier names to credit multipliers
btcpay_user_tiers str | None None JSON string mapping user IDs to tier names
seed_balance_sats int 0 Free starter balance granted to new users (0 to disable)
authority_public_key str | None None Authority's Nostr npub (Schnorr certificates) or Ed25519 public key (legacy JWT). Required for purchase_credits.
credit_ttl_seconds int | None None Credit expiration in seconds. None = no expiration.
constraints_enabled bool False Enable constraint engine evaluation on tool calls.
constraints_config str | None None JSON constraint configuration (see Constraint Engine section).

The Certification Fee Cascade

We didn't build Tollbooth to sell. We built it to give away — like the Massachusetts Turnpike Authority. The Authority doesn't operate every toll plaza. Independent operators run the booths. What the Authority does is simpler: it certifies purchase orders and collects a small fee for each certification.

When a user purchases credits, the settlement involves a certification cascade:

  1. Milo pays the operator's Lightning invoice for the full requested amount
  2. The operator holds a pre-funded cert-sat balance at their Authority
  3. The Authority deducts a 2% certification fee from the operator's reserve when signing the purchase certificate
  4. Each Authority is itself an operator of its upstream Authority — the same 2% fee cascades up through the chain to the Prime Authority

The certification fee is the operator's cost of doing business — Milo always receives exactly the credits they paid for. The operator pre-funds their Authority reserve and treats the fee as overhead, just like payment processing costs.

The Economics

For Milo (the user): Nothing changes. Buy credits, use tools, drive the turnpike. The invoice is always for the full amount requested.

For the operator: A free, production-tested monetization framework. No license fee. The 2% certification fee is a cost of doing business — pre-fund your Authority reserve and forget about it.

For the ecosystem: Revenue scales with adoption, not effort. Every new MCP server that installs Tollbooth becomes a node in the Lightning economy. The fee cascade flows naturally through the Authority chain — no direct payouts, no separate settlement channels.

Architecture

Tollbooth is a three-party ecosystem built on the DPYC Honor Chain:

Repo Role
tollbooth-authority The institution — tax collection, Schnorr signing, purchase order certification
tollbooth-dpyc (this package) The booth — operator-side credit ledger, BTCPay client, tool gating, auto-certification
dpyc-community The registry — membership, governance, and service discovery
dpyc-oracle The concierge — community onboarding, tax rates, membership lookup
thebrain-mcp Reference operator — PersonalBrain knowledge graph, 40+ metered tools
excalibur-mcp Reference operator — social media posting via Tollbooth credits
tollbooth-authority               tollbooth-dpyc (this package)     your-mcp-server (consumer)
================================  ================================  ================================
Schnorr signing + tax ledger      TollboothConfig                   Settings ──constructs──> TollboothConfig
certify_purchase → Nostr cert     UserLedger + LedgerCache          implements VaultBackend
Authority BTCPay                  BTCPayClient                      TOOL_COSTS maps tools to ToolTier
                                  VaultBackend (Protocol)           AuthorityCertifier (auto-certify)
                                  DPYCRegistry (service discovery)
                                  Constraint engine + pricing

Dependency flows one way: your-mcp-server --> tollbooth-dpyc. Authority is a network peer, not a code dependency. Core runtime dependencies: httpx, pynostr. Nostr relay features require websocket-client (install with pip install tollbooth-dpyc[nostr]).

Auto-Certification

AuthorityCertifier lets operator servers auto-obtain Authority certificates during purchase_credits — one tool call instead of two. Opens a short-lived fastmcp.Client SSE connection with Horizon OAuth, calls certify_credits, and returns the parsed certificate dict.

from tollbooth import AuthorityCertifier

certifier = AuthorityCertifier(
    authority_url="https://authority.fastmcp.app/mcp",
    operator_npub="npub1...",
)
cert = await certifier.certify(amount_sats=100)
# cert["certificate"], cert["jti"], cert["net_sats"], ...

The DPYCRegistry resolves service URLs automatically:

from tollbooth import resolve_authority_service

service = await resolve_authority_service("npub1operator...")
# service["url"] → "https://authority.fastmcp.app/mcp"

Oracle Delegation

OracleClient lets operator servers delegate community queries to the DPYC Oracle — no separate MCP connection from the AI agent needed. Oracle tools are free and unauthenticated.

from tollbooth import OracleClient, resolve_oracle_service

# Resolve Oracle URL via registry (walks authority chain to Prime Authority)
service = await resolve_oracle_service("npub1operator...")
# service["url"] → "https://dpyc-oracle.fastmcp.app/mcp"

# Call any Oracle tool
client = OracleClient(service["url"])
result = await client.call_tool("get_tax_rate")
# result → {"rate_percent": 2, "min_sats": 10, ...}

Constraint Engine

The constraint engine lets operators define access rules and pricing modifiers for their tools. Constraints are evaluated before every tool call via ConstraintGate.

Evaluation modes: ALL_MUST_PASS (default), ANY_MUST_PASS, FIRST_MATCH.

Configure via JSON:

{
  "constraints": [
    {"type": "free_trial", "max_free_calls": 5},
    {"type": "temporal_window", "start_hour": 9, "end_hour": 17, "timezone": "US/Eastern"},
    {"type": "happy_hour", "start_hour": 17, "end_hour": 19, "discount_percent": 50}
  ],
  "mode": "ALL_MUST_PASS"
}

Secure Courier

Credentials delivered via encrypted Nostr DMs — they never appear in the chat window. SecureCourierService wraps four MCP tools (session status, open channel, receive, forget) with NIP-44 encryption, template validation, and vault-first lookup.

On first-time relay receipt, the service automatically sends the ncred1... credential card back to the patron via Nostr DM. Patrons can scan or paste this card to reactivate their session later — no Courier exchange needed. Vault restores skip the DM (no spam on reconnect). Delivery is fire-and-forget; failures never block the credential flow.

CredentialVaultBackend is a Protocol for pluggable credential storage. After first delivery, receive() checks the vault before touching relays. Relay copies are deleted via NIP-09 after pickup.

Credential Cards

ncred1... bech32-encoded credential cards package encrypted credentials into scannable QR codes. Built on kind-21420 Nostr events with NIP-44v2 encryption. Install with pip install tollbooth-dpyc[qr] for QR rendering.

Nostr Audit Trail

NostrAuditPublisher publishes a kind-30078 NIP-78 event on every vault write. Content is NIP-44v2 encrypted (ECDH + ChaCha20-Poly1305) so only the patron's nsec can read it. Wrap any VaultBackend with AuditedVault to add the audit trail transparently.

Low-Balance Notifications

NotificationManager sends proactive NIP-44 DMs when a patron's balance crosses configurable thresholds (WARNING, CRITICAL) or when credit tranches approach expiration. Delivery is fire-and-forget on a daemon thread — never blocks the tool path.

OpenTimestamps Bitcoin Anchoring

Anchor ledger state to the Bitcoin blockchain for irrefutable, timestamped proofs. MerkleTree builds a SHA-256 tree over all (npub, ledger_json) entries. OTSCalendarClient submits the root hash to public OTS calendar servers (no API key required). InclusionProof provides verifiable Merkle proofs for individual patron entries.

DPYC Identity (Nostr npub)

Every participant in the Tollbooth ecosystem — Authorities, Operators, and Users — is identified by a Nostr keypair. The npub (public key) is your identity on the DPYC Honor Chain. The nsec (private key) stays with you — never shared, never sent to a service.

Generate a keypair:

pip install nostr-sdk
python scripts/generate_nostr_keypair.py

Then set the appropriate environment variable in your .env:

Role Variable Purpose
Authority DPYC_AUTHORITY_NPUB This Authority's identity on the Honor Chain
Authority DPYC_UPSTREAM_AUTHORITY_NPUB The Authority above in the chain (empty for Prime)
Operator DPYC_OPERATOR_NPUB This Operator's identity on the Honor Chain
Operator DPYC_AUTHORITY_NPUB The Authority this Operator is registered with

Users provide their npub via the Secure Courier credential exchange flow — no env var needed.

Development

git clone https://github.com/lonniev/tollbooth-dpyc.git
cd tollbooth-dpyc
python -m venv venv
source venv/bin/activate
pip install -e ".[dev,nostr,qr]"
pytest tests/ -q

Further Reading

The Phantom Tollbooth on the Lightning Turnpike — the full story of how we're monetizing the monetization of AI APIs, and then fading to the background.

Trademarks

DPYC, Tollbooth DPYC, and Don't Pester Your Customer are trademarks of Lonnie VanZandt. See the TRADEMARKS.md in the dpyc-community repository for usage guidelines.

License

Apache 2.0 — see LICENSE.


Because in the end, the tollbooth was never the destination. It was always just the beginning of the journey.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tollbooth_dpyc-0.7.7.tar.gz (2.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tollbooth_dpyc-0.7.7-py3-none-any.whl (218.7 kB view details)

Uploaded Python 3

File details

Details for the file tollbooth_dpyc-0.7.7.tar.gz.

File metadata

  • Download URL: tollbooth_dpyc-0.7.7.tar.gz
  • Upload date:
  • Size: 2.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tollbooth_dpyc-0.7.7.tar.gz
Algorithm Hash digest
SHA256 bbd14f09f9230368cddeca2b008b65dc1b1cdbb5e99e43bd6c8b894f18c7f777
MD5 e1e476a61ec4e91fb5ecd1d269476abd
BLAKE2b-256 0497004893c1a4ad588ca9023cc27fd73a366bfcc9e95a05aff9fed8adb16a1c

See more details on using hashes here.

Provenance

The following attestation bundles were made for tollbooth_dpyc-0.7.7.tar.gz:

Publisher: publish.yml on lonniev/tollbooth-dpyc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tollbooth_dpyc-0.7.7-py3-none-any.whl.

File metadata

  • Download URL: tollbooth_dpyc-0.7.7-py3-none-any.whl
  • Upload date:
  • Size: 218.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tollbooth_dpyc-0.7.7-py3-none-any.whl
Algorithm Hash digest
SHA256 125d84f8025cdd01308eb3d42a6882cebabc16860a1be6af0914b4de833c2635
MD5 2e932afec9117a871a91a59a6e32397d
BLAKE2b-256 6cd0c49fb6f638341de3249f9408e6d44ded7bb11daee91cda9dc6e1debe5e0b

See more details on using hashes here.

Provenance

The following attestation bundles were made for tollbooth_dpyc-0.7.7-py3-none-any.whl:

Publisher: publish.yml on lonniev/tollbooth-dpyc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page