roastify-mcp
by lonniev
README.md
# roastify-mcp
A [Tollbooth DPYC](https://github.com/lonniev/dpyc-community) operator for the
[Roastify](https://roastify.app/) Developer API. Roastify is a white-label coffee
dropshipper: it roasts, prints branded packaging, and ships direct to customers.
This operator lets an AI agent read the Roastify catalog, generate packaging artwork, and
keep a private library of designs in a GitHub repo the patron owns — paid for in Bitcoin
Lightning satoshis with no account, no KYC, and no subscription.
## What you're building
```
YOU (operator, human in the loop)
│ │
│ set & tune prices │ drive credential intake
▼ ▼
┌───────────────┐ ┌──────────────────────────────────────────────┐
│ Pricing Studio│ prices │ Roastify — YOUR OPERATOR MCP │
│ (iOS) ├───────▶│ FastMCP · deployed on Horizon │
└───────────────┘ Neon │ ┌────────────────────────────────────────┐ │
│ │ roastify domain module ◀─ Roastify API │ │
Patron (Citizen) │ │ @runtime.paid_tool(FROZEN_UUID) tools │ │
+ MCP client ─────────▶│ ├────────────────────────────────────────┤ │
(Claude, etc.) npub │ │ tollbooth-dpyc SDK (the wheel) │ │
+sats │ │ ledger · vault (AES-256-GCM) · pricing │ │
│ │ /ConstraintGate · Secure Courier·audit │ │
│ └────────────────────────────────────────┘ │
└───┬─────────┬──────────┬───────────┬─────────┘
▼ ▼ ▼ ▼
Neon Postgres BTCPay▶ Sponsor Nostr relays
(your schema) Lightning Authority proofs·courier
ledger+pricing invoices certify + DMs·audit
provision │
api.roastify.app ◀── domain │ ▼
(each patron's calls └──▶ DPYC Oracle +
own API key) dpyc-community
```
### Onboarding roadmap
1. **Nostr keypair** — generate one (`nak key generate`). The nsec is the only env var:
`TOLLBOOTH_NOSTR_OPERATOR_NSEC`.
2. **Sponsor Authority** — register; it provisions your Neon schema automatically.
3. **Secure Courier** — deliver your BTCPay secrets (`btcpay_host`, `btcpay_api_key`,
`btcpay_store_id`) by Nostr DM. They are vaulted, never put in the environment.
4. **Set prices in Pricing Studio** — see below. Tools start unpriced and uncallable.
5. **Deploy on Prefect Horizon** — `fastmcp.json` is ready.
### Get Pricing Studio
Your tools ship **unpriced**, and nobody can call a paid tool that has no price.
**Pricing Studio** (iOS) is the operator console: it reads and writes the pricing model live
in Neon, so prices never live in code — see
[tollbooth-pricing-studio](https://github.com/lonniev/tollbooth-pricing-studio).
That indirection is the point. Dynamic per-tool pricing — surge, happy-hour, loyalty
discounts, free trials for a first-time patron — is what DPYC gives you that a flat paywall
never could.
## Bring your own Roastify key
Every patron delivers **their own** Roastify API key through the Secure Courier, vaulted per
npub. There is no operator-held key and no fallback path.
This follows from the domain rather than being a policy choice. Roastify scopes the catalog,
saved designs, and plan tier to the merchant account behind the key — a patron on Base sees a
different catalog than one on Pro. A single operator-held key would hand one merchant's world
to every caller, which is a wrong answer rather than a limitation to work around. It also
means this operator never holds a credential it could misuse, and revoking your key in the
Merchant App severs access without involving the operator.
```
roastify_get_patron_onboarding_status # what's missing
roastify_request_patron_credentials # → you get a Nostr DM
(reply with your Roastify API key)
roastify_receive_patron_credentials # vaulted, encrypted at rest
```
The same reply may carry the optional `github_token` (a fine-grained token with Contents
read/write), `github_repo` (`owner/repo`) and `github_branch` (default `main`) that the design
library needs.
Live keys start `rty_live_`. Sandbox keys start `rty_test_` and do not fulfill — useful for
exercising the read tools without touching production.
## Tools
| Tool | Tier | What it does |
|---|---|---|
| `roastify_browse_catalog` | read | Catalog products and coffee blends together, plan tier marked |
| `roastify_get_catalog_product` | read | One product with variants, prices, stock, dieline template |
| `roastify_get_blend` | read | One blend: roast level, decaf status, variants |
| `roastify_list_my_products` | read | Your saved Roastify products, cursor-paginated |
| `roastify_get_my_product` | read | One saved product design in full |
| `roastify_check_stock` | read | Stock for one SKU or the whole list |
| `roastify_generate_artwork` | write | Generate artwork from a saved design |
| `roastify_artwork_status` | free | Check that job |
| **Design library** | | |
| `roastify_stash_design` | write | Store a design in your library — a commit in your GitHub repo |
| `roastify_list_designs` | read | Your stored designs, metadata only, newest first |
| `roastify_fetch_design` | read | One stored design in full, images re-inlined (for the courier) |
| `roastify_list_design_versions` | read | A design's committed versions — its git history |
| `roastify_delete_design` | write | Delete one stored design |
| `roastify_get_design_text` | read | A design's editable text layers, with each layer's size |
| `roastify_update_design_text` | write | Apply text edits and commit a new version |
| `roastify_add_design_element` | write | Add a text or image element and commit a new version |
| `roastify_move_elements` | write | Move or resize a group of elements and commit a new version |
| `roastify_set_product_description` | write | Set the product's store-page description on a stored design |
Plus the standard DPYC catalog (balance, purchase, courier, pricing, Oracle, status).
## What this operator does not do, and why
**It cannot create, update, delete, or sync products.** Not a missing feature — the Roastify
API has no such endpoints. `/v1/products` is read-only, and the words *integration*, *sync*,
*shopify*, and *upload* do not appear anywhere in the
[OpenAPI spec](https://docs.roastify.app/openapi.json). Saved products and storefront sync are
Merchant App capabilities by design.
**It does not place orders.** Roastify already routes orders to its own operations and reports
tracking back to your storefront. An MCP layer between those adds a hop without adding
judgment, so order automation is deliberately left to Shopify and Roastify.
**Artwork generation is template personalization, not design.** `roastify_generate_artwork`
rewrites the named text and image placeholders of a design *you already authored* in the
Roastify Design Studio. It cannot author a design from scratch.
**A generated artwork URL is not attached to anything.** In Roastify's API the only consumer
of an artwork URL is an order line — and orders are out of scope here. You receive a URL and
carry it onward yourself. That seam is accepted deliberately; it is not an oversight, and this
operator will not simulate a design flow the API cannot complete.
**The catalog has no palate data.** Origin, region, altitude, processing method, and varietal
exist only in the Merchant App UI. The machine-readable signals are `roastLevel`, `isDecaf`,
size, and price; everything else about a coffee's character is prose in its description. Bean
selection is correspondingly weaker than it looks, and this operator does not dress up an
LLM's reading of a description as structured provenance data.
## The design library, the courier and the Design Bench
Designs live in a GitHub repo the patron owns, one folder per design, and the operator commits
to it on the patron's behalf with the vaulted token. Every save is a commit, so a design has
real version history, and the patron can browse, rename or delete designs in GitHub itself.
Reading or writing a design on a Roastify product needs the merchant's own browser session,
which no API offers. `frontend/` carries two pieces for that:
- **The courier** — a script a bookmarklet injects into `merchant.roastify.app`. It stashes a
product's design into the library and applies a finished design back onto a product. It
holds no editing logic.
- **The Design Bench** — the web app at `roastify.tollbooth-dpyc.com`, where designs are edited.
Its Cloudflare function proxies `/mcp` so the courier can reach this operator from the
merchant's origin.
An agent edits a design's text with `roastify_get_design_text` and `roastify_update_design_text`;
the human applies the new version with the courier.
## Artwork is asynchronous
Roastify renders artwork on its own servers, so `roastify_generate_artwork` hands back a job
id straight away and `roastify_artwork_status` checks it. Status checks are **free** — you
learn the work finished by looking, and charging for each look would be charging for waiting.
```
job = roastify_generate_artwork(product_id=..., fields=[...], client_req_id="my-req-1")
roastify_artwork_status(job_id=job["job_id"]) # free, poll until artwork_url appears
```
Pass `client_req_id` and a repeated request is safe: it becomes Roastify's
`Idempotency-Key`, so the artwork is not generated twice.
The operator does no waiting of its own — both calls return immediately — so there is no
detached executor here and nothing to recycle. That machinery would earn its place only if
this operator ever rendered artwork itself.
## Development
```bash
pip install -e ".[dev]"
ruff check .
pytest -v
python -m roastify_mcp.server # needs TOLLBOOTH_NOSTR_OPERATOR_NSEC to fully run
```
## Upstream status
Roastify's API is version **0.3.1** and its own spec marks it **beta, "subject to change."**
Expect to follow breaking upstream edits, and weigh that before promising this operator's
catalog to third-party merchants.
## License
Apache 2.0 — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessResponsive