What you’ll build
- A pre-charge hook that runs before each MCP tool call.
- If charge succeeds → run the tool.
- If charge fails (not authorized / low balance) → return the paywall message verbatim so the LLM can show the user an authorization/top-up link.
- All without changing your MCP tool logic.
High-level flow
- Identify user from the MCP request (session, token, or your auth system).
- Call Paywalls charge endpoint with
user
andamount
. - Handle response:
{ success: true }
→ proceed to call the MCP tool.{ success: false, message }
→ return a message that must be shown verbatim to the user (it contains authorization/top-up links and instructions).
- Execute tool and return the result.
Core charge snippet (drop-in)
This is the recommended pattern: pre-charge → branch on success
→ execute.
amount
is a string (e.g.,"0.001"
). Use flat fee or compute dynamically (see “Pricing strategies” below).- Paywalls API returns HTTP 200 both for success and for paywall-required flows; check the
success
boolean. - The
message
string is ready to show to the end user (contains authorization/top-up links).
User identity in MCP
You must provide a stable user ID on every billable call. That ID goes into the Paywallsuser
field.
Example: grabbing userId from Authorization (demo-only)
This is not secure for production—use your own auth/SSO. Shown here to illustrate wiring.
- Use your existing session/JWT/SSO to set a stable internal user ID.
- Don’t expose PII as
user
(don’t send emails/phone numbers). - If your MCP runs behind your own API, inject
X-Paywall-User
as a header in server-side calls to Paywalls.
Pricing strategies for MCP tools
You decide how to price each tool call:- Flat fee per call: e.g.,
"0.001"
USD per invocation. - Tiered by tool: different amounts per
toolKey
. - Dynamic: compute from input size, expected tokens, or external rates.
- Split charge: pre-charge a minimum, then post-charge extra (via
user/charge
) based on actual usage.
Enrich charge calls (metadata & idempotency)
Add metadata to reconcile charges with tool runs:We recommend an Idempotency-Key per tool call to avoid double billing on network retries.
Error handling patterns
- HTTP != success → show a generic error and log
resp.status
+ body. - **HTTP 200 + **
{ success: false, message }
→ return message verbatim (it includes authorization/top-up flows). - Timeouts → don’t charge; return a transient error (or the verbatim message if charge timed out with a paywall link).
- Race conditions → put a short cooldown on the same
(userId, toolKey, requestId)
pair to avoid duplicate processing.
Environment variables
Testing locally
- Use a fake user ID like
user_local_123
. - Start with a small amount, e.g.
"0.0001"
. - Trigger not authorized → verify the MCP returns the message verbatim.
- Trigger low balance → verify top-up path.
- Trigger success → ensure your tool executes post-charge.
FAQ
Q: Can I charge after the tool runs?A: Yes—use
/user/charge
again with the final amount, but we recommend pre-charging a minimum to avoid unpaid executions.
Q: Can I combine with the Chat Proxy?A: Absolutely. Use the Chat Proxy for LLM calls and
/user/charge
for non-LLM tool invocations, or for extra fees.
Q: How do I show the paywall UI?A: The
message
returned on { success: false }
contains everything the LLM needs to show (links & copy). Don’t modify it.