@typeroll/mcp-server 0.29.0 → 0.32.0

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/AGENTS.md CHANGED
@@ -197,6 +197,42 @@ maps to one HTTP endpoint; the actual logic runs in the customer's portal
197
197
  one `html` field rendered verbatim (then sanitized like HTML-mode
198
198
  content). Use it for the genuinely unique thing no block covers.
199
199
  Prefer real blocks when one fits.
200
+ - **Structured records at scale** (template_capabilities_version ≥ 0.31.0).
201
+ Four things landed together for directory-shaped sites:
202
+ - `collection_completeness` — **start an enrichment pass here**, not by
203
+ paging every item. Returns per-field gap counts plus the N worst
204
+ records (missing fields, never-verified fields, fields whose last write
205
+ is older than the staleness window), computed at read time. Fields no
206
+ API key may write are excluded by default: a gap you can't close is
207
+ noise.
208
+ - **Per-field write authority.** A collection field can declare
209
+ `writable_by` (`portal | owner | agent | app | import`). A write you're
210
+ not permitted, or one that would overwrite a higher-precedence writer
211
+ (a human correction, the listed business's own edit), comes back as
212
+ **409 with the losing field names** — never a silent no-op. Treat that
213
+ as "already handled" and record it; retrying will lose again.
214
+ - **Item references.** `item_ref` / `item_ref_list` fields point at items
215
+ in another collection (`ref_collection`). The reverse direction is
216
+ computed at render time — don't try to maintain backlinks yourself.
217
+ Render them with a `core/repeater` whose `source_type` is `related`
218
+ (a ref field on the current item) or `backlinks` (who points at it).
219
+ - **Taxonomy pages.** `CollectionDef.facets` generates one page per
220
+ distinct field value. ⚠️ This turns record count into ROUTE count, and
221
+ route count is what the build timeout measures. `min_items` (default 2)
222
+ keeps thin-content pages out, and combination pages must be listed
223
+ explicitly in `facet_combinations` — never assume a cartesian product.
224
+ - **`core/embed`** (template_capabilities_version ≥ 0.30.0) is
225
+ `core/html` plus behaviour: an `html` field and a `js` field. Reach
226
+ for it when a one-off placement needs JavaScript. **A `<script>` tag
227
+ written into `core/html` — or into any page/block markup — is stripped
228
+ by the sanitizer no matter which credential wrote it**, so this field
229
+ is the supported route, not a workaround. The code runs in an IIFE
230
+ with `el` bound to the block's root element and ships in the page's
231
+ block bundle, outside the sanitized body. Through an API key it's
232
+ accepted under your key's authority (audit-logged, notice in the
233
+ response). Scope guide: one placement → `core/embed`; a reusable
234
+ widget → `create_block_type` with `script`; a site-wide tag →
235
+ `settings.scripts_head` / `scripts_body_end`.
200
236
  - **Forms 2.0** (template_capabilities_version ≥ 0.18.0): forms can
201
237
  carry `steps[]` — each step is a Block[] tree mixing `form/*` field
