Organization example, interview to dry run
Meridian Jewelers is Streamliner's fictional demo account. This walks one streamline from interview to dry run. All names, hosts and numbers are invented.
Contents
- 1. Interview notes
- 2. What the server offers
- 3. tenant.json changes
- 4. agents/register.json
- 5. private/agents/register.json
- 6. Templates
- 7. Check, sign, dry run
1. Interview notes
- Department: the stores. Job: ring up one sale (customer → pieces → price → payment → sales rep → submit).
- Who: sales associates, store managers, regional managers (roles
associate,store_manager,regional). - System: the POS MCP server
https://api.meridian.example/mcp, OAuth, one connectionpos. - Rules (in the POS API, not in config): offers below 90% of tag go to a manager; a sale can't be submitted with a balance. Card payments go to the processor's page; financing through the credit partner's sealed form.
- Done when:
sale_submitsucceeds. Overview: the ticket (cart_get).
2. What the server offers
inspect-mcp.mjs https://api.meridian.example/mcp --draft org (abridged):
| Tool | Annotations | Decision |
|---|---|---|
customer_search, item_search, staff_search, cart_get, offer_status | readOnly | readOnly |
cart_add_item, cart_remove_item | idempotent | plain, direct (the Add button on search results) |
customer_create, cart_set_rep, offer_submit | — (small, reversible, inside the job) | plain |
payment_request_card | opens a hosted page | plain (the page is the gate) |
financing_apply | opens a sealed form | plain; form financing.apply |
payment_add_cash | destructive | confirm, card confirm.payment |
sale_submit | destructive | confirm, preview cart_get, card confirm.sale |
admin_void_all | destructive | not allowlisted (not this job) |
3. tenant.json changes
Add "register" to kinds, the pos connection to connections, and img.meridian.example to imageHosts.
4. agents/register.json
{
"id": "register",
"name": "Register",
"description": "Ring up a sale: customer, pieces, price, payment, submit.",
"icon": "bag.fill",
"color": { "light": "#1F7A5C", "dark": "#3FBF8F" },
"roles": ["associate", "store_manager", "regional"],
"newChatLabel": "New sale",
"chatTitle": { "rules": [
{ "tool": "cart_set_customer", "text": "{{ $.customer.id }} · {{ $.customer.name }}" },
{ "tool": "offer_submit", "text": "Offer {{ $.offer.number }} · {{ $.offer.amount | money }}" }
] },
"welcome": {
"messages": ["Hi {{ user.firstName }}. This chat is one sale.",
"Tell me who the customer is and what they're looking at. Nothing is submitted until you tap Submit."],
"starters": [ { "label": "Find a customer", "text": "Find customer ", "send": false },
{ "label": "Scan a tag", "text": "Add item ", "send": false } ]
},
"composer": { "placeholder": "Customer, item, price, payment…", "quickPhrases": [
{ "group": "Price", "items": [ { "label": "Customer offers…", "text": "The customer offers {{amount}} for {{item}}" } ] },
{ "group": "Finish", "items": [ { "label": "Submit the sale", "text": "Submit the sale", "send": true } ] } ] },
"overview": { "title": "Ticket", "source": { "tool": "cart_get", "args": { "cartId": "{{ chat.cartId }}" } },
"requires": ["chat.cartId"], "template": "register.overview", "compactTemplate": "register.overview.compact",
"refreshOn": ["cart_*", "customer_*", "offer_*", "payment_*", "sale_*"],
"poll": { "seconds": 15, "when": { "path": "$.offer.status", "eq": "pending" } }, "empty": "Nothing on the ticket yet." },
"renderers": { "customer_search": "customer.list", "item_search": "item.list", "offer_submit": "offer.decision", "sale_submit": "sale.receipt" },
"activity": { "item_search": "Searching inventory", "sale_submit": "Submitting the sale" },
"completion": { "tools": ["sale_submit"], "banner": "Sale {{ $.saleNumber }} submitted", "next": "New sale" },
"forms": ["card.hosted", "financing.apply"]
}
5. private/agents/register.json
{
"id": "register",
"instructions": "You are the register at a Meridian Jewelers store. You help one sales associate complete one sale in this chat.\n\nWork in this order unless the associate leads elsewhere: customer, items, price, payment, sales rep, submit.\n- Confirm the customer by name and the last four digits of their phone before adding anything.\n- When the customer offers a price below the tag, call offer_submit and report the decision exactly as returned. Never state cost or margin.\n- Never ask for, repeat or accept card numbers, Social Security numbers, dates of birth or license numbers in chat. Use payment_request_card or financing_apply.\n- sale_submit and payment_add_cash wait for the associate's tap.\n- Keep replies to one or two short sentences; the ticket shows the details.\n- Today is {{ now | date:'long' }}. The associate is {{ user.name }} at {{ user.store }}.",
"model": { "provider": "anthropic", "id": "claude-haiku-5-5", "effort": "low", "maxTokens": 2048, "credentialRef": "anthropic-main" },
"fallbackModel": { "provider": "anthropic", "id": "claude-sonnet-5-5", "effort": "low", "maxTokens": 2048, "credentialRef": "anthropic-main", "when": ["overloaded", "refusal"] },
"mcp": [ { "id": "pos", "url": "https://api.meridian.example/mcp", "auth": "oauth", "connection": "pos", "resource": "https://api.meridian.example/mcp",
"tools": [
{ "name": "customer_search", "readOnly": true },
{ "name": "customer_create" },
{ "name": "item_search", "readOnly": true },
{ "name": "cart_get", "readOnly": true },
{ "name": "cart_add_item", "direct": true },
{ "name": "cart_remove_item", "direct": true },
{ "name": "cart_set_rep" },
{ "name": "staff_search", "readOnly": true },
{ "name": "offer_submit" },
{ "name": "offer_status", "readOnly": true },
{ "name": "payment_add_cash", "confirm": { "template": "confirm.payment", "button": "Record cash" } },
{ "name": "payment_request_card" },
{ "name": "financing_apply" },
{ "name": "sale_submit", "confirm": { "template": "confirm.sale", "preview": "cart_get", "button": "Submit sale" } }
] } ],
"limits": { "maxToolCallsPerTurn": 10, "maxTurnsPerChat": 120, "tokensPerUserPerDay": 600000 },
"transcript": { "keepToolResults": 6 }
}
The key is named (anthropic-main); the owner stores the actual key in the account, never in this file.
6. Templates
templates/item.list.json, templates/confirm.sale.json and the overview follow the patterns in templates.md. Bind each against a real result from the server: item_search returns { "results": [{ "sku", "title", "photo", "tagPrice", "onHand", "metal" }] }; cart_get returns { cartId, customer, items[], offer, subtotal, tax, total, payments[], paid, balance, rep, status, saleNumber }.
7. Check, sign, dry run
node scripts/check.mjs ./meridian
node scripts/sign-config.mjs ./meridian --publisher-key ~/keys/meridian.publisher.jwk
node scripts/publish.mjs ./meridian --host https://<back end> --key ~/keys/meridian.server-key.jwk --baseline ./meridian-live
DRY RUN — nothing was written: meridian on https://<back end>, version 13 → 14 (key claude-setup-oct)
+ agents/register.json "Register" for roles associate, store_manager, regional
+ private/agents/register.json model claude-haiku-5-5; tools: customer_search (read), …, sale_submit (confirm)
+ templates/confirm.sale.json
~ tenant.json
~ /kinds/2
4 added, 1 changed, 0 deleted; the manifest goes last
Show that to the owner, say what each line means in plain words, and publish (--yes) only on their explicit yes.