> ## Documentation Index
> Fetch the complete documentation index at: https://docs.petakopi.my/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up by an AI agent

> Instructions for a coding agent to connect itself to the petakopi.my MCP server, sign the owner in and check which shops it can read.

> [!NOTE]
>
> Using Claude Code or another coding agent? Paste this link to it and say "set up petakopi.my".

Agent: follow the steps below, then report the shops you can see.

## Facts

| Item | Value |
| - | - |
| Server URL | `https://petakopi.my/mcp` |
| Transport | Streamable HTTP. Send JSON-RPC by POST. GET returns 405. The server is stateless: no session id is needed. |
| Auth | OAuth 2.1 authorization code with PKCE (`S256` only) |
| Client registration | Dynamic client registration (RFC 7591) at `https://petakopi.my/oauth/register`. Public clients only: send `token_endpoint_auth_method: none` or leave it out. No client secret. |
| Authorization server metadata | `https://petakopi.my/.well-known/oauth-authorization-server` |
| Protected resource metadata | `https://petakopi.my/.well-known/oauth-protected-resource/mcp` |
| Redirect URIs | `http://localhost:<port>/…`, `http://127.0.0.1:<port>/…` or `http://[::1]:<port>/…` on any port, plus `https` on `claude.ai`, `claude.com` and `chatgpt.com`. Registration fails with `invalid_redirect_uri` if any one URI in the request is outside this list. |
| Scope | `shop:read` only |
| Tokens | Access tokens last 1 hour. Refresh tokens rotate: each refresh returns a new one, and a reused refresh token gets `invalid_grant`. |
| `resource` parameter | Optional. If you send it, it must equal `https://petakopi.my/mcp`. |
| Access | Read-only |
| Shop access | Off for every shop by default. The owner switches each shop on under **Manage → AI Apps**. Tools read only switched-on shops. You cannot switch a shop on. |
| 401 response | Carries a `WWW-Authenticate` header that points at the protected resource metadata. Start OAuth discovery from there. |
| 404 on every endpoint | The feature is not switched on for this account yet. Stop and tell the owner to contact petakopi.my. |

Before you start, ask the owner to switch on AI access for at least one shop under **Manage → AI Apps**. Otherwise the approval page shows an error asking them to do that first.

The owner signs in on petakopi.my in a browser and taps **Approve** on a page that lists the shops you can read. You cannot complete this step for them. Tell the owner a browser window will open, and wait.

## Set up, by client

> [tab] Claude Code
>
> Run:
>
> ```bash theme={null}
> claude mcp add --transport http petakopi https://petakopi.my/mcp
> ```
>
> Then ask the owner to type `/mcp` in Claude Code, select **petakopi** and sign in in the browser.
>
> Plugin alternative: the plugin adds the same server plus a skill for reading the reports. Ask the owner to run:
>
> ```text theme={null}
> /plugin marketplace add petakopi/claude-plugin
> /plugin install petakopi@petakopi
> ```
>
> Then `/mcp` to sign in. The skill starts with `/petakopi:analyse-shop`.

> [tab] Codex CLI
>
> Run:
>
> ```bash theme={null}
> codex mcp add petakopi --url https://petakopi.my/mcp
> ```
>
> Or add this to `~/.codex/config.toml`:
>
> ```toml theme={null}
> [mcp_servers.petakopi]
> url = "https://petakopi.my/mcp"
> ```
>
> Then sign in:
>
> ```bash theme={null}
> codex mcp login petakopi
> ```
>
> petakopi.my supports dynamic client registration only. If login fails during registration, run `codex mcp login petakopi --oauth-client-registration dcr`. Codex listens on `http://127.0.0.1/callback`, which petakopi.my accepts.