202
238
  blocks (text/email/phone/number, textarea, select/radio_group/
@@ -239,6 +275,20 @@ maps to one HTTP endpoint; the actual logic runs in the customer's portal
239
275
  - **Deploys.** Customers see live changes only after a deploy. Preview
240
276
  always sees drafts. `trigger_deploy` enqueues; `get_deploy_status`
241
277
  reports `queued → running → succeeded | failed`.
278
+ `trigger_deploy dry_run=true` builds without publishing — use it to prove
279
+ a structural change compiles (new collection, schema edit, template
280
+ rewrite) without touching the live site.
281
+ A finished job carries `cost`: total, cpu/memory/request split,
282
+ `duration_s`, per-phase timings, and output size. Estimates from a rate
283
+ card, not billing records, and gross of free tier — quote them as "roughly"
284
+ if a customer asks, and reach for `cost.phases` when the question is *why*
285
+ a build got slow.
286
+
287
+ - **Is the site live?** There is no site-level status field —
288
+ `Site.status` was removed in 0.30.0 because it was set once at creation and
289
+ never advanced, so it reported live sites as "planning". Read
290
+ `get_site → urls.production` instead: non-null means the domain is verified
291
+ and serving. For "has anything shipped", use `list_deploys`.
242
292
 
243
293
  - **Site URLs.** `get_site` returns a `urls` object with:
244
294
  - `production` — the customer's real domain (or null)
@@ -606,6 +656,116 @@ Redirect hygiene is automatic in both directions (since 0.16.1):
606
656
  are removed (reported as `removed_redirects`). Manually created redirects
607
657
  are kept — delete them yourself via `delete_redirect` if they're obsolete.
608
658
 
659
+ ### "Before you start an import"
660
+
661
+ ```
662
+ get_migration_readiness
663
+ ```
664
+
665
+ Call this before moving any content. Every check it runs fails SILENTLY
666
+ otherwise — the import succeeds, previews render, the customer signs off, and
667
+ something is quietly wrong:
668
+
669
+ - **media storage** (blocker) — without it every `<img>` keeps its original
670
+ URL, so the new site is still served images by the old host. Nothing looks
671
+ broken until that hosting is cancelled, at which point every image on every
672
+ page breaks at once.
673
+ - **hosting adapter** (blocker) — without credentials, deploys return a job id
674
+ and publish nothing, while reporting success.
675
+ - verification origin, AI reconstruction, form notification email, and whether
676
+ the target actually has a design to rebuild INTO (warnings).
677
+
678
+ `ready: false` means STOP and report the blockers, each of which carries a
679
+ `fix`. Don't start "and fix it after": the content work would have to be
680
+ redone. The in-portal migration workflow enforces the same gate as its first
681
+ step (`skip_preflight: true` overrides it, and logs that it did).
682
+
683
+ ### "Don't lose URLs in a migration"
684
+
685
+ Two different questions, and you need both answers:
686
+
687
+ ```
688
+ list_migration_urls status="unhandled" # what the DATA says is uncovered
689
+ verify_migration_urls # what the SERVER actually answers
690
+ ```
691
+
692
+ `list_migration_urls` classifies every inventory URL against the site's
693
+ current pages + redirects. It's recomputed on read, so creating a redirect
694
+ flips the entry on your next call — no bookkeeping of your own.
695
+
696
+ `verify_migration_urls` requests each URL against the deployed site (its
697
+ fallback subdomain by default, because the real domain still points at the
698
+ old host pre-cutover) and reports `ok` / `ok_redirect` / `missing` /
699
+ `broken_redirect` / `error`. This is the one that catches a redirect
700
+ pointing at an unpublished page, a typo'd `path`, and redirect loops — all
701
+ of which read as "handled" in the coverage report and as a 404 to Googlebot.
702
+ **Deploy first**: it tests saved, deployed content, not your drafts.
703
+
704
+ Every unhandled URL gets exactly one of three outcomes — there is no fourth:
705
+
706
+ - it moved → `create_redirect`
707
+ - it's gone on purpose → `update_migration_url url_id=… excluded=true` (with
708
+ a note saying who signed off)
709
+ - it should exist → migrate it
710
+
711
+ Populate the inventory yourself when the in-portal WordPress migration
712
+ didn't: `add_migration_urls` takes up to 2000 entries from a sitemap walk, a
713
+ GSC export (pass `gsc_clicks` so the report prioritises itself), or a crawl.
714
+ Pass `source_origin` whenever more than one old domain is in play — it
715
+ rejects foreign-origin URLs, which is what stops one market's `/kontakt`
716
+ from reading as another market's coverage.
717
+
718
+ For a whole family of sites, read the `tr-migrate-multisite` skill.
719
+
720
+ ### "Retire a family of old URLs in one rule"
721
+
722
+ ```
723
+ create_redirect from_path="/category/*" to_path="/blogg/:splat"
724
+ create_redirect from_path="/blog/:slug" to_path="/artiklar/:slug"
725
+ ```
726
+
727
+ A trailing `*` captures everything under a prefix (including the prefix
728
+ itself) and `:splat` replays it; `:name` matches exactly one segment and is
729
+ replayed by name. This is the right tool after a WordPress migration, where
730
+ the dead URLs come in shapes — `/category/`, `/tag/`, `/author/`, `/2019/` —
731
+ and the inventory only knows the subset it happened to find.
732
+
733
+ Constraints, all enforced at write time rather than discovered in production:
734
+
735
+ - **Trailing `*` only.** Cloudflare silently drops a mid-path splat, so the
736
+ rule would save fine and do nothing.
737
+ - **`:splat` requires a `*`**, and `:name` in the target must be declared in
738
+ `from_path`.
739
+ - **Query strings can't be matched** — `_redirects` keys on the path. A
740
+ WordPress `/?p=123` URL has to be handled at the source.
741
+ - **A rule that would hide a live page is refused**, naming the pages.
742
+ Redirects are applied BEFORE static files, so `/blogg/*` makes every real
743
+ article under `/blogg/` unreachable. Narrow the prefix.
744
+
745
+ Rules are emitted most-specific-first, so `/blogg/recept/*` and `/blogg/*`
746
+ can coexist — the narrower one fires. `list_migration_urls` counts
747
+ pattern-covered URLs as `redirected`, so the coverage report reflects what
748
+ production will do.
749
+
750
+ ### "Link language versions together (hreflang)"
751
+
752
+ One Typeroll site owns one domain, so `example.se` / `example.de` /
753
+ `example.co.uk` are three sites. Nothing can derive which page corresponds
754
+ to which — declare it per page:
755
+
756
+ ```
757
+ update_page page_id=om-oss patch={ alternates: [
758
+ { hreflang: "de", href: "https://example.de/ueber-uns" },
759
+ { hreflang: "x-default", href: "https://example.com/about-us" }
760
+ ]}
761
+ ```
762
+
763
+ The renderer injects this page's own self-reference, so list only the OTHER
764
+ variants. Clusters must be **reciprocal** — write all sides, `batch_update_pages`
765
+ is the sane way. Use absolute URLs on the FINAL domains (never the
766
+ `*.typeroll` fallback). Invalid tags/hrefs are rejected at write time with
767
+ the reason rather than silently dropped at render.
768
+
609
769
  ### "Change the site's fallback URL (slug)"
