Create a Paid MCP App
SolvaPay-monetized MCP server on Cloudflare Workers. OpenAPI auto-generation or hand-written tools.
Human at a terminal?
npm create solvapay@latest <name> -- --type mcp(use@latest). Ships from-openapi (one-to-one) and from-scratch modes, runs install +solvapay initin one pass.Agent?
scripts/describe.mjs+scripts/scaffold.mjsper references/from-openapi/guide.md. Intent-driven clustering and hand-tuned narration require an LLM — the CLI cannot authorsrc/tools/*.ts. Internal dev runs use the same agent path with--devandcreate-solvapay@preview.
Scope
This skill covers any MCP server whose tools return text or structuredContent — data, intelligence and analytics, search and retrieval, integrations with external APIs, actions and workflows, computations, content generation. Domain-agnostic.
The only UI this skill ships is SolvaPay's built-in checkout / account / topup widget, which mounts only when the user deliberately invokes the account viewer (slash prompts /upgrade, /manage_account, /topup remap onto it with the matching view). For custom graphical widgets, keep this skill for server + paywall wiring and add references/mcp-apps-ui.md.
Guardrails
- Never expose
SOLVAPAY_SECRET_KEYto client code, public env vars, or deploy-time plaintext. Upload vianpx wrangler secret putand keep it in a gitignored.envonly for local dev. - Never wrap SolvaPay intent tools (
account,activate_plan) or the UI transport tools withpayable.mcp()— they are the paywall recovery path, not paid business logic. - Never set
_meta.ui.resourceUrion merchant payable tools. Hosts MUST open the iframe on every advertised call (SEP-1865), which flashes an empty widget on silent successes. - Never return custom iframe/UI on paywall gates — text-only narration naming
`account`with the appropriateview(or`activate_plan`when aplanRefis known). - Always use
mode: 'json-stateless'on stateless edge runtimes (Cloudflare Workers, Deno, Supabase Edge). - Always hide UI-only virtual tools from text-only hosts with
hideToolsByAudience: ['ui']. - Always confirm the resolved
SOLVAPAY_PRODUCT_REFaftersolvapay init; under--yes/ non-TTY, never treat an auto-picked product as final until the user confirms it belongs to this MCP. - If the requested business model is usage-based or metered, verify that the selected product has the intended usage-based plan before handoff.
selections.plans[]is validated during scaffold but is not created by scaffold. - Deploy-existing = scaffolding only. On "deploy my existing server" tasks, add only deploy scaffolding (
scripts/deploy.mjs,wrangler.jsonc[vars],.env) and never open or editsrc/worker.ts— not for imports, CORS,Env, the canonical template, or "stale API shape" patches. Note any API drift in the handoff as a follow-up. Exception: paywall-wiring tasks ("add SolvaPay paywall to my existing MCP server") do editsrc/worker.ts— see references/existing-server.md.
Gotchas
- Deploy pre-flight: run
npx wrangler whoamifirst (not justwrangler login) to confirm auth and print the*.workers.devsubdomain. @solvapayis not a valid package — use subpaths (@solvapay/mcp,@solvapay/mcp/fetch, etc.).ctx.registerPayable(name, config)takes exactly two arguments — not(toolDef, paymentConfig, handler).- Paid handlers return
c.respond(data, { text: narration })— never rawcontentarrays from paid handlers. - Run
node scripts/describe.mjsagainst a local spec file — fetch URLs to/tmp/spec-*.jsonfirst; don't pass URLs directly. - Binary/multipart operations must be skipped. After running
describe.mjs, grep the original spec formultipart/form-data,application/octet-stream,image/, andapplication/pdfunderrequestBody.contentand responsecontent. For each matching operation (e.g.uploadFile,uploadImage), settier: "skip"inselections.json— MCP tools return text, not file streams, so these can't be wrapped usefully. - Empty or relative OpenAPI
serversmust be resolved before scaffold. Confirm the upstream base URL with the user, and watch for path-prefix outliers (e.g./apifhir/...among/api/fhir/...) before generating tools. selections.jsonmust live outside the scaffold target dir (e.g./tmp/selections-<uuid>.json) — upstream API keys must not land in the project tree.- Don't scaffold into an unrelated app repo root without confirming where the MCP worker should live — it's its own deployable unit.
scripts/describe.mjsandscripts/scaffold.mjsin this skill are wrappers — see scripts/README.md for resolution order.
Mandatory read order
Before writing tool code:
- This SKILL.md — routing, input mode, host.
- references/tool-design.md —
registerPayableshape, response contract. - One input-mode guide: references/from-openapi/guide.md or references/from-scratch/guide.md or references/existing-server.md.
- If intent-driven mode (OpenAPI): also read references/from-openapi/intent-driven.md (G2/G3/G7 gate shapes) and references/from-openapi/scaffold.md (G6 gate,
selections.jsonpreview) before executing any gate.
Do not write registerPayable(...), additionalTools, or src/tools/* until all required files are loaded.
Confirmation level (G0 — ask once)
Before any other gate, ask how chatty you should be. See references/hitl-conventions.md.
"How chatty should I be?
standard(default) confirms each big decision;autoonly confirms irreversible steps (scaffold, deploy, go-live);chattyreviews every intent and file."
Routing
1. Existing paid-MCP project? All of: @solvapay/mcp or @solvapay/server in package.json, wrangler.jsonc, and src/worker.ts with createSolvaPayMcpFetch / createSolvaPayMcpServer → references/from-scratch/scaffold-and-extend.md. Do not scaffold.
2. Greenfield — ask once: "OpenAPI/Swagger spec, or hand-written tools?"
| Answer | Guide | Validator |
|---|---|---|
| Has spec (agent) | references/from-openapi/guide.md | validate-selections.mjs → scaffold → verify |
| Hand-written / new | references/from-scratch/guide.md | wrangler dev smoke |
| Existing MCP server (add paywall) | references/existing-server.md | tool invocation smoke |
Inside an unrelated app repo: if cwd is a Next.js app, backend, or monorepo without a paid-MCP server in scope, stop and ask where the MCP server should live before scaffolding (sibling dir or apps/ / packages/ subdirectory).
Dev mode (internal testing only). Use preview tooling and append
--devwhen testing against api-dev:npm create solvapay@preview <name> -- --type mcp --dev,node scripts/describe.mjs --dev ...,node scripts/scaffold.mjs --dev ..., andnpx -y solvapay@preview init --dev. Never for end users.
3. Host: default Cloudflare → references/hosting/cloudflare/README.md. Other hosts → references/hosting/alternatives.md. Optional custom MCP UI → references/mcp-apps-ui.md.
4. Credentials: after scaffold → references/solvapay-init.md (npx -y solvapay@latest init).
Human CLI shortcut (terminal only): see the human block at the top of this file.
OpenAPI flow & gates
describe.mjs → author selections.json → validate-selections.mjs (loop until pass) → scaffold.mjs <selections> <target-dir> → author tools per references/tool-design.md → solvapay init → confirm product/plan → verify → test → deploy. SOLVAPAY_SECRET_KEY is set by solvapay init, never in selections.json. Full G0–G9 HITL gates: references/hitl-conventions.md.
Scripts
| Script | Action | Purpose |
|---|---|---|
scripts/describe.mjs |
Run | Parse OpenAPI spec |
scripts/validate-selections.mjs |
Run | Validate selections.json before scaffold |
scripts/scaffold.mjs |
Run | Generate worker from selections |
scripts/README.md |
See | Resolution order, upstream --help |
Verification loop
- Run the mode's validator (verify script,
wrangler dev, or guide checklist). - On failure → mode-guide troubleshooting → fix → re-run until pass.
- Only then complete the handoff template.
Handoff template
## MCP app handoff
- **Input mode:** from-openapi / from-scratch / existing-server
- **Host:** Cloudflare / alternative
- **Worker URL:** [url]
- **Tools authored:** [list]
- **Gates cleared:** G0–G[n]
- **Product ref:** [prd_... + how it was confirmed]
- **Plan / metering status:** [free default / usage-based plan verified / needs user action]
- **Sandbox:** [success path + gate path verified; note skipped checks explicitly]
- **Paid-path verification:** [paywallGate / merchantBootstrap / upstream smoke: passed, skipped, or not run]
- **Known gaps:** [if any]
Task progress
- Read references/tool-design.md + matching mode guide
- Route (existing vs greenfield, input mode, host)
- Complete mode guide (OpenAPI: describe → validate → scaffold → author)
- Run references/solvapay-init.md
- Run verification loop until pass
- Complete handoff template