> [tab] Gemini CLI
>
> Run:
>
> ```bash theme={null}
> gemini mcp add --transport http petakopi https://petakopi.my/mcp
> ```
>
> Or add this to `.gemini/settings.json` (`httpUrl` is Streamable HTTP; `url` means SSE and will not work):
>
> ```json theme={null}
> {
>   "mcpServers": {
>     "petakopi": {
>       "httpUrl": "https://petakopi.my/mcp"
>     }
>   }
> }
> ```
>
> Leave out the `oauth` block: Gemini CLI finds the metadata from the 401 and registers itself. Then ask the owner to type `/mcp auth petakopi` in Gemini CLI. The default callback is `http://localhost:<random-port>/oauth/callback`, which petakopi.my accepts.

> [tab] Other clients
>
> Most MCP clients accept this shape. The URL field name varies by client: `url`, `httpUrl` or `serverUrl`.
>
> ```json theme={null}
> {
>   "mcpServers": {
>     "petakopi": {
>       "url": "https://petakopi.my/mcp"
>     }
>   }
> }
> ```
>
> The client must support:
>
> 1. Streamable HTTP transport.
> 2. Discovery from a 401 `WWW-Authenticate` header (RFC 9728), then the authorization server metadata (RFC 8414).
> 3. Dynamic client registration (RFC 7591) as a public client, with a loopback redirect URI.
> 4. Authorization code with PKCE `S256`, scope `shop:read`.
> 5. Refresh tokens that rotate.
>
> If the client cannot do OAuth, it cannot connect: petakopi.my has no API keys.

## Verify

Call `petakopi_list_shops` with no arguments.

| Result | Meaning |
| - | - |
| A table of shops with **Slug**, **AI access**, **Daily report time**, **Menu pricing**, **Loyalty** and **Vouchers** columns | Connected. Report the shop names and slugs to the owner, and which reports are not ready yet. |
| A row with **AI access** set to **Off** | The owner has not switched that shop on. Ask them to switch it on under **Manage → AI Apps**. Do not retry until they say it is on. |
| "AI access is off for \<Shop>. Turn it on at …" from another tool | Same as an **Off** row. Pass the message and its link to the owner. Do not retry. |
| "You don't manage any shops on petakopi.my yet" | The signed-in account is not an owner. Ask the owner to sign in again with the account they use in Manage. |
| "Shop not found" from another tool | The slug is not one of the owner's shops. Use a slug from `petakopi_list_shops`. |
| 401 after about an hour | The token refresh failed. Re-authenticate with the client's sign-in command. |

## What you can call

Every tool except `petakopi_list_shops` needs `shop`, the slug from `petakopi_list_shops`.

**Periods.** Tools that take `period` accept `today`, `yesterday`, `this_week`, `last_week`, `this_month`, `last_month`, `last_7_days`, `last_30_days`, `month` (with `month`) and `custom` (with `from` and `to` as `YYYY-MM-DD`, at most 366 days apart). `month` is `this_month`, `last_month` or `YYYY-MM`.

| Tool | Purpose | Key arguments |
| - | - | - |
| `petakopi_list_shops` | Every shop the owner has, their slugs, whether AI access is on, and which reports are ready. Call it first. | None |
| `petakopi_sales_summary` | Net takings, orders, average order, discounts, and splits by channel and POS payment method | `period` (default `this_month`), `month`, `from`, `to`, `compare` (default `true`) |
| `petakopi_sales_trends` | Takings by business day, weekday or hour | `period` (default `last_30_days`), `group_by`: `day`, `day_of_week`, `hour` (default `day`), `compare` (default `false`) |
| `petakopi_menu_items` | Items with price, ingredient cost, units, keep and suggested price | `period`: `last_7_days`, `last_30_days`, `this_month`, `last_month`, `month`, `custom` (default `last_30_days`); `sort`: `best_sellers`, `worst_sellers`, `lowest_keep`, `highest_keep`, `needs_price_rise`, `uncosted`; `category`, `label`, `limit` (1 to 100, default 15), `response_format` |
| `petakopi_item_options` | One item's option groups and options: extra price, default, available online, how often each was picked, its cost rule (no cost change, adds which materials, swaps which material for which, replaces the recipe, or no rule yet), the item's cost and keep with that option. Use it to find options that lose money. | `item` (name or uuid; part of a name works when only one item matches), `period` (default `last_30_days`), `month`, `from`, `to` |
| `petakopi_uncosted_sales` | Why an item shows "sold without a cost": each sale line in the current reporting month (plus last month during days 1 to 7) with no ingredient cost, with time, order number, options picked, quantity and reason, waste logged without a cost, and the flagged items' current option groups. Needs a Daily report time. | `item` (part of an item name), `limit` (1 to 100, default 20) |
| `petakopi_product_status` | Each product's listing (on sale, sold out, hidden), categories, whether it shows on the order page and on the POS menu (and why not), lifetime units sold and last sale date. Use it to find products to archive. Archived products are left out unless asked. | `show`: `all`, `not_offered`, `never_sold` (default `all`); `name` (part of a product name), `include_archived` (default `false`), `limit` (1 to 200, default 50) |
| `petakopi_materials` | The shop's materials with pack price, pack size and unit, yield %, cost per usable gram, ml or piece, and whether a zero price is confirmed free. Use it to cost a new drink by hand. | `name` (part of a material name), `limit` (1 to 200, default 50) |
| `petakopi_material_usage` | Where each material is used (recipes, add-ons, direct use in product costings) and where each recipe is used (live products' costings, archived products that still point at it), with a status: in use, only in unused recipes or add-ons, or unused. Use it to clean up. | `view`: `materials`, `recipes` (default `materials`); `name`, `unused_only` (default `false`), `limit` (1 to 200, default 50) |
| `petakopi_break_even` | Monthly fixed costs, operations %, and daily sales needed to cover costs and to keep the profit target | `shop` only |
| `petakopi_profit_and_loss` | The month's net sales, costs, result and verdict: covered, shortfall or incomplete. Needs a Daily report time. | `month` (default `this_month`), `response_format` |
| `petakopi_expenses` | Operating costs by category: actual, estimated, carried and unknown | `month` (default `this_month`), `compare` (default `true`), `response_format` |
| `petakopi_loyalty_and_vouchers` | Stamps, rewards, repeat customers and voucher redemptions | `period` (default `last_30_days`), `month`, `from`, `to`, `compare` (default `true`) |

`response_format` is `concise` (default) or `detailed`, which adds line items.

| Prompt | What it does | Arguments |
| - | - | - |
| `monthly_profit_review` | Verdict, three biggest changes, missing inputs and three actions for one month | `shop`, `month` (both optional) |
| `pricing_opportunities` | The five price changes worth most over the last 30 days | `shop` (optional) |
| `plan_next_month` | Daily sales targets, weak weekdays and hours, and a weekly plan | `shop`, `month` (both optional) |

The server returns its instructions in the `initialize` response. They take precedence over this page. Five rules from them:

1. Start with `petakopi_list_shops`. If the owner has one shop, use it without asking.
2. Quote RM amounts exactly as the tools print them, for example RM 2,500.00. Do not convert or round them.
3. State the period, and say "so far" when it is still open.
4. Never mix takings (sales tools) with net sales (`petakopi_profit_and_loss`) in one sum.
5. Repeat a tool's **Notes** when they affect the conclusion.

## Boundaries

* **Read-only.** No tool changes prices, sends messages or edits anything. Send the owner to [petakopi.my/manage](https://petakopi.my/manage) to act.
* **Owner's switched-on shops only.** The token reads only shops the signed-in account owns and has switched on. The connection stays in place while a shop is off.
* **No customer details.** Tools never return customer names, phone numbers, order notes or feedback text.
* **Up to 5 minutes old.** Figures can lag behind the Manage pages.
* **Revocable.** The owner can cut you off under **Manage → AI Apps** at any time. Your next call then returns 401.
* **Rate limited.** `/mcp` allows 120 calls a minute per token, then returns 429. Do not call tools in a loop; one call per report answers most questions.

For what the owner sees, read [Ask AI about your shop](https://docs.petakopi.my/ai-assistant/overview.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.