Guides

Build with AI

Connect the pos-app MCP server to Claude Code, Cursor, Codex…, sample prompts, reporting bugs to Pancake and the rules AI tools must follow.

View Markdown

This page is for people building apps with AI tools (Claude Code, Cursor, Codex, Copilot…) and for the AI tools themselves.

Give your AI tool the context

Option Use when What the AI can do
pos-app MCP server (recommended) Your AI tool supports MCP Look up the docs, see the shop's real ids, check the app, try it in POS, read errors, deploy, report bugs to Pancake
AGENTS.md Every project created with create-pos-app Read the mandatory rules on every change
llms-full.txt Tools without MCP, or chat on the web Read all the docs at once

They work together: a create-pos-app project already has AGENTS.md; just add the MCP server.

MCP

pos-app mcp starts an MCP server so AI tools can look up the docs, see the shop's real metadata, check the app, read runtime errors and deploy without leaving the chat. It runs on your machine (stdio), in the app's project folder, with the account signed in through pos-app login.

Connect the MCP server

  1. Sign the CLI in once: in POS go to Settings › Advanced › Custom apps, click Connect CLI › Create token and run the command it shows (see Manage apps › Connect the CLI).
  2. Add the server to your AI tool, in the app's project folder (replace 123 with your shop id):
Terminal
# Claude Code
claude mcp add pos-app -- npx @pancake-pos/app-cli mcp --shop 123

Cursor (.cursor/mcp.json in the project):

JSON
{
  "mcpServers": {
    "pos-app": { "command": "npx", "args": ["@pancake-pos/app-cli", "mcp", "--shop", "123"] }
  }
}

VS Code with Copilot (.vscode/mcp.json in the project):

JSON
{
  "servers": {
    "pos-app": { "type": "stdio", "command": "npx", "args": ["@pancake-pos/app-cli", "mcp", "--shop", "123"] }
  }
}
TOML
# Codex: ~/.codex/config.toml
[mcp_servers.pos-app]
command = "npx"
args = ["@pancake-pos/app-cli", "mcp", "--shop", "123"]

Claude Desktop (claude_desktop_config.json) does not run inside the project folder, so add --cwd:

JSON
{
  "mcpServers": {
    "pos-app": { "command": "npx", "args": ["@pancake-pos/app-cli", "mcp", "--shop", "123", "--cwd", "/path/to/my-app"] }
  }
}
  1. Restart the AI tool and ask it to "list the pos-app tools". In Claude Code, /mcp shows whether the server is connected.

Tools

Tool Input Returns
search_docs query Matching sections of these docs, as Markdown
get_target target, for example purchase.details.action Context data schema, expected return value, manifest fields and a short example
list_shop_meta resource Ids and names the app can use in conditions and code: statuses, tags, warehouses, product categories, order sources
sample_context target, optional record id A realistic pos.data built from the shop's data, filtered by the app's access and the signed-in user's permissions
check_app none The pos-app check result: each problem with file, line, rule id and a suggested fix
dev_start, dev_status none Starts pos-app dev; returns the local URL, the POS link to open in development mode and build errors
get_errors version (dev by default), since Runtime errors POS recorded for the app: target, message, stack trace, time
deploy message Runs check and build, then uploads a new inactive version; returns the version and the link to the custom apps page
send_feedback kind (bug or feature), title, description, include_errors Sends a POS Apps bug report or feature request to Pancake's POS support team; returns an FB-… id
  • deploy never activates a version. An admin reviews access and activates it on the Custom apps page, exactly as with pos-app deploy.
  • If version in pos-app.json already exists, deploy fails with duplicate_version; bump version and deploy again.
  • Tools act as the signed-in user on the given shop. They never return app tokens or API keys, and they do not write shop data: list_shop_meta and sample_context only read. App code still reads and writes data through pos.api.

Typical loop

  1. get_target and search_docs to pick the right targets and types.
  2. list_shop_meta and sample_context to write conditions and code against real ids and fields.
  3. Write the code, then check_app until it reports no errors.
  4. dev_start, try the app in POS, then get_errors to fix runtime errors.
  5. deploy and ask an admin to activate the new version.

