@typeroll/mcp-server 0.31.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 +112 -1
- package/dist/bundled-content.js +3 -2
- package/dist/server.js +2 -0
- package/dist/tools/migration.js +112 -0
- package/dist/tools/pages.js +10 -0
- package/dist/tools/redirects.js +9 -3
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/README.md +1 -0
- package/skills/tr-migrate-multisite.md +348 -0
- package/skills/tr-migrate-wp.md +71 -6
package/AGENTS.md
CHANGED
|
@@ -656,6 +656,116 @@ Redirect hygiene is automatic in both directions (since 0.16.1):
|
|
|
656
656
|
are removed (reported as `removed_redirects`). Manually created redirects
|
|
657
657
|
are kept — delete them yourself via `delete_redirect` if they're obsolete.
|
|
658
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
|
+
|
|
659
769
|
### "Change the site's fallback URL (slug)"
|
|
660
770
|
|
|
661
771
|
```
|
|
@@ -827,7 +937,8 @@ preview.
|
|
|
827
937
|
| **Block types** | `list_block_types`, `read_block_type`, `find_pages_using_block_type`, `export_block_types`, `import_block_types` |
|
|
828
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` |
|
|
829
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` |
|
|
830
|
-
| **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. |
|
|
831
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>`) |
|
|
832
943
|
| **Settings** | `update_site_settings` (whitelist) |
|
|
833
944
|
| **Search + bulk** | `search_pages`, `bulk_replace_text` |
|