@typeroll/mcp-server 0.31.0 → 0.33.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
@@ -84,6 +84,11 @@ maps to one HTTP endpoint; the actual logic runs in the customer's portal
84
84
  token authorises shipping arbitrary CSS/JS to the live site, just
85
85
  like editing a partial's HTML does).
86
86
 
87
+ - **Funnel attribution.** `read_funnel_attribution` and
88
+ `update_funnel_attribution` manage validated, site-level rules that forward
89
+ allowlisted query parameters to exact HTTPS link targets. Optional
90
+ first-/last-touch storage is consent-gated and off unless configured.
91
+
87
92
  - **Page templates.** A `PageTemplate` is a Block[] tree that wraps a
88
93
  page's body. The template contains exactly one block of type
89
94
  `template_content_slot` — at render time that block gets replaced by
@@ -656,6 +661,116 @@ Redirect hygiene is automatic in both directions (since 0.16.1):
656
661
  are removed (reported as `removed_redirects`). Manually created redirects
657
662
  are kept — delete them yourself via `delete_redirect` if they're obsolete.
658
663
 
664
+ ### "Before you start an import"
665
+
666
+ ```
667
+ get_migration_readiness
668
+ ```
669
+
670
+ Call this before moving any content. Every check it runs fails SILENTLY
671
+ otherwise — the import succeeds, previews render, the customer signs off, and
672
+ something is quietly wrong:
673
+
674
+ - **media storage** (blocker) — without it every `<img>` keeps its original
675
+ URL, so the new site is still served images by the old host. Nothing looks
676
+ broken until that hosting is cancelled, at which point every image on every
677
+ page breaks at once.
678
+ - **hosting adapter** (blocker) — without credentials, deploys return a job id
679
+ and publish nothing, while reporting success.
680
+ - verification origin, AI reconstruction, form notification email, and whether
681
+ the target actually has a design to rebuild INTO (warnings).
682
+
683
+ `ready: false` means STOP and report the blockers, each of which carries a
684
+ `fix`. Don't start "and fix it after": the content work would have to be
685
+ redone. The in-portal migration workflow enforces the same gate as its first
686
+ step (`skip_preflight: true` overrides it, and logs that it did).
687
+
688
+ ### "Don't lose URLs in a migration"
689
+
690
+ Two different questions, and you need both answers:
691
+
692
+ ```
693
+ list_migration_urls status="unhandled" # what the DATA says is uncovered
694
+ verify_migration_urls # what the SERVER actually answers
695
+ ```
696
+
697
+ `list_migration_urls` classifies every inventory URL against the site's
698
+ current pages + redirects. It's recomputed on read, so creating a redirect
699
+ flips the entry on your next call — no bookkeeping of your own.
700
+
701
+ `verify_migration_urls` requests each URL against the deployed site (its
702
+ fallback subdomain by default, because the real domain still points at the
703
+ old host pre-cutover) and reports `ok` / `ok_redirect` / `missing` /
704
+ `broken_redirect` / `error`. This is the one that catches a redirect
705
+ pointing at an unpublished page, a typo'd `path`, and redirect loops — all
706
+ of which read as "handled" in the coverage report and as a 404 to Googlebot.
707
+ **Deploy first**: it tests saved, deployed content, not your drafts.
708
+
709
+ Every unhandled URL gets exactly one of three outcomes — there is no fourth:
710
+
711
+ - it moved → `create_redirect`
712
+ - it's gone on purpose → `update_migration_url url_id=… excluded=true` (with
713
+ a note saying who signed off)
714
+ - it should exist → migrate it
715
+
716
+ Populate the inventory yourself when the in-portal WordPress migration
717
+ didn't: `add_migration_urls` takes up to 2000 entries from a sitemap walk, a
718
+ GSC export (pass `gsc_clicks` so the report prioritises itself), or a crawl.
719
+ Pass `source_origin` whenever more than one old domain is in play — it
720
+ rejects foreign-origin URLs, which is what stops one market's `/kontakt`
721
+ from reading as another market's coverage.
722
+
723
+ For a whole family of sites, read the `tr-migrate-multisite` skill.
724
+
725
+ ### "Retire a family of old URLs in one rule"
726
+
727
+ ```
728
+ create_redirect from_path="/category/*" to_path="/blogg/:splat"
729
+ create_redirect from_path="/blog/:slug" to_path="/artiklar/:slug"
730
+ ```
731
+
732
+ A trailing `*` captures everything under a prefix (including the prefix
733
+ itself) and `:splat` replays it; `:name` matches exactly one segment and is
734
+ replayed by name. This is the right tool after a WordPress migration, where
735
+ the dead URLs come in shapes — `/category/`, `/tag/`, `/author/`, `/2019/` —
736
+ and the inventory only knows the subset it happened to find.
737
+
738
+ Constraints, all enforced at write time rather than discovered in production:
739
+
740
+ - **Trailing `*` only.** Cloudflare silently drops a mid-path splat, so the
741
+ rule would save fine and do nothing.
742
+ - **`:splat` requires a `*`**, and `:name` in the target must be declared in
743
+ `from_path`.
744
+ - **Query strings can't be matched** — `_redirects` keys on the path. A
745
+ WordPress `/?p=123` URL has to be handled at the source.
746
+ - **A rule that would hide a live page is refused**, naming the pages.
747
+ Redirects are applied BEFORE static files, so `/blogg/*` makes every real
748
+ article under `/blogg/` unreachable. Narrow the prefix.
749
+
750
+ Rules are emitted most-specific-first, so `/blogg/recept/*` and `/blogg/*`
751
+ can coexist — the narrower one fires. `list_migration_urls` counts
752
+ pattern-covered URLs as `redirected`, so the coverage report reflects what
753
+ production will do.
754
+
755
+ ### "Link language versions together (hreflang)"
756
+
757
+ One Typeroll site owns one domain, so `example.se` / `example.de` /
758
+ `example.co.uk` are three sites. Nothing can derive which page corresponds
759
+ to which — declare it per page:
760
+
761
+ ```
762
+ update_page page_id=om-oss patch={ alternates: [
763
+ { hreflang: "de", href: "https://example.de/ueber-uns" },
764
+ { hreflang: "x-default", href: "https://example.com/about-us" }
765
+ ]}
766
+ ```
767
+
768
+ The renderer injects this page's own self-reference, so list only the OTHER
769
+ variants. Clusters must be **reciprocal** — write all sides, `batch_update_pages`
770
+ is the sane way. Use absolute URLs on the FINAL domains (never the
771
+ `*.typeroll` fallback). Invalid tags/hrefs are rejected at write time with
772
+ the reason rather than silently dropped at render.
773
+
659
774
  ### "Change the site's fallback URL (slug)"
660
775
 
661
776
  ```
@@ -827,9 +942,11 @@ preview.
827
942
  | **Block types** | `list_block_types`, `read_block_type`, `find_pages_using_block_type`, `export_block_types`, `import_block_types` |
828
943
  | **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` |
829
944
  | **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` |
830
- | **Redirects** | `list_redirects`, `create_redirect`, `delete_redirect` |
945
+ | **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. |
946
+ | **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. |
831
947
  | **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>`) |
832
948
  | **Settings** | `update_site_settings` (whitelist) |
949
+ | **Funnel attribution** | `read_funnel_attribution`, `update_funnel_attribution` (admin; redeploy after changes) |
833
950
  | **Search + bulk** | `search_pages`, `bulk_replace_text` |
834
951
  | **Branches** | `create_branch`, `read_version`, `delete_branch`, `merge_branch` |
835
952
  | **Deploy** | `trigger_deploy`, `list_deploys`, `get_deploy_status` |