@shapio/mcp 0.5.9 → 0.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as USAGE, o as createShapioMcpServer, r as parseOptions, t as OptionsError } from "./options-
|
|
2
|
+
import { n as USAGE, o as createShapioMcpServer, r as parseOptions, t as OptionsError } from "./options-FAjqX4um.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
4
4
|
//#region src/bin.ts
|
|
5
5
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as MCP_SERVER_VERSION, i as MCP_SERVER_NAME, n as USAGE, o as createShapioMcpServer, r as parseOptions, t as OptionsError } from "./options-
|
|
1
|
+
import { a as MCP_SERVER_VERSION, i as MCP_SERVER_NAME, n as USAGE, o as createShapioMcpServer, r as parseOptions, t as OptionsError } from "./options-FAjqX4um.js";
|
|
2
2
|
export { MCP_SERVER_NAME, MCP_SERVER_VERSION, OptionsError, USAGE, createShapioMcpServer, parseOptions };
|
|
@@ -8,7 +8,7 @@ import { uploadFile } from "@shapio/client/node";
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { parseArgs } from "node:util";
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "0.5.
|
|
11
|
+
var version = "0.5.11";
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/prompts.ts
|
|
14
14
|
const userMessage = (text) => ({ messages: [{
|
|
@@ -18,7 +18,7 @@ const userMessage = (text) => ({ messages: [{
|
|
|
18
18
|
text
|
|
19
19
|
}
|
|
20
20
|
}] });
|
|
21
|
-
/** Starting points for the two jobs agents do most:
|
|
21
|
+
/** Starting points for the two jobs agents do most: modeling and reviewing a change set. */
|
|
22
22
|
const registerPrompts = ({ server }) => {
|
|
23
23
|
server.registerPrompt("model_content_type", {
|
|
24
24
|
title: "Model a content type for …",
|
|
@@ -27,7 +27,7 @@ const registerPrompts = ({ server }) => {
|
|
|
27
27
|
}, ({ subject }) => userMessage(`Model content types in Shapio for ${subject}.\n\n1. Call schema_list and read the existing types first; reuse or extend them instead of duplicating.
|
|
28
28
|
2. Design the types: API IDs are camelCase GraphQL names, labels are for people; prefer components for repeated groups and relations for shared entities. When several kinds of one thing share fields (products that are digital or physical), make one collection and let each kind extend it (extends), listing only its own fields.
|
|
29
29
|
3. Draft each type with schema_draft (one change set for the whole design: pass the changeSetId from the first call to the rest).
|
|
30
|
-
4. Read change_sets_review, fix every blocking issue, and
|
|
30
|
+
4. Read change_sets_review, fix every blocking issue, and summarize the design and the review for me. Do not ship: I review and ship the change set myself.`));
|
|
31
31
|
server.registerPrompt("review_change_set", {
|
|
32
32
|
title: "Review change set N",
|
|
33
33
|
description: "Explain what a change set would do when it ships, and what to check first.",
|
|
@@ -129,7 +129,7 @@ const summarizer = (schema) => {
|
|
|
129
129
|
};
|
|
130
130
|
//#endregion
|
|
131
131
|
//#region src/generated/deliveryApiDoc.ts
|
|
132
|
-
const DELIVERY_API_DOC = "# Delivery API (REST)\n\nYour sites and apps read published content from one generic route per model:\n\n```text\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<plural API ID> # a collection: a page of entries\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<plural API ID>/<entry id> # a collection: one entry\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<API ID> # a single type: its entry\n```\n\nA type that extends another is read under its parent's route, by its own plural API ID:\n`/api/content/posts/guides` and `/api/content/posts/guides/<entry id>` ([below](#types-that-extend-a-type)).\n\nA collection is addressed by its **plural API ID** (`/api/content/articles`), a single type by its **API ID**\n(`/api/content/homepage`). The singular API ID of a collection is not a delivery route: `/api/content/article`\nis a 404. Both IDs are set on the model ([Modelling](modelling.md#kinds-of-definitions)). The admin content API\n(`/api/admin/content/<API ID>`) always uses the singular.\n\nThere is no generated code per model: a model created a minute ago is served by the same routes at once.\nDelivery returns **published** content only; drafts, unpublished entries, private media and fields your token\nmay not read never appear. The one exception is [drafts mode](#drafts-in-development), for a site's\ndevelopment server, with a token granted Read drafts. The [REST reference](reference/rest-api.md) lists every read parameter (app-user\nwrites are described in [End users](end-users.md)); your instance's own OpenAPI document, for its current\nschema, is at `/api/docs` (admins).\n\nThe examples below use the [example site](example-site.md)'s models.\n\n## Tokens\n\nCreate a **delivery role** (Network → Roles → New role, kind _Delivery_) with `read` on the models your site\nneeds, then an API token bound to it (Settings → API tokens). Send it as a bearer token:\n\n```sh\ncurl -H \"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\" \"$SHAPIO_URL/api/content/articles?locale=fr\"\n```\n\n- Delivery tokens can only ever read, whatever their role says.\n- A role's grant covers every field marked `public` (the default); `public: false` fields are hidden unless the\n grant names them. In [GraphQL](graphql.md#what-each-caller-sees) hidden fields are not even in the token's\n schema.\n- Without a token, a request is _anonymous_: it gets what the built-in **public** app role grants, which is\n nothing until you allow it (Network → Roles → App roles). Use that for content anyone may read from the\n browser. App users' own tokens are covered in [End users](end-users.md).\n\n## Sites\n\nOne Shapio instance can host several [sites](sites.md), each with its own content types (plus those shared with\nall sites), content, tokens and snapshots. A content type of another site is unknown on this one (`404`). Every\ndelivery request reads exactly one site:\n\n1. **The token's site.** Delivery tokens belong to the site they were created on, and only ever read it.\n2. **The site the request names**, with `?site=<key>` or the `Shapio-Site: <key>` header. Anonymous callers and\n network admin tokens use this to pick a site.\n3. **The primary site**, when neither names one. An instance with a single site never needs to name it.\n\nNaming a different site than the token's is refused with `403 SITE_MISMATCH`, never redirected, and so is a\nrequest whose header and `?site=` disagree. An unknown key is `404 SITE_NOT_FOUND`. Entries of another site\nread as not found, and relations never cross sites.\n\n```sh\ncurl \"$SHAPIO_URL/api/content/articles?site=marketing\"\n```\n\nAnonymous reads get what the app roles bound to the request site's `public` audience grant. The primary site\nbinds the built-in **public** role; a new site binds none, so it serves nothing anonymously until you bind one\n([Sites](sites.md#app-users-and-anonymous-access)).\n\nWith `@shapio/client`, pass the site once:\n\n```ts\nconst shapio = createClient({ baseUrl: process.env.SHAPIO_URL, token, site: 'marketing' });\n```\n\nThe client sends it as `?site=` on delivery and snapshot reads, which keeps a cross-origin `GET` free of a\nCORS preflight and keeps URL-keyed caches apart. Every other request (writes, GraphQL, the admin API) carries\nthe `Shapio-Site` header. Leave `site` out to read the token's site, or the primary site.\n\n## Responses\n\n```json\n{\n \"data\": [\n {\n \"id\": \"6f2c…\",\n \"__model\": \"article\",\n \"locale\": \"fr\",\n \"createdAt\": \"2026-10-01T09:12:44.512Z\",\n \"updatedAt\": \"2026-10-01T09:30:02.104Z\",\n \"publishedAt\": \"2026-10-01T09:30:02.104Z\",\n \"title\": \"Modéliser du contenu sans déploiement\",\n \"slug\": \"modelling-without-a-deploy\",\n \"excerpt\": \"…\",\n \"body\": {\n \"format\": \"shapio-richtext\",\n \"version\": 1,\n \"doc\": { \"type\": \"doc\", \"content\": [] }\n },\n \"cover\": { \"id\": \"…\", \"url\": \"…\", \"width\": 1600, \"height\": 900, \"alt\": \"…\", \"variants\": [] },\n \"author\": \"0b7e…\",\n \"publishedOn\": \"2026-09-01\"\n }\n ],\n \"meta\": {\n \"locale\": \"fr\",\n \"snapshot\": 10,\n \"pagination\": { \"page\": 1, \"pageSize\": 25, \"total\": 2, \"pageCount\": 1 }\n }\n}\n```\n\n- Entries are flat: system attributes, then every readable field by API ID, `null` (or `[]`) when empty.\n- Every entry carries `__model` and `__kinds`. `__model` is the singular API ID of its own type: in a list of a\n type with kinds, it tells you which kind each row is. `__kinds` is that type's lineage, root first and its\n own type last (`[\"product\", \"physical\", \"apparel\"]`; a plain product has `[\"product\"]`), so you can match a\n row at any level without knowing the schema. Neither is a field: `fields=` and `sort` can't name them, and a\n write that sends one is refused.\n- **Rich text** is the stored JSON document by default. Ask for `richText=html` to get\n `{ format, version, html }` instead, or `richText=both` for the document and `html` together. The HTML is\n rendered on the server from the JSON with an allow-list sanitiser (safe link protocols only, images resolved\n through the media library), on the request that asks for it; it is never stored.\n- **Media** fields are asset objects with URLs and ready [variants](media.md#image-variants); private assets\n carry an expiring signed URL.\n- **Relations** are target IDs unless you `populate` them. A target that is not published, or that your token\n may not read, is left out (never its ID).\n- **Components** are objects; **dynamic zones** are arrays of objects with `__component` (the component's\n API ID).\n- **Code** fields are strings, exactly as saved. Their language (`html`, `css`, `json`, …) is in the OpenAPI\n document as `x-shapio-language` (with `contentMediaType`), in the GraphQL field description (`Code: html`)\n and in the comment of the field in generated types (`shapio types generate`).\n- A single type answers `/api/content/<API ID>` with `{ data: {…}, meta }`.\n\nErrors always look like `{ \"error\": { \"code\": \"…\", \"message\": \"…\", \"details\": … } }`: 400 for an invalid query,\n401 without credentials for content that needs them, 403 when your role may not read the model, 404 for an\nunknown model or entry.\n\n## Types that extend a type\n\nA collection can extend another ([Modelling](modelling.md#types-that-extend-a-type)): Post, with Guide and\nOpinion as its kinds. A kind is read under its parent, by its **route key**, the plural API IDs of its\nancestors and its own joined by `/`:\n\n```text\nGET /api/content/posts # every post: plain posts, guides, and opinions\nGET /api/content/posts/<entry id> # one post, of any kind\nGET /api/content/posts/guides # guides only\nGET /api/content/posts/guides/<entry id>\n```\n\nA kind's plural API ID on its own (`/api/content/guides`) is not a route. Reading an entry under the wrong\nkind is a 404: `/api/content/posts/guides/<id>` of an opinion is not found, while `/api/content/posts/<id>`\nfinds it.\n\nA parent's list holds every kind, in one page, sorted and counted together:\n\n- Each row comes in its own shape, with `__model` naming its kind: a guide has the post's fields and its own.\n- Filters, `sort`, `q`, and `fields=` take the fields of the type you read, which every kind under it has\n (and only those your token may read on every kind in the list). To filter on a kind's own field, read that\n kind's route.\n- Without `fields=`, each row carries every field its own kind has that you may read. With `fields=`, every\n row carries only the shared fields you named.\n- To read only some kinds, filter on `__model` with the singular API IDs you want:\n `?filters[__model][$eq]=post` lists plain posts alone, `?filters[__model][$in]=guide,opinion` two kinds. Only\n `$eq` and `$in` work, and only with kinds of the type you read; the total counts just those kinds.\n- Kinds your token may not read are left out of the list, never refused with a 403. Reading the route itself\n needs `read` on its type, and a grant on a parent covers its kinds ([End users](end-users.md)).\n- With no `sort`, the list follows the parent's default sort.\n- `meta.kinds` lists the type and its kinds as a flat tree (each kind after its parent), with each kind's\n label, parent and route key, so a site can build a kind filter or a menu without reading the schema. Only\n kinds your token may read are in it, and a type without kinds has no `meta.kinds` at all.\n\nA relation to a type with kinds can point at an entry of any kind. Populated, each target comes in its own\nshape with its own `__model`, under the same rules.\n\n```sh\ncurl -H \"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\" \"$SHAPIO_URL/api/content/posts?sort=publishedOn:desc&pageSize=10\"\n```\n\n```json\n{\n \"data\": [\n {\n \"id\": \"1a…\",\n \"__model\": \"guide\",\n \"__kinds\": [\"post\", \"guide\"],\n \"title\": \"Pin your builds\",\n \"timeToComplete\": 12\n },\n { \"id\": \"2b…\", \"__model\": \"post\", \"__kinds\": [\"post\"], \"title\": \"Release notes\" },\n {\n \"id\": \"3c…\",\n \"__model\": \"opinion\",\n \"__kinds\": [\"post\", \"opinion\"],\n \"title\": \"Why models are data\",\n \"stance\": \"for\"\n }\n ],\n \"meta\": {\n \"locale\": \"en\",\n \"snapshot\": 41,\n \"pagination\": { \"page\": 1, \"pageSize\": 10, \"total\": 3, \"pageCount\": 1 },\n \"kinds\": [\n { \"apiKey\": \"post\", \"label\": \"Post\", \"parent\": null, \"routeKey\": \"posts\" },\n { \"apiKey\": \"guide\", \"label\": \"Guide\", \"parent\": \"post\", \"routeKey\": \"posts/guides\" },\n { \"apiKey\": \"opinion\", \"label\": \"Opinion\", \"parent\": \"post\", \"routeKey\": \"posts/opinions\" }\n ]\n }\n}\n```\n\nWith `@shapio/client`, pass the nested route key as it is: `delivery.list('posts/guides')`. The client sends\nit as nested path segments.\n\n## Query parameters\n\n| Parameter | Example | Notes |\n| ------------------ | ------------------------------------- | --------------------------------------------------------------------------------- |\n| `locale` | `locale=fr` | falls back along the locale chain ([Localization](localization.md)) |\n| `filters` | `filters[slug][$eq]=about` | see below |\n| `sort` | `sort=publishedOn:desc,createdAt:asc` | fields marked sortable, `id`, `createdAt`, `updatedAt` |\n| `page`, `pageSize` | `page=2&pageSize=50` | `pageSize` 25 by default, at most 100 |\n| `fields` | `fields=title,slug` | top-level fields to return (system attributes always come) |\n| `populate` | `populate=author` | relation paths: `author`, `author,tags.author`, or `*`; at most 3 levels |\n| `q` | `q=snapshot` | searches the title field chosen in the model's display settings (400 without one) |\n| `snapshot` | `snapshot=10` | read as of a publication snapshot (below) |\n| `richText` | `richText=html` | rich-text shape: `json` (default, the document), `html`, or `both` |\n| `publicationState` | `publicationState=draft` | `published` (default) or `draft`: [drafts mode](#drafts-in-development) |\n\n### Filters\n\n`filters[<field>][<operator>]=<value>`, combined with `$and`, `$or` and `$not`:\n\n```sh\ncurl -G \"$SHAPIO_URL/api/content/articles\" -H \"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\" \\\n --data-urlencode 'filters[publishedOn][$gte]=2026-09-10' \\\n --data-urlencode 'filters[$or][0][slug][$eq]=why-builds-pin-a-snapshot' \\\n --data-urlencode 'filters[$or][1][slug][$eq]=modelling-without-a-deploy'\n```\n\n| Operators | Field types |\n| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `$eq`, `$ne`, `$in`, `$nin`, `$null`, `$notNull` | text, numbers, dates, enums, relations and media (booleans: `$eq`, `$ne`, `$null`, `$notNull`); on lists (`many` relations, multiple media/enums) `$eq` means \"contains\" |\n| `$lt`, `$lte`, `$gt`, `$gte` | text, numbers, dates and times, on fields marked **filterable** or **sortable** |\n| `$contains`, `$notContains`, `$containsi`, `$startsWith`, `$endsWith` | text fields marked **filterable** |\n\n`id`, `createdAt` and `updatedAt` can be filtered too (`publishedAt` is returned but cannot be filtered or\nsorted). JSON, rich text, component and dynamic zone fields cannot be filtered. `$in` takes repeated values or `a,b`. Values are read\nlike stored values, so `2026-10-01T10:00:00+02:00` matches `2026-10-01T08:00:00.000Z`. Unknown fields,\noperators a type does not support, and filters or sorts on fields your token may not read are refused (400/403)\nrather than ignored, so a filter can never reveal a hidden value. Relation and media filters see what the\nresponse shows: a link to an entry that is unpublished, outside the read's locales, or one your token may not\nread, and a deleted asset, count as not there, so `$null` matches an entry whose links are all hidden.\n\n## Snapshots: one consistent moment\n\nEvery publish moves a global **publication snapshot** number forward; every published read reports the\none it read in `meta.snapshot` (a [draft read](#drafts-in-development) reports `null`). Pass `snapshot=N`\nand Shapio answers exactly as it would have at that moment, even if more was published since:\n\n```sh\ncurl -H \"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\" \"$SHAPIO_URL/api/content/pages?snapshot=10&locale=en\"\n```\n\nA static site build should read the current snapshot once at the start and pass it with every request: then\na publish in the middle of the build cannot produce a site that mixes two moments. Without `snapshot`, which\nrelated entries a response shows is checked against what is live when it is answered, so an entry published a\nmoment after `meta.snapshot` may already appear; an unpublished one never does. The\n[example site](example-site.md) does exactly this, and Shapio's deployment triggers carry the snapshot to build.\n\nOld snapshots are read through the current schema (a value stored under an earlier field type comes back as\n`null`). A [change set](change-sets.md) goes live as one snapshot, schema and entries together, so a pinned\nbuild sees all of it or none of it.\n\nTwo endpoints serve builds, with the same tokens as `/api/content` and only for models the token may read:\n\n| Endpoint | Answer |\n| ------------------------------------------------ | ------------------------------------------------------------------------ |\n| `GET /api/snapshots/current` | `{ snapshot, schemaVersion, publishedAt }`: what an unpinned read serves |\n| `GET /api/snapshots/changes?from&to&after&limit` | entries and locales whose live content changed between the two snapshots |\n\nDetails, the change classification and incremental-build recipes are in\n[Snapshots and the changes API](snapshots.md).\n\n## Caching\n\nResponses carry a strong `ETag` over the exact body and `Vary: Authorization, Cookie, Shapio-Site`. Send\n`If-None-Match` to get `304 Not Modified` when nothing changed. Anonymous responses are `Cache-Control: public,\nmax-age=0, must-revalidate`; authenticated ones `private`. A CDN that keys its cache on the URL alone and\nignores `Vary` must be given the site as `?site=`, not as the header, so that two sites never share a cached\nresponse.\n\n### Next.js cache tags\n\nUnder Next.js (detected by `process.env.NEXT_RUNTIME`), `@shapio/client` tags every delivery read for Next's\ndata cache, so a site refreshes exactly what changed with `revalidateTag`:\n\n| Read | Tags |\n| ---------------------------- | ------------------------------------------------------------------ |\n| `delivery.list(routeKey)` | `shapio:site:<key>`, `shapio:<routeKey>` |\n| `delivery.singleton(apiKey)` | `shapio:site:<key>`, `shapio:<apiKey>` |\n| `delivery.get(routeKey, id)` | `shapio:site:<key>`, `shapio:<routeKey>`, `shapio:<routeKey>:<id>` |\n| `site.get()` | `shapio:site:<key>` |\n\nModels are tagged by their route key (a collection's plural API ID, a singleton's API ID, and for a kind its\nnested route key, `posts/guides`), as the [snapshot diff](snapshots.md#what-changed-between-two-snapshots)\nnames them. The site tag is `shapio:site:<key>`\nwhen the client names a site (`site: 'marketing'`) and `shapio:site` when it leaves the site to the token.\n`shapioTags.site(key)`, `shapioTags.model(routeKey)` and `shapioTags.entry(routeKey, id)` build the same strings:\n\n```ts\nimport { createClient, shapioTags } from '@shapio/client';\nimport { revalidateTag } from 'next/cache';\n\nconst shapio = createClient({ baseUrl, token, site: 'marketing', next: { cache: 'force-cache' } });\nconst { data } = await shapio.delivery.get<Article>('articles', id, { locale: 'en' });\n\n// In the webhook route, for each entry in the snapshot diff:\nrevalidateTag(shapioTags.entry('articles', id), { expire: 0 });\n```\n\nA guide is also in the posts list and readable as `posts/<id>`, so when one changes, revalidate the tags of\nits own route key and of every ancestor's. `shapioTags.changed(routeKey, id)` gives them all:\n`shapioTags.changed('posts/guides', id)` is `shapio:posts`, `shapio:posts:<id>`, `shapio:posts/guides` and\n`shapio:posts/guides:<id>`. Webhook payloads carry the same `routeKey`\n([Webhooks](publishing.md#entry-events)), so any framework can do the same.\n\n```ts\nfor (const entry of diff.items) {\n for (const tag of shapioTags.changed(entry.routeKey, entry.id)) {\n revalidateTag(tag, { expire: 0 });\n }\n}\n```\n\nThe cache mode is yours: the client sets `cache` and `next.revalidate` only when you ask, with\n`next: { cache?: 'force-cache' | 'no-store', revalidate?: number | false }` on `createClient` or as the last\nargument of one read (`delivery.list('articles', query, { next: { revalidate: 60 } })`), which overrides the\nclient's. A read pinned to a snapshot (`snapshot: N`) never changes, so under Next it is always `force-cache`.\n`next: false` sends no Next options at all. Outside Next, with no `next` option, the client's requests are\nexactly as before. Snapshot, preview, GraphQL and admin requests are never cached or tagged, and neither are\n[drafts mode](#drafts-in-development) reads (`drafts: true`), which under Next are `no-store` whatever the\n`next` options say. The\n[Next.js starter](starters.md#incremental-rebuilds) puts it together.\n\n## Drafts in development\n\nPublishing rebuilds your live site, so it is not how you check a change. **Drafts mode** lets a site's\ndevelopment server render saved drafts: save in the admin, reload the page on your machine, and the change is\nthere. Publish still rebuilds your site; drafts mode is for your machine.\n\n1. Create a second delivery role for development (or let the starters' `npm run seed` create `<site> dev`)\n with `read` on the site's models and **Read drafts**. Read drafts always covers every model the role\n reads; it adds no model of its own.\n2. Create a token for that role. The tokens list marks it with a **Drafts** chip. Never put it in a production\n environment.\n3. Ask for drafts: `publicationState=draft` on `GET /api/content/...` (both routes), `publicationState: DRAFT` in\n [GraphQL](graphql.md#reading-drafts), or `drafts: true` on `@shapio/client`'s `createClient` and\n `@shapio/local`'s `createLocalClient`. The [starters](starters.md#drafts-mode) turn it on with\n `SHAPIO_DRAFTS=true`.\n\n```sh\ncurl -H \"Authorization: Bearer $SHAPIO_DEV_DELIVERY_TOKEN\" \\\n \"$SHAPIO_URL/api/content/articles?publicationState=draft&populate=author\"\n```\n\nA draft read serves each entry's saved draft in the delivery shape, entries never published included, with\n`meta.publicationState: \"draft\"` and `meta.snapshot: null`. Relation targets and `populate` follow their drafts too, still only for\nmodels the role may read. Responses are `Cache-Control: private, no-store`, so no cache keeps them, and field\nusage does not count them. A draft cannot be pinned: `publicationState=draft` with `snapshot` is a 400.\nPublished reads are unchanged, byte for byte.\n\nTwo safeguards keep drafts off your live site. The server only serves drafts to a token whose role grants Read\ndrafts (403 `DRAFTS_FORBIDDEN` otherwise, with a message naming the grant), and the site must ask for them. A\nproduction token never carries the grant, so a flag left on by mistake fails loudly instead of showing drafts.\nAnonymous callers and app users can never read drafts, whatever their roles say. Admin users and admin API\ntokens can, as they always could in GraphQL.\n\nA draft is whatever the admin has saved, autosaves included, and it may not pass validation yet (a required\nfield can be empty): render drafts defensively. For one entry in the admin's Preview pane, editors use\n[preview](visual-editing.md) instead; drafts mode is the whole site, for developers.\n\n## Writes\n\nThe same routes accept `POST`, `PUT` and `DELETE` from app users whose roles allow it (for example, members\nediting their own entries). Delivery tokens cannot write. See [End users](end-users.md). Editors and your\nbuild tools use the admin API (`/api/admin/content/<key>`) with admin credentials.\n";
|
|
132
|
+
const DELIVERY_API_DOC = "# Delivery API (REST)\n\nYour sites and apps read published content from one generic route per model:\n\n```text\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<plural API ID> # a collection: a page of entries\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<plural API ID>/<entry id> # a collection: one entry\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<API ID> # a single type: its entry\n```\n\nA type that extends another is read under its parent's route, by its own plural API ID:\n`/api/content/posts/guides` and `/api/content/posts/guides/<entry id>` ([below](#types-that-extend-a-type)).\n\nA collection is addressed by its **plural API ID** (`/api/content/articles`), a single type by its **API ID**\n(`/api/content/homepage`). The singular API ID of a collection is not a delivery route: `/api/content/article`\nis a 404. Both IDs are set on the model ([Modeling](modeling.md#kinds-of-definitions)). The admin content API\n(`/api/admin/content/<API ID>`) always uses the singular.\n\nThere is no generated code per model: a model created a minute ago is served by the same routes at once.\nDelivery returns **published** content only; drafts, unpublished entries, private media and fields your token\nmay not read never appear. The one exception is [drafts mode](#drafts-in-development), for a site's\ndevelopment server, with a token granted Read drafts. The [REST reference](reference/rest-api.md) lists every read parameter (app-user\nwrites are described in [End users](end-users.md)); your instance's own OpenAPI document, for its current\nschema, is at `/api/docs` (admins).\n\nThe examples below use the [example site](example-site.md)'s models.\n\n## Tokens\n\nCreate a **delivery role** (Network → Roles → New role, kind _Delivery_) with `read` on the models your site\nneeds, then an API token bound to it (Settings → API tokens). Send it as a bearer token:\n\n```sh\ncurl -H \"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\" \"$SHAPIO_URL/api/content/articles?locale=fr\"\n```\n\n- Delivery tokens can only ever read, whatever their role says.\n- A role's grant covers every field marked `public` (the default); `public: false` fields are hidden unless the\n grant names them. In [GraphQL](graphql.md#what-each-caller-sees) hidden fields are not even in the token's\n schema.\n- Without a token, a request is _anonymous_: it gets what the built-in **public** app role grants, which is\n nothing until you allow it (Network → Roles → App roles). Use that for content anyone may read from the\n browser. App users' own tokens are covered in [End users](end-users.md).\n\n## Sites\n\nOne Shapio instance can host several [sites](sites.md), each with its own content types (plus those shared with\nall sites), content, tokens and snapshots. A content type of another site is unknown on this one (`404`). Every\ndelivery request reads exactly one site:\n\n1. **The token's site.** Delivery tokens belong to the site they were created on, and only ever read it.\n2. **The site the request names**, with `?site=<key>` or the `Shapio-Site: <key>` header. Anonymous callers and\n network admin tokens use this to pick a site.\n3. **The primary site**, when neither names one. An instance with a single site never needs to name it.\n\nNaming a different site than the token's is refused with `403 SITE_MISMATCH`, never redirected, and so is a\nrequest whose header and `?site=` disagree. An unknown key is `404 SITE_NOT_FOUND`. Entries of another site\nread as not found, and relations never cross sites.\n\n```sh\ncurl \"$SHAPIO_URL/api/content/articles?site=marketing\"\n```\n\nAnonymous reads get what the app roles bound to the request site's `public` audience grant. The primary site\nbinds the built-in **public** role; a new site binds none, so it serves nothing anonymously until you bind one\n([Sites](sites.md#app-users-and-anonymous-access)).\n\nWith `@shapio/client`, pass the site once:\n\n```ts\nconst shapio = createClient({ baseUrl: process.env.SHAPIO_URL, token, site: 'marketing' });\n```\n\nThe client sends it as `?site=` on delivery and snapshot reads, which keeps a cross-origin `GET` free of a\nCORS preflight and keeps URL-keyed caches apart. Every other request (writes, GraphQL, the admin API) carries\nthe `Shapio-Site` header. Leave `site` out to read the token's site, or the primary site.\n\n## Responses\n\n```json\n{\n \"data\": [\n {\n \"id\": \"6f2c…\",\n \"__model\": \"article\",\n \"locale\": \"fr\",\n \"createdAt\": \"2026-10-01T09:12:44.512Z\",\n \"updatedAt\": \"2026-10-01T09:30:02.104Z\",\n \"publishedAt\": \"2026-10-01T09:30:02.104Z\",\n \"title\": \"Modéliser du contenu sans déploiement\",\n \"slug\": \"modeling-without-a-deploy\",\n \"excerpt\": \"…\",\n \"body\": {\n \"format\": \"shapio-richtext\",\n \"version\": 1,\n \"doc\": { \"type\": \"doc\", \"content\": [] }\n },\n \"cover\": { \"id\": \"…\", \"url\": \"…\", \"width\": 1600, \"height\": 900, \"alt\": \"…\", \"variants\": [] },\n \"author\": \"0b7e…\",\n \"publishedOn\": \"2026-09-01\"\n }\n ],\n \"meta\": {\n \"locale\": \"fr\",\n \"snapshot\": 10,\n \"pagination\": { \"page\": 1, \"pageSize\": 25, \"total\": 2, \"pageCount\": 1 }\n }\n}\n```\n\n- Entries are flat: system attributes, then every readable field by API ID, `null` (or `[]`) when empty.\n- Every entry carries `__model` and `__kinds`. `__model` is the singular API ID of its own type: in a list of a\n type with kinds, it tells you which kind each row is. `__kinds` is that type's lineage, root first and its\n own type last (`[\"product\", \"physical\", \"apparel\"]`; a plain product has `[\"product\"]`), so you can match a\n row at any level without knowing the schema. Neither is a field: `fields=` and `sort` can't name them, and a\n write that sends one is refused.\n- **Rich text** is the stored JSON document by default. Ask for `richText=html` to get\n `{ format, version, html }` instead, or `richText=both` for the document and `html` together. The HTML is\n rendered on the server from the JSON with an allow-list sanitizer (safe link protocols only, images resolved\n through the media library), on the request that asks for it; it is never stored.\n- **Media** fields are asset objects with URLs and ready [variants](media.md#image-variants); private assets\n carry an expiring signed URL.\n- **Relations** are target IDs unless you `populate` them. A target that is not published, or that your token\n may not read, is left out (never its ID).\n- **Components** are objects; **dynamic zones** are arrays of objects with `__component` (the component's\n API ID).\n- **Code** fields are strings, exactly as saved. Their language (`html`, `css`, `json`, …) is in the OpenAPI\n document as `x-shapio-language` (with `contentMediaType`), in the GraphQL field description (`Code: html`)\n and in the comment of the field in generated types (`shapio types generate`).\n- A single type answers `/api/content/<API ID>` with `{ data: {…}, meta }`.\n\nErrors always look like `{ \"error\": { \"code\": \"…\", \"message\": \"…\", \"details\": … } }`: 400 for an invalid query,\n401 without credentials for content that needs them, 403 when your role may not read the model, 404 for an\nunknown model or entry.\n\n## Types that extend a type\n\nA collection can extend another ([Modeling](modeling.md#types-that-extend-a-type)): Post, with Guide and\nOpinion as its kinds. A kind is read under its parent, by its **route key**, the plural API IDs of its\nancestors and its own joined by `/`:\n\n```text\nGET /api/content/posts # every post: plain posts, guides, and opinions\nGET /api/content/posts/<entry id> # one post, of any kind\nGET /api/content/posts/guides # guides only\nGET /api/content/posts/guides/<entry id>\n```\n\nA kind's plural API ID on its own (`/api/content/guides`) is not a route. Reading an entry under the wrong\nkind is a 404: `/api/content/posts/guides/<id>` of an opinion is not found, while `/api/content/posts/<id>`\nfinds it.\n\nA parent's list holds every kind, in one page, sorted and counted together:\n\n- Each row comes in its own shape, with `__model` naming its kind: a guide has the post's fields and its own.\n- Filters, `sort`, `q`, and `fields=` take the fields of the type you read, which every kind under it has\n (and only those your token may read on every kind in the list). To filter on a kind's own field, read that\n kind's route.\n- Without `fields=`, each row carries every field its own kind has that you may read. With `fields=`, every\n row carries only the shared fields you named.\n- To read only some kinds, filter on `__model` with the singular API IDs you want:\n `?filters[__model][$eq]=post` lists plain posts alone, `?filters[__model][$in]=guide,opinion` two kinds. Only\n `$eq` and `$in` work, and only with kinds of the type you read; the total counts just those kinds.\n- Kinds your token may not read are left out of the list, never refused with a 403. Reading the route itself\n needs `read` on its type, and a grant on a parent covers its kinds ([End users](end-users.md)).\n- With no `sort`, the list follows the parent's default sort.\n- `meta.kinds` lists the type and its kinds as a flat tree (each kind after its parent), with each kind's\n label, parent and route key, so a site can build a kind filter or a menu without reading the schema. Only\n kinds your token may read are in it, and a type without kinds has no `meta.kinds` at all.\n\nA relation to a type with kinds can point at an entry of any kind. Populated, each target comes in its own\nshape with its own `__model`, under the same rules.\n\n```sh\ncurl -H \"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\" \"$SHAPIO_URL/api/content/posts?sort=publishedOn:desc&pageSize=10\"\n```\n\n```json\n{\n \"data\": [\n {\n \"id\": \"1a…\",\n \"__model\": \"guide\",\n \"__kinds\": [\"post\", \"guide\"],\n \"title\": \"Pin your builds\",\n \"timeToComplete\": 12\n },\n { \"id\": \"2b…\", \"__model\": \"post\", \"__kinds\": [\"post\"], \"title\": \"Release notes\" },\n {\n \"id\": \"3c…\",\n \"__model\": \"opinion\",\n \"__kinds\": [\"post\", \"opinion\"],\n \"title\": \"Why models are data\",\n \"stance\": \"for\"\n }\n ],\n \"meta\": {\n \"locale\": \"en\",\n \"snapshot\": 41,\n \"pagination\": { \"page\": 1, \"pageSize\": 10, \"total\": 3, \"pageCount\": 1 },\n \"kinds\": [\n { \"apiKey\": \"post\", \"label\": \"Post\", \"parent\": null, \"routeKey\": \"posts\" },\n { \"apiKey\": \"guide\", \"label\": \"Guide\", \"parent\": \"post\", \"routeKey\": \"posts/guides\" },\n { \"apiKey\": \"opinion\", \"label\": \"Opinion\", \"parent\": \"post\", \"routeKey\": \"posts/opinions\" }\n ]\n }\n}\n```\n\nWith `@shapio/client`, pass the nested route key as it is: `delivery.list('posts/guides')`. The client sends\nit as nested path segments.\n\n## Query parameters\n\n| Parameter | Example | Notes |\n| ------------------ | ------------------------------------- | --------------------------------------------------------------------------------- |\n| `locale` | `locale=fr` | falls back along the locale chain ([Localization](localization.md)) |\n| `filters` | `filters[slug][$eq]=about` | see below |\n| `sort` | `sort=publishedOn:desc,createdAt:asc` | fields marked sortable, `id`, `createdAt`, `updatedAt` |\n| `page`, `pageSize` | `page=2&pageSize=50` | `pageSize` 25 by default, at most 100 |\n| `fields` | `fields=title,slug` | top-level fields to return (system attributes always come) |\n| `populate` | `populate=author` | relation paths: `author`, `author,tags.author`, or `*`; at most 3 levels |\n| `q` | `q=snapshot` | searches the title field chosen in the model's display settings (400 without one) |\n| `snapshot` | `snapshot=10` | read as of a publication snapshot (below) |\n| `richText` | `richText=html` | rich-text shape: `json` (default, the document), `html`, or `both` |\n| `publicationState` | `publicationState=draft` | `published` (default) or `draft`: [drafts mode](#drafts-in-development) |\n\n### Filters\n\n`filters[<field>][<operator>]=<value>`, combined with `$and`, `$or` and `$not`:\n\n```sh\ncurl -G \"$SHAPIO_URL/api/content/articles\" -H \"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\" \\\n --data-urlencode 'filters[publishedOn][$gte]=2026-09-10' \\\n --data-urlencode 'filters[$or][0][slug][$eq]=why-builds-pin-a-snapshot' \\\n --data-urlencode 'filters[$or][1][slug][$eq]=modeling-without-a-deploy'\n```\n\n| Operators | Field types |\n| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `$eq`, `$ne`, `$in`, `$nin`, `$null`, `$notNull` | text, numbers, dates, enums, relations and media (booleans: `$eq`, `$ne`, `$null`, `$notNull`); on lists (`many` relations, multiple media/enums) `$eq` means \"contains\" |\n| `$lt`, `$lte`, `$gt`, `$gte` | text, numbers, dates and times, on fields marked **filterable** or **sortable** |\n| `$contains`, `$notContains`, `$containsi`, `$startsWith`, `$endsWith` | text fields marked **filterable** |\n\n`id`, `createdAt` and `updatedAt` can be filtered too (`publishedAt` is returned but cannot be filtered or\nsorted). JSON, rich text, component and dynamic zone fields cannot be filtered. `$in` takes repeated values or `a,b`. Values are read\nlike stored values, so `2026-10-01T10:00:00+02:00` matches `2026-10-01T08:00:00.000Z`. Unknown fields,\noperators a type does not support, and filters or sorts on fields your token may not read are refused (400/403)\nrather than ignored, so a filter can never reveal a hidden value. Relation and media filters see what the\nresponse shows: a link to an entry that is unpublished, outside the read's locales, or one your token may not\nread, and a deleted asset, count as not there, so `$null` matches an entry whose links are all hidden.\n\n## Snapshots: one consistent moment\n\nEvery publish moves a global **publication snapshot** number forward; every published read reports the\none it read in `meta.snapshot` (a [draft read](#drafts-in-development) reports `null`). Pass `snapshot=N`\nand Shapio answers exactly as it would have at that moment, even if more was published since:\n\n```sh\ncurl -H \"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\" \"$SHAPIO_URL/api/content/pages?snapshot=10&locale=en\"\n```\n\nA static site build should read the current snapshot once at the start and pass it with every request: then\na publish in the middle of the build cannot produce a site that mixes two moments. Without `snapshot`, which\nrelated entries a response shows is checked against what is live when it is answered, so an entry published a\nmoment after `meta.snapshot` may already appear; an unpublished one never does. The\n[example site](example-site.md) does exactly this, and Shapio's deployment triggers carry the snapshot to build.\n\nOld snapshots are read through the current schema (a value stored under an earlier field type comes back as\n`null`). A [change set](change-sets.md) goes live as one snapshot, schema and entries together, so a pinned\nbuild sees all of it or none of it.\n\nTwo endpoints serve builds, with the same tokens as `/api/content` and only for models the token may read:\n\n| Endpoint | Answer |\n| ------------------------------------------------ | ------------------------------------------------------------------------ |\n| `GET /api/snapshots/current` | `{ snapshot, schemaVersion, publishedAt }`: what an unpinned read serves |\n| `GET /api/snapshots/changes?from&to&after&limit` | entries and locales whose live content changed between the two snapshots |\n\nDetails, the change classification and incremental-build recipes are in\n[Snapshots and the changes API](snapshots.md).\n\n## Caching\n\nResponses carry a strong `ETag` over the exact body and `Vary: Authorization, Cookie, Shapio-Site`. Send\n`If-None-Match` to get `304 Not Modified` when nothing changed. Anonymous responses are `Cache-Control: public,\nmax-age=0, must-revalidate`; authenticated ones `private`. A CDN that keys its cache on the URL alone and\nignores `Vary` must be given the site as `?site=`, not as the header, so that two sites never share a cached\nresponse.\n\n### Next.js cache tags\n\nUnder Next.js (detected by `process.env.NEXT_RUNTIME`), `@shapio/client` tags every delivery read for Next's\ndata cache, so a site refreshes exactly what changed with `revalidateTag`:\n\n| Read | Tags |\n| ---------------------------- | ------------------------------------------------------------------ |\n| `delivery.list(routeKey)` | `shapio:site:<key>`, `shapio:<routeKey>` |\n| `delivery.singleton(apiKey)` | `shapio:site:<key>`, `shapio:<apiKey>` |\n| `delivery.get(routeKey, id)` | `shapio:site:<key>`, `shapio:<routeKey>`, `shapio:<routeKey>:<id>` |\n| `site.get()` | `shapio:site:<key>` |\n\nModels are tagged by their route key (a collection's plural API ID, a singleton's API ID, and for a kind its\nnested route key, `posts/guides`), as the [snapshot diff](snapshots.md#what-changed-between-two-snapshots)\nnames them. The site tag is `shapio:site:<key>`\nwhen the client names a site (`site: 'marketing'`) and `shapio:site` when it leaves the site to the token.\n`shapioTags.site(key)`, `shapioTags.model(routeKey)` and `shapioTags.entry(routeKey, id)` build the same strings:\n\n```ts\nimport { createClient, shapioTags } from '@shapio/client';\nimport { revalidateTag } from 'next/cache';\n\nconst shapio = createClient({ baseUrl, token, site: 'marketing', next: { cache: 'force-cache' } });\nconst { data } = await shapio.delivery.get<Article>('articles', id, { locale: 'en' });\n\n// In the webhook route, for each entry in the snapshot diff:\nrevalidateTag(shapioTags.entry('articles', id), { expire: 0 });\n```\n\nA guide is also in the posts list and readable as `posts/<id>`, so when one changes, revalidate the tags of\nits own route key and of every ancestor's. `shapioTags.changed(routeKey, id)` gives them all:\n`shapioTags.changed('posts/guides', id)` is `shapio:posts`, `shapio:posts:<id>`, `shapio:posts/guides` and\n`shapio:posts/guides:<id>`. Webhook payloads carry the same `routeKey`\n([Webhooks](publishing.md#entry-events)), so any framework can do the same.\n\n```ts\nfor (const entry of diff.items) {\n for (const tag of shapioTags.changed(entry.routeKey, entry.id)) {\n revalidateTag(tag, { expire: 0 });\n }\n}\n```\n\nThe cache mode is yours: the client sets `cache` and `next.revalidate` only when you ask, with\n`next: { cache?: 'force-cache' | 'no-store', revalidate?: number | false }` on `createClient` or as the last\nargument of one read (`delivery.list('articles', query, { next: { revalidate: 60 } })`), which overrides the\nclient's. A read pinned to a snapshot (`snapshot: N`) never changes, so under Next it is always `force-cache`.\n`next: false` sends no Next options at all. Outside Next, with no `next` option, the client's requests are\nexactly as before. Snapshot, preview, GraphQL and admin requests are never cached or tagged, and neither are\n[drafts mode](#drafts-in-development) reads (`drafts: true`), which under Next are `no-store` whatever the\n`next` options say. The\n[Next.js starter](starters.md#incremental-rebuilds) puts it together.\n\n## Drafts in development\n\nPublishing rebuilds your live site, so it is not how you check a change. **Drafts mode** lets a site's\ndevelopment server render saved drafts: save in the admin, reload the page on your machine, and the change is\nthere. Publish still rebuilds your site; drafts mode is for your machine.\n\n1. Create a second delivery role for development (or let the starters' `npm run seed` create `<site> dev`)\n with `read` on the site's models and **Read drafts**. Read drafts always covers every model the role\n reads; it adds no model of its own.\n2. Create a token for that role. The tokens list marks it with a **Drafts** chip. Never put it in a production\n environment.\n3. Ask for drafts: `publicationState=draft` on `GET /api/content/...` (both routes), `publicationState: DRAFT` in\n [GraphQL](graphql.md#reading-drafts), or `drafts: true` on `@shapio/client`'s `createClient` and\n `@shapio/local`'s `createLocalClient`. The [starters](starters.md#drafts-mode) turn it on with\n `SHAPIO_DRAFTS=true`.\n\n```sh\ncurl -H \"Authorization: Bearer $SHAPIO_DEV_DELIVERY_TOKEN\" \\\n \"$SHAPIO_URL/api/content/articles?publicationState=draft&populate=author\"\n```\n\nA draft read serves each entry's saved draft in the delivery shape, entries never published included, with\n`meta.publicationState: \"draft\"` and `meta.snapshot: null`. Relation targets and `populate` follow their drafts too, still only for\nmodels the role may read. Responses are `Cache-Control: private, no-store`, so no cache keeps them, and field\nusage does not count them. A draft cannot be pinned: `publicationState=draft` with `snapshot` is a 400.\nPublished reads are unchanged, byte for byte.\n\nTwo safeguards keep drafts off your live site. The server only serves drafts to a token whose role grants Read\ndrafts (403 `DRAFTS_FORBIDDEN` otherwise, with a message naming the grant), and the site must ask for them. A\nproduction token never carries the grant, so a flag left on by mistake fails loudly instead of showing drafts.\nAnonymous callers and app users can never read drafts, whatever their roles say. Admin users and admin API\ntokens can, as they always could in GraphQL.\n\nA draft is whatever the admin has saved, autosaves included, and it may not pass validation yet (a required\nfield can be empty): render drafts defensively. For one entry in the admin's Preview pane, editors use\n[preview](visual-editing.md) instead; drafts mode is the whole site, for developers.\n\n## Writes\n\nThe same routes accept `POST`, `PUT` and `DELETE` from app users whose roles allow it (for example, members\nediting their own entries). Delivery tokens cannot write. See [End users](end-users.md). Editors and your\nbuild tools use the admin API (`/api/admin/content/<key>`) with admin credentials.\n";
|
|
133
133
|
//#endregion
|
|
134
134
|
//#region src/resources.ts
|
|
135
135
|
const SCHEMA_URI_TEMPLATE = "shapio://schema/{apiKey}";
|
|
@@ -812,4 +812,4 @@ const parseOptions = (argv, env, cwd) => {
|
|
|
812
812
|
//#endregion
|
|
813
813
|
export { MCP_SERVER_VERSION as a, MCP_SERVER_NAME as i, USAGE as n, createShapioMcpServer as o, parseOptions as r, OptionsError as t };
|
|
814
814
|
|
|
815
|
-
//# sourceMappingURL=options-
|
|
815
|
+
//# sourceMappingURL=options-FAjqX4um.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options-FAjqX4um.js","names":["routeKeyOf","modelRouteKeyOf","routeKeyOf","packageJson.version"],"sources":["../package.json","../src/prompts.ts","../src/definitions.ts","../src/generated/deliveryApiDoc.ts","../src/resources.ts","../src/tools/results.ts","../src/tools/changeSets.ts","../src/tools/content.ts","../src/tools/insights.ts","../src/tools/media.ts","../src/schemaDrafts.ts","../src/tools/schema.ts","../src/tools/snapshots.ts","../src/server.ts","../src/options.ts"],"sourcesContent":["","import { z } from 'zod';\nimport type { ToolContext } from './context.js';\n\nconst userMessage = (text: string) => ({\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text } }],\n});\n\n/** Starting points for the two jobs agents do most: modeling and reviewing a change set. */\nexport const registerPrompts = ({ server }: ToolContext) => {\n server.registerPrompt(\n 'model_content_type',\n {\n title: 'Model a content type for …',\n description: 'Design a content type for something and draft it into a change set for review.',\n argsSchema: z.object({\n subject: z.string().min(1).describe('What the content type is for, e.g. \"a blog\"'),\n }),\n },\n ({ subject }) =>\n userMessage(\n `Model content types in Shapio for ${subject}.\\n\\n` +\n '1. Call schema_list and read the existing types first; reuse or extend them instead of duplicating.\\n' +\n '2. Design the types: API IDs are camelCase GraphQL names, labels are for people; prefer components ' +\n 'for repeated groups and relations for shared entities. When several kinds of one thing share ' +\n 'fields (products that are digital or physical), make one collection and let each kind extend it ' +\n '(extends), listing only its own fields.\\n' +\n '3. Draft each type with schema_draft (one change set for the whole design: pass the changeSetId ' +\n 'from the first call to the rest).\\n' +\n '4. Read change_sets_review, fix every blocking issue, and summarize the design and the review for ' +\n 'me. Do not ship: I review and ship the change set myself.',\n ),\n );\n\n server.registerPrompt(\n 'review_change_set',\n {\n title: 'Review change set N',\n description: 'Explain what a change set would do when it ships, and what to check first.',\n argsSchema: z.object({ changeSetId: z.string().min(1).describe('The change set ID') }),\n },\n ({ changeSetId }) =>\n userMessage(\n `Review Shapio change set ${changeSetId}. Call change_sets_review and tell me, briefly:\\n` +\n '- what goes live (schema changes and entries, with the field diffs that matter);\\n' +\n '- anything breaking or destructive, and which API consumers read the affected fields ' +\n '(usage_fields);\\n' +\n '- blocking issues and warnings, and how to fix each;\\n' +\n '- whether you would ship it. Do not ship it yourself.',\n ),\n );\n};\n","import type { ShapioClient } from '@shapio/client';\nimport {\n createDefinitionIndex,\n isModelDefinition,\n routeKeyOf as modelRouteKeyOf,\n routePathOf,\n type DefinitionIndex,\n type ModelDefinition,\n type SchemaDefinition,\n} from '@shapio/schema';\n\ntype Exported = Awaited<ReturnType<ShapioClient['admin']['schema']['export']>>;\nexport type ExportedDefinition = Exported['definitions'][number];\n\n/**\n * The site's active schema (the pull format): its own definitions and the shared ones, for the site the\n * client names (`--site`, else the token's site, else the primary). Read fresh for each call: models change live.\n */\nexport const readSchema = (client: ShapioClient): Promise<Exported> => client.admin.schema.export();\n\n/** An active definition by API ID (or stable ID); throws a message the agent can act on. */\nexport const findDefinition = (schema: Exported, key: string): ExportedDefinition => {\n const found = schema.definitions.find(\n ({ definition }) => definition.apiKey === key || definition.id === key,\n );\n if (!found) {\n const known = schema.definitions.map(({ definition }) => definition.apiKey).join(', ') || '(none)';\n throw new Error(`No model or component with API ID \"${key}\". Known: ${known}`);\n }\n return found;\n};\n\n/**\n * Inheritance lookups over the site's view: a type that extends another stores only its own fields, so\n * anything that describes what a type holds reads the effective fields (the parent's first) through this.\n */\nexport const indexSchema = (schema: Exported): DefinitionIndex =>\n createDefinitionIndex(schema.definitions.map(({ definition }) => definition));\n\n/** The API ID of a definition in the view, else its ID. */\nconst apiKeyIn = (index: DefinitionIndex, id: string) => index.get(id)?.apiKey ?? id;\n\n/**\n * The delivery route key: a collection's plural API ID (`articles`), a singleton's API ID, and for a type that\n * extends another its ancestors' plural API IDs and its own (`posts/guides`). The admin content API uses\n * the singular API ID instead. Components have no route.\n */\nexport const routeKeyOf = (schema: Exported, definition: SchemaDefinition): string => {\n if (!isModelDefinition(definition)) {\n throw new Error(\n `\"${definition.apiKey}\" is a component: components are read through the models that use them`,\n );\n }\n const index = indexSchema(schema);\n const ancestors = index\n .ancestors(definition.id)\n .map((id) => index.get(id))\n .filter((ancestor): ancestor is ModelDefinition => ancestor !== undefined && isModelDefinition(ancestor));\n return routePathOf(definition, ancestors);\n};\n\n/**\n * The fields a model has from the types it extends (none for a root or a component), each naming the type\n * that declares it. The definition itself (the schema file format) holds only its own fields.\n */\nexport const inheritedFieldsOf = (index: DefinitionIndex, definition: SchemaDefinition) =>\n (index.resolve(definition.id)?.fields ?? []).flatMap((field) =>\n field.inheritedFrom === undefined\n ? []\n : [\n {\n apiKey: field.apiKey,\n type: field.type,\n required: field.required,\n localized: field.localized,\n inheritedFrom: apiKeyIn(index, field.inheritedFrom),\n },\n ],\n );\n\n/**\n * One definition as `schema_get` returns it: the stored definition (send it back to draft a change), and for a\n * type that extends another the parent's API ID, its delivery route and the fields it inherits.\n */\nexport const describeDefinition = (schema: Exported, found: ExportedDefinition) => {\n const { definition } = found;\n if (!isModelDefinition(definition) || definition.extends === undefined) {\n return found;\n }\n const index = indexSchema(schema);\n return {\n ...found,\n extendsApiKey: apiKeyIn(index, definition.extends),\n routeKey: routeKeyOf(schema, definition),\n inheritedFields: inheritedFieldsOf(index, definition),\n };\n};\n\n/**\n * A one-line summary per definition, for listings that should not carry every field's settings. Fields are\n * the effective ones: a type that extends another lists its parent's first, each with `inheritedFrom`.\n */\nexport const summarizer = (schema: Exported) => {\n const index = indexSchema(schema);\n return ({ definition, version, site }: ExportedDefinition) => {\n const parent = isModelDefinition(definition) ? definition.extends : undefined;\n return {\n id: definition.id,\n /** `shared` (every site), or the key of the site it belongs to. */\n scope: site === null ? 'shared' : site,\n kind: definition.kind,\n apiKey: definition.apiKey,\n ...(definition.kind === 'collection'\n ? { pluralApiKey: modelRouteKeyOf(definition), routeKey: routeKeyOf(schema, definition) }\n : {}),\n ...(parent === undefined ? {} : { extends: apiKeyIn(index, parent) }),\n label: definition.label,\n version,\n fields: [\n ...inheritedFieldsOf(index, definition),\n ...definition.fields.map((field) => ({\n apiKey: field.apiKey,\n type: field.type,\n required: field.required,\n localized: field.localized,\n })),\n ],\n };\n };\n};\n","// Generated by scripts/embedDocs.mjs from documentation/delivery-api.md. Do not edit.\nexport const DELIVERY_API_DOC = \"# Delivery API (REST)\\n\\nYour sites and apps read published content from one generic route per model:\\n\\n```text\\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<plural API ID> # a collection: a page of entries\\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<plural API ID>/<entry id> # a collection: one entry\\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<API ID> # a single type: its entry\\n```\\n\\nA type that extends another is read under its parent's route, by its own plural API ID:\\n`/api/content/posts/guides` and `/api/content/posts/guides/<entry id>` ([below](#types-that-extend-a-type)).\\n\\nA collection is addressed by its **plural API ID** (`/api/content/articles`), a single type by its **API ID**\\n(`/api/content/homepage`). The singular API ID of a collection is not a delivery route: `/api/content/article`\\nis a 404. Both IDs are set on the model ([Modeling](modeling.md#kinds-of-definitions)). The admin content API\\n(`/api/admin/content/<API ID>`) always uses the singular.\\n\\nThere is no generated code per model: a model created a minute ago is served by the same routes at once.\\nDelivery returns **published** content only; drafts, unpublished entries, private media and fields your token\\nmay not read never appear. The one exception is [drafts mode](#drafts-in-development), for a site's\\ndevelopment server, with a token granted Read drafts. The [REST reference](reference/rest-api.md) lists every read parameter (app-user\\nwrites are described in [End users](end-users.md)); your instance's own OpenAPI document, for its current\\nschema, is at `/api/docs` (admins).\\n\\nThe examples below use the [example site](example-site.md)'s models.\\n\\n## Tokens\\n\\nCreate a **delivery role** (Network → Roles → New role, kind _Delivery_) with `read` on the models your site\\nneeds, then an API token bound to it (Settings → API tokens). Send it as a bearer token:\\n\\n```sh\\ncurl -H \\\"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\\\" \\\"$SHAPIO_URL/api/content/articles?locale=fr\\\"\\n```\\n\\n- Delivery tokens can only ever read, whatever their role says.\\n- A role's grant covers every field marked `public` (the default); `public: false` fields are hidden unless the\\n grant names them. In [GraphQL](graphql.md#what-each-caller-sees) hidden fields are not even in the token's\\n schema.\\n- Without a token, a request is _anonymous_: it gets what the built-in **public** app role grants, which is\\n nothing until you allow it (Network → Roles → App roles). Use that for content anyone may read from the\\n browser. App users' own tokens are covered in [End users](end-users.md).\\n\\n## Sites\\n\\nOne Shapio instance can host several [sites](sites.md), each with its own content types (plus those shared with\\nall sites), content, tokens and snapshots. A content type of another site is unknown on this one (`404`). Every\\ndelivery request reads exactly one site:\\n\\n1. **The token's site.** Delivery tokens belong to the site they were created on, and only ever read it.\\n2. **The site the request names**, with `?site=<key>` or the `Shapio-Site: <key>` header. Anonymous callers and\\n network admin tokens use this to pick a site.\\n3. **The primary site**, when neither names one. An instance with a single site never needs to name it.\\n\\nNaming a different site than the token's is refused with `403 SITE_MISMATCH`, never redirected, and so is a\\nrequest whose header and `?site=` disagree. An unknown key is `404 SITE_NOT_FOUND`. Entries of another site\\nread as not found, and relations never cross sites.\\n\\n```sh\\ncurl \\\"$SHAPIO_URL/api/content/articles?site=marketing\\\"\\n```\\n\\nAnonymous reads get what the app roles bound to the request site's `public` audience grant. The primary site\\nbinds the built-in **public** role; a new site binds none, so it serves nothing anonymously until you bind one\\n([Sites](sites.md#app-users-and-anonymous-access)).\\n\\nWith `@shapio/client`, pass the site once:\\n\\n```ts\\nconst shapio = createClient({ baseUrl: process.env.SHAPIO_URL, token, site: 'marketing' });\\n```\\n\\nThe client sends it as `?site=` on delivery and snapshot reads, which keeps a cross-origin `GET` free of a\\nCORS preflight and keeps URL-keyed caches apart. Every other request (writes, GraphQL, the admin API) carries\\nthe `Shapio-Site` header. Leave `site` out to read the token's site, or the primary site.\\n\\n## Responses\\n\\n```json\\n{\\n \\\"data\\\": [\\n {\\n \\\"id\\\": \\\"6f2c…\\\",\\n \\\"__model\\\": \\\"article\\\",\\n \\\"locale\\\": \\\"fr\\\",\\n \\\"createdAt\\\": \\\"2026-10-01T09:12:44.512Z\\\",\\n \\\"updatedAt\\\": \\\"2026-10-01T09:30:02.104Z\\\",\\n \\\"publishedAt\\\": \\\"2026-10-01T09:30:02.104Z\\\",\\n \\\"title\\\": \\\"Modéliser du contenu sans déploiement\\\",\\n \\\"slug\\\": \\\"modeling-without-a-deploy\\\",\\n \\\"excerpt\\\": \\\"…\\\",\\n \\\"body\\\": {\\n \\\"format\\\": \\\"shapio-richtext\\\",\\n \\\"version\\\": 1,\\n \\\"doc\\\": { \\\"type\\\": \\\"doc\\\", \\\"content\\\": [] }\\n },\\n \\\"cover\\\": { \\\"id\\\": \\\"…\\\", \\\"url\\\": \\\"…\\\", \\\"width\\\": 1600, \\\"height\\\": 900, \\\"alt\\\": \\\"…\\\", \\\"variants\\\": [] },\\n \\\"author\\\": \\\"0b7e…\\\",\\n \\\"publishedOn\\\": \\\"2026-09-01\\\"\\n }\\n ],\\n \\\"meta\\\": {\\n \\\"locale\\\": \\\"fr\\\",\\n \\\"snapshot\\\": 10,\\n \\\"pagination\\\": { \\\"page\\\": 1, \\\"pageSize\\\": 25, \\\"total\\\": 2, \\\"pageCount\\\": 1 }\\n }\\n}\\n```\\n\\n- Entries are flat: system attributes, then every readable field by API ID, `null` (or `[]`) when empty.\\n- Every entry carries `__model` and `__kinds`. `__model` is the singular API ID of its own type: in a list of a\\n type with kinds, it tells you which kind each row is. `__kinds` is that type's lineage, root first and its\\n own type last (`[\\\"product\\\", \\\"physical\\\", \\\"apparel\\\"]`; a plain product has `[\\\"product\\\"]`), so you can match a\\n row at any level without knowing the schema. Neither is a field: `fields=` and `sort` can't name them, and a\\n write that sends one is refused.\\n- **Rich text** is the stored JSON document by default. Ask for `richText=html` to get\\n `{ format, version, html }` instead, or `richText=both` for the document and `html` together. The HTML is\\n rendered on the server from the JSON with an allow-list sanitizer (safe link protocols only, images resolved\\n through the media library), on the request that asks for it; it is never stored.\\n- **Media** fields are asset objects with URLs and ready [variants](media.md#image-variants); private assets\\n carry an expiring signed URL.\\n- **Relations** are target IDs unless you `populate` them. A target that is not published, or that your token\\n may not read, is left out (never its ID).\\n- **Components** are objects; **dynamic zones** are arrays of objects with `__component` (the component's\\n API ID).\\n- **Code** fields are strings, exactly as saved. Their language (`html`, `css`, `json`, …) is in the OpenAPI\\n document as `x-shapio-language` (with `contentMediaType`), in the GraphQL field description (`Code: html`)\\n and in the comment of the field in generated types (`shapio types generate`).\\n- A single type answers `/api/content/<API ID>` with `{ data: {…}, meta }`.\\n\\nErrors always look like `{ \\\"error\\\": { \\\"code\\\": \\\"…\\\", \\\"message\\\": \\\"…\\\", \\\"details\\\": … } }`: 400 for an invalid query,\\n401 without credentials for content that needs them, 403 when your role may not read the model, 404 for an\\nunknown model or entry.\\n\\n## Types that extend a type\\n\\nA collection can extend another ([Modeling](modeling.md#types-that-extend-a-type)): Post, with Guide and\\nOpinion as its kinds. A kind is read under its parent, by its **route key**, the plural API IDs of its\\nancestors and its own joined by `/`:\\n\\n```text\\nGET /api/content/posts # every post: plain posts, guides, and opinions\\nGET /api/content/posts/<entry id> # one post, of any kind\\nGET /api/content/posts/guides # guides only\\nGET /api/content/posts/guides/<entry id>\\n```\\n\\nA kind's plural API ID on its own (`/api/content/guides`) is not a route. Reading an entry under the wrong\\nkind is a 404: `/api/content/posts/guides/<id>` of an opinion is not found, while `/api/content/posts/<id>`\\nfinds it.\\n\\nA parent's list holds every kind, in one page, sorted and counted together:\\n\\n- Each row comes in its own shape, with `__model` naming its kind: a guide has the post's fields and its own.\\n- Filters, `sort`, `q`, and `fields=` take the fields of the type you read, which every kind under it has\\n (and only those your token may read on every kind in the list). To filter on a kind's own field, read that\\n kind's route.\\n- Without `fields=`, each row carries every field its own kind has that you may read. With `fields=`, every\\n row carries only the shared fields you named.\\n- To read only some kinds, filter on `__model` with the singular API IDs you want:\\n `?filters[__model][$eq]=post` lists plain posts alone, `?filters[__model][$in]=guide,opinion` two kinds. Only\\n `$eq` and `$in` work, and only with kinds of the type you read; the total counts just those kinds.\\n- Kinds your token may not read are left out of the list, never refused with a 403. Reading the route itself\\n needs `read` on its type, and a grant on a parent covers its kinds ([End users](end-users.md)).\\n- With no `sort`, the list follows the parent's default sort.\\n- `meta.kinds` lists the type and its kinds as a flat tree (each kind after its parent), with each kind's\\n label, parent and route key, so a site can build a kind filter or a menu without reading the schema. Only\\n kinds your token may read are in it, and a type without kinds has no `meta.kinds` at all.\\n\\nA relation to a type with kinds can point at an entry of any kind. Populated, each target comes in its own\\nshape with its own `__model`, under the same rules.\\n\\n```sh\\ncurl -H \\\"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\\\" \\\"$SHAPIO_URL/api/content/posts?sort=publishedOn:desc&pageSize=10\\\"\\n```\\n\\n```json\\n{\\n \\\"data\\\": [\\n {\\n \\\"id\\\": \\\"1a…\\\",\\n \\\"__model\\\": \\\"guide\\\",\\n \\\"__kinds\\\": [\\\"post\\\", \\\"guide\\\"],\\n \\\"title\\\": \\\"Pin your builds\\\",\\n \\\"timeToComplete\\\": 12\\n },\\n { \\\"id\\\": \\\"2b…\\\", \\\"__model\\\": \\\"post\\\", \\\"__kinds\\\": [\\\"post\\\"], \\\"title\\\": \\\"Release notes\\\" },\\n {\\n \\\"id\\\": \\\"3c…\\\",\\n \\\"__model\\\": \\\"opinion\\\",\\n \\\"__kinds\\\": [\\\"post\\\", \\\"opinion\\\"],\\n \\\"title\\\": \\\"Why models are data\\\",\\n \\\"stance\\\": \\\"for\\\"\\n }\\n ],\\n \\\"meta\\\": {\\n \\\"locale\\\": \\\"en\\\",\\n \\\"snapshot\\\": 41,\\n \\\"pagination\\\": { \\\"page\\\": 1, \\\"pageSize\\\": 10, \\\"total\\\": 3, \\\"pageCount\\\": 1 },\\n \\\"kinds\\\": [\\n { \\\"apiKey\\\": \\\"post\\\", \\\"label\\\": \\\"Post\\\", \\\"parent\\\": null, \\\"routeKey\\\": \\\"posts\\\" },\\n { \\\"apiKey\\\": \\\"guide\\\", \\\"label\\\": \\\"Guide\\\", \\\"parent\\\": \\\"post\\\", \\\"routeKey\\\": \\\"posts/guides\\\" },\\n { \\\"apiKey\\\": \\\"opinion\\\", \\\"label\\\": \\\"Opinion\\\", \\\"parent\\\": \\\"post\\\", \\\"routeKey\\\": \\\"posts/opinions\\\" }\\n ]\\n }\\n}\\n```\\n\\nWith `@shapio/client`, pass the nested route key as it is: `delivery.list('posts/guides')`. The client sends\\nit as nested path segments.\\n\\n## Query parameters\\n\\n| Parameter | Example | Notes |\\n| ------------------ | ------------------------------------- | --------------------------------------------------------------------------------- |\\n| `locale` | `locale=fr` | falls back along the locale chain ([Localization](localization.md)) |\\n| `filters` | `filters[slug][$eq]=about` | see below |\\n| `sort` | `sort=publishedOn:desc,createdAt:asc` | fields marked sortable, `id`, `createdAt`, `updatedAt` |\\n| `page`, `pageSize` | `page=2&pageSize=50` | `pageSize` 25 by default, at most 100 |\\n| `fields` | `fields=title,slug` | top-level fields to return (system attributes always come) |\\n| `populate` | `populate=author` | relation paths: `author`, `author,tags.author`, or `*`; at most 3 levels |\\n| `q` | `q=snapshot` | searches the title field chosen in the model's display settings (400 without one) |\\n| `snapshot` | `snapshot=10` | read as of a publication snapshot (below) |\\n| `richText` | `richText=html` | rich-text shape: `json` (default, the document), `html`, or `both` |\\n| `publicationState` | `publicationState=draft` | `published` (default) or `draft`: [drafts mode](#drafts-in-development) |\\n\\n### Filters\\n\\n`filters[<field>][<operator>]=<value>`, combined with `$and`, `$or` and `$not`:\\n\\n```sh\\ncurl -G \\\"$SHAPIO_URL/api/content/articles\\\" -H \\\"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\\\" \\\\\\n --data-urlencode 'filters[publishedOn][$gte]=2026-09-10' \\\\\\n --data-urlencode 'filters[$or][0][slug][$eq]=why-builds-pin-a-snapshot' \\\\\\n --data-urlencode 'filters[$or][1][slug][$eq]=modeling-without-a-deploy'\\n```\\n\\n| Operators | Field types |\\n| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\\n| `$eq`, `$ne`, `$in`, `$nin`, `$null`, `$notNull` | text, numbers, dates, enums, relations and media (booleans: `$eq`, `$ne`, `$null`, `$notNull`); on lists (`many` relations, multiple media/enums) `$eq` means \\\"contains\\\" |\\n| `$lt`, `$lte`, `$gt`, `$gte` | text, numbers, dates and times, on fields marked **filterable** or **sortable** |\\n| `$contains`, `$notContains`, `$containsi`, `$startsWith`, `$endsWith` | text fields marked **filterable** |\\n\\n`id`, `createdAt` and `updatedAt` can be filtered too (`publishedAt` is returned but cannot be filtered or\\nsorted). JSON, rich text, component and dynamic zone fields cannot be filtered. `$in` takes repeated values or `a,b`. Values are read\\nlike stored values, so `2026-10-01T10:00:00+02:00` matches `2026-10-01T08:00:00.000Z`. Unknown fields,\\noperators a type does not support, and filters or sorts on fields your token may not read are refused (400/403)\\nrather than ignored, so a filter can never reveal a hidden value. Relation and media filters see what the\\nresponse shows: a link to an entry that is unpublished, outside the read's locales, or one your token may not\\nread, and a deleted asset, count as not there, so `$null` matches an entry whose links are all hidden.\\n\\n## Snapshots: one consistent moment\\n\\nEvery publish moves a global **publication snapshot** number forward; every published read reports the\\none it read in `meta.snapshot` (a [draft read](#drafts-in-development) reports `null`). Pass `snapshot=N`\\nand Shapio answers exactly as it would have at that moment, even if more was published since:\\n\\n```sh\\ncurl -H \\\"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\\\" \\\"$SHAPIO_URL/api/content/pages?snapshot=10&locale=en\\\"\\n```\\n\\nA static site build should read the current snapshot once at the start and pass it with every request: then\\na publish in the middle of the build cannot produce a site that mixes two moments. Without `snapshot`, which\\nrelated entries a response shows is checked against what is live when it is answered, so an entry published a\\nmoment after `meta.snapshot` may already appear; an unpublished one never does. The\\n[example site](example-site.md) does exactly this, and Shapio's deployment triggers carry the snapshot to build.\\n\\nOld snapshots are read through the current schema (a value stored under an earlier field type comes back as\\n`null`). A [change set](change-sets.md) goes live as one snapshot, schema and entries together, so a pinned\\nbuild sees all of it or none of it.\\n\\nTwo endpoints serve builds, with the same tokens as `/api/content` and only for models the token may read:\\n\\n| Endpoint | Answer |\\n| ------------------------------------------------ | ------------------------------------------------------------------------ |\\n| `GET /api/snapshots/current` | `{ snapshot, schemaVersion, publishedAt }`: what an unpinned read serves |\\n| `GET /api/snapshots/changes?from&to&after&limit` | entries and locales whose live content changed between the two snapshots |\\n\\nDetails, the change classification and incremental-build recipes are in\\n[Snapshots and the changes API](snapshots.md).\\n\\n## Caching\\n\\nResponses carry a strong `ETag` over the exact body and `Vary: Authorization, Cookie, Shapio-Site`. Send\\n`If-None-Match` to get `304 Not Modified` when nothing changed. Anonymous responses are `Cache-Control: public,\\nmax-age=0, must-revalidate`; authenticated ones `private`. A CDN that keys its cache on the URL alone and\\nignores `Vary` must be given the site as `?site=`, not as the header, so that two sites never share a cached\\nresponse.\\n\\n### Next.js cache tags\\n\\nUnder Next.js (detected by `process.env.NEXT_RUNTIME`), `@shapio/client` tags every delivery read for Next's\\ndata cache, so a site refreshes exactly what changed with `revalidateTag`:\\n\\n| Read | Tags |\\n| ---------------------------- | ------------------------------------------------------------------ |\\n| `delivery.list(routeKey)` | `shapio:site:<key>`, `shapio:<routeKey>` |\\n| `delivery.singleton(apiKey)` | `shapio:site:<key>`, `shapio:<apiKey>` |\\n| `delivery.get(routeKey, id)` | `shapio:site:<key>`, `shapio:<routeKey>`, `shapio:<routeKey>:<id>` |\\n| `site.get()` | `shapio:site:<key>` |\\n\\nModels are tagged by their route key (a collection's plural API ID, a singleton's API ID, and for a kind its\\nnested route key, `posts/guides`), as the [snapshot diff](snapshots.md#what-changed-between-two-snapshots)\\nnames them. The site tag is `shapio:site:<key>`\\nwhen the client names a site (`site: 'marketing'`) and `shapio:site` when it leaves the site to the token.\\n`shapioTags.site(key)`, `shapioTags.model(routeKey)` and `shapioTags.entry(routeKey, id)` build the same strings:\\n\\n```ts\\nimport { createClient, shapioTags } from '@shapio/client';\\nimport { revalidateTag } from 'next/cache';\\n\\nconst shapio = createClient({ baseUrl, token, site: 'marketing', next: { cache: 'force-cache' } });\\nconst { data } = await shapio.delivery.get<Article>('articles', id, { locale: 'en' });\\n\\n// In the webhook route, for each entry in the snapshot diff:\\nrevalidateTag(shapioTags.entry('articles', id), { expire: 0 });\\n```\\n\\nA guide is also in the posts list and readable as `posts/<id>`, so when one changes, revalidate the tags of\\nits own route key and of every ancestor's. `shapioTags.changed(routeKey, id)` gives them all:\\n`shapioTags.changed('posts/guides', id)` is `shapio:posts`, `shapio:posts:<id>`, `shapio:posts/guides` and\\n`shapio:posts/guides:<id>`. Webhook payloads carry the same `routeKey`\\n([Webhooks](publishing.md#entry-events)), so any framework can do the same.\\n\\n```ts\\nfor (const entry of diff.items) {\\n for (const tag of shapioTags.changed(entry.routeKey, entry.id)) {\\n revalidateTag(tag, { expire: 0 });\\n }\\n}\\n```\\n\\nThe cache mode is yours: the client sets `cache` and `next.revalidate` only when you ask, with\\n`next: { cache?: 'force-cache' | 'no-store', revalidate?: number | false }` on `createClient` or as the last\\nargument of one read (`delivery.list('articles', query, { next: { revalidate: 60 } })`), which overrides the\\nclient's. A read pinned to a snapshot (`snapshot: N`) never changes, so under Next it is always `force-cache`.\\n`next: false` sends no Next options at all. Outside Next, with no `next` option, the client's requests are\\nexactly as before. Snapshot, preview, GraphQL and admin requests are never cached or tagged, and neither are\\n[drafts mode](#drafts-in-development) reads (`drafts: true`), which under Next are `no-store` whatever the\\n`next` options say. The\\n[Next.js starter](starters.md#incremental-rebuilds) puts it together.\\n\\n## Drafts in development\\n\\nPublishing rebuilds your live site, so it is not how you check a change. **Drafts mode** lets a site's\\ndevelopment server render saved drafts: save in the admin, reload the page on your machine, and the change is\\nthere. Publish still rebuilds your site; drafts mode is for your machine.\\n\\n1. Create a second delivery role for development (or let the starters' `npm run seed` create `<site> dev`)\\n with `read` on the site's models and **Read drafts**. Read drafts always covers every model the role\\n reads; it adds no model of its own.\\n2. Create a token for that role. The tokens list marks it with a **Drafts** chip. Never put it in a production\\n environment.\\n3. Ask for drafts: `publicationState=draft` on `GET /api/content/...` (both routes), `publicationState: DRAFT` in\\n [GraphQL](graphql.md#reading-drafts), or `drafts: true` on `@shapio/client`'s `createClient` and\\n `@shapio/local`'s `createLocalClient`. The [starters](starters.md#drafts-mode) turn it on with\\n `SHAPIO_DRAFTS=true`.\\n\\n```sh\\ncurl -H \\\"Authorization: Bearer $SHAPIO_DEV_DELIVERY_TOKEN\\\" \\\\\\n \\\"$SHAPIO_URL/api/content/articles?publicationState=draft&populate=author\\\"\\n```\\n\\nA draft read serves each entry's saved draft in the delivery shape, entries never published included, with\\n`meta.publicationState: \\\"draft\\\"` and `meta.snapshot: null`. Relation targets and `populate` follow their drafts too, still only for\\nmodels the role may read. Responses are `Cache-Control: private, no-store`, so no cache keeps them, and field\\nusage does not count them. A draft cannot be pinned: `publicationState=draft` with `snapshot` is a 400.\\nPublished reads are unchanged, byte for byte.\\n\\nTwo safeguards keep drafts off your live site. The server only serves drafts to a token whose role grants Read\\ndrafts (403 `DRAFTS_FORBIDDEN` otherwise, with a message naming the grant), and the site must ask for them. A\\nproduction token never carries the grant, so a flag left on by mistake fails loudly instead of showing drafts.\\nAnonymous callers and app users can never read drafts, whatever their roles say. Admin users and admin API\\ntokens can, as they always could in GraphQL.\\n\\nA draft is whatever the admin has saved, autosaves included, and it may not pass validation yet (a required\\nfield can be empty): render drafts defensively. For one entry in the admin's Preview pane, editors use\\n[preview](visual-editing.md) instead; drafts mode is the whole site, for developers.\\n\\n## Writes\\n\\nThe same routes accept `POST`, `PUT` and `DELETE` from app users whose roles allow it (for example, members\\nediting their own entries). Delivery tokens cannot write. See [End users](end-users.md). Editors and your\\nbuild tools use the admin API (`/api/admin/content/<key>`) with admin credentials.\\n\";\n","import { ResourceTemplate } from '@modelcontextprotocol/server';\nimport { serializeDefinition } from '@shapio/schema';\nimport type { ToolContext } from './context.js';\nimport { findDefinition, readSchema } from './definitions.js';\nimport { DELIVERY_API_DOC } from './generated/deliveryApiDoc.js';\n\nexport const SCHEMA_URI_TEMPLATE = 'shapio://schema/{apiKey}';\nexport const DELIVERY_DOC_URI = 'shapio://docs/delivery-api';\n\n/** Each definition in the pull format (`shapio schema pull`'s files), and the delivery API guide. */\nexport const registerResources = ({ server, client }: ToolContext) => {\n server.registerResource(\n 'schema',\n new ResourceTemplate(SCHEMA_URI_TEMPLATE, {\n list: async () => {\n const schema = await readSchema(client);\n return {\n resources: schema.definitions.map(({ definition }) => ({\n uri: `shapio://schema/${definition.apiKey}`,\n name: definition.apiKey,\n title: definition.label,\n mimeType: 'application/json',\n })),\n };\n },\n }),\n {\n title: 'Content type definition',\n description:\n \"A model or component of this server's site (its own or shared with all sites) in the schema file format, with stable IDs.\",\n mimeType: 'application/json',\n },\n async (uri, variables) => {\n const apiKey = String(variables.apiKey);\n const { definition } = findDefinition(await readSchema(client), apiKey);\n return {\n contents: [{ uri: uri.href, mimeType: 'application/json', text: serializeDefinition(definition) }],\n };\n },\n );\n\n server.registerResource(\n 'delivery-api',\n DELIVERY_DOC_URI,\n {\n title: 'Delivery API guide',\n description: 'How sites and apps read published content: routes, filters, locales, snapshots, tokens.',\n mimeType: 'text/markdown',\n },\n (uri) => ({ contents: [{ uri: uri.href, mimeType: 'text/markdown', text: DELIVERY_API_DOC }] }),\n );\n};\n","import type { CallToolResult } from '@modelcontextprotocol/server';\nimport { ShapioApiError } from '@shapio/client';\n\n/** A tool's answer: the API response as pretty JSON text (what agents read best). */\nexport const jsonResult = (value: unknown): CallToolResult => ({\n content: [{ type: 'text', text: JSON.stringify(value ?? null, null, 2) }],\n});\n\n/**\n * A refusal or failure the agent should see and act on (a stale version, a missing permission, validation\n * issues), with Shapio's error code and details. Never thrown: the MCP client gets `isError`.\n */\nexport const errorResult = (error: unknown): CallToolResult => {\n const body =\n error instanceof ShapioApiError\n ? { status: error.status, code: error.code, message: error.message, details: error.details }\n : { code: 'TOOL_FAILED', message: error instanceof Error ? error.message : String(error) };\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ error: body }, null, 2) }] };\n};\n\n/** Runs a tool body, turning any failure into an error result. */\nexport const runTool = async (body: () => Promise<unknown>): Promise<CallToolResult> => {\n try {\n return jsonResult(await body());\n } catch (error) {\n return errorResult(error);\n }\n};\n","import { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { runTool } from './results.js';\n\nconst SET_ID = z.string().min(1).describe('Change set ID');\n\n/**\n * Change sets: the reviewable bundle of schema drafts and entry publications that ships as one snapshot.\n * Agents create and fill them; shipping is a person's call (`change_sets_ship` exists only with\n * --allow-ship, and the server also needs the token's role to hold changes.ship).\n */\nexport const registerChangeSetTools = ({ server, client, options }: ToolContext) => {\n server.registerTool(\n 'change_sets_list',\n {\n title: 'List change sets',\n description: 'Change sets, newest first (every status but discarded unless status is given).',\n inputSchema: z.object({\n status: z\n .string()\n .optional()\n .describe('Comma-separated: open, scheduled, shipping, shipped, failed, discarded'),\n cursor: z.string().optional(),\n limit: z.number().int().positive().max(100).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (query) => runTool(() => client.admin.changeSets.list(query)),\n );\n\n server.registerTool(\n 'change_sets_create',\n {\n title: 'Create a change set',\n description: 'An empty, open change set to collect schema drafts and entry publications for review.',\n inputSchema: z.object({\n title: z.string().min(1).max(200),\n description: z.string().max(5000).optional(),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ title, description }) =>\n runTool(() => client.admin.changeSets.create({ title, ...(description ? { description } : {}) })),\n );\n\n server.registerTool(\n 'change_sets_add_entry',\n {\n title: 'Add an entry to a change set',\n description:\n \"Proposes publishing (or unpublishing) one entry locale when the set ships. The entry's current draft \" +\n 'is what ships; the review shows the field diff against what is live.',\n inputSchema: z.object({\n changeSetId: SET_ID,\n model: z.string().min(1).describe(\"The model's API ID\"),\n entryId: z.string().min(1),\n locale: z.string().optional().describe('Omit for non-localized models and the default locale'),\n action: z.enum(['publish', 'unpublish']).default('publish'),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ changeSetId, model, entryId, locale, action }) =>\n runTool(() =>\n client.admin.changeSets.addEntry(changeSetId, {\n modelKey: model,\n entryId,\n action,\n ...(locale ? { locale } : {}),\n }),\n ),\n );\n\n server.registerTool(\n 'change_sets_review',\n {\n title: 'Review a change set',\n description:\n 'The set (status, version, items) and its review: field diffs per entry, the schema plan per draft ' +\n '(breaking, destructive, conversions), blocking issues, warnings and the consumers of affected fields.',\n inputSchema: z.object({ changeSetId: SET_ID }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ changeSetId }) =>\n runTool(async () => {\n const [set, review] = await Promise.all([\n client.admin.changeSets.get(changeSetId),\n client.admin.changeSets.review(changeSetId),\n ]);\n return { set, review };\n }),\n );\n\n if (options.allowShip) {\n server.registerTool(\n 'change_sets_ship',\n {\n title: 'Ship a change set',\n description:\n 'Makes the set live as one snapshot. Only when the person you work for asked for it. expectedVersion ' +\n 'is the set version change_sets_review showed; breaking or destructive schema changes need the ' +\n \"acknowledgement. Refused when the token's role lacks changes.ship.\",\n inputSchema: z.object({\n changeSetId: SET_ID,\n expectedVersion: z.number().int().nonnegative(),\n acknowledgeBreaking: z.boolean().optional(),\n acknowledgeDestructive: z.boolean().optional(),\n }),\n annotations: { destructiveHint: true, openWorldHint: false },\n },\n ({ changeSetId, ...body }) => runTool(() => client.admin.changeSets.ship(changeSetId, body)),\n );\n }\n};\n","import type { ContentFilter, ContentSort } from '@shapio/client';\nimport { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { findDefinition, readSchema, routeKeyOf } from '../definitions.js';\nimport { runTool } from './results.js';\n\nconst MODEL = z.string().min(1).describe('The model\\'s API ID (singular, e.g. \"article\")');\nconst LOCALE = z.string().min(1).optional().describe('Locale code; the default locale when omitted');\nconst PUBLISHED = z\n .boolean()\n .optional()\n .describe('Read published content through the delivery API instead of drafts (implied by snapshot)');\nconst SNAPSHOT = z\n .number()\n .int()\n .nonnegative()\n .optional()\n .describe('Read published content as of publication snapshot N');\n\nconst QUERY = z.object({\n model: MODEL,\n filters: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n 'Filter tree by field API ID, e.g. { \"title\": { \"$containsi\": \"launch\" } }; $and/$or take lists',\n ),\n sort: z.array(z.object({ field: z.string().min(1), direction: z.enum(['asc', 'desc']) })).optional(),\n page: z.number().int().positive().optional(),\n pageSize: z.number().int().positive().max(100).optional(),\n q: z.string().optional().describe('Free-text search over the title field'),\n locale: LOCALE,\n fields: z.array(z.string()).optional().describe('Top-level fields to return'),\n populate: z.array(z.string()).optional().describe('Relation paths to expand, e.g. \"author\"'),\n status: z\n .enum(['draft', 'published', 'modified'])\n .optional()\n .describe('Drafts only: entries in this status'),\n published: PUBLISHED,\n snapshot: SNAPSHOT,\n});\n\nconst DATA = z\n .record(z.string(), z.unknown())\n .describe(\"Field values by API ID. Rich text is Shapio's JSON document format, never HTML.\");\n\n/** Entries: query and read (drafts or published), create and update drafts. Never publishes. */\nexport const registerContentTools = ({ server, client }: ToolContext) => {\n server.registerTool(\n 'content_query',\n {\n title: 'Query entries',\n description:\n \"A page of a model's entries. Drafts by default (what editors see); published=true or snapshot=N reads \" +\n 'what the delivery API serves.',\n inputSchema: QUERY,\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, published, snapshot, status, filters, sort, ...rest }) =>\n runTool(async () => {\n const query = {\n ...rest,\n ...(filters ? { filters: filters as ContentFilter } : {}),\n ...(sort ? { sort: sort as ContentSort[] } : {}),\n };\n if (published || snapshot !== undefined) {\n const schema = await readSchema(client);\n const { definition } = findDefinition(schema, model);\n const routeKey = routeKeyOf(schema, definition);\n return definition.kind === 'singleton'\n ? client.delivery.singleton(routeKey, {\n ...(rest.locale ? { locale: rest.locale } : {}),\n ...(snapshot !== undefined ? { snapshot } : {}),\n })\n : client.delivery.list(routeKey, { ...query, ...(snapshot !== undefined ? { snapshot } : {}) });\n }\n return client.admin.content.list(model, { ...query, ...(status ? { status } : {}) });\n }),\n );\n\n server.registerTool(\n 'content_get',\n {\n title: 'Read an entry',\n description:\n \"One entry: its draft with every locale's status and version (send version back as expectedVersion \" +\n 'to content_update), or the published version when published=true or snapshot=N.',\n inputSchema: z.object({\n model: MODEL,\n id: z.string().min(1),\n locale: LOCALE,\n published: PUBLISHED,\n snapshot: SNAPSHOT,\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, id, locale, published, snapshot }) =>\n runTool(async () => {\n if (published || snapshot !== undefined) {\n const schema = await readSchema(client);\n const { definition } = findDefinition(schema, model);\n return client.delivery.get(routeKeyOf(schema, definition), id, {\n ...(locale ? { locale } : {}),\n ...(snapshot !== undefined ? { snapshot } : {}),\n });\n }\n return client.admin.content.get(model, id, locale ? { locale } : {});\n }),\n );\n\n server.registerTool(\n 'content_create',\n {\n title: 'Create a draft entry',\n description:\n 'Creates an entry as a draft (never published). Validated by the server against the live model. ' +\n 'Add it to a change set with change_sets_add_entry to propose publishing it.',\n inputSchema: z.object({ model: MODEL, data: DATA, locale: LOCALE }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ model, data, locale }) =>\n runTool(() => client.admin.content.create(model, { data, ...(locale ? { locale } : {}) })),\n );\n\n server.registerTool(\n 'content_update',\n {\n title: 'Update a draft entry',\n description:\n \"Saves changed fields into one locale's draft (absent fields are kept, null clears). expectedVersion is \" +\n 'the version content_get returned; a stale one is refused (CONTENT_VERSION_CONFLICT): read again and redo. ' +\n 'Pass expectedVersion null to start a new locale of the entry.',\n inputSchema: z.object({\n model: MODEL,\n id: z.string().min(1),\n data: DATA,\n expectedVersion: z.number().int().nonnegative().nullable(),\n locale: LOCALE,\n }),\n annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: false },\n },\n ({ model, id, data, expectedVersion, locale }) =>\n runTool(() =>\n client.admin.content.update(model, id, { data, expectedVersion, ...(locale ? { locale } : {}) }),\n ),\n );\n};\n","import { HEALTH_RULES } from '@shapio/client';\nimport { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { findDefinition, readSchema } from '../definitions.js';\nimport { runTool } from './results.js';\n\n/** Checks and signals: pre-flight for one entry, content health findings, field usage by consumers. */\nexport const registerInsightTools = ({ server, client }: ToolContext) => {\n server.registerTool(\n 'preflight_run',\n {\n title: 'Pre-flight an entry',\n description:\n 'What publishing these locales of an entry would run into (validation, missing locales, references, ' +\n 'alt text), without publishing anything.',\n inputSchema: z.object({\n model: z.string().min(1).describe(\"The model's API ID\"),\n id: z.string().min(1),\n locales: z.array(z.string()).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, id, locales }) =>\n runTool(() => client.admin.preflight.run(model, id, locales ? { locales } : {})),\n );\n\n server.registerTool(\n 'health_list',\n {\n title: 'List content health findings',\n description: `Open findings of the content health rules (${HEALTH_RULES.join(', ')}), by rule and model.`,\n inputSchema: z.object({\n rule: z.enum(HEALTH_RULES).optional(),\n model: z.string().optional().describe(\"The model's API ID\"),\n cursor: z.string().optional(),\n limit: z.number().int().positive().max(100).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, ...query }) =>\n runTool(() => client.admin.contentHealth.list({ ...query, ...(model ? { modelKey: model } : {}) })),\n );\n\n server.registerTool(\n 'usage_fields',\n {\n title: 'Field usage',\n description:\n 'Which fields of a model sites and apps read, per API token, over the last N days: check before ' +\n 'renaming or removing a field.',\n inputSchema: z.object({\n model: z.string().min(1).describe(\"The model's API ID\"),\n days: z.number().int().positive().max(365).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, days }) =>\n runTool(async () => {\n const { definition } = findDefinition(await readSchema(client), model);\n return client.admin.usage.fields({ modelId: definition.id, ...(days ? { days } : {}) });\n }),\n );\n};\n","import { realpath } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { uploadFile } from '@shapio/client/node';\nimport { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { runTool } from './results.js';\n\n/**\n * The file a `media_upload` may read: inside the media root after resolving symlinks, so an agent (or text\n * injected into what it reads) cannot have the server read ~/.ssh or anything else outside the project.\n */\nexport const resolveMediaPath = async (mediaRoot: string, path: string): Promise<string> => {\n const root = await realpath(mediaRoot);\n const target = await realpath(resolve(root, path)).catch(() => {\n throw new Error(`No file at ${path} (relative to the media root ${root})`);\n });\n const inside = relative(root, target);\n if (inside === '' || inside.startsWith('..') || isAbsolute(inside)) {\n throw new Error(\n `media_upload only reads files under the media root (${root}); start the server with --media-root to change it`,\n );\n }\n return target;\n};\n\n/** The media library: browse, and upload files from the media root. */\nexport const registerMediaTools = ({ server, client, options }: ToolContext) => {\n server.registerTool(\n 'media_list',\n {\n title: 'List media',\n description: 'A page of media assets (ID, filename, type, alt text, visibility, URLs), newest first.',\n inputSchema: z.object({\n folder: z.string().optional().describe('A folder ID, or \"root\" for assets in no folder'),\n mimeType: z.string().optional().describe('\"image/png\" or \"image/*\"'),\n search: z.string().optional(),\n cursor: z.string().optional().describe('nextCursor of the previous page'),\n limit: z.number().int().positive().max(100).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (query) => runTool(() => client.admin.media.assets.list(query)),\n );\n\n server.registerTool(\n 'media_upload',\n {\n title: 'Upload a file',\n description:\n `Uploads a local file into the media library and returns the asset (use its ID in media fields). Only ` +\n `files under ${options.mediaRoot} can be read. Media is stored at once (it is not part of change sets).`,\n inputSchema: z.object({\n path: z.string().min(1).describe('Path relative to the media root (or absolute, inside it)'),\n alt: z.string().max(1000).optional().describe('Alt text for images'),\n mimeType: z.string().optional().describe('Needed when the extension is not a common one'),\n folderId: z.string().optional(),\n visibility: z.enum(['public', 'private']).optional(),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ path, alt, mimeType, folderId, visibility }) =>\n runTool(async () => {\n const file = await resolveMediaPath(options.mediaRoot, path);\n const asset = await uploadFile(client, {\n baseUrl: options.baseUrl,\n path: file,\n ...(mimeType ? { mimeType } : {}),\n ...(folderId ? { folderId } : {}),\n ...(visibility ? { visibility } : {}),\n });\n return alt\n ? client.admin.media.assets.update(asset.id, { expectedVersion: asset.version, alt })\n : asset;\n }),\n );\n};\n","import { randomUUID } from 'node:crypto';\nimport type { ChangeSet, ChangeSetSchemaItem, ShapioClient } from '@shapio/client';\nimport { isStableId, type DefinitionInput } from '@shapio/schema';\nimport { readSchema } from './definitions.js';\n\n/** A definition as an agent writes it: the authored format, IDs optional, references by API ID allowed. */\nexport type DraftDefinition = Record<string, unknown> & { apiKey?: unknown; kind?: unknown; id?: unknown };\n\ntype FieldLike = { type?: unknown; settings?: Record<string, unknown> };\n\nconst schemaItemsOf = (set: ChangeSet) =>\n set.items.filter((item): item is ChangeSetSchemaItem => item.kind === 'schema');\n\n/**\n * Relation targets, component references and `extends` (the parent type) are stable IDs. Agents think in API\n * IDs, so a reference that is not an ID is looked up among the active definitions and the set's drafts (and\n * this definition itself, for a self-relation); anything unknown is left for the server to report.\n */\nconst resolveReferences = (definition: DraftDefinition, idsByApiKey: ReadonlyMap<string, string>) => {\n const resolve = (value: unknown) =>\n typeof value === 'string' && !isStableId(value) ? (idsByApiKey.get(value) ?? value) : value;\n const fields = Array.isArray(definition.fields) ? (definition.fields as FieldLike[]) : [];\n return {\n ...definition,\n ...(definition.extends === undefined ? {} : { extends: resolve(definition.extends) }),\n fields: fields.map((field) => {\n const settings = field.settings;\n if (!settings) {\n return field;\n }\n if (field.type === 'relation') {\n return { ...field, settings: { ...settings, target: resolve(settings.target) } };\n }\n if (field.type === 'component') {\n return { ...field, settings: { ...settings, component: resolve(settings.component) } };\n }\n if (field.type === 'dynamiczone' && Array.isArray(settings.components)) {\n return { ...field, settings: { ...settings, components: settings.components.map(resolve) } };\n }\n return field;\n }),\n };\n};\n\n/**\n * Puts a definition into a change set as a draft: picks its stable ID (the active definition's, the set's\n * existing draft's, or a new one), its base version and the draft version to replace, then lets the server\n * parse, validate and plan it. Nothing goes live until a person ships the set. The site's view decides what\n * exists: a new definition belongs to the site, or is shared with all sites when `shared` (creates only; an\n * existing definition keeps its scope).\n */\nexport const putDefinitionDraft = async (\n client: ShapioClient,\n changeSetId: string,\n definition: DraftDefinition,\n { shared = false }: { shared?: boolean } = {},\n) => {\n if (typeof definition.apiKey !== 'string' || typeof definition.kind !== 'string') {\n throw new Error('A definition needs at least \"kind\" (collection, singleton or component) and \"apiKey\"');\n }\n const [schema, set] = await Promise.all([readSchema(client), client.admin.changeSets.get(changeSetId)]);\n const drafts = schemaItemsOf(set);\n const apiKey = definition.apiKey;\n const explicitId = typeof definition.id === 'string' ? definition.id : undefined;\n const active = schema.definitions.find(\n ({ definition: candidate }) =>\n candidate.id === explicitId || (!explicitId && candidate.apiKey === apiKey),\n );\n const drafted = drafts.find(\n (item) => item.definitionId === explicitId || (!explicitId && item.apiKey === apiKey),\n );\n const id = explicitId ?? active?.definition.id ?? drafted?.definitionId ?? randomUUID();\n const idsByApiKey = new Map<string, string>([\n ...schema.definitions.map(({ definition: candidate }) => [candidate.apiKey, candidate.id] as const),\n ...drafts.map((item) => [item.apiKey, item.definitionId] as const),\n [apiKey, id],\n ]);\n const payload = resolveReferences({ ...definition, id }, idsByApiKey) as unknown as DefinitionInput;\n const draft = await client.admin.changeSets.putSchemaDraft(changeSetId, id, {\n category: definition.kind === 'component' ? 'component' : 'model',\n definition: payload,\n baseVersion: active?.version ?? null,\n ...(!active && shared ? { shared: true } : {}),\n ...(drafted ? { expectedDraftVersion: drafted.draftVersion } : {}),\n });\n return { changeSetId, definitionId: id, operation: draft.operation, draftVersion: draft.version, draft };\n};\n","import { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { describeDefinition, findDefinition, readSchema, summarizer } from '../definitions.js';\nimport { putDefinitionDraft } from '../schemaDrafts.js';\nimport { runTool } from './results.js';\n\nconst READ_ONLY = { readOnlyHint: true, openWorldHint: false } as const;\n\nconst SHARED_DESCRIPTION =\n 'For a new definition only: true shares it with all sites (needs schema permission on every site). ' +\n \"Default false: it belongs to this server's site. An existing definition keeps its scope.\";\n\nconst DEFINITION_DESCRIPTION =\n 'A whole definition in Shapio\\'s authored format: { kind: \"collection\" | \"singleton\" | \"component\", apiKey, ' +\n 'label, pluralApiKey? (collections), extends? (collections), localized?, draftAndPublish?, fields: [{ apiKey, ' +\n 'label, type, required?, localized?, settings? }] }. IDs are optional: an existing definition keeps its IDs ' +\n '(matched by API ID). Relation targets, component references and extends may be given as API IDs. A ' +\n 'collection with extends inherits every field of that collection: list only its own fields, never an ' +\n \"inherited one, and give it the parent's localized and draftAndPublish. Read shapio://schema/{apiKey} to \" +\n 'see the format of an existing one.';\n\n/** The schema: list and read definitions, and draft changes into a change set (never activated here). */\nexport const registerSchemaTools = ({ server, client }: ToolContext) => {\n server.registerTool(\n 'schema_list',\n {\n title: 'List content types',\n description:\n \"Every model (collection, singleton) and component of this server's site (its own and the ones shared \" +\n 'with all sites) with its API IDs, scope, version and fields (types only). A collection that extends ' +\n 'another names it in extends and lists the inherited fields first, each with inheritedFrom. Delivery ' +\n \"reads use routeKey for collections (the plural API ID, nested under the parent's: posts/guides).\",\n inputSchema: z.object({}),\n annotations: READ_ONLY,\n },\n () =>\n runTool(async () => {\n const schema = await readSchema(client);\n return {\n schemaVersion: schema.schemaVersion,\n definitions: schema.definitions.map(summarizer(schema)),\n };\n }),\n );\n\n server.registerTool(\n 'schema_get',\n {\n title: 'Read a content type',\n description:\n 'One definition in full (fields with settings), by API ID or stable ID, with its version. A collection ' +\n 'that extends another holds only its own fields; extendsApiKey, routeKey and inheritedFields describe ' +\n 'what it inherits.',\n inputSchema: z.object({\n apiKey: z.string().min(1).describe('API ID (or stable ID) of the definition'),\n }),\n annotations: READ_ONLY,\n },\n ({ apiKey }) =>\n runTool(async () => {\n const schema = await readSchema(client);\n return describeDefinition(schema, findDefinition(schema, apiKey));\n }),\n );\n\n server.registerTool(\n 'schema_draft',\n {\n title: 'Draft a content type change',\n description:\n 'Proposes a new or changed definition as a draft in a change set (a new set when changeSetId is ' +\n 'omitted). Nothing changes on the live schema until a person reviews and ships the set. Returns the ' +\n 'change set ID; read change_sets_review next.',\n inputSchema: z.object({\n definition: z.record(z.string(), z.unknown()).describe(DEFINITION_DESCRIPTION),\n changeSetId: z.string().optional().describe('An open change set; omit to start a new one'),\n title: z.string().min(1).max(200).optional().describe('Title of the new change set'),\n shared: z.boolean().optional().describe(SHARED_DESCRIPTION),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ definition, changeSetId, title, shared }) =>\n runTool(async () => {\n const apiKey = typeof definition.apiKey === 'string' ? definition.apiKey : 'draft';\n const setId =\n changeSetId ?? (await client.admin.changeSets.create({ title: title ?? `Schema: ${apiKey}` })).id;\n return putDefinitionDraft(client, setId, definition, { shared: shared ?? false });\n }),\n );\n\n server.registerTool(\n 'change_sets_add_schema_draft',\n {\n title: 'Add a schema draft to a change set',\n description:\n \"Puts a new or changed definition into an existing change set as a draft (replaces this set's \" +\n 'earlier draft of the same definition). Ships only when a person ships the set.',\n inputSchema: z.object({\n changeSetId: z.string().min(1),\n definition: z.record(z.string(), z.unknown()).describe(DEFINITION_DESCRIPTION),\n shared: z.boolean().optional().describe(SHARED_DESCRIPTION),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ changeSetId, definition, shared }) =>\n runTool(() => putDefinitionDraft(client, changeSetId, definition, { shared: shared ?? false })),\n );\n};\n","import { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { runTool } from './results.js';\n\n/** Publication snapshots: the ledger, the diff between two, and restore (as a reviewable change set). */\nexport const registerSnapshotTools = ({ server, client }: ToolContext) => {\n server.registerTool(\n 'snapshots_list',\n {\n title: 'List snapshots',\n description: 'The publication ledger, newest first: what went live, when, why and by whom.',\n inputSchema: z.object({\n cursor: z.string().optional(),\n limit: z.number().int().positive().max(100).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (query) => runTool(() => client.admin.snapshots.list(query)),\n );\n\n server.registerTool(\n 'snapshots_changes',\n {\n title: 'Changes between snapshots',\n description:\n 'Which entries were published, updated or unpublished between snapshot \"from\" and \"to\" (default: now). ' +\n 'Names entries, never values.',\n inputSchema: z.object({\n from: z.number().int().nonnegative(),\n to: z.number().int().nonnegative().optional(),\n after: z.string().optional().describe('nextCursor of the previous page'),\n limit: z.number().int().positive().max(500).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (query) => runTool(() => client.snapshots.changes(query)),\n );\n\n server.registerTool(\n 'snapshots_restore',\n {\n title: 'Propose restoring a snapshot',\n description:\n 'Creates an open change set that would bring live content back to what snapshot N served. Nothing ' +\n 'changes until a person ships it; the schema is never rolled back.',\n inputSchema: z.object({ seq: z.number().int().positive() }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ seq }) => runTool(() => client.admin.snapshots.restore(seq)),\n );\n};\n","import { McpServer } from '@modelcontextprotocol/server';\nimport { createClient, type ShapioClient } from '@shapio/client';\nimport packageJson from '../package.json' with { type: 'json' };\nimport type { ToolContext } from './context.js';\nimport type { McpOptions } from './options.js';\nimport { registerPrompts } from './prompts.js';\nimport { registerResources } from './resources.js';\nimport { registerChangeSetTools } from './tools/changeSets.js';\nimport { registerContentTools } from './tools/content.js';\nimport { registerInsightTools } from './tools/insights.js';\nimport { registerMediaTools } from './tools/media.js';\nimport { registerSchemaTools } from './tools/schema.js';\nimport { registerSnapshotTools } from './tools/snapshots.js';\n\nexport const MCP_SERVER_NAME = 'shapio';\nexport const MCP_SERVER_VERSION: string = packageJson.version;\n\nconst INSTRUCTIONS =\n 'Shapio is a headless CMS whose content types are live data. Agents propose, people ship: draft schema ' +\n 'changes and entries, collect them in a change set, and leave shipping to a person. Model and field ' +\n 'identifiers are called \"API IDs\". Read shapio://docs/delivery-api for how sites read content.';\n\n/** The MCP server for one Shapio instance, with every tool, resource and prompt registered. */\nexport const createShapioMcpServer = (\n options: McpOptions,\n client: ShapioClient = createClient({\n baseUrl: options.baseUrl,\n token: options.token,\n ...(options.site !== undefined ? { site: options.site } : {}),\n }),\n): McpServer => {\n const server = new McpServer(\n { name: MCP_SERVER_NAME, version: MCP_SERVER_VERSION },\n { instructions: INSTRUCTIONS },\n );\n const context: ToolContext = { server, client, options };\n registerSchemaTools(context);\n registerContentTools(context);\n registerMediaTools(context);\n registerInsightTools(context);\n registerChangeSetTools(context);\n registerSnapshotTools(context);\n registerResources(context);\n registerPrompts(context);\n return server;\n};\n","import { resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\n/** How the server was started: the Shapio instance, its token, and the two guards. */\nexport type McpOptions = {\n /** Shapio's base URL including any BASE_PATH (`SHAPIO_URL`). */\n baseUrl: string;\n /** An admin-scope API token (`SHAPIO_TOKEN`). Give its role no `changes.ship`: shipping stays with people. */\n token: string;\n /** Registers `change_sets_ship` (`--allow-ship`). A client-side guard only: the token's role decides. */\n allowShip: boolean;\n /** `media_upload` reads files only under this directory (`--media-root`, default: the working directory). */\n mediaRoot: string;\n /**\n * The site key every request is about (`--site`, else `SHAPIO_SITE`), on multi-site instances. Left out:\n * the token's site (a site token), else the primary site. A site token cannot name another site.\n */\n site?: string;\n};\n\n/** A site key as Shapio creates them (sites are named by key in URLs, `?site=` and config). */\nconst SITE_KEY = /^[a-z][a-z0-9-]{0,62}$/;\n\nexport const USAGE = `Usage: shapio-mcp [--allow-ship] [--media-root <dir>] [--site <key>]\n\nA Model Context Protocol server (stdio) for a Shapio instance.\n\nEnvironment:\n SHAPIO_URL Shapio's base URL, including any BASE_PATH (e.g. https://cms.example.com)\n SHAPIO_TOKEN An admin-scope API token. Give its role no \"changes.ship\" permission: agents propose\n change sets, people ship them.\n SHAPIO_SITE The site key to work on (multi-site instances); --site overrides it. Default: the\n token's site, else the primary site.\n\nOptions:\n --allow-ship Offer the change_sets_ship tool (the token's role must also allow changes.ship)\n --media-root <dir> Directory media_upload may read files from (default: the working directory)\n --site <key> The site to work on (overrides SHAPIO_SITE)\n -h, --help Show this help\n`;\n\nexport class OptionsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'OptionsError';\n }\n}\n\n/** Reads the command line and environment; throws OptionsError with a message meant for stderr. */\nexport const parseOptions = (\n argv: readonly string[],\n env: Readonly<Record<string, string | undefined>>,\n cwd: string,\n): McpOptions | 'help' => {\n let parsed;\n try {\n parsed = parseArgs({\n args: [...argv],\n options: {\n 'allow-ship': { type: 'boolean', default: false },\n 'media-root': { type: 'string' },\n site: { type: 'string' },\n help: { type: 'boolean', short: 'h', default: false },\n },\n strict: true,\n allowPositionals: false,\n });\n } catch (error) {\n throw new OptionsError(error instanceof Error ? error.message : String(error));\n }\n if (parsed.values.help) {\n return 'help';\n }\n const baseUrl = env.SHAPIO_URL?.trim();\n const token = env.SHAPIO_TOKEN?.trim();\n if (!baseUrl || !/^https?:\\/\\//.test(baseUrl)) {\n throw new OptionsError('Set SHAPIO_URL to the Shapio base URL (http:// or https://).');\n }\n if (!token) {\n throw new OptionsError('Set SHAPIO_TOKEN to an admin-scope API token.');\n }\n const site = (parsed.values.site ?? env.SHAPIO_SITE)?.trim() || undefined;\n if (site !== undefined && !SITE_KEY.test(site)) {\n throw new OptionsError(`\"${site}\" is not a site key (lower case, starting with a letter).`);\n }\n return {\n baseUrl: baseUrl.replace(/\\/+$/, ''),\n token,\n allowShip: parsed.values['allow-ship'],\n mediaRoot: resolve(cwd, parsed.values['media-root'] ?? '.'),\n ...(site !== undefined ? { site } : {}),\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shapio/mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.11",
|
|
4
4
|
"description": "MCP server for Shapio: lets coding agents model content, write drafts and open change sets for review.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://shapio.dev",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@modelcontextprotocol/server": "2.2.0",
|
|
34
|
-
"@shapio/client": "0.5.
|
|
35
|
-
"@shapio/schema": "0.5.
|
|
34
|
+
"@shapio/client": "0.5.11",
|
|
35
|
+
"@shapio/schema": "0.5.11",
|
|
36
36
|
"zod": "4.6.5"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"options-C2L1ym2L.js","names":["routeKeyOf","modelRouteKeyOf","routeKeyOf","packageJson.version"],"sources":["../package.json","../src/prompts.ts","../src/definitions.ts","../src/generated/deliveryApiDoc.ts","../src/resources.ts","../src/tools/results.ts","../src/tools/changeSets.ts","../src/tools/content.ts","../src/tools/insights.ts","../src/tools/media.ts","../src/schemaDrafts.ts","../src/tools/schema.ts","../src/tools/snapshots.ts","../src/server.ts","../src/options.ts"],"sourcesContent":["","import { z } from 'zod';\nimport type { ToolContext } from './context.js';\n\nconst userMessage = (text: string) => ({\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text } }],\n});\n\n/** Starting points for the two jobs agents do most: modelling and reviewing a change set. */\nexport const registerPrompts = ({ server }: ToolContext) => {\n server.registerPrompt(\n 'model_content_type',\n {\n title: 'Model a content type for …',\n description: 'Design a content type for something and draft it into a change set for review.',\n argsSchema: z.object({\n subject: z.string().min(1).describe('What the content type is for, e.g. \"a blog\"'),\n }),\n },\n ({ subject }) =>\n userMessage(\n `Model content types in Shapio for ${subject}.\\n\\n` +\n '1. Call schema_list and read the existing types first; reuse or extend them instead of duplicating.\\n' +\n '2. Design the types: API IDs are camelCase GraphQL names, labels are for people; prefer components ' +\n 'for repeated groups and relations for shared entities. When several kinds of one thing share ' +\n 'fields (products that are digital or physical), make one collection and let each kind extend it ' +\n '(extends), listing only its own fields.\\n' +\n '3. Draft each type with schema_draft (one change set for the whole design: pass the changeSetId ' +\n 'from the first call to the rest).\\n' +\n '4. Read change_sets_review, fix every blocking issue, and summarise the design and the review for ' +\n 'me. Do not ship: I review and ship the change set myself.',\n ),\n );\n\n server.registerPrompt(\n 'review_change_set',\n {\n title: 'Review change set N',\n description: 'Explain what a change set would do when it ships, and what to check first.',\n argsSchema: z.object({ changeSetId: z.string().min(1).describe('The change set ID') }),\n },\n ({ changeSetId }) =>\n userMessage(\n `Review Shapio change set ${changeSetId}. Call change_sets_review and tell me, briefly:\\n` +\n '- what goes live (schema changes and entries, with the field diffs that matter);\\n' +\n '- anything breaking or destructive, and which API consumers read the affected fields ' +\n '(usage_fields);\\n' +\n '- blocking issues and warnings, and how to fix each;\\n' +\n '- whether you would ship it. Do not ship it yourself.',\n ),\n );\n};\n","import type { ShapioClient } from '@shapio/client';\nimport {\n createDefinitionIndex,\n isModelDefinition,\n routeKeyOf as modelRouteKeyOf,\n routePathOf,\n type DefinitionIndex,\n type ModelDefinition,\n type SchemaDefinition,\n} from '@shapio/schema';\n\ntype Exported = Awaited<ReturnType<ShapioClient['admin']['schema']['export']>>;\nexport type ExportedDefinition = Exported['definitions'][number];\n\n/**\n * The site's active schema (the pull format): its own definitions and the shared ones, for the site the\n * client names (`--site`, else the token's site, else the primary). Read fresh for each call: models change live.\n */\nexport const readSchema = (client: ShapioClient): Promise<Exported> => client.admin.schema.export();\n\n/** An active definition by API ID (or stable ID); throws a message the agent can act on. */\nexport const findDefinition = (schema: Exported, key: string): ExportedDefinition => {\n const found = schema.definitions.find(\n ({ definition }) => definition.apiKey === key || definition.id === key,\n );\n if (!found) {\n const known = schema.definitions.map(({ definition }) => definition.apiKey).join(', ') || '(none)';\n throw new Error(`No model or component with API ID \"${key}\". Known: ${known}`);\n }\n return found;\n};\n\n/**\n * Inheritance lookups over the site's view: a type that extends another stores only its own fields, so\n * anything that describes what a type holds reads the effective fields (the parent's first) through this.\n */\nexport const indexSchema = (schema: Exported): DefinitionIndex =>\n createDefinitionIndex(schema.definitions.map(({ definition }) => definition));\n\n/** The API ID of a definition in the view, else its ID. */\nconst apiKeyIn = (index: DefinitionIndex, id: string) => index.get(id)?.apiKey ?? id;\n\n/**\n * The delivery route key: a collection's plural API ID (`articles`), a singleton's API ID, and for a type that\n * extends another its ancestors' plural API IDs and its own (`posts/guides`). The admin content API uses\n * the singular API ID instead. Components have no route.\n */\nexport const routeKeyOf = (schema: Exported, definition: SchemaDefinition): string => {\n if (!isModelDefinition(definition)) {\n throw new Error(\n `\"${definition.apiKey}\" is a component: components are read through the models that use them`,\n );\n }\n const index = indexSchema(schema);\n const ancestors = index\n .ancestors(definition.id)\n .map((id) => index.get(id))\n .filter((ancestor): ancestor is ModelDefinition => ancestor !== undefined && isModelDefinition(ancestor));\n return routePathOf(definition, ancestors);\n};\n\n/**\n * The fields a model has from the types it extends (none for a root or a component), each naming the type\n * that declares it. The definition itself (the schema file format) holds only its own fields.\n */\nexport const inheritedFieldsOf = (index: DefinitionIndex, definition: SchemaDefinition) =>\n (index.resolve(definition.id)?.fields ?? []).flatMap((field) =>\n field.inheritedFrom === undefined\n ? []\n : [\n {\n apiKey: field.apiKey,\n type: field.type,\n required: field.required,\n localized: field.localized,\n inheritedFrom: apiKeyIn(index, field.inheritedFrom),\n },\n ],\n );\n\n/**\n * One definition as `schema_get` returns it: the stored definition (send it back to draft a change), and for a\n * type that extends another the parent's API ID, its delivery route and the fields it inherits.\n */\nexport const describeDefinition = (schema: Exported, found: ExportedDefinition) => {\n const { definition } = found;\n if (!isModelDefinition(definition) || definition.extends === undefined) {\n return found;\n }\n const index = indexSchema(schema);\n return {\n ...found,\n extendsApiKey: apiKeyIn(index, definition.extends),\n routeKey: routeKeyOf(schema, definition),\n inheritedFields: inheritedFieldsOf(index, definition),\n };\n};\n\n/**\n * A one-line summary per definition, for listings that should not carry every field's settings. Fields are\n * the effective ones: a type that extends another lists its parent's first, each with `inheritedFrom`.\n */\nexport const summarizer = (schema: Exported) => {\n const index = indexSchema(schema);\n return ({ definition, version, site }: ExportedDefinition) => {\n const parent = isModelDefinition(definition) ? definition.extends : undefined;\n return {\n id: definition.id,\n /** `shared` (every site), or the key of the site it belongs to. */\n scope: site === null ? 'shared' : site,\n kind: definition.kind,\n apiKey: definition.apiKey,\n ...(definition.kind === 'collection'\n ? { pluralApiKey: modelRouteKeyOf(definition), routeKey: routeKeyOf(schema, definition) }\n : {}),\n ...(parent === undefined ? {} : { extends: apiKeyIn(index, parent) }),\n label: definition.label,\n version,\n fields: [\n ...inheritedFieldsOf(index, definition),\n ...definition.fields.map((field) => ({\n apiKey: field.apiKey,\n type: field.type,\n required: field.required,\n localized: field.localized,\n })),\n ],\n };\n };\n};\n","// Generated by scripts/embedDocs.mjs from documentation/delivery-api.md. Do not edit.\nexport const DELIVERY_API_DOC = \"# Delivery API (REST)\\n\\nYour sites and apps read published content from one generic route per model:\\n\\n```text\\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<plural API ID> # a collection: a page of entries\\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<plural API ID>/<entry id> # a collection: one entry\\nGET {PUBLIC_URL}{BASE_PATH}/api/content/<API ID> # a single type: its entry\\n```\\n\\nA type that extends another is read under its parent's route, by its own plural API ID:\\n`/api/content/posts/guides` and `/api/content/posts/guides/<entry id>` ([below](#types-that-extend-a-type)).\\n\\nA collection is addressed by its **plural API ID** (`/api/content/articles`), a single type by its **API ID**\\n(`/api/content/homepage`). The singular API ID of a collection is not a delivery route: `/api/content/article`\\nis a 404. Both IDs are set on the model ([Modelling](modelling.md#kinds-of-definitions)). The admin content API\\n(`/api/admin/content/<API ID>`) always uses the singular.\\n\\nThere is no generated code per model: a model created a minute ago is served by the same routes at once.\\nDelivery returns **published** content only; drafts, unpublished entries, private media and fields your token\\nmay not read never appear. The one exception is [drafts mode](#drafts-in-development), for a site's\\ndevelopment server, with a token granted Read drafts. The [REST reference](reference/rest-api.md) lists every read parameter (app-user\\nwrites are described in [End users](end-users.md)); your instance's own OpenAPI document, for its current\\nschema, is at `/api/docs` (admins).\\n\\nThe examples below use the [example site](example-site.md)'s models.\\n\\n## Tokens\\n\\nCreate a **delivery role** (Network → Roles → New role, kind _Delivery_) with `read` on the models your site\\nneeds, then an API token bound to it (Settings → API tokens). Send it as a bearer token:\\n\\n```sh\\ncurl -H \\\"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\\\" \\\"$SHAPIO_URL/api/content/articles?locale=fr\\\"\\n```\\n\\n- Delivery tokens can only ever read, whatever their role says.\\n- A role's grant covers every field marked `public` (the default); `public: false` fields are hidden unless the\\n grant names them. In [GraphQL](graphql.md#what-each-caller-sees) hidden fields are not even in the token's\\n schema.\\n- Without a token, a request is _anonymous_: it gets what the built-in **public** app role grants, which is\\n nothing until you allow it (Network → Roles → App roles). Use that for content anyone may read from the\\n browser. App users' own tokens are covered in [End users](end-users.md).\\n\\n## Sites\\n\\nOne Shapio instance can host several [sites](sites.md), each with its own content types (plus those shared with\\nall sites), content, tokens and snapshots. A content type of another site is unknown on this one (`404`). Every\\ndelivery request reads exactly one site:\\n\\n1. **The token's site.** Delivery tokens belong to the site they were created on, and only ever read it.\\n2. **The site the request names**, with `?site=<key>` or the `Shapio-Site: <key>` header. Anonymous callers and\\n network admin tokens use this to pick a site.\\n3. **The primary site**, when neither names one. An instance with a single site never needs to name it.\\n\\nNaming a different site than the token's is refused with `403 SITE_MISMATCH`, never redirected, and so is a\\nrequest whose header and `?site=` disagree. An unknown key is `404 SITE_NOT_FOUND`. Entries of another site\\nread as not found, and relations never cross sites.\\n\\n```sh\\ncurl \\\"$SHAPIO_URL/api/content/articles?site=marketing\\\"\\n```\\n\\nAnonymous reads get what the app roles bound to the request site's `public` audience grant. The primary site\\nbinds the built-in **public** role; a new site binds none, so it serves nothing anonymously until you bind one\\n([Sites](sites.md#app-users-and-anonymous-access)).\\n\\nWith `@shapio/client`, pass the site once:\\n\\n```ts\\nconst shapio = createClient({ baseUrl: process.env.SHAPIO_URL, token, site: 'marketing' });\\n```\\n\\nThe client sends it as `?site=` on delivery and snapshot reads, which keeps a cross-origin `GET` free of a\\nCORS preflight and keeps URL-keyed caches apart. Every other request (writes, GraphQL, the admin API) carries\\nthe `Shapio-Site` header. Leave `site` out to read the token's site, or the primary site.\\n\\n## Responses\\n\\n```json\\n{\\n \\\"data\\\": [\\n {\\n \\\"id\\\": \\\"6f2c…\\\",\\n \\\"__model\\\": \\\"article\\\",\\n \\\"locale\\\": \\\"fr\\\",\\n \\\"createdAt\\\": \\\"2026-10-01T09:12:44.512Z\\\",\\n \\\"updatedAt\\\": \\\"2026-10-01T09:30:02.104Z\\\",\\n \\\"publishedAt\\\": \\\"2026-10-01T09:30:02.104Z\\\",\\n \\\"title\\\": \\\"Modéliser du contenu sans déploiement\\\",\\n \\\"slug\\\": \\\"modelling-without-a-deploy\\\",\\n \\\"excerpt\\\": \\\"…\\\",\\n \\\"body\\\": {\\n \\\"format\\\": \\\"shapio-richtext\\\",\\n \\\"version\\\": 1,\\n \\\"doc\\\": { \\\"type\\\": \\\"doc\\\", \\\"content\\\": [] }\\n },\\n \\\"cover\\\": { \\\"id\\\": \\\"…\\\", \\\"url\\\": \\\"…\\\", \\\"width\\\": 1600, \\\"height\\\": 900, \\\"alt\\\": \\\"…\\\", \\\"variants\\\": [] },\\n \\\"author\\\": \\\"0b7e…\\\",\\n \\\"publishedOn\\\": \\\"2026-09-01\\\"\\n }\\n ],\\n \\\"meta\\\": {\\n \\\"locale\\\": \\\"fr\\\",\\n \\\"snapshot\\\": 10,\\n \\\"pagination\\\": { \\\"page\\\": 1, \\\"pageSize\\\": 25, \\\"total\\\": 2, \\\"pageCount\\\": 1 }\\n }\\n}\\n```\\n\\n- Entries are flat: system attributes, then every readable field by API ID, `null` (or `[]`) when empty.\\n- Every entry carries `__model` and `__kinds`. `__model` is the singular API ID of its own type: in a list of a\\n type with kinds, it tells you which kind each row is. `__kinds` is that type's lineage, root first and its\\n own type last (`[\\\"product\\\", \\\"physical\\\", \\\"apparel\\\"]`; a plain product has `[\\\"product\\\"]`), so you can match a\\n row at any level without knowing the schema. Neither is a field: `fields=` and `sort` can't name them, and a\\n write that sends one is refused.\\n- **Rich text** is the stored JSON document by default. Ask for `richText=html` to get\\n `{ format, version, html }` instead, or `richText=both` for the document and `html` together. The HTML is\\n rendered on the server from the JSON with an allow-list sanitiser (safe link protocols only, images resolved\\n through the media library), on the request that asks for it; it is never stored.\\n- **Media** fields are asset objects with URLs and ready [variants](media.md#image-variants); private assets\\n carry an expiring signed URL.\\n- **Relations** are target IDs unless you `populate` them. A target that is not published, or that your token\\n may not read, is left out (never its ID).\\n- **Components** are objects; **dynamic zones** are arrays of objects with `__component` (the component's\\n API ID).\\n- **Code** fields are strings, exactly as saved. Their language (`html`, `css`, `json`, …) is in the OpenAPI\\n document as `x-shapio-language` (with `contentMediaType`), in the GraphQL field description (`Code: html`)\\n and in the comment of the field in generated types (`shapio types generate`).\\n- A single type answers `/api/content/<API ID>` with `{ data: {…}, meta }`.\\n\\nErrors always look like `{ \\\"error\\\": { \\\"code\\\": \\\"…\\\", \\\"message\\\": \\\"…\\\", \\\"details\\\": … } }`: 400 for an invalid query,\\n401 without credentials for content that needs them, 403 when your role may not read the model, 404 for an\\nunknown model or entry.\\n\\n## Types that extend a type\\n\\nA collection can extend another ([Modelling](modelling.md#types-that-extend-a-type)): Post, with Guide and\\nOpinion as its kinds. A kind is read under its parent, by its **route key**, the plural API IDs of its\\nancestors and its own joined by `/`:\\n\\n```text\\nGET /api/content/posts # every post: plain posts, guides, and opinions\\nGET /api/content/posts/<entry id> # one post, of any kind\\nGET /api/content/posts/guides # guides only\\nGET /api/content/posts/guides/<entry id>\\n```\\n\\nA kind's plural API ID on its own (`/api/content/guides`) is not a route. Reading an entry under the wrong\\nkind is a 404: `/api/content/posts/guides/<id>` of an opinion is not found, while `/api/content/posts/<id>`\\nfinds it.\\n\\nA parent's list holds every kind, in one page, sorted and counted together:\\n\\n- Each row comes in its own shape, with `__model` naming its kind: a guide has the post's fields and its own.\\n- Filters, `sort`, `q`, and `fields=` take the fields of the type you read, which every kind under it has\\n (and only those your token may read on every kind in the list). To filter on a kind's own field, read that\\n kind's route.\\n- Without `fields=`, each row carries every field its own kind has that you may read. With `fields=`, every\\n row carries only the shared fields you named.\\n- To read only some kinds, filter on `__model` with the singular API IDs you want:\\n `?filters[__model][$eq]=post` lists plain posts alone, `?filters[__model][$in]=guide,opinion` two kinds. Only\\n `$eq` and `$in` work, and only with kinds of the type you read; the total counts just those kinds.\\n- Kinds your token may not read are left out of the list, never refused with a 403. Reading the route itself\\n needs `read` on its type, and a grant on a parent covers its kinds ([End users](end-users.md)).\\n- With no `sort`, the list follows the parent's default sort.\\n- `meta.kinds` lists the type and its kinds as a flat tree (each kind after its parent), with each kind's\\n label, parent and route key, so a site can build a kind filter or a menu without reading the schema. Only\\n kinds your token may read are in it, and a type without kinds has no `meta.kinds` at all.\\n\\nA relation to a type with kinds can point at an entry of any kind. Populated, each target comes in its own\\nshape with its own `__model`, under the same rules.\\n\\n```sh\\ncurl -H \\\"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\\\" \\\"$SHAPIO_URL/api/content/posts?sort=publishedOn:desc&pageSize=10\\\"\\n```\\n\\n```json\\n{\\n \\\"data\\\": [\\n {\\n \\\"id\\\": \\\"1a…\\\",\\n \\\"__model\\\": \\\"guide\\\",\\n \\\"__kinds\\\": [\\\"post\\\", \\\"guide\\\"],\\n \\\"title\\\": \\\"Pin your builds\\\",\\n \\\"timeToComplete\\\": 12\\n },\\n { \\\"id\\\": \\\"2b…\\\", \\\"__model\\\": \\\"post\\\", \\\"__kinds\\\": [\\\"post\\\"], \\\"title\\\": \\\"Release notes\\\" },\\n {\\n \\\"id\\\": \\\"3c…\\\",\\n \\\"__model\\\": \\\"opinion\\\",\\n \\\"__kinds\\\": [\\\"post\\\", \\\"opinion\\\"],\\n \\\"title\\\": \\\"Why models are data\\\",\\n \\\"stance\\\": \\\"for\\\"\\n }\\n ],\\n \\\"meta\\\": {\\n \\\"locale\\\": \\\"en\\\",\\n \\\"snapshot\\\": 41,\\n \\\"pagination\\\": { \\\"page\\\": 1, \\\"pageSize\\\": 10, \\\"total\\\": 3, \\\"pageCount\\\": 1 },\\n \\\"kinds\\\": [\\n { \\\"apiKey\\\": \\\"post\\\", \\\"label\\\": \\\"Post\\\", \\\"parent\\\": null, \\\"routeKey\\\": \\\"posts\\\" },\\n { \\\"apiKey\\\": \\\"guide\\\", \\\"label\\\": \\\"Guide\\\", \\\"parent\\\": \\\"post\\\", \\\"routeKey\\\": \\\"posts/guides\\\" },\\n { \\\"apiKey\\\": \\\"opinion\\\", \\\"label\\\": \\\"Opinion\\\", \\\"parent\\\": \\\"post\\\", \\\"routeKey\\\": \\\"posts/opinions\\\" }\\n ]\\n }\\n}\\n```\\n\\nWith `@shapio/client`, pass the nested route key as it is: `delivery.list('posts/guides')`. The client sends\\nit as nested path segments.\\n\\n## Query parameters\\n\\n| Parameter | Example | Notes |\\n| ------------------ | ------------------------------------- | --------------------------------------------------------------------------------- |\\n| `locale` | `locale=fr` | falls back along the locale chain ([Localization](localization.md)) |\\n| `filters` | `filters[slug][$eq]=about` | see below |\\n| `sort` | `sort=publishedOn:desc,createdAt:asc` | fields marked sortable, `id`, `createdAt`, `updatedAt` |\\n| `page`, `pageSize` | `page=2&pageSize=50` | `pageSize` 25 by default, at most 100 |\\n| `fields` | `fields=title,slug` | top-level fields to return (system attributes always come) |\\n| `populate` | `populate=author` | relation paths: `author`, `author,tags.author`, or `*`; at most 3 levels |\\n| `q` | `q=snapshot` | searches the title field chosen in the model's display settings (400 without one) |\\n| `snapshot` | `snapshot=10` | read as of a publication snapshot (below) |\\n| `richText` | `richText=html` | rich-text shape: `json` (default, the document), `html`, or `both` |\\n| `publicationState` | `publicationState=draft` | `published` (default) or `draft`: [drafts mode](#drafts-in-development) |\\n\\n### Filters\\n\\n`filters[<field>][<operator>]=<value>`, combined with `$and`, `$or` and `$not`:\\n\\n```sh\\ncurl -G \\\"$SHAPIO_URL/api/content/articles\\\" -H \\\"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\\\" \\\\\\n --data-urlencode 'filters[publishedOn][$gte]=2026-09-10' \\\\\\n --data-urlencode 'filters[$or][0][slug][$eq]=why-builds-pin-a-snapshot' \\\\\\n --data-urlencode 'filters[$or][1][slug][$eq]=modelling-without-a-deploy'\\n```\\n\\n| Operators | Field types |\\n| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\\n| `$eq`, `$ne`, `$in`, `$nin`, `$null`, `$notNull` | text, numbers, dates, enums, relations and media (booleans: `$eq`, `$ne`, `$null`, `$notNull`); on lists (`many` relations, multiple media/enums) `$eq` means \\\"contains\\\" |\\n| `$lt`, `$lte`, `$gt`, `$gte` | text, numbers, dates and times, on fields marked **filterable** or **sortable** |\\n| `$contains`, `$notContains`, `$containsi`, `$startsWith`, `$endsWith` | text fields marked **filterable** |\\n\\n`id`, `createdAt` and `updatedAt` can be filtered too (`publishedAt` is returned but cannot be filtered or\\nsorted). JSON, rich text, component and dynamic zone fields cannot be filtered. `$in` takes repeated values or `a,b`. Values are read\\nlike stored values, so `2026-10-01T10:00:00+02:00` matches `2026-10-01T08:00:00.000Z`. Unknown fields,\\noperators a type does not support, and filters or sorts on fields your token may not read are refused (400/403)\\nrather than ignored, so a filter can never reveal a hidden value. Relation and media filters see what the\\nresponse shows: a link to an entry that is unpublished, outside the read's locales, or one your token may not\\nread, and a deleted asset, count as not there, so `$null` matches an entry whose links are all hidden.\\n\\n## Snapshots: one consistent moment\\n\\nEvery publish moves a global **publication snapshot** number forward; every published read reports the\\none it read in `meta.snapshot` (a [draft read](#drafts-in-development) reports `null`). Pass `snapshot=N`\\nand Shapio answers exactly as it would have at that moment, even if more was published since:\\n\\n```sh\\ncurl -H \\\"Authorization: Bearer $SHAPIO_DELIVERY_TOKEN\\\" \\\"$SHAPIO_URL/api/content/pages?snapshot=10&locale=en\\\"\\n```\\n\\nA static site build should read the current snapshot once at the start and pass it with every request: then\\na publish in the middle of the build cannot produce a site that mixes two moments. Without `snapshot`, which\\nrelated entries a response shows is checked against what is live when it is answered, so an entry published a\\nmoment after `meta.snapshot` may already appear; an unpublished one never does. The\\n[example site](example-site.md) does exactly this, and Shapio's deployment triggers carry the snapshot to build.\\n\\nOld snapshots are read through the current schema (a value stored under an earlier field type comes back as\\n`null`). A [change set](change-sets.md) goes live as one snapshot, schema and entries together, so a pinned\\nbuild sees all of it or none of it.\\n\\nTwo endpoints serve builds, with the same tokens as `/api/content` and only for models the token may read:\\n\\n| Endpoint | Answer |\\n| ------------------------------------------------ | ------------------------------------------------------------------------ |\\n| `GET /api/snapshots/current` | `{ snapshot, schemaVersion, publishedAt }`: what an unpinned read serves |\\n| `GET /api/snapshots/changes?from&to&after&limit` | entries and locales whose live content changed between the two snapshots |\\n\\nDetails, the change classification and incremental-build recipes are in\\n[Snapshots and the changes API](snapshots.md).\\n\\n## Caching\\n\\nResponses carry a strong `ETag` over the exact body and `Vary: Authorization, Cookie, Shapio-Site`. Send\\n`If-None-Match` to get `304 Not Modified` when nothing changed. Anonymous responses are `Cache-Control: public,\\nmax-age=0, must-revalidate`; authenticated ones `private`. A CDN that keys its cache on the URL alone and\\nignores `Vary` must be given the site as `?site=`, not as the header, so that two sites never share a cached\\nresponse.\\n\\n### Next.js cache tags\\n\\nUnder Next.js (detected by `process.env.NEXT_RUNTIME`), `@shapio/client` tags every delivery read for Next's\\ndata cache, so a site refreshes exactly what changed with `revalidateTag`:\\n\\n| Read | Tags |\\n| ---------------------------- | ------------------------------------------------------------------ |\\n| `delivery.list(routeKey)` | `shapio:site:<key>`, `shapio:<routeKey>` |\\n| `delivery.singleton(apiKey)` | `shapio:site:<key>`, `shapio:<apiKey>` |\\n| `delivery.get(routeKey, id)` | `shapio:site:<key>`, `shapio:<routeKey>`, `shapio:<routeKey>:<id>` |\\n| `site.get()` | `shapio:site:<key>` |\\n\\nModels are tagged by their route key (a collection's plural API ID, a singleton's API ID, and for a kind its\\nnested route key, `posts/guides`), as the [snapshot diff](snapshots.md#what-changed-between-two-snapshots)\\nnames them. The site tag is `shapio:site:<key>`\\nwhen the client names a site (`site: 'marketing'`) and `shapio:site` when it leaves the site to the token.\\n`shapioTags.site(key)`, `shapioTags.model(routeKey)` and `shapioTags.entry(routeKey, id)` build the same strings:\\n\\n```ts\\nimport { createClient, shapioTags } from '@shapio/client';\\nimport { revalidateTag } from 'next/cache';\\n\\nconst shapio = createClient({ baseUrl, token, site: 'marketing', next: { cache: 'force-cache' } });\\nconst { data } = await shapio.delivery.get<Article>('articles', id, { locale: 'en' });\\n\\n// In the webhook route, for each entry in the snapshot diff:\\nrevalidateTag(shapioTags.entry('articles', id), { expire: 0 });\\n```\\n\\nA guide is also in the posts list and readable as `posts/<id>`, so when one changes, revalidate the tags of\\nits own route key and of every ancestor's. `shapioTags.changed(routeKey, id)` gives them all:\\n`shapioTags.changed('posts/guides', id)` is `shapio:posts`, `shapio:posts:<id>`, `shapio:posts/guides` and\\n`shapio:posts/guides:<id>`. Webhook payloads carry the same `routeKey`\\n([Webhooks](publishing.md#entry-events)), so any framework can do the same.\\n\\n```ts\\nfor (const entry of diff.items) {\\n for (const tag of shapioTags.changed(entry.routeKey, entry.id)) {\\n revalidateTag(tag, { expire: 0 });\\n }\\n}\\n```\\n\\nThe cache mode is yours: the client sets `cache` and `next.revalidate` only when you ask, with\\n`next: { cache?: 'force-cache' | 'no-store', revalidate?: number | false }` on `createClient` or as the last\\nargument of one read (`delivery.list('articles', query, { next: { revalidate: 60 } })`), which overrides the\\nclient's. A read pinned to a snapshot (`snapshot: N`) never changes, so under Next it is always `force-cache`.\\n`next: false` sends no Next options at all. Outside Next, with no `next` option, the client's requests are\\nexactly as before. Snapshot, preview, GraphQL and admin requests are never cached or tagged, and neither are\\n[drafts mode](#drafts-in-development) reads (`drafts: true`), which under Next are `no-store` whatever the\\n`next` options say. The\\n[Next.js starter](starters.md#incremental-rebuilds) puts it together.\\n\\n## Drafts in development\\n\\nPublishing rebuilds your live site, so it is not how you check a change. **Drafts mode** lets a site's\\ndevelopment server render saved drafts: save in the admin, reload the page on your machine, and the change is\\nthere. Publish still rebuilds your site; drafts mode is for your machine.\\n\\n1. Create a second delivery role for development (or let the starters' `npm run seed` create `<site> dev`)\\n with `read` on the site's models and **Read drafts**. Read drafts always covers every model the role\\n reads; it adds no model of its own.\\n2. Create a token for that role. The tokens list marks it with a **Drafts** chip. Never put it in a production\\n environment.\\n3. Ask for drafts: `publicationState=draft` on `GET /api/content/...` (both routes), `publicationState: DRAFT` in\\n [GraphQL](graphql.md#reading-drafts), or `drafts: true` on `@shapio/client`'s `createClient` and\\n `@shapio/local`'s `createLocalClient`. The [starters](starters.md#drafts-mode) turn it on with\\n `SHAPIO_DRAFTS=true`.\\n\\n```sh\\ncurl -H \\\"Authorization: Bearer $SHAPIO_DEV_DELIVERY_TOKEN\\\" \\\\\\n \\\"$SHAPIO_URL/api/content/articles?publicationState=draft&populate=author\\\"\\n```\\n\\nA draft read serves each entry's saved draft in the delivery shape, entries never published included, with\\n`meta.publicationState: \\\"draft\\\"` and `meta.snapshot: null`. Relation targets and `populate` follow their drafts too, still only for\\nmodels the role may read. Responses are `Cache-Control: private, no-store`, so no cache keeps them, and field\\nusage does not count them. A draft cannot be pinned: `publicationState=draft` with `snapshot` is a 400.\\nPublished reads are unchanged, byte for byte.\\n\\nTwo safeguards keep drafts off your live site. The server only serves drafts to a token whose role grants Read\\ndrafts (403 `DRAFTS_FORBIDDEN` otherwise, with a message naming the grant), and the site must ask for them. A\\nproduction token never carries the grant, so a flag left on by mistake fails loudly instead of showing drafts.\\nAnonymous callers and app users can never read drafts, whatever their roles say. Admin users and admin API\\ntokens can, as they always could in GraphQL.\\n\\nA draft is whatever the admin has saved, autosaves included, and it may not pass validation yet (a required\\nfield can be empty): render drafts defensively. For one entry in the admin's Preview pane, editors use\\n[preview](visual-editing.md) instead; drafts mode is the whole site, for developers.\\n\\n## Writes\\n\\nThe same routes accept `POST`, `PUT` and `DELETE` from app users whose roles allow it (for example, members\\nediting their own entries). Delivery tokens cannot write. See [End users](end-users.md). Editors and your\\nbuild tools use the admin API (`/api/admin/content/<key>`) with admin credentials.\\n\";\n","import { ResourceTemplate } from '@modelcontextprotocol/server';\nimport { serializeDefinition } from '@shapio/schema';\nimport type { ToolContext } from './context.js';\nimport { findDefinition, readSchema } from './definitions.js';\nimport { DELIVERY_API_DOC } from './generated/deliveryApiDoc.js';\n\nexport const SCHEMA_URI_TEMPLATE = 'shapio://schema/{apiKey}';\nexport const DELIVERY_DOC_URI = 'shapio://docs/delivery-api';\n\n/** Each definition in the pull format (`shapio schema pull`'s files), and the delivery API guide. */\nexport const registerResources = ({ server, client }: ToolContext) => {\n server.registerResource(\n 'schema',\n new ResourceTemplate(SCHEMA_URI_TEMPLATE, {\n list: async () => {\n const schema = await readSchema(client);\n return {\n resources: schema.definitions.map(({ definition }) => ({\n uri: `shapio://schema/${definition.apiKey}`,\n name: definition.apiKey,\n title: definition.label,\n mimeType: 'application/json',\n })),\n };\n },\n }),\n {\n title: 'Content type definition',\n description:\n \"A model or component of this server's site (its own or shared with all sites) in the schema file format, with stable IDs.\",\n mimeType: 'application/json',\n },\n async (uri, variables) => {\n const apiKey = String(variables.apiKey);\n const { definition } = findDefinition(await readSchema(client), apiKey);\n return {\n contents: [{ uri: uri.href, mimeType: 'application/json', text: serializeDefinition(definition) }],\n };\n },\n );\n\n server.registerResource(\n 'delivery-api',\n DELIVERY_DOC_URI,\n {\n title: 'Delivery API guide',\n description: 'How sites and apps read published content: routes, filters, locales, snapshots, tokens.',\n mimeType: 'text/markdown',\n },\n (uri) => ({ contents: [{ uri: uri.href, mimeType: 'text/markdown', text: DELIVERY_API_DOC }] }),\n );\n};\n","import type { CallToolResult } from '@modelcontextprotocol/server';\nimport { ShapioApiError } from '@shapio/client';\n\n/** A tool's answer: the API response as pretty JSON text (what agents read best). */\nexport const jsonResult = (value: unknown): CallToolResult => ({\n content: [{ type: 'text', text: JSON.stringify(value ?? null, null, 2) }],\n});\n\n/**\n * A refusal or failure the agent should see and act on (a stale version, a missing permission, validation\n * issues), with Shapio's error code and details. Never thrown: the MCP client gets `isError`.\n */\nexport const errorResult = (error: unknown): CallToolResult => {\n const body =\n error instanceof ShapioApiError\n ? { status: error.status, code: error.code, message: error.message, details: error.details }\n : { code: 'TOOL_FAILED', message: error instanceof Error ? error.message : String(error) };\n return { isError: true, content: [{ type: 'text', text: JSON.stringify({ error: body }, null, 2) }] };\n};\n\n/** Runs a tool body, turning any failure into an error result. */\nexport const runTool = async (body: () => Promise<unknown>): Promise<CallToolResult> => {\n try {\n return jsonResult(await body());\n } catch (error) {\n return errorResult(error);\n }\n};\n","import { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { runTool } from './results.js';\n\nconst SET_ID = z.string().min(1).describe('Change set ID');\n\n/**\n * Change sets: the reviewable bundle of schema drafts and entry publications that ships as one snapshot.\n * Agents create and fill them; shipping is a person's call (`change_sets_ship` exists only with\n * --allow-ship, and the server also needs the token's role to hold changes.ship).\n */\nexport const registerChangeSetTools = ({ server, client, options }: ToolContext) => {\n server.registerTool(\n 'change_sets_list',\n {\n title: 'List change sets',\n description: 'Change sets, newest first (every status but discarded unless status is given).',\n inputSchema: z.object({\n status: z\n .string()\n .optional()\n .describe('Comma-separated: open, scheduled, shipping, shipped, failed, discarded'),\n cursor: z.string().optional(),\n limit: z.number().int().positive().max(100).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (query) => runTool(() => client.admin.changeSets.list(query)),\n );\n\n server.registerTool(\n 'change_sets_create',\n {\n title: 'Create a change set',\n description: 'An empty, open change set to collect schema drafts and entry publications for review.',\n inputSchema: z.object({\n title: z.string().min(1).max(200),\n description: z.string().max(5000).optional(),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ title, description }) =>\n runTool(() => client.admin.changeSets.create({ title, ...(description ? { description } : {}) })),\n );\n\n server.registerTool(\n 'change_sets_add_entry',\n {\n title: 'Add an entry to a change set',\n description:\n \"Proposes publishing (or unpublishing) one entry locale when the set ships. The entry's current draft \" +\n 'is what ships; the review shows the field diff against what is live.',\n inputSchema: z.object({\n changeSetId: SET_ID,\n model: z.string().min(1).describe(\"The model's API ID\"),\n entryId: z.string().min(1),\n locale: z.string().optional().describe('Omit for non-localized models and the default locale'),\n action: z.enum(['publish', 'unpublish']).default('publish'),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ changeSetId, model, entryId, locale, action }) =>\n runTool(() =>\n client.admin.changeSets.addEntry(changeSetId, {\n modelKey: model,\n entryId,\n action,\n ...(locale ? { locale } : {}),\n }),\n ),\n );\n\n server.registerTool(\n 'change_sets_review',\n {\n title: 'Review a change set',\n description:\n 'The set (status, version, items) and its review: field diffs per entry, the schema plan per draft ' +\n '(breaking, destructive, conversions), blocking issues, warnings and the consumers of affected fields.',\n inputSchema: z.object({ changeSetId: SET_ID }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ changeSetId }) =>\n runTool(async () => {\n const [set, review] = await Promise.all([\n client.admin.changeSets.get(changeSetId),\n client.admin.changeSets.review(changeSetId),\n ]);\n return { set, review };\n }),\n );\n\n if (options.allowShip) {\n server.registerTool(\n 'change_sets_ship',\n {\n title: 'Ship a change set',\n description:\n 'Makes the set live as one snapshot. Only when the person you work for asked for it. expectedVersion ' +\n 'is the set version change_sets_review showed; breaking or destructive schema changes need the ' +\n \"acknowledgement. Refused when the token's role lacks changes.ship.\",\n inputSchema: z.object({\n changeSetId: SET_ID,\n expectedVersion: z.number().int().nonnegative(),\n acknowledgeBreaking: z.boolean().optional(),\n acknowledgeDestructive: z.boolean().optional(),\n }),\n annotations: { destructiveHint: true, openWorldHint: false },\n },\n ({ changeSetId, ...body }) => runTool(() => client.admin.changeSets.ship(changeSetId, body)),\n );\n }\n};\n","import type { ContentFilter, ContentSort } from '@shapio/client';\nimport { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { findDefinition, readSchema, routeKeyOf } from '../definitions.js';\nimport { runTool } from './results.js';\n\nconst MODEL = z.string().min(1).describe('The model\\'s API ID (singular, e.g. \"article\")');\nconst LOCALE = z.string().min(1).optional().describe('Locale code; the default locale when omitted');\nconst PUBLISHED = z\n .boolean()\n .optional()\n .describe('Read published content through the delivery API instead of drafts (implied by snapshot)');\nconst SNAPSHOT = z\n .number()\n .int()\n .nonnegative()\n .optional()\n .describe('Read published content as of publication snapshot N');\n\nconst QUERY = z.object({\n model: MODEL,\n filters: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n 'Filter tree by field API ID, e.g. { \"title\": { \"$containsi\": \"launch\" } }; $and/$or take lists',\n ),\n sort: z.array(z.object({ field: z.string().min(1), direction: z.enum(['asc', 'desc']) })).optional(),\n page: z.number().int().positive().optional(),\n pageSize: z.number().int().positive().max(100).optional(),\n q: z.string().optional().describe('Free-text search over the title field'),\n locale: LOCALE,\n fields: z.array(z.string()).optional().describe('Top-level fields to return'),\n populate: z.array(z.string()).optional().describe('Relation paths to expand, e.g. \"author\"'),\n status: z\n .enum(['draft', 'published', 'modified'])\n .optional()\n .describe('Drafts only: entries in this status'),\n published: PUBLISHED,\n snapshot: SNAPSHOT,\n});\n\nconst DATA = z\n .record(z.string(), z.unknown())\n .describe(\"Field values by API ID. Rich text is Shapio's JSON document format, never HTML.\");\n\n/** Entries: query and read (drafts or published), create and update drafts. Never publishes. */\nexport const registerContentTools = ({ server, client }: ToolContext) => {\n server.registerTool(\n 'content_query',\n {\n title: 'Query entries',\n description:\n \"A page of a model's entries. Drafts by default (what editors see); published=true or snapshot=N reads \" +\n 'what the delivery API serves.',\n inputSchema: QUERY,\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, published, snapshot, status, filters, sort, ...rest }) =>\n runTool(async () => {\n const query = {\n ...rest,\n ...(filters ? { filters: filters as ContentFilter } : {}),\n ...(sort ? { sort: sort as ContentSort[] } : {}),\n };\n if (published || snapshot !== undefined) {\n const schema = await readSchema(client);\n const { definition } = findDefinition(schema, model);\n const routeKey = routeKeyOf(schema, definition);\n return definition.kind === 'singleton'\n ? client.delivery.singleton(routeKey, {\n ...(rest.locale ? { locale: rest.locale } : {}),\n ...(snapshot !== undefined ? { snapshot } : {}),\n })\n : client.delivery.list(routeKey, { ...query, ...(snapshot !== undefined ? { snapshot } : {}) });\n }\n return client.admin.content.list(model, { ...query, ...(status ? { status } : {}) });\n }),\n );\n\n server.registerTool(\n 'content_get',\n {\n title: 'Read an entry',\n description:\n \"One entry: its draft with every locale's status and version (send version back as expectedVersion \" +\n 'to content_update), or the published version when published=true or snapshot=N.',\n inputSchema: z.object({\n model: MODEL,\n id: z.string().min(1),\n locale: LOCALE,\n published: PUBLISHED,\n snapshot: SNAPSHOT,\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, id, locale, published, snapshot }) =>\n runTool(async () => {\n if (published || snapshot !== undefined) {\n const schema = await readSchema(client);\n const { definition } = findDefinition(schema, model);\n return client.delivery.get(routeKeyOf(schema, definition), id, {\n ...(locale ? { locale } : {}),\n ...(snapshot !== undefined ? { snapshot } : {}),\n });\n }\n return client.admin.content.get(model, id, locale ? { locale } : {});\n }),\n );\n\n server.registerTool(\n 'content_create',\n {\n title: 'Create a draft entry',\n description:\n 'Creates an entry as a draft (never published). Validated by the server against the live model. ' +\n 'Add it to a change set with change_sets_add_entry to propose publishing it.',\n inputSchema: z.object({ model: MODEL, data: DATA, locale: LOCALE }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ model, data, locale }) =>\n runTool(() => client.admin.content.create(model, { data, ...(locale ? { locale } : {}) })),\n );\n\n server.registerTool(\n 'content_update',\n {\n title: 'Update a draft entry',\n description:\n \"Saves changed fields into one locale's draft (absent fields are kept, null clears). expectedVersion is \" +\n 'the version content_get returned; a stale one is refused (CONTENT_VERSION_CONFLICT): read again and redo. ' +\n 'Pass expectedVersion null to start a new locale of the entry.',\n inputSchema: z.object({\n model: MODEL,\n id: z.string().min(1),\n data: DATA,\n expectedVersion: z.number().int().nonnegative().nullable(),\n locale: LOCALE,\n }),\n annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: false },\n },\n ({ model, id, data, expectedVersion, locale }) =>\n runTool(() =>\n client.admin.content.update(model, id, { data, expectedVersion, ...(locale ? { locale } : {}) }),\n ),\n );\n};\n","import { HEALTH_RULES } from '@shapio/client';\nimport { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { findDefinition, readSchema } from '../definitions.js';\nimport { runTool } from './results.js';\n\n/** Checks and signals: pre-flight for one entry, content health findings, field usage by consumers. */\nexport const registerInsightTools = ({ server, client }: ToolContext) => {\n server.registerTool(\n 'preflight_run',\n {\n title: 'Pre-flight an entry',\n description:\n 'What publishing these locales of an entry would run into (validation, missing locales, references, ' +\n 'alt text), without publishing anything.',\n inputSchema: z.object({\n model: z.string().min(1).describe(\"The model's API ID\"),\n id: z.string().min(1),\n locales: z.array(z.string()).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, id, locales }) =>\n runTool(() => client.admin.preflight.run(model, id, locales ? { locales } : {})),\n );\n\n server.registerTool(\n 'health_list',\n {\n title: 'List content health findings',\n description: `Open findings of the content health rules (${HEALTH_RULES.join(', ')}), by rule and model.`,\n inputSchema: z.object({\n rule: z.enum(HEALTH_RULES).optional(),\n model: z.string().optional().describe(\"The model's API ID\"),\n cursor: z.string().optional(),\n limit: z.number().int().positive().max(100).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, ...query }) =>\n runTool(() => client.admin.contentHealth.list({ ...query, ...(model ? { modelKey: model } : {}) })),\n );\n\n server.registerTool(\n 'usage_fields',\n {\n title: 'Field usage',\n description:\n 'Which fields of a model sites and apps read, per API token, over the last N days: check before ' +\n 'renaming or removing a field.',\n inputSchema: z.object({\n model: z.string().min(1).describe(\"The model's API ID\"),\n days: z.number().int().positive().max(365).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ model, days }) =>\n runTool(async () => {\n const { definition } = findDefinition(await readSchema(client), model);\n return client.admin.usage.fields({ modelId: definition.id, ...(days ? { days } : {}) });\n }),\n );\n};\n","import { realpath } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { uploadFile } from '@shapio/client/node';\nimport { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { runTool } from './results.js';\n\n/**\n * The file a `media_upload` may read: inside the media root after resolving symlinks, so an agent (or text\n * injected into what it reads) cannot have the server read ~/.ssh or anything else outside the project.\n */\nexport const resolveMediaPath = async (mediaRoot: string, path: string): Promise<string> => {\n const root = await realpath(mediaRoot);\n const target = await realpath(resolve(root, path)).catch(() => {\n throw new Error(`No file at ${path} (relative to the media root ${root})`);\n });\n const inside = relative(root, target);\n if (inside === '' || inside.startsWith('..') || isAbsolute(inside)) {\n throw new Error(\n `media_upload only reads files under the media root (${root}); start the server with --media-root to change it`,\n );\n }\n return target;\n};\n\n/** The media library: browse, and upload files from the media root. */\nexport const registerMediaTools = ({ server, client, options }: ToolContext) => {\n server.registerTool(\n 'media_list',\n {\n title: 'List media',\n description: 'A page of media assets (ID, filename, type, alt text, visibility, URLs), newest first.',\n inputSchema: z.object({\n folder: z.string().optional().describe('A folder ID, or \"root\" for assets in no folder'),\n mimeType: z.string().optional().describe('\"image/png\" or \"image/*\"'),\n search: z.string().optional(),\n cursor: z.string().optional().describe('nextCursor of the previous page'),\n limit: z.number().int().positive().max(100).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (query) => runTool(() => client.admin.media.assets.list(query)),\n );\n\n server.registerTool(\n 'media_upload',\n {\n title: 'Upload a file',\n description:\n `Uploads a local file into the media library and returns the asset (use its ID in media fields). Only ` +\n `files under ${options.mediaRoot} can be read. Media is stored at once (it is not part of change sets).`,\n inputSchema: z.object({\n path: z.string().min(1).describe('Path relative to the media root (or absolute, inside it)'),\n alt: z.string().max(1000).optional().describe('Alt text for images'),\n mimeType: z.string().optional().describe('Needed when the extension is not a common one'),\n folderId: z.string().optional(),\n visibility: z.enum(['public', 'private']).optional(),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ path, alt, mimeType, folderId, visibility }) =>\n runTool(async () => {\n const file = await resolveMediaPath(options.mediaRoot, path);\n const asset = await uploadFile(client, {\n baseUrl: options.baseUrl,\n path: file,\n ...(mimeType ? { mimeType } : {}),\n ...(folderId ? { folderId } : {}),\n ...(visibility ? { visibility } : {}),\n });\n return alt\n ? client.admin.media.assets.update(asset.id, { expectedVersion: asset.version, alt })\n : asset;\n }),\n );\n};\n","import { randomUUID } from 'node:crypto';\nimport type { ChangeSet, ChangeSetSchemaItem, ShapioClient } from '@shapio/client';\nimport { isStableId, type DefinitionInput } from '@shapio/schema';\nimport { readSchema } from './definitions.js';\n\n/** A definition as an agent writes it: the authored format, IDs optional, references by API ID allowed. */\nexport type DraftDefinition = Record<string, unknown> & { apiKey?: unknown; kind?: unknown; id?: unknown };\n\ntype FieldLike = { type?: unknown; settings?: Record<string, unknown> };\n\nconst schemaItemsOf = (set: ChangeSet) =>\n set.items.filter((item): item is ChangeSetSchemaItem => item.kind === 'schema');\n\n/**\n * Relation targets, component references and `extends` (the parent type) are stable IDs. Agents think in API\n * IDs, so a reference that is not an ID is looked up among the active definitions and the set's drafts (and\n * this definition itself, for a self-relation); anything unknown is left for the server to report.\n */\nconst resolveReferences = (definition: DraftDefinition, idsByApiKey: ReadonlyMap<string, string>) => {\n const resolve = (value: unknown) =>\n typeof value === 'string' && !isStableId(value) ? (idsByApiKey.get(value) ?? value) : value;\n const fields = Array.isArray(definition.fields) ? (definition.fields as FieldLike[]) : [];\n return {\n ...definition,\n ...(definition.extends === undefined ? {} : { extends: resolve(definition.extends) }),\n fields: fields.map((field) => {\n const settings = field.settings;\n if (!settings) {\n return field;\n }\n if (field.type === 'relation') {\n return { ...field, settings: { ...settings, target: resolve(settings.target) } };\n }\n if (field.type === 'component') {\n return { ...field, settings: { ...settings, component: resolve(settings.component) } };\n }\n if (field.type === 'dynamiczone' && Array.isArray(settings.components)) {\n return { ...field, settings: { ...settings, components: settings.components.map(resolve) } };\n }\n return field;\n }),\n };\n};\n\n/**\n * Puts a definition into a change set as a draft: picks its stable ID (the active definition's, the set's\n * existing draft's, or a new one), its base version and the draft version to replace, then lets the server\n * parse, validate and plan it. Nothing goes live until a person ships the set. The site's view decides what\n * exists: a new definition belongs to the site, or is shared with all sites when `shared` (creates only; an\n * existing definition keeps its scope).\n */\nexport const putDefinitionDraft = async (\n client: ShapioClient,\n changeSetId: string,\n definition: DraftDefinition,\n { shared = false }: { shared?: boolean } = {},\n) => {\n if (typeof definition.apiKey !== 'string' || typeof definition.kind !== 'string') {\n throw new Error('A definition needs at least \"kind\" (collection, singleton or component) and \"apiKey\"');\n }\n const [schema, set] = await Promise.all([readSchema(client), client.admin.changeSets.get(changeSetId)]);\n const drafts = schemaItemsOf(set);\n const apiKey = definition.apiKey;\n const explicitId = typeof definition.id === 'string' ? definition.id : undefined;\n const active = schema.definitions.find(\n ({ definition: candidate }) =>\n candidate.id === explicitId || (!explicitId && candidate.apiKey === apiKey),\n );\n const drafted = drafts.find(\n (item) => item.definitionId === explicitId || (!explicitId && item.apiKey === apiKey),\n );\n const id = explicitId ?? active?.definition.id ?? drafted?.definitionId ?? randomUUID();\n const idsByApiKey = new Map<string, string>([\n ...schema.definitions.map(({ definition: candidate }) => [candidate.apiKey, candidate.id] as const),\n ...drafts.map((item) => [item.apiKey, item.definitionId] as const),\n [apiKey, id],\n ]);\n const payload = resolveReferences({ ...definition, id }, idsByApiKey) as unknown as DefinitionInput;\n const draft = await client.admin.changeSets.putSchemaDraft(changeSetId, id, {\n category: definition.kind === 'component' ? 'component' : 'model',\n definition: payload,\n baseVersion: active?.version ?? null,\n ...(!active && shared ? { shared: true } : {}),\n ...(drafted ? { expectedDraftVersion: drafted.draftVersion } : {}),\n });\n return { changeSetId, definitionId: id, operation: draft.operation, draftVersion: draft.version, draft };\n};\n","import { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { describeDefinition, findDefinition, readSchema, summarizer } from '../definitions.js';\nimport { putDefinitionDraft } from '../schemaDrafts.js';\nimport { runTool } from './results.js';\n\nconst READ_ONLY = { readOnlyHint: true, openWorldHint: false } as const;\n\nconst SHARED_DESCRIPTION =\n 'For a new definition only: true shares it with all sites (needs schema permission on every site). ' +\n \"Default false: it belongs to this server's site. An existing definition keeps its scope.\";\n\nconst DEFINITION_DESCRIPTION =\n 'A whole definition in Shapio\\'s authored format: { kind: \"collection\" | \"singleton\" | \"component\", apiKey, ' +\n 'label, pluralApiKey? (collections), extends? (collections), localized?, draftAndPublish?, fields: [{ apiKey, ' +\n 'label, type, required?, localized?, settings? }] }. IDs are optional: an existing definition keeps its IDs ' +\n '(matched by API ID). Relation targets, component references and extends may be given as API IDs. A ' +\n 'collection with extends inherits every field of that collection: list only its own fields, never an ' +\n \"inherited one, and give it the parent's localized and draftAndPublish. Read shapio://schema/{apiKey} to \" +\n 'see the format of an existing one.';\n\n/** The schema: list and read definitions, and draft changes into a change set (never activated here). */\nexport const registerSchemaTools = ({ server, client }: ToolContext) => {\n server.registerTool(\n 'schema_list',\n {\n title: 'List content types',\n description:\n \"Every model (collection, singleton) and component of this server's site (its own and the ones shared \" +\n 'with all sites) with its API IDs, scope, version and fields (types only). A collection that extends ' +\n 'another names it in extends and lists the inherited fields first, each with inheritedFrom. Delivery ' +\n \"reads use routeKey for collections (the plural API ID, nested under the parent's: posts/guides).\",\n inputSchema: z.object({}),\n annotations: READ_ONLY,\n },\n () =>\n runTool(async () => {\n const schema = await readSchema(client);\n return {\n schemaVersion: schema.schemaVersion,\n definitions: schema.definitions.map(summarizer(schema)),\n };\n }),\n );\n\n server.registerTool(\n 'schema_get',\n {\n title: 'Read a content type',\n description:\n 'One definition in full (fields with settings), by API ID or stable ID, with its version. A collection ' +\n 'that extends another holds only its own fields; extendsApiKey, routeKey and inheritedFields describe ' +\n 'what it inherits.',\n inputSchema: z.object({\n apiKey: z.string().min(1).describe('API ID (or stable ID) of the definition'),\n }),\n annotations: READ_ONLY,\n },\n ({ apiKey }) =>\n runTool(async () => {\n const schema = await readSchema(client);\n return describeDefinition(schema, findDefinition(schema, apiKey));\n }),\n );\n\n server.registerTool(\n 'schema_draft',\n {\n title: 'Draft a content type change',\n description:\n 'Proposes a new or changed definition as a draft in a change set (a new set when changeSetId is ' +\n 'omitted). Nothing changes on the live schema until a person reviews and ships the set. Returns the ' +\n 'change set ID; read change_sets_review next.',\n inputSchema: z.object({\n definition: z.record(z.string(), z.unknown()).describe(DEFINITION_DESCRIPTION),\n changeSetId: z.string().optional().describe('An open change set; omit to start a new one'),\n title: z.string().min(1).max(200).optional().describe('Title of the new change set'),\n shared: z.boolean().optional().describe(SHARED_DESCRIPTION),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ definition, changeSetId, title, shared }) =>\n runTool(async () => {\n const apiKey = typeof definition.apiKey === 'string' ? definition.apiKey : 'draft';\n const setId =\n changeSetId ?? (await client.admin.changeSets.create({ title: title ?? `Schema: ${apiKey}` })).id;\n return putDefinitionDraft(client, setId, definition, { shared: shared ?? false });\n }),\n );\n\n server.registerTool(\n 'change_sets_add_schema_draft',\n {\n title: 'Add a schema draft to a change set',\n description:\n \"Puts a new or changed definition into an existing change set as a draft (replaces this set's \" +\n 'earlier draft of the same definition). Ships only when a person ships the set.',\n inputSchema: z.object({\n changeSetId: z.string().min(1),\n definition: z.record(z.string(), z.unknown()).describe(DEFINITION_DESCRIPTION),\n shared: z.boolean().optional().describe(SHARED_DESCRIPTION),\n }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ changeSetId, definition, shared }) =>\n runTool(() => putDefinitionDraft(client, changeSetId, definition, { shared: shared ?? false })),\n );\n};\n","import { z } from 'zod';\nimport type { ToolContext } from '../context.js';\nimport { runTool } from './results.js';\n\n/** Publication snapshots: the ledger, the diff between two, and restore (as a reviewable change set). */\nexport const registerSnapshotTools = ({ server, client }: ToolContext) => {\n server.registerTool(\n 'snapshots_list',\n {\n title: 'List snapshots',\n description: 'The publication ledger, newest first: what went live, when, why and by whom.',\n inputSchema: z.object({\n cursor: z.string().optional(),\n limit: z.number().int().positive().max(100).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (query) => runTool(() => client.admin.snapshots.list(query)),\n );\n\n server.registerTool(\n 'snapshots_changes',\n {\n title: 'Changes between snapshots',\n description:\n 'Which entries were published, updated or unpublished between snapshot \"from\" and \"to\" (default: now). ' +\n 'Names entries, never values.',\n inputSchema: z.object({\n from: z.number().int().nonnegative(),\n to: z.number().int().nonnegative().optional(),\n after: z.string().optional().describe('nextCursor of the previous page'),\n limit: z.number().int().positive().max(500).optional(),\n }),\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (query) => runTool(() => client.snapshots.changes(query)),\n );\n\n server.registerTool(\n 'snapshots_restore',\n {\n title: 'Propose restoring a snapshot',\n description:\n 'Creates an open change set that would bring live content back to what snapshot N served. Nothing ' +\n 'changes until a person ships it; the schema is never rolled back.',\n inputSchema: z.object({ seq: z.number().int().positive() }),\n annotations: { destructiveHint: false, openWorldHint: false },\n },\n ({ seq }) => runTool(() => client.admin.snapshots.restore(seq)),\n );\n};\n","import { McpServer } from '@modelcontextprotocol/server';\nimport { createClient, type ShapioClient } from '@shapio/client';\nimport packageJson from '../package.json' with { type: 'json' };\nimport type { ToolContext } from './context.js';\nimport type { McpOptions } from './options.js';\nimport { registerPrompts } from './prompts.js';\nimport { registerResources } from './resources.js';\nimport { registerChangeSetTools } from './tools/changeSets.js';\nimport { registerContentTools } from './tools/content.js';\nimport { registerInsightTools } from './tools/insights.js';\nimport { registerMediaTools } from './tools/media.js';\nimport { registerSchemaTools } from './tools/schema.js';\nimport { registerSnapshotTools } from './tools/snapshots.js';\n\nexport const MCP_SERVER_NAME = 'shapio';\nexport const MCP_SERVER_VERSION: string = packageJson.version;\n\nconst INSTRUCTIONS =\n 'Shapio is a headless CMS whose content types are live data. Agents propose, people ship: draft schema ' +\n 'changes and entries, collect them in a change set, and leave shipping to a person. Model and field ' +\n 'identifiers are called \"API IDs\". Read shapio://docs/delivery-api for how sites read content.';\n\n/** The MCP server for one Shapio instance, with every tool, resource and prompt registered. */\nexport const createShapioMcpServer = (\n options: McpOptions,\n client: ShapioClient = createClient({\n baseUrl: options.baseUrl,\n token: options.token,\n ...(options.site !== undefined ? { site: options.site } : {}),\n }),\n): McpServer => {\n const server = new McpServer(\n { name: MCP_SERVER_NAME, version: MCP_SERVER_VERSION },\n { instructions: INSTRUCTIONS },\n );\n const context: ToolContext = { server, client, options };\n registerSchemaTools(context);\n registerContentTools(context);\n registerMediaTools(context);\n registerInsightTools(context);\n registerChangeSetTools(context);\n registerSnapshotTools(context);\n registerResources(context);\n registerPrompts(context);\n return server;\n};\n","import { resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\n/** How the server was started: the Shapio instance, its token, and the two guards. */\nexport type McpOptions = {\n /** Shapio's base URL including any BASE_PATH (`SHAPIO_URL`). */\n baseUrl: string;\n /** An admin-scope API token (`SHAPIO_TOKEN`). Give its role no `changes.ship`: shipping stays with people. */\n token: string;\n /** Registers `change_sets_ship` (`--allow-ship`). A client-side guard only: the token's role decides. */\n allowShip: boolean;\n /** `media_upload` reads files only under this directory (`--media-root`, default: the working directory). */\n mediaRoot: string;\n /**\n * The site key every request is about (`--site`, else `SHAPIO_SITE`), on multi-site instances. Left out:\n * the token's site (a site token), else the primary site. A site token cannot name another site.\n */\n site?: string;\n};\n\n/** A site key as Shapio creates them (sites are named by key in URLs, `?site=` and config). */\nconst SITE_KEY = /^[a-z][a-z0-9-]{0,62}$/;\n\nexport const USAGE = `Usage: shapio-mcp [--allow-ship] [--media-root <dir>] [--site <key>]\n\nA Model Context Protocol server (stdio) for a Shapio instance.\n\nEnvironment:\n SHAPIO_URL Shapio's base URL, including any BASE_PATH (e.g. https://cms.example.com)\n SHAPIO_TOKEN An admin-scope API token. Give its role no \"changes.ship\" permission: agents propose\n change sets, people ship them.\n SHAPIO_SITE The site key to work on (multi-site instances); --site overrides it. Default: the\n token's site, else the primary site.\n\nOptions:\n --allow-ship Offer the change_sets_ship tool (the token's role must also allow changes.ship)\n --media-root <dir> Directory media_upload may read files from (default: the working directory)\n --site <key> The site to work on (overrides SHAPIO_SITE)\n -h, --help Show this help\n`;\n\nexport class OptionsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'OptionsError';\n }\n}\n\n/** Reads the command line and environment; throws OptionsError with a message meant for stderr. */\nexport const parseOptions = (\n argv: readonly string[],\n env: Readonly<Record<string, string | undefined>>,\n cwd: string,\n): McpOptions | 'help' => {\n let parsed;\n try {\n parsed = parseArgs({\n args: [...argv],\n options: {\n 'allow-ship': { type: 'boolean', default: false },\n 'media-root': { type: 'string' },\n site: { type: 'string' },\n help: { type: 'boolean', short: 'h', default: false },\n },\n strict: true,\n allowPositionals: false,\n });\n } catch (error) {\n throw new OptionsError(error instanceof Error ? error.message : String(error));\n }\n if (parsed.values.help) {\n return 'help';\n }\n const baseUrl = env.SHAPIO_URL?.trim();\n const token = env.SHAPIO_TOKEN?.trim();\n if (!baseUrl || !/^https?:\\/\\//.test(baseUrl)) {\n throw new OptionsError('Set SHAPIO_URL to the Shapio base URL (http:// or https://).');\n }\n if (!token) {\n throw new OptionsError('Set SHAPIO_TOKEN to an admin-scope API token.');\n }\n const site = (parsed.values.site ?? env.SHAPIO_SITE)?.trim() || undefined;\n if (site !== undefined && !SITE_KEY.test(site)) {\n throw new OptionsError(`\"${site}\" is not a site key (lower case, starting with a letter).`);\n }\n return {\n baseUrl: baseUrl.replace(/\\/+$/, ''),\n token,\n allowShip: parsed.values['allow-ship'],\n mediaRoot: resolve(cwd, parsed.values['media-root'] ?? '.'),\n ...(site !== undefined ? { site } : {}),\n };\n};\n"],"mappings":"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"}
|