shelf-smoke

Machine-buyable URL smoke API/MCP. Prepaid TEST credits. Sample key via POST /keys (no hardcoded secret). No human mid-loop.

Hosted MCP Server

npx add-mcp 'https://shelf.kymac.co/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

shelf-smoke

Machine-buyable URL smoke for other AI bots. Live host https://shelf.kymac.co. Sole advertised entry: this file (GET /llms.txt). Do not start at OpenAPI, MCP, or /.

Bot-alone path: this file → get key → quote → free-try smoke. No human mid-loop. Prepaid checkout is only after the free credit is spent.

GET /health is public. It reports billing (test | live | gated) and runner_ready (Playwright/Chromium can launch — not just process-up). Probe fails closed when runner_ready is false. Live Stripe stays gated (unlock-live); current hosts report test when TEST secrets are present, otherwise gated. Issue a caller key at POST /keys (ssm_… shown once). First issue grants 1 free credit (credit.grant reason=first_key). Prefer Authorization: Bearer ssm_… on gated routes. X-API-Key: ssm_… is an alias for the same secret. Key id is the stable caller_id for Ledger events.

Install sequence (no human mid-loop):

  1. GET /llms.txt (this file — sole advertised entry)
  2. POST /keys — secret ssm_… returned once; id is caller_id. First issue grants 1 free credit.
  3. Authenticate with Authorization: Bearer ssm_… (X-API-Key is an alias only)
  4. GET /credits/quote?op=smokecost, balance, would_succeed without debit
  5. POST /smoke against the golden URL https://example.com/
  6. GET /credits/packs then checkout/confirm only after the free try is exhausted
  7. GET /credits/balance and GET /credits/events — Ledger metering-truth

Golden proof

Known-good URL: https://example.com/

Every POST /smoke HTTP 200 includes credits_burned and credits_remaining.

Pass (HTTP 200; compare ok; burns 1 credit):

{"ok":true,"url":"https://example.com/","viewports":[{"name":"desktop","width":1280,"url":"https://example.com/","ok":true,"status":200},{"name":"phone","width":390,"url":"https://example.com/","ok":true,"status":200}],"credits_burned":1,"credits_remaining":0}

Fail (HTTP 200, ok:false; runner finished — failed smokes do not debit). Example when a URL returns HTTP 404:

{"ok":false,"url":"https://example.com/missing","viewports":[{"name":"desktop","width":1280,"url":"https://example.com/missing","ok":false,"status":404,"error":"HTTP 404"},{"name":"phone","width":390,"url":"https://example.com/missing","ok":false,"status":404,"error":"HTTP 404"}],"credits_burned":0,"credits_remaining":1}

HTTP 200 always means the runner finished — read ok for the verdict.

Fail shapes

Every HTTP fail body has code, message, retryable, and error (alias of message). No bare text 500s.

401 on gated routes (WWW-Authenticate: Bearer on both):

  • missing: {"code":"missing_api_key","message":"missing API key","retryable":false,"error":"missing API key"}
  • invalid: {"code":"invalid_api_key","message":"invalid API key","retryable":false,"error":"invalid API key"}

402 when smoke would run without enough credits:

  • {"code":"insufficient_credits","message":"insufficient credits","retryable":false,"error":"insufficient credits","caller_id":"k_…","balance":0,"required":1}

400 invalid request: {"code":"invalid_request","message":"…","retryable":false,"error":"…"}

404 unknown route: {"code":"not_found","message":"not found","retryable":false,"error":"not found"}

500 unhandled: {"code":"internal_error","message":"…","retryable":true,"error":"…"}

504 timeout: {"code":"timeout","message":"smoke timed out","retryable":true,"error":"smoke timed out"}

503 Stripe TEST missing: {"code":"stripe_not_configured","message":"stripe test mode not configured","retryable":true,"error":"stripe test mode not configured"}

Ledger events (append-only, this caller_id):

  • credit.grant — caller_id, credits_added, reason (first_key), ts
  • credit.purchase — caller_id, pack_id, credits_added, amount_cents, currency, stripe_payment_intent_id (TEST), ts
  • credit.debit — caller_id, credits_burned, reason (smoke_success), request_id, ts
  • credit.balance_snapshot (optional) — caller_id, balance, ts (or derive from events)

Stripe TEST only (STRIPE_SECRET_KEY=sk_test_…). Live keys fail closed (unlock-live).

MCP Streamable HTTP is POST /mcp (not an install entry). Machine transport discovery: GET /.well-known/mcp.json. Copy-paste client snippet: GET /mcp.json (this file is the only advertised pointer to the snippet; not an install entry). Tools: issue_key, verify_key, quote_credits, list_credit_packs, checkout_credits, confirm_credits, credit_balance, credit_events, smoke, plus optional health. smoke returns the same credit fields and fail JSON as HTTP. Gated tools prefer Authorization: Bearer or env SHELF_SMOKE_API_KEY; X-API-Key is an alias. Stdio: npx tsx src/mcp-stdio.ts with SHELF_SMOKE_API_KEY.