610
770
 
611
771
  ```
@@ -777,7 +937,8 @@ preview.
777
937
  | **Block types** | `list_block_types`, `read_block_type`, `find_pages_using_block_type`, `export_block_types`, `import_block_types` |
778
938
  | **Collections** | `create_collection`, `update_collection_schema`, `delete_collection`, `list_collections`, `read_collection`, `list_collection_items` (richtext hidden by default), `read_collection_item`, `batch_read_collection_items`, `create_collection_item`, `update_collection_item`, `delete_collection_item`, `regenerate_collection_listing` |
779
939
  | **Media** | `list_media`, `read_media`, `create_upload_url`, `upload_media_from_url`, `upload_media_inline`, `update_media`, `delete_media`, `finalize_media`, `finalize_all_media`, `generate_image_variants`, `suggest_alt_text_context` |
780
- | **Redirects** | `list_redirects`, `create_redirect`, `delete_redirect` |
940
+ | **Redirects** | `list_redirects`, `create_redirect`, `delete_redirect`. `from_path` may be a PATTERN: a trailing `*` (with `:splat` in the target) or `:name` for one segment — one rule retires a whole family of dead URLs (`/category/*` → `/blogg/:splat`). Mid-path splats and query strings are refused, as is any rule that would hide a live page. |
941
+ | **Migration inventory** | `get_migration_readiness` (preflight — CALL FIRST on any import), `list_migration_urls`, `add_migration_urls`, `update_migration_url`, `delete_migration_url`, `verify_migration_urls`. The legacy site's URL list with LIVE coverage status (`migrated` / `redirected` / `excluded` / `unhandled`, recomputed on every read from current pages + redirects). `verify_migration_urls` is the pre-cutover check that REQUESTS every URL against the deployed site — see "Don't lose URLs in a migration" below. |
781
942
  | **Forms** | `list_forms`, `read_form`, `create_form`, `update_form`, `delete_form`, `list_form_submissions`, `delete_form_submission` (removes one submission — e.g. cleaning up a test entry; `delete_form` with `delete_submissions` is the bulk path). **Steps (form/* block trees) are the ONLY stored model** (0.29.0+, `forms_steps_only` capability): pass `steps` for funnels, or `fields` for simple forms — the server converts a flat field list to a single static step (read_form shows the resulting steps). **Placing a form: prefer a `core/form` block with `data.form_id`** — it renders every form with validation + signed token handled. The raw `<form method="POST">` embed with hidden `_token` + honeypot `_hp` (from read/create's `submit_token`/`submit_url`) is the HTML-mode fallback, single-step forms only; no client JS (the sanitizer strips inline `<script>`) |
782
943
  | **Settings** | `update_site_settings` (whitelist) |
783
944
  | **Search + bulk** | `search_pages`, `bulk_replace_text` |
package/README.md CHANGED
@@ -152,7 +152,9 @@ the full reference + concrete operation recipes.
152
152
  - **Bulk** — `bulk_replace_text` with dry-run.
153
153
  - **Branches** — create, read, delete, merge. Branch deploys get their
154
154
  own URL at `{branch}.{project}.pages.dev`.
155
- - **Deploy** — trigger, list, get status.
155
+ - **Deploy** — trigger (with `dry_run` to build without publishing), list, get
156
+ status. A finished job reports `cost`: what the build consumed in server
157
+ time, broken down per phase. Estimates from a rate card, not billing records.
156
158
  - **Preview** — `get_preview_link` (signed URL for browser navigation;
157
159
  supports `page_id`, `slug`, or `collection_name + item_id`; pass
158
160
  `include_working_copy: true` to also render unsaved drafts).
@@ -189,9 +191,12 @@ The MCP server is purely an ergonomics layer on top of that.
189
191
  - **HTML sanitization** happens at save time on the server — `<script>`,
190
192
  event handlers, and `javascript:` URLs are stripped from page/partial
191
193
  content (including `core/html` block output). The scriptable surfaces
192
- (`scripts_*`, `custom_css`, block-type `script`) are deliberate
193
- exceptions: the first are writable with an API key, and block-type
194
- scripts additionally require the site's per-site opt-in.
194
+ are deliberate exceptions, and all of them are writable with an API key
195
+ under the key holder's own authority: `scripts_*` and `custom_css` on
196
+ the site settings, `script` on a block type, and the `js` field of a
197
+ `core/embed` block instance. Those writes are audit-logged and the
198
+ response carries a notice naming the stored JS. Only the in-portal chat
199
+ assistant is additionally gated, on a per-site opt-in.
195
200
  - Keys can be **revoked** at any time from the portal. Revocation takes
196
201
  effect on the next request (no in-flight requests get cancelled, but
197
202
  the next one returns 401).