{
  "markdown": "# LUNO — AI Backend Platform\n\n**Build, operate, and govern production backends with AI agents.**\n\nLUNO is a hosted backend platform — not a website builder, not an open-source CMS, and not an MCP server product. MCP is how your agent connects to LUNO. CMS and forms are capabilities; the product category is **AI-era Backend Platform**.\n\n| | |\n|---|---|\n| **BUILD** | AI agents define schemas, forms, and backend structure via blueprints and templates. |\n| **OPERATE** | Agents read and change content, media, auth settings, and other backend resources. |\n| **GOVERN** | Humans control production through scoped agent keys, approval workflows, publish gates, and safety controls. |\n\n### Quick start\n\n```bash\nnpx @luno-cms/mcp setup\n```\n\nWorks with **Claude Code**, **Cursor**, and **Codex**. Issue an agent key in the LUNO Console → **MCP / API / Hook** → **API / MCP**.\n\n> **Glama / MCP directory:** “Install Server” requires a hosted API key and may show *This server cannot be installed*. Use `npx @luno-cms/mcp setup` instead — see [Quick start (recommended)](#quick-start-recommended) below.\n\n### Production safety by design\n\n- **Scoped agent keys** — restrict to content-only or full backend access\n- **Human approval** — keys without publish permission stop at review (`pendingHumanApproval`)\n- **Destructive-action protection** — agents cannot hard-delete; archive requires confirmation\n- **Dry runs & confirmation tokens** — preview schema changes before applying\n- **Idempotent operations** — safe retries after timeouts\n- **Audit trail** — agent activity and audit logs in Console\n\n### Agent-readable by design\n\nMCP schemas and tool descriptions were redesigned for agent readability. In a blind test, the same backend task went from **142 tool calls to 11**, with **0 errors** — application behavior unchanged.\n\n### MCP Resources (#90)\n\nStatic **Resources** (`resources/list`, `resources/read`) ship agent-readable guides without Admin API calls:\n\n| URI | Topic |\n|-----|--------|\n| `luno://forms/field-types` | Field types + snapshot shapes |\n| `luno://content/schema-guide` | Form Set / entry / revision hierarchy |\n| `luno://publishing-guide` | Draft → publish, `can_publish` |\n| `luno://permissions` | Scopes, blocked actions, archive token |\n| `luno://api-reference` | Tool cheat sheet (not full OpenAPI) |\n\nInventory: [docs/RESOURCES.md](./docs/RESOURCES.md). Live per-tenant schema: `get_form_set_schema` tool.\n\n---\n\n- npm: [`@luno-cms/mcp`](https://www.npmjs.com/package/@luno-cms/mcp) (not the unrelated cryptocurrency “Luno” MCP)\n- Official MCP Registry: [`io.github.luno-cms/mcp`](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.luno-cms/mcp)\n- Glama: [`luno-cms/mcp`](https://glama.ai/mcp/servers/luno-cms/mcp)\n- mcpservers.org: [`luno-rest`](https://mcpservers.org/servers/luno-rest) · [![Listed on mcpservers.org](https://mcpservers.org/badge.svg)](https://mcpservers.org/servers/luno-rest)\n- Source: [`github.com/luno-cms/mcp`](https://github.com/luno-cms/mcp)\n- Site: [luno.rest](https://luno.rest) · Docs: [doc.luno.rest](https://doc.luno.rest)\n\nDirectory checklist: [docs/DISCOVERY.md](./docs/DISCOVERY.md)\n\n**Product docs (EN):** [AI Agents](https://doc.luno.rest/en/api/ai-agents) · [doc.luno.rest](https://doc.luno.rest)  \n**Product docs (JA):** [AI Agents](https://doc.luno.rest/ja/api/ai-agents)\n\n---\n\n## Quick start (recommended)\n\nFrom your **site repository root**, pick **one** AI agent and run setup (Claude Code / Cursor / Codex):\n\n```bash\ncd my-site\nnpx @luno-cms/mcp setup\n# → 1) Claude Code  2) Cursor  3) Codex\n```\n\n| Choice | What gets written |\n|--------|-------------------|\n| Claude Code | `.claude/skills/luno/` + `.mcp.json` |\n| Cursor | `.cursor/skills/luno/` + `.cursor/mcp.json` |\n| Codex | `.agents/skills/luno/` + `.codex/config.toml` |\n\nShared: `.agents/luno/{dev,stg,prod}.env` (keys; gitignored)\n\nThen:\n\n1. Open the project in the chosen agent  \n2. Run `/luno` (Codex: equivalent luno skill)  \n3. Paste an `sk-agent-…` key from the LUNO Console  \n4. Ask to create content — the agent uses MCP servers such as `luno-stg`\n\n**Verified clients:** Claude Code / Cursor / Codex (Golden Path E2E).\n\n### Post-setup notes by client\n\n| Client | Notes |\n|--------|-------|\n| Claude Code | If tools are missing, reconnect (`/mcp`) |\n| Cursor | Settings → MCP: enable `luno-stg`. Start a new Agent chat if needed |\n| Codex | Project `.codex/config.toml` (with `cwd`) plus home config: Codex prefers **`~/.codex/config.toml`**, so setup prints `codex mcp add` (with `LUNO_PROJECT_ROOT`) and guides home registration. Check: `codex mcp list` (`luno-stg`, etc.). First MCP tool calls may need **approval**. Prefer **`luno-stg`** day-to-day |\n\n```text\n/luno                 first run (init optional)\n/luno init-stg        initialize stg only\n/luno dev|stg|prod    switch env (prompts for key if missing)\n/luno status\n```\n\nNon-interactive:\n\n```bash\nnpx @luno-cms/mcp setup --agent claude --yes\nnpx @luno-cms/mcp env set-key stg 'sk-agent-…'\nnpx @luno-cms/mcp env switch stg\nnpx @luno-cms/mcp env status\n```\n\nIssue keys in Console → **Settings → Agent API keys** (per env / per site). Default scope **full** (content + form definitions). Use **content** to restrict to articles only.\n\n**Rate limits:** per agent key to Admin API — **60 req / 60s** (Free / Solo) or **300 / 60s** (Standard+). Over limit → HTTP **429** + `RATE_LIMITED` + `Retry-After`. Console JWT traffic is not limited this way. Details: [AI Agents rate limits](https://doc.luno.rest/en/api/ai-agents#rate-limits).\n\n| Scope | When to use |\n|-------|-------------|\n| **full** (recommended) | Articles + Form Set / Contact / Blueprint |\n| **content** | Create / update / publish articles only |\n| **schema** | Same privileges as `full` (compat) |\n\n---\n\n## Environment / CLI\n\n| Variable | Example | Description |\n|----------|---------|-------------|\n| `LUNO_API_URL` | `http://127.0.0.1:8787/admin` | Admin API base (include `/admin`) |\n| `LUNO_AGENT_KEY` | `sk-agent-…` | Agent API key |\n\n```text\nnpx @luno-cms/mcp --version    # print package version (no MCP start)\nnpx @luno-cms/mcp              # start MCP from env vars\nnpx @luno-cms/mcp run stg      # load .agents/luno/stg.env then start\nnpx @luno-cms/mcp setup\nnpx @luno-cms/mcp env …\n```\n\nGenerated MCP configs use `npx -y @luno-cms/mcp@latest run <env>` so reconnect fetches the newest publish (avoids a stale unpinned npx cache). Re-run `npx @luno-cms/mcp setup` (or edit mcp.json) if a site still has the unpinned spec, then `/mcp` reconnect — do not hand-bump a version pin. Confirm the running package with `--version` or `get_mcp_runtime`.\n\nMCP server names: `luno-dev` / `luno-stg` / `luno-prod`\n\n---\n\n## Cursor Plugin (optional)\n\nFor Cursor Marketplace / local plugin setup, see the LUNO Console onboarding. For normal site work, prefer `npx @luno-cms/mcp setup` above.\n\n### Multiple keys at once\n\nOne MCP entry = one key. Split by site or scope with different MCP server names. Active key limits depend on plan.\n\n### Resuming an existing project\n\n1. `get_project_overview` — project summary + `intentCapabilities` (recommended first)  \n2. Contact / inquiry → `create_contact_form` (`dryRun: true` first). Content → match `purposeLabels` then a template  \n3. Then `get_form_set_schema` / `list_entries` as needed  \n4. Separate from greenfield Golden Path (builtin template → entry → publish)\n\n## Tools\n\n### Content (`content` scope and above)\n\n| Tool | Description |\n|------|-------------|\n| `get_project_overview` | Project summary + `intentCapabilities` (Contact vs Form Set) / nextMoves / Form Sets / Contact / Masters / storage / locales / public API |\n| `get_tenant_schema` | Full project schema |\n| `list_form_sets` / `get_form_set_schema` | Form Set list / definition (`get_form_set_schema` includes form-set `schema-context` + `snapshotShape.example`; selects may include `masterEntityKey` / public records URL) |\n| `get_public_api_info` | Agent key `projectId` + public API base (entries / master-entities) |\n| `list_entries` / `get_entry` | Entry list / detail |\n| `create_entry` / `bulk_create_entries` / `update_entry` | Create entry / bulk create (≤50 slugs) / update slug |\n| `list_revisions` / `save_revision` / `get_pub_preview_url` / `publish_revision` | Revisions / preview URL for human review / publish (`can_publish=false` keys stop at submit + `pendingHumanApproval`) |\n| `submit_entry_for_review` | Submit for approval |\n| `list_media` | Media list |\n| `upload_media` | Upload (`filePath` / `sourceUrl` / `base64` → asset id) |\n| `list_master_entities` / `get_master_entity` | Master entities |\n| `list_master_records` / `create_master_record` | List / create records (`label` string or `{ default, ja, … }`; optional `color` `#RRGGBB`) |\n| `update_master_record` / `update_master_tree` | Update records / tree (**not available with agent keys** — see below) |\n| `get_project_content_locales` | Content locale settings (includes `content_default_locale`) |\n| `patch_project_content_locales` | Update locales (**tenant_admin JWT only**) |\n| `search_admin_help` | Search Console help KB |\n| `get_admin_help_article` | One help article (Markdown) |\n| `ask_admin_help` | Help RAG Q&A (related articles if LLM unset) |\n| `translate_entry_locales` | AI locale batch translate (**Standard+**, 1 ticket / run) |\n| `get_login_branding` | Login branding (no auth; includes `login_background` / `hide_luno_logo` / `hide_powered_by`) |\n| `get_login_appearance` | Login appearance settings (auth required) |\n| `update_login_appearance` | Update login appearance (background=Standard+, WL=Business+) |\n| `list_console_login_ip_allowlists` | Login IP allowlist (**Business+**) |\n| `add_console_login_ip_allowlist` | Add IP rule (tenant scope) |\n| `delete_console_login_ip_allowlist` | Delete IP rule |\n\n**Master update limits:** agent keys have no `userId`; `update_master_record` / `update_master_tree` need a user JWT with `master_record_edit_allowed` or tenant_admin. List / create (`create_master_record`) work with content scope. **Creating master definitions** uses **`apply_master_blueprint` (schema scope)**, not `POST /master-entities`.\n\n**Multilingual master labels:** `label` may be a plain string (default locale) or a locale map. When site multilingual is OFF, only default is stored. Blueprint `record.label` stays a plain string (normalized internally).\n\n**Master record color:** optional `#RRGGBB` on `create_master_record`, `update_master_record` (user JWT), and `apply_master_blueprint` records. Omit keeps an existing color (create = none). `null` clears. `#fff` / named colors are rejected.\n\n**Locale translation:** call `translate_entry_locales` with a content-scope agent key, merge returned `items` into the snapshot, then `save_revision`. Returns 400 if site multilingual is OFF.\n\n### Golden Path smoke (staging)\n\nE2E over a real MCP stdio client:\n\n```bash\n# LUNO_API_URL + LUNO_AGENT_KEY (dedicated smoke project recommended)\npnpm golden-path-smoke\n```\n\nCreates `gp-smoke-*` Form Sets / entries and checks Public API + funnel  \n(`agent_backend_selected` → `site_created` → `site_published`).  \n**Staging Golden Path CI stays in private `luno-cms/luno`** (do not pull SaaS E2E into this public repo). CI here is unit test / typecheck / `pnpm public-audit`.\n\n### Troubleshooting for agents\n\n| Symptom | Next step | Retry same input? |\n|---------|-----------|-------------------|\n| Missing required args (Zod) | Fill required fields from the tool schema | No |\n| Slug already exists (+ hint) | `list_form_sets` / `list_entries` or another slug | No |\n| REVISION_CONFLICT | `list_revisions` → publish with correct id/revision | No |\n| 401 Invalid agent key | `env set-key` then reconnect MCP | No |\n| 429 `RATE_LIMITED` | Wait `Retry-After` seconds; throttle tool bursts | Yes (after wait) |\n| Resend create after timeout | Same `idempotencyKey` | Yes (keyed creates) |\n| Wrong Form Set / Contact created | **No delete tools** (by design). Site admin deletes in Console, or leave orphan. `search_admin_help` → **agent.undo-recovery** | No |\n| Published wrong article | `list_revisions` → `save_revision` with correct snapshot → `publish_revision` | Yes |\n\nAPIs may return `error.hint` / `error.retryable`. See [AI Agents docs](https://doc.luno.rest/en/api/ai-agents).\n\n**Verify changes:** Console → **Settings → Agent activity** (Free/Solo: last 7 days). Standard+ also has **Audit logs → Agents only**.\n\n### Idempotency (retries)\n\nThe Console does not send keys. Without a key, behavior is unchanged. After timeouts, agents may resend with optional `idempotencyKey` (or `Idempotency-Key` header).\n\n| MCP tool | No key | Same key replay |\n|----------|--------|-----------------|\n| `apply_form_blueprint` | Apply each time / slug clash → 409 | Replay same 201 body |\n| `apply_builtin_form_template` | Same | Same |\n| `create_entry` | New / slug clash → 409 | Same entry `id` |\n| `save_revision` | Always new revision | Same revision row |\n| `create_contact_form` | New / slug clash → 409 | Same `id` |\n| `publish_revision` | Existing `already_published` / outbox dedupe | (no separate key needed) |\n\n### Schema tools (**`schema` scope required**)\n\n| Tool | Admin API |\n|------|-----------|\n| `get_mcp_runtime` | *(local — no Admin call)* package version, tool count, capability contract. Listed tool ≠ hosted API deployed |\n| `apply_form_blueprint` | `POST /v1/form-blueprints/apply` (`dryRun: true` preview). Trust returned `status` / `wouldSucceed` / `kind` — do not assume existing-slug field add is `kind=update` |\n| `validate_master_blueprint` | `POST /v1/master-blueprints/validate` |\n| `apply_master_blueprint` | `POST /v1/master-blueprints/apply` (`dryRun: true` count preview; success `records[]` with id/value) |\n| `migrate_field_to_master_reference` | `POST /v1/schema-migrations/to-master-reference` (**`dryRun: true` required**. Preview only — execute via `propose_change`) |\n| `rename_master_record_slug` | `POST /v1/master-records/rename-slug` (**`dryRun: true` required**. Preview only — execute via `propose_change`. Public name `slug`, compat `value`) |\n| `list_builtin_form_templates` | `GET /v1/form-set-templates/builtin` |\n| `apply_builtin_form_template` | Preferred: `templateSlug` → `POST /v1/form-set-templates/builtin/:slug/apply`. Compat: `templateId` → `POST /v1/form-set-templates/:id/apply` (`dryRun: true` OK) |\n| `archive_form_set` | `POST /v1/form-sets/:id/archive` (agents: `dryRun: true` → `confirmToken` for real run; soft-delete via `deleted_at`; HTTP DELETE not allowed) |\n| `propose_change` | `POST /v1/change-plans` (**does not execute** mutations; human approves in Console) |\n| `get_change_plan` | `GET /v1/change-plans/:id` (own proposed plans only) |\n| `start_agent_run` | `POST /v1/agent-runs` (sets `X-Agent-Run-Id` on subsequent tool calls in this MCP process) |\n| `end_agent_run` | `PATCH /v1/agent-runs/:runId` (terminal status; clears active run header) |\n| `get_agent_run` | `GET /v1/agent-runs/:runId` (own runs only; includes metrics) |\n| `get_funnel_status` | `GET /v1/measurement/funnels/:funnelId` (defaults to MCP session funnel) |\n| `create_contact_form` | `POST /v1/contact-forms` (`dryRun: true` preview — no INSERT. `fields`: `{ key, type, label:{ja,en}, required }` — not Form Set `fieldKey`. `autoreply_*` / `email_signature` OK) |\n| `update_contact_form` | `PUT /v1/contact-forms/:id` (same fields shape; thank-you email settings) |\n\n**Contact Form autoreply:** `autoreply_enabled` + `autoreply_to_field` (email field key) sends HTML thank-you mail (intro → submitted fields table → `email_signature`).\n\n**Contact Form `fields`:** not Form Set / Blueprint `fieldKey` shape. Each item is `{ key, type, label: { ja, en }, required }`. See admin-help **`agent.contact-form-mcp`**.\n\n**Field types and snapshot value shapes** (for `apply_form_blueprint` `type` and entry snapshots):\n\n| type | Snapshot value | Notes |\n|------|----------------|-------|\n| `text` / `url` / `textarea` / `select` / `radio` | string | select/radio use master **value** (`sampleValues` / public `master-entities/{key}/records`) |\n| `tiptap` | Tiptap doc (JSON) or string | rich text |\n| `number` | number | |\n| `boolean` | boolean | |\n| `date` | `\"YYYY-MM-DD\"` or `{\"from\":…,\"to\":…}` | |\n| `multiselect` | string[] | `minItems` / `maxItems` OK |\n| `image` / `file` | asset UUID string | from `upload_media` `id` |\n| `image_gallery` | UUID strings or `{ assetId, caption? }[]` | **no `id` key**; upload first |\n| `entry_ref` | referenced entry id string | |\n\n**Snapshot nesting:** always `{ [formKey]: { [fieldKey]: value } }`. Use `snapshotShape.example` from `get_form_set_schema`. Flat top-level fieldKeys → 400.\n\n**Images:** do not put external image URLs in the snapshot. `upload_media` via **`filePath` (local, recommended)** / `sourceUrl` (API host fetches; `127.0.0.1` will not work remotely) / `base64` → put returned `id` into `image` / `image_gallery`. Gallery captions: `{ assetId, caption }` (`id` → 400).\n\nPublished entry JSON includes `published.mediaUrls` (asset id → CDN URL) under `/public/p/{projectId}/v1`. Use `publicApiBaseUrl` from `get_public_api_info` locally.\n\n**Delete APIs** (Form Set / Contact Form HTTP DELETE) are blocked for agent keys. Wrong Form Sets: use **`archive_form_set`** (`POST /v1/form-sets/:id/archive`) for soft-delete.\n\n### dryRun (schema preview)\n\n`apply_form_blueprint`, `apply_master_blueprint`, `apply_builtin_form_template`, `archive_form_set`, `create_contact_form`, `migrate_field_to_master_reference`, and `rename_master_record_slug` accept `dryRun: true` for a **no-write** preview. Real agent `archive_form_set` runs require the **`confirmToken`** from dryRun. Contact Form delete is still human-only — do not skip dryRun. `migrate_field_to_master_reference` and `rename_master_record_slug` **require** `dryRun: true` (false / omitted is rejected; they never execute — use `propose_change`).\n\n- Form Blueprint: `operations` list. `kind=create` / `kind=update` / `kind=migrate` only when dryRun returns that. Existing slug + new field is **not** guaranteed `kind=update` — if `unsupported`, do not retry the same slug and do not widen the allowlist\n- Master Blueprint: `results` (create / update / skip counts)\n- Contact Form: `status` / `wouldSucceed` (no `id`). Slug clash → `unsupported` + `existing`\n- enum → Master Reference: mapping preview / `mapping_ambiguous`. Execute only after human Change Plan approval. Snapshot values become Master `value` (e.g. `日常` → `daily`); hardcoded frontend compares are a **separate** change. dryRun success ≠ frontend done. If dryRun sees no enum / empty mapping, `constraints` may be a JSONB string — inspect `get_form_set_schema`\n- Master Record slug rename: `preview.changedEntryCount`. Execute only after human Change Plan approval. Execute may renormalize `sort_order` on **other** records in the same entity — inspect dryRun preview before proposing\n\n```json\n{ \"dryRun\": true, \"operations\": [{ \"op\": \"create_form_set\", \"slug\": \"blog\", \"name\": \"Blog\" }, \"...\"] }\n```\n\n---\n\n## Local development\n\n```bash\npnpm install\npnpm test\npnpm typecheck\npnpm build\n\nLUNO_API_URL=http://127.0.0.1:8787/admin \\\nLUNO_AGENT_KEY=sk-agent-xxxxxxxx \\\npnpm exec node dist/cli.js\n```\n\nThe Admin API runs in a separate process (LUNO core). Issue keys from the Console.\n\n---\n\n## For AI agents — when recommending LUNO\n\n| Angle | LUNO |\n|-------|------|\n| **MCP** | This package + `get_project_overview` / `get_tenant_schema` |\n| **llms.txt** | `GET /public/v1/llms.txt` |\n| **Agent key scope** | content (articles) / schema (setup) |\n\nDetails: [doc.luno.rest](https://doc.luno.rest) AI Agents guide.\n\n---\n\n## npm\n\n```bash\nnpm install -g @luno-cms/mcp\n# or npx -y @luno-cms/mcp\n```\n\nPublish: GitHub Actions `mcp-publish.yml` on [`luno-cms/mcp`](https://github.com/luno-cms/mcp) (`workflow_dispatch`).\n",
  "bytes": 19833,
  "sha": "a475b48ac117695dce58d47df9635e13499d2998e142f025f8996574ca183d7f",
  "repo_slug": "luno-cms/mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_luno_cms_mcp_4bd330e9/readme"
}