createMcpAppAdapter returns a SolvaPayTransport that tunnels every data call through app.callServerTool instead of HTTP. Mount it on SolvaPayProvider and every hook (usePurchase, useMerchant, <CurrentPlanCard>, <LaunchCustomerPortalButton>, etc.) works unchanged.
Prerequisites
- An MCP host such as
basic-host - A SolvaPay product with at least one active plan
- An MCP server that implements the SolvaPay tool surface — see MCP Server integration for the server-side paywall patterns
@solvapay/reactand@modelcontextprotocol/ext-appsinstalled in your MCP App bundle
Install
Quick start
Wire the adapter intoSolvaPayProvider and you’re done — every SDK hook routes through the MCP transport.
app.connect() still has to run once before the provider mounts — do it in a top-level bootstrap effect alongside whatever host-context handling you need.
Tool contract
The adapter maps each transport method to a single MCP tool name. ExportMCP_TOOL_NAMES in your server so the two stay in lockstep.
Your server only needs to implement the tools the UI actually uses. Unimplemented tools surface as a thrown error from the adapter — catch and feature-detect in your component.
Server side
On the server, register each tool with the canonical name so the client adapter can find it. Import the constants so you never hand-type a string.createMcpOAuthBridge from @solvapay/mcp/fetch (or /express) to surface customer_ref on extra.authInfo — the core helpers read it from the synthesised request headers. For the full batteries-included setup use createSolvaPayMcpServer from @solvapay/mcp. A complete working server lives at examples/mcp-checkout-app/src/server.ts.
Authentication
Because the real identity lives server-side on the OAuth bridge’scustomer_ref, the provider only needs a sentinel token to flip isAuthenticated true. Supply a lightweight auth adapter alongside the transport:
Hosted checkout from inside the iframe
Open checkout in a new browser tab. Pre-fetch the session URL on mount and render a real<a target="_blank"> anchor — scripted window.open after an async round-trip is blocked by typical host sandboxes, but anchor clicks are permitted.
focus / visibilitychange, call refetch() from usePurchase so returning from the hosted tab flips the card to its new state automatically.
Account management
Once a customer has paid, drop<CurrentPlanCard /> into the tree and the SDK does the rest — plan name, next-billing line, payment-method summary, plus Update card and Cancel plan actions. The card returns null when there is no active purchase, so you can render it unconditionally. The MCP manage_account view passes hideUpdatePaymentButton and hideCancelButton and pairs the card with a single Manage account customer-portal CTA — both flows run through the portal there.
<CurrentPlanCard />renders the active plan, mirrored card brand/last4, and inline Update card / Cancel plan actions. The MCPmanage_accountview hides both — card updates and cancellation route through the Manage account customer-portal CTA — and surfaces a one-line hint pointing at it.<LaunchCustomerPortalButton />opens the hosted customer portal in a new tab (default label: “Manage account”). The button renders enabled from first paint and fetches the portal URL in the background; multiple instances under the same provider share a single in-flightcreateCustomerSessionround-trip. When the URL has resolved, click is a real<a target="_blank">navigation (sandbox-safe). On a cache-miss click the handler awaits the in-flight promise and falls back towindow.open.usePaymentMethod()exposes the mirrored card under{ paymentMethod, loading, refetch }when you need to build a custom account view. The card brand and last4 come from thepayment_intent.succeededwebhook persisted on the Customer — no card-element iframe required inside the MCP App sandbox.
Text-only paywall
The MCP App surface uses SolvaPay’s text-only paywall. AregisterPayable tool emits a plain-text Purchase required response — no embedded UI meta, no structured checkout payload — so the host model can read the copy, call create_checkout_session, and surface the returned URL however it likes. There is no McpPaywallView / McpNudgeView / McpUpsellStrip component anymore; render checkout through <PaymentForm> or the hosted URL instead.
Complete example
A full working example — server, client, OAuth bridge, polling, and the five-state purchase flow — lives in the SDK repo atexamples/mcp-checkout-app. Clone it, set SOLVAPAY_SECRET_KEY and SOLVAPAY_PRODUCT_REF, point basic-host at http://localhost:3006/mcp, and you have an end-to-end paywalled MCP App running locally.
Known boundaries
trackUsagestays on the server. Usage metering belongs on your backend, not the client — continue to callsolvaPay.trackUsage(...)from@solvapay/serverinside your tool handlers.- Inline card entry on Claude. Claude’s MCP Apps host applies a fixed sandbox Content Security Policy that does not forward the spec’s
frameDomainsdeclaration. Because Stripe’s card surfaces load in a nested iframe, the inline Payment Element cannot render inside Claude’s widget. The SDK detects this at runtime and falls back to SolvaPay-hosted checkout in a new tab (see Hosted checkout from inside the iframe), so checkout still completes and the payment stays on SolvaPay’s rail. On hosts that honor the spec (e.g. ChatGPT), the inline Payment Element renders directly — no configuration needed.
Next steps
- MCP Server integration — server-side paywall patterns with
createSolvaPayMcpServerandregisterPayable - React SDK guide — hooks and components used under
createMcpAppAdapter - Purchase management — cancel, reactivate, renewal semantics