Rose for Agents (r4a)¶
Prototype (IX-5025)
The r4a-mcp connector is configured for private Cloud Run deployment until buyer
authentication is implemented. The only vendor with a working buying funnel is a fake one,
Ibanly, which is live at ibanly.app. See What is not built.
What it is¶
Rose for Websites sells to people visiting a client's site. Rose for Agents sells to AI assistants (Claude, ChatGPT, Cursor, Codex) that are doing a buyer's task: research a category, compare vendors, try a product, sign up, pay, start using it.
r4a is one MCP connector, hosted by Rose, that the buyer installs once. Through it the assistant reaches every Rose customer. A customer provides no tools of its own: r4a exposes the same tools for every vendor and calls the customer's systems on its behalf.
Brand chat (backend/apps/mcp) |
Rose for Agents (backend/apps/r4a_mcp) |
WebMCP | |
|---|---|---|---|
| Who is working | the user talks TO a brand | the assistant works on the user's task | an AI browser on the client's page |
| The vendor's agent is | the speaker, relayed word for word | a source the assistant interprets | a source, for that one site |
| Tools | ask_brand_agent, compare_vendors |
ask_vendor + the buying tools |
ask_sales_agent |
| Output | ChatGPT card or verbatim text | plain data with a provenance note | plain text |
The two hosted connectors give the model opposite instructions (relay verbatim versus interpret and attribute), which is why r4a is its own app and not a mode of brand chat.
Actors¶
| Actor | Runs where | Role |
|---|---|---|
| Buyer | their own machine | gives the task, approves paid checkouts, owns the account |
| Assistant | Claude, ChatGPT, Cursor… | plans, calls r4a tools, interprets answers |
r4a-mcp |
Rose infrastructure | the connector: instructions, tools, request signing |
| Website agent | Rose infrastructure (search API) | the vendor's existing Rose agent; answers ask_vendor |
| Vendor endpoint | the customer's infrastructure | creates accounts, takes payment, issues credentials |
Because r4a is hosted by Rose, anything only the customer's system can do (create an account, issue a credential) is an outbound call from r4a to an endpoint the customer hosts. Evaluating and listing offers need no such call.
The flow¶
A one-time sign-in, then three phases. Only evaluation needs nothing from the customer's
own systems. Labels are
shortened to stay legible: every tool call also carries the brand, and vendor paths are
the full ones listed in The contract. The assistant may skip the proxied
"use" step and call the vendor directly with the credentials it received.
0. Connect and sign in, once¶
Designed, not built
Today the connector is anonymous. This step is the design; see Buyer identity.
The buyer installs the connector once and signs in once. After that every tool call carries the buyer's token, so r4a knows who is buying without the assistant ever typing an email.
The assistant host (Claude, ChatGPT, Cursor) runs this flow by itself: it is the standard
MCP authorization flow, triggered by the 401. It is also why sign-in has to cover the
whole connector: a server that answers some tools anonymously never sends the 401 that
starts the login.
1. Evaluate: the vendor's agent as a source¶
2. Try and sign up: r4a calls the customer¶
3. Pay, get access, use¶
A customer on the none backend (Rose for Websites today) stops after phase 1:
provision_account answers human_signup_required with the customer's sign-up link.
Tools¶
Every tool is exposed by r4a. The customer column is what a vendor has to supply.
| Step | Tool | Logic supplied by | The customer provides | Status |
|---|---|---|---|---|
| Evaluate | ask_vendor |
r4a, through the vendor's Rose website agent | nothing (comes with Rose for Websites) | real |
| Offers | get_offers |
r4a, from the vendor spec | data, no code | built (spec is a file) |
| Trial | try_product |
customer handler | POST /trial |
built against the fake vendor |
| Sign up | provision_account |
customer handler | POST /accounts |
built; buyer email not verified |
| Pay | checkout |
customer handler | POST /accounts/{id}/checkout |
built with fake tokens; no Stripe |
| Access | get_access |
customer handler | GET /accounts/{id}/access |
built |
| Use | try_product + account_id |
customer handler, proxied | POST /accounts/{id}/use |
built |
ask_vendor returns vendor, domain, answer, sources, suggested_follow_ups,
conversation_id and a provenance line. The connector's instructions tell the model to
ask one precise question per call, reuse conversation_id per vendor, compare several
vendors, answer in its own words, and attribute claims.
Buyer identity¶
Designed, not built
provision_account currently takes an email argument from the assistant and the
vendor trusts it. That is the main gap between this prototype and a real customer.
Why sign-in is required. Without it, anyone who knows the connector URL can create
accounts at a customer in someone else's name, make the customer send email to arbitrary
addresses, and spend Rose's LLM budget through ask_vendor. With it, every action has an
accountable owner, rate limits and spend caps can be per buyer, and the customer receives
a lead it can trust.
Existing-account takeover. Returning an existing account id for an unverified
email lets a caller retrieve its API key through get_access, use it or change its
plan. Until verified ownership is implemented, both Ibanly signup paths reject
existing emails with 409, including concurrent requests, without returning an
account id or key. This closes email-based recovery; callers who already possess
an account id still need to be checked against the signed-in owner in IX-5119.
How much proof. Identity is not one thing; the level required can depend on what is being bought.
| Level | Proof | Good for |
|---|---|---|
| 1. Verified email | a code sent to the address, or a social or SSO login | evaluation, trials, free plans |
| 2. Work identity | verified email on a company domain (no free-mail), optionally matching the company the buyer claims to act for | paid self-serve plans |
| 3. Verified organisation | company SSO, or a DNS record proving control of the domain | high-value or contractual purchases |
A customer states the minimum level it accepts in its spec; r4a refuses the buying tools below it and tells the assistant what the buyer has to do.
How the customer gets it. r4a, not the customer, authenticates the buyer. The contract
then carries a buyer object (email, email_verified, level, stable buyer_id) inside
the signed request body, so the signature that proves the call comes from r4a also covers
who the buyer is. The customer builds no agent authentication of its own.
What changes in the tools. provision_account loses its email parameter: the account
owner is the signed-in buyer. The assistant can no longer sign someone else up, and
"only use an email the user gave you" stops being an instruction the model has to follow.
First end-to-end vendor: Ibanly. The visitor signs in to Rose through R4A MCP; R4A creates an Ibanly account for that verified visitor, then uses Ibanly's account-required service. There is no second Ibanly login. All MCP tools require authentication, and subsequent checkout, credential access and service use must verify that the account belongs to the signed-in buyer. This is tracked in IX-5119; Ibanly website-agent onboarding is IX-5120. Rose for Websites signup and trial follow separately in IX-5121.
What it builds on. The installed MCP library (mcp 1.28) supports this directly: the
connector runs as an OAuth resource server (token_verifier plus AuthSettings with
issuer_url and resource_server_url) and a tool reads the buyer's claims with
get_access_token(). The selected authorization server is Supabase Auth,
which the backoffice already uses for email-code login, so an r4a buyer and a Rose
for Websites user share one Rose identity. Provider configuration and host
compatibility still need verification during implementation.
Customers and the vendor contract¶
A customer is a spec in backend/apps/r4a_mcp/r4a_mcp/specs/<slug>.json plus a backend.
| Level | Backend | Customer effort | The assistant can |
|---|---|---|---|
| 0 | none |
none | evaluate, see offers, get a sign-up link |
| 1 | http |
host the contract below | also try, sign up, pay, get credentials, use |
Planned, not built: a declarative trial (r4a calls the customer's existing public API from a template) and payment through a connected Stripe account. Both would remove customer code from those two steps, leaving only account creation and access, which no one else can do for the customer.
The contract¶
Paths are relative to the spec's base_url.
| Method and path | Request | Success | Other |
|---|---|---|---|
POST /trial |
{input} |
200 {output, note} |
422 bad input |
POST /accounts |
{email} |
201 {account_id} for a new account |
409 existing email (until buyer ownership is verified), 422 |
POST /accounts/{id}/checkout |
{offer_id, payment_token?} |
200 {status, offer, amount_eur, invoice} |
402 {status, amount_eur}, 404, 422 |
GET /accounts/{id}/access |
200 {api_key, offer, usage} |
409 no active plan, 404 |
|
POST /accounts/{id}/use |
{input} |
200 {output, usage} |
409, 404, 422 |
r4a maps a 402 to a payment_required tool result, any other non-success to
{"error": …} using the vendor's detail, and a transport failure to an error naming
the vendor. It keeps no account state: accounts live at the customer. It never returns
base_url or secret_env to the assistant.
Request signing¶
The customer's endpoints are reachable from the internet, so they must only accept r4a.
Method and path are part of the signed message, so a captured body cannot be replayed against another endpoint. The comparison is constant-time. r4a fails closed too: with no secret configured it does not call the vendor at all.
REST API or SDK?¶
The REST contract is the product. An SDK is optional sugar, and not needed for the prototype. Five endpoints and JSON bodies are within reach of any backend team, in any language. The reference vendor is about 300 lines of TypeScript with no r4a dependency.
The one part that is easy to get wrong is signature verification: it must hash the raw request bytes (not re-serialised JSON), compare in constant time, and enforce the replay window. That is the same situation as Stripe or GitHub webhooks, and the same answer applies: publish the contract, plus a small verification helper per language.
| Option | Cost to Rose | When |
|---|---|---|
| REST contract + OpenAPI + this page | already there | now |
| Signature helper (about 30 lines, Node and Python) | small | first real customer |
| Full SDK (router, types, idempotency, retries) | a maintained library per language | when several customers ask, generated from the OpenAPI |
Run it locally¶
One-time setup for the vendor: cd backend/apps/r4a_ibanly && just install.
cd backend/apps/r4a_ibanly && just e2e # boots Worker + connector, buys through both, stops them
cd backend/apps/r4a_mcp && just up # or keep them running: Ibanly :8787 + r4a-mcp :8766
claude mcp add --transport http r4a http://127.0.0.1:8766/mcp
Expected: just e2e ends with OK: evaluate, try, sign up, pay, use and access all passed.
and exits 0; any wrong step exits non-zero. An unsigned curl to :8787/r4a/trial returns
401, and http://127.0.0.1:8787/ is the demo site.
ask_vendor additionally needs IX_API_KEY and ROSE_MCP_DB_DSN in the environment
(and ROSE_API_BASE to target staging). The buying tools need neither.
Tests:
cd backend && poetry run pytest apps/r4a_mcp/tests -q # the connector, against a scripted vendor
cd backend/apps/r4a_ibanly && just check # the vendor, inside the Workers runtime
The vendor is a separate codebase on another stack (a TypeScript Cloudflare Worker) that shares nothing with r4a. That a second stack implements the contract from the REST description alone is the evidence behind REST API or SDK?. Three things keep the two sides from drifting:
- a shared signature test vector, asserted byte for byte in
apps/r4a_mcp/tests/test_commerce.pyand inapps/r4a_ibanly/test/worker.test.ts; - the connector's tests pin what r4a sends and how it maps each vendor status;
just e2eruns the real connector against the real Worker.
Point the connector at any deployment of the vendor with R4A_VENDOR_URL_IBANLY.
What is not built¶
- Buyer sign-in and identity. The connector is anonymous and
provision_accounttrusts the email it is given. Design in Buyer identity; Supabase Auth implementation and Ibanly account ownership are tracked in IX-5119. - Buyer-facing connector rollout. The deployment workflow sends
developtor4a-mcp-testandmaintor4a-mcp-production(prerequisites and staff verification inbackend/apps/r4a_mcp/README.md). The test MCP selects the isolated Supabase test-branch database secret; production selects the production secret. Schema migration promotion is tracked in IX-5148. Cloud Run stays behind IAM until buyer OAuth is implemented. Ibanly's signing secret is configured separately in IX-5142. The Ibanly vendor is deployed by hand withwrangler deployand has no CI yet. - Per-customer secrets. One shared local secret; real customers need their own, in Secret Manager, with rotation.
- Outbound-call safety.
base_urlcomes from Rose's own spec files today. Once customers enter it themselves, r4a needs a public-host allow-list (no private addresses), timeouts and response size caps. - Abuse controls. No rate limits or spend caps per buyer.
- Customer self-service. Specs are files; there is no backoffice editor.
- Events. Funnel stages are log lines (
agent_event), not a table or a dashboard. - An automatic, no-sign-up trial of Rose itself, and real payment.
Code map¶
| Path | What |
|---|---|
backend/apps/r4a_mcp/r4a_mcp/server.py |
connector, instructions, ask_vendor |
backend/apps/r4a_mcp/r4a_mcp/commerce.py |
buying tools, vendor spec model, signed vendor client |
backend/apps/r4a_mcp/r4a_mcp/specs/ |
one spec per customer (rose.json, ibanly.json) |
backend/apps/r4a_ibanly/ |
Ibanly, the demo vendor for ibanly.app: fake company, real IBAN validation. One Cloudflare Worker (TypeScript): public site, the contract, Durable Object accounts. Reference implementation of the contract. Live at ibanly.app |
docs/plans/2026-09-16-ix-5025-rose-for-agents.md |
plan and decisions |