Sample prompts

Text
Create a POS app "Debt reminder": a block in the order form showing the customer's total debt,
and a "Send reminder" button that calls https://crm.example.com/api/remind. Read-only access is enough.
Use sample_context for real order data, run check_app until it is clean, then dev_start so I can try it in POS.
Text
The "Push to ERP" button in version 1.2.0 is failing. Read get_errors for that version, find the cause, fix it,
bump the version and deploy with a note.
Text
Show the "Approve order" action only for orders waiting for confirmation that come from Facebook.
Get the status and order source ids with list_shop_meta instead of hard-coding names.

Report bugs and request features

When you hit a platform bug (runtime, components, SDK, CLI, MCP, docs, Open API) or need something that does not exist yet, ask your AI tool to send it to Pancake with the send_feedback tool:

Text
Report to Pancake: pos-table does not show the date column when format is "date".
  1. The AI drafts a title and description (what happened, what you expected, steps to reproduce; or the need and the use case) and shows it to you first.
  2. Only after you agree does it send. The report goes straight to Pancake's POS support channel; the AI gives you an FB-… id to quote when following up.

Reports automatically include the CLI and runtime versions, OS, Node, the app handle and version, and the app's 5 most recent runtime errors (skip them with include_errors: false). Code and shop data are never sent; tokens pasted by mistake are masked before sending. Each shop can send up to 20 reports per day.

Without MCP

  • All docs as Markdown: /developers/apps/llms-full.txt. Index: /developers/apps/llms.txt. Every page has a Markdown version at the same path with .md, for example /developers/apps/targets.md.
  • These LLM files are in English by default. Vietnamese versions live under /developers/apps/vi/, for example /developers/apps/vi/llms-full.txt.
  • The create-pos-app template generates an AGENTS.md with the content in the last section. Keep it in the project so AI tools always read the rules.

Sample prompt:

Text
Read https://pos.pages.fm/developers/apps/llms-full.txt.
Create a POS app "Debt reminder": a block in the order form showing the customer's total debt,
and a "Send reminder" button that calls https://crm.example.com/api/remind. Read-only access is enough.

Mandatory rules

Runtime environment

  1. Code in extensions/ runs in a Web Worker with no real DOM.
    • Do not use window, document.querySelector, document.getElementById, localStorage, sessionStorage, alert, confirm, canvas, getBoundingClientRect.
    • Do not import UI libraries (antd, MUI, Tailwind, chart libraries, drag-and-drop libraries) or CSS files.
    • Use Preact (recommended) or plain JS to render pos-* elements into document.body. Do not use React in extensions/.
    • Do not install any library to connect to POS or render UI; the POS runtime is already in the worker.
  2. Iframe pages (page/) are regular web apps: any library is allowed, but they must get pos from connect(), must not access window.top/window.parent, and must not use routes starting with /_pos/ (reserved for POS).

UI

  1. Worker UI extensions use only elements from UI components. Do not use div, span, p, img, table, button, input.
  2. Text must be inside pos-text, pos-heading, pos-badge, pos-link, pos-button or pos-banner.
  3. Do not use style or className. Event values come from event.detail.
  4. Display amounts with pos-money or a column with format: 'money'; do not format them yourself.

Data

  1. Get POS data only through pos.api, with paths like /shops/${pos.shop.id}/... and endpoints listed in the Open API.
  2. Never hardcode a shop id, API key or token; never send the app token elsewhere, except to the backend of a self-hosted page; never put secrets in code.
  3. Every method used in code (POST, PUT, DELETE) must be listed in access.methods. Every external domain (including pos-image sources) must be listed in network.allowed_origins.
  4. Read the current record from data.record (data.records in bulk actions). Call the Open API with record.system_id for orders and record.id for products, purchases, exports and warehouse transfers.

Modules and manifest

  1. Every module has export default async function (data).
    • Blocks, modal/fullscreen actions, render: "worker" pages: render into document.body.
    • headless actions: return { message?, refresh? } or throw.
    • should_render: return a boolean, finish within 300 ms, no slow API calls.
    • *.before_submit: return { ok: true } or { ok: false, message }, finish within timeout_ms.
  2. Every extension in pos-app.json has a unique id; target must exist in Extension points.
  3. Prefer wrapping modules with defineExtension(target, fn) from @pancake-pos/ui-extensions so the types of data and of the return value match the target.

Quality

  1. Wrap every API call in try/catch; show errors with pos-banner (in UI) or pos.toast.show(..., { tone: 'error' }).
  2. Handle 403 (the user lacks permission) and 429 (retry later with an increasing delay).
  3. Do not call the API on every render; use useEffect with explicit dependencies, pagination and AbortController.
  4. Write the UI in the user's language (pos.user.locale).
  5. After every change, run npx eslint . (or the check_app tool) and fix every problem. Do not add eslint-disable to silence a rule; pos-app check still blocks the deploy.

Common mistakes

Wrong Right
<div className="box">…</div> <pos-section heading="…">…</pos-section>
<input onChange={(e) => set(e.target.value)} /> <pos-text-field onChange={(e) => set(e.detail)} />
fetch('https://pos.pages.fm/api/v1/shops/123/orders?api_key=...') pos.api.get(`/shops/${pos.shop.id}/orders`)
localStorage.setItem(...) in an extension Store it through the Open API (CRM tables) or your own backend
alert('Done') pos.toast.show('Done')
data.order, data.purchase data.record (data.resource tells which resource)
`/shops/${shopId}/purchases/${record.display_id}` `/shops/${shopId}/purchases/${record.id}`
import { Table } from 'antd' in an extension <pos-table columns={…} rows={…} />
Calling PUT while access.methods only has GET Add "PUT" to access.methods
new Intl.NumberFormat(...).format(total) + ' VND' <pos-money value={total} />

Pre-deploy checklist

  • pos-app.json is valid: handle, a new version, api_version, no duplicate id.
  • access.methods covers every write request; no unnecessary permissions.
  • Every external domain is in network.allowed_origins.
  • Modules in extensions/ use no real DOM, UI libraries or CSS.
  • No tokens, API keys or secrets in code.
  • Errors and loading states are handled.
  • npx pos-app check reports no errors.
  • Tested with npx pos-app dev (an account with the Settings permission), then deployed to a test shop and tried with a narrow-permission staff account.

Sample AGENTS.md

Markdown
# POS App

This project is a custom app that runs inside Pancake POS.
Docs: https://pos.pages.fm/developers/apps/llms-full.txt (read before changing code).

## Structure
- pos-app.json: manifest (permissions, external domains, extensions).
- extensions/: modules that run in a Web Worker and render pos-* elements with Preact.
- page/: iframe page (React + Vite), gets `pos` from `connect()` in `@pancake-pos/ui-extensions/page`; routes starting with /_pos/ are reserved for POS.

## Rules
- extensions/: no real DOM, window, localStorage, React, UI libraries or CSS. Only pos-* elements from the docs.
- Text goes inside pos-text/pos-heading/pos-badge/pos-link/pos-button/pos-banner. Event values are in event.detail.
- POS data only through pos.api with paths like /shops/${pos.shop.id}/... and Open API endpoints.
- Never hardcode shop ids, API keys, tokens or secrets.
- Write methods must be in access.methods; external domains must be in network.allowed_origins.
- Modules: export default async function (data). Headless returns { message }; before_submit returns { ok } or { ok: false, message }.
- Catch errors on every API call; handle 403 and 429.

## Commands
- npx pos-app dev: try in POS (turn on Development mode on the custom apps management page); POS reloads when code changes.
- npx eslint .: check the code after every change; do not add eslint-disable to silence rules.
- npx pos-app check: validate the manifest and code; fix every error before deploying.
- npx pos-app deploy: build, validate the manifest, upload a new version.
- With the pos-app MCP server: get_target/list_shop_meta/sample_context before writing code, check_app after every change, get_errors after testing in POS, deploy when done (uploads an inactive version; an admin activates it). For a platform bug or a missing feature: draft a report, show it to the user, and call send_feedback only after they agree.