@typeroll/mcp-server 0.45.63 → 0.45.65
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 +24 -5
- package/README.md +5 -0
- package/dist/bundled-content.js +7 -7
- package/dist/server.js +3 -0
- package/dist/tools/access.js +36 -4
- package/dist/tools/apps.js +5 -2
- package/dist/tools/domain.js +8 -4
- package/dist/tools/migration.js +1 -1
- package/dist/tools/settings.js +2 -2
- package/dist/tools/sites.js +1 -1
- package/dist/tools/workflows.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/skills/tr-import-url.md +9 -2
- package/skills/tr-migrate-astro.md +7 -0
- package/skills/tr-migrate-multisite.md +10 -0
- package/skills/tr-migrate-wp.md +12 -0
- package/skills/tr-migration-evidence.md +31 -0
package/dist/server.js
CHANGED
|
@@ -94,6 +94,9 @@ function effectFor(name) {
|
|
|
94
94
|
|| name === 'revoke_site_share'
|
|
95
95
|
|| name === 'revoke_organization_api_key'
|
|
96
96
|
|| name === 'create_organization_invite'
|
|
97
|
+
|| name === 'set_organization_ai_key'
|
|
98
|
+
|| name === 'remove_organization_ai_key'
|
|
99
|
+
|| name === 'set_organization_ai_site_access'
|
|
97
100
|
|| name === 'disconnect_organization_publishing_provider'
|
|
98
101
|
|| name === 'connect_organization_cloudflare'
|
|
99
102
|
|| name === 'prepare_organization_media_storage'
|
package/dist/tools/access.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// Access tools — API keys, cross-organization site sharing
|
|
2
|
-
// invites
|
|
3
|
-
//
|
|
4
|
-
// further than it
|
|
1
|
+
// Access tools — API keys, cross-organization site sharing, organization
|
|
2
|
+
// invites and the Organization's own AI key for the portal assistant. Each
|
|
3
|
+
// wraps the authenticated public API with the same permission checks as the
|
|
4
|
+
// portal: a key never mints a key or a grant that reaches further than it
|
|
5
|
+
// does.
|
|
5
6
|
import { z } from 'zod';
|
|
6
7
|
import { ok, withErrorBoundary } from './helpers.js';
|
|
7
8
|
const keyId = z.string().regex(/^[a-f0-9]{12}$/).describe('Key id (the 12-hex prefix) from list_api_keys / list_organization_api_keys.');
|
|
@@ -72,4 +73,35 @@ export const accessTools = [
|
|
|
72
73
|
inputSchema: { ttl_days: z.number().int().min(1).max(30).optional() },
|
|
73
74
|
handler: withErrorBoundary(async (args, { client }) => ok(await client.rootPost('organization/invites', args))),
|
|
74
75
|
},
|
|
76
|
+
{
|
|
77
|
+
name: 'read_organization_ai_settings',
|
|
78
|
+
noSite: true,
|
|
79
|
+
description: 'Read the Organization\'s built-in AI assistant settings: whether its own Anthropic API key is set (last four characters, verified_at, who set it and when — never the key), site_access ({ mode: "all" | "selected", site_ids }) and every Site of the Organization with ai_enabled. The portal AI chat appears only on Sites with ai_enabled. This setting never limits MCP or the API: you can edit every Site you have access to. Requires an organization API key, or a personal connection in an Organization where the person is an owner or admin.',
|
|
80
|
+
inputSchema: {},
|
|
81
|
+
handler: withErrorBoundary(async (_args, { client }) => ok(await client.rootGet('organization/ai'))),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'set_organization_ai_key',
|
|
85
|
+
noSite: true,
|
|
86
|
+
description: 'Add or replace the Organization\'s own Anthropic API key for the portal\'s built-in AI assistant. Typeroll checks the key with Anthropic before saving: a rejected key returns 422 ai_key_rejected and an unreachable Anthropic 502 ai_key_unverified, and nothing is stored. The key is stored encrypted and never returned. A first key enables no Sites; replacing a key keeps the Site choice. Only call this with a key the user gave you for this purpose and their explicit go-ahead; prefer that an owner or admin enters it in the portal (Organization settings → AI assistant) so it does not pass through this conversation. Never repeat the key back. Requires an organization API key.',
|
|
87
|
+
inputSchema: { api_key: z.string().min(20).max(512).describe('The Anthropic API key (starts with sk-ant-).') },
|
|
88
|
+
handler: withErrorBoundary(async (args, { client }) => ok(await client.rootPut('organization/ai/key', { api_key: args.api_key }))),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'remove_organization_ai_key',
|
|
92
|
+
noSite: true,
|
|
93
|
+
description: 'Remove the Organization\'s Anthropic API key. The portal AI chat disappears from every Site and the Site choice starts over; MCP and the API are unaffected. Requires an organization API key and the user\'s explicit go-ahead.',
|
|
94
|
+
inputSchema: {},
|
|
95
|
+
handler: withErrorBoundary(async (_args, { client }) => ok(await client.rootDelete('organization/ai/key'))),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'set_organization_ai_site_access',
|
|
99
|
+
noSite: true,
|
|
100
|
+
description: 'Choose which of the Organization\'s own Sites show the portal AI chat: mode "all" (every Site, including Sites created later) or mode "selected" with site_ids from read_organization_ai_settings (an empty list turns it off everywhere). A Site shared in from another Organization follows that owner\'s setting. Needs a key first (409 ai_key_required). Requires an organization API key.',
|
|
101
|
+
inputSchema: {
|
|
102
|
+
mode: z.enum(['all', 'selected']),
|
|
103
|
+
site_ids: z.array(z.string()).max(1000).optional().describe('Required when mode is "selected".'),
|
|
104
|
+
},
|
|
105
|
+
handler: withErrorBoundary(async (args, { client }) => ok(await client.rootPut('organization/ai/site-access', args.mode === 'all' ? { mode: 'all' } : { mode: 'selected', site_ids: args.site_ids ?? [] }))),
|
|
106
|
+
},
|
|
75
107
|
];
|
package/dist/tools/apps.js
CHANGED
|
@@ -15,7 +15,8 @@ export const appTools = [
|
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
name: 'read_app',
|
|
18
|
-
description: 'Read one Typeroll app by registry id, including its field schema and masked state. Use list_apps to discover ids and required config fields. Secret values are never returned. For setup guidance without admin access, use read_app_documentation. Admin permission required.'
|
|
18
|
+
description: 'Read one Typeroll app by registry id, including its field schema and masked state. Use list_apps to discover ids and required config fields. Secret values are never returned. For setup guidance without admin access, use read_app_documentation. Admin permission required. ' +
|
|
19
|
+
'For analytics, `setup` reports its Cloudflare Web Analytics site: status "ready" with the account it lives in (`account.owner` "organization" = the Cloudflare account of the site\'s Hosting Group, used for customer publishing; "installation" = the installation\'s own account, used for managed and self-hosted sites), or "action_required" with `code`, `message`, `next_step` and optional `settings_url`.',
|
|
19
20
|
inputSchema: {
|
|
20
21
|
app_id: z.string().min(1).describe('Registry id returned by list_apps, for example analytics or integrations.'),
|
|
21
22
|
},
|
|
@@ -25,7 +26,9 @@ export const appTools = [
|
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
name: 'update_app',
|
|
28
|
-
description: 'Enable, configure, or disable any registered Typeroll app through the same admin API key used for publishing. Read the app first and send config keys from its field schema. Omitted fields preserve existing values, including encrypted secrets; secret values are masked on reads and encrypted server-side on writes.
|
|
29
|
+
description: 'Enable, configure, or disable any registered Typeroll app through the same admin API key used for publishing. Read the app first and send config keys from its field schema. Omitted fields preserve existing values, including encrypted secrets; secret values are masked on reads and encrypted server-side on writes. If affects_build is true, trigger_deploy is required to publish the change. For setup guidance without admin access, use read_app_documentation. Admin permission required. ' +
|
|
30
|
+
'Analytics: enabling creates the Cloudflare Web Analytics site in the account that hosts the site (customer publishing: the Hosting Group\'s Cloudflare connection, never the installation\'s account) and fills beacon_token and site_tag; a pasted beacon_token is resolved to its site_tag in that same account. ' +
|
|
31
|
+
'When no beacon could be set up, the call fails with HTTP 409, a stable `code` (for example analytics_cloudflare_access_required, analytics_cloudflare_connection_required, analytics_host_required, analytics_provider_error, analytics_token_not_in_account) and `details.next_step`; `details.saved` says whether the enabled state was stored. Relay the next step to the person (approving Analytics access needs an organization owner or admin in the browser), then call update_app again. A 200 response carries `setup` too.',
|
|
29
32
|
inputSchema: {
|
|
30
33
|
app_id: z.string().min(1).describe('Registry id returned by list_apps.'),
|
|
31
34
|
enabled: z.boolean(),
|
package/dist/tools/domain.js
CHANGED
|
@@ -190,22 +190,26 @@ export const domainTools = [
|
|
|
190
190
|
},
|
|
191
191
|
{
|
|
192
192
|
name: 'approve_publishing_domain_cutover',
|
|
193
|
-
description: 'Approve switching website traffic to the verified frozen deployment. Read publishing domains first and review DNS requirements
|
|
194
|
-
inputSchema: {
|
|
193
|
+
description: 'Approve switching website traffic to the verified frozen deployment. Read publishing domains first and review DNS requirements, certificate readiness and redirect_preparations. Existing traffic is preserved until validation succeeds. With automatic DNS, redirect hosts switch after the website host: Typeroll ensures their zone redirect rule, then changes their reviewed record to a proxied placeholder. The publication goes live only after every redirect host answers 301 to the website host with the same path and query. When preparation.validation_blocker.resolution is accept_validation_window, Cloudflare can validate the website host only after its one existing record points at Pages: approving with accept_validation_window: true lets Typeroll change that record, and the host does not answer until Cloudflare validates it (Cloudflare does not document how long; previous_records holds the rollback). Get the user\'s explicit go-ahead for that window and choose a quiet time. External DNS management remains the caller’s responsibility.',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
revision: z.string(), candidate_id: z.string(),
|
|
196
|
+
accept_validation_window: z.boolean().optional().describe('Accept the interruption until Cloudflare validates the website host after its single reviewed record changes. Only valid when preparation.validation_blocker.resolution is accept_validation_window.'),
|
|
197
|
+
},
|
|
195
198
|
handler: withErrorBoundary(async (args, { client, siteId }) => ok(await client.post(siteId, 'publishing/cutover', args))),
|
|
196
199
|
},
|
|
197
200
|
{
|
|
198
201
|
name: 'read_publishing_domains',
|
|
199
|
-
description: 'Read the desired and active website and media hosts, retained media aliases, DNS management mode and domain configuration revision. Saving intent does not switch traffic.',
|
|
202
|
+
description: 'Read the desired and active website, redirect and media hosts, retained media aliases, DNS management mode and domain configuration revision. host_checks lists each future host with its Cloudflare zone and structured blockers and warnings (code, host, message), for example apex_zone_required (an apex such as example.com needs an active Cloudflare zone in the hosting account), zone_not_active (the zone is pending until its nameservers change) or redirect_zone_required. Resolve blockers before preparing. preparation and redirect_preparations hold DNS records, the zone redirect rule and certificate state after preparation. Saving intent does not switch traffic.',
|
|
200
203
|
inputSchema: {},
|
|
201
204
|
handler: withErrorBoundary(async (_args, { client, siteId }) => ok(await client.get(siteId, 'publishing/domains'))),
|
|
202
205
|
},
|
|
203
206
|
{
|
|
204
207
|
name: 'set_publishing_domains',
|
|
205
|
-
description: 'Save future website and media hosts before preparing a deployment. Requires the current revision from read_publishing_domains. This does not publish content or change traffic DNS. Choose external DNS when your agent manages Cloudflare independently.',
|
|
208
|
+
description: 'Save future website, redirect and media hosts before preparing a deployment. Requires the current revision from read_publishing_domains. This does not publish content or change traffic DNS. Choose external DNS when your agent manages Cloudflare independently. redirect_hosts (for example www.example.com when example.com is the website host) answer 301 to the website host, keeping path and query, through a Single Redirect rule in the host\'s Cloudflare zone; they are switched and verified with the website. The response includes host_checks: resolve its blockers before preparing.',
|
|
206
209
|
inputSchema: {
|
|
207
210
|
revision: z.string(), website_host: z.string().nullable(), media_host: z.string().nullable(),
|
|
208
211
|
media_path_prefix: z.string().optional().describe('Empty on a separate media host; /media when using the website host.'),
|
|
212
|
+
redirect_hosts: z.array(z.string()).max(4).optional().describe('Hostnames that redirect permanently to website_host, such as www.example.com. Omit or send [] for none.'),
|
|
209
213
|
dns_mode: z.enum(['automatic', 'external']),
|
|
210
214
|
},
|
|
211
215
|
handler: withErrorBoundary(async (args, { client, siteId }) => ok(await client.put(siteId, 'publishing/domains', args))),
|
package/dist/tools/migration.js
CHANGED
|
@@ -233,7 +233,7 @@ export const migrationTools = [
|
|
|
233
233
|
},
|
|
234
234
|
{
|
|
235
235
|
name: 'get_migration_launch_report',
|
|
236
|
-
description: 'Return the launch decision report with separate inventory, exclusion-reason, internal-link, deployed URL parity, deploy freshness, reviewed SEO/content parity
|
|
236
|
+
description: 'Return the launch decision report with separate inventory, exclusion-reason, internal-link, deployed URL parity, deploy freshness, reviewed SEO/content parity, migration fidelity and script requirements. scripts lists copied source-platform scripts (WordPress plugins, theme, jQuery, page builders, cookie plugins), tracking code outside the consent gate, and analytics/advertising integrations without the cookie banner; each blocks launch. launch_ready is false when any evidence is absent, incomplete, failed, targets an old origin, or predates the latest hosted deploy. Use this final gate instead of treating zero unhandled URLs as launch readiness.',
|
|
237
237
|
handler: withErrorBoundary(async (_args, { client, siteId }) => {
|
|
238
238
|
const res = await client.get(siteId, 'migration-report');
|
|
239
239
|
return ok(res);
|
package/dist/tools/settings.js
CHANGED
|
@@ -64,8 +64,8 @@ export const settingsTools = [
|
|
|
64
64
|
sitewide_nofollow: z.boolean().optional().describe('Ask search engines not to follow links. Defaults to false.'),
|
|
65
65
|
image_sizes_default: z.string().optional().describe('Site-wide default `sizes` attribute for responsive-image <picture> output, e.g. "(max-width: 640px) 360px, 560px". Tells the browser how wide images actually render so it stops over-fetching the larger srcset variant. A page can override via its own image_sizes_default; a per-<img> `sizes` attribute wins over both. Leave unset for the generic "(max-width: 768px) 100vw, 800px".'),
|
|
66
66
|
// Scriptable surfaces. Trusted because the caller has an API key.
|
|
67
|
-
scripts_head: z.string().optional().describe('Raw HTML injected into <head> on every page. Use for
|
|
68
|
-
scripts_body_end: z.string().optional().describe('Raw HTML injected just before </body> on every page. Use for
|
|
67
|
+
scripts_head: z.string().optional().describe('Raw HTML injected into <head> on every page, before consent. Use for fonts and third-party CSS links. Not for analytics or advertising tags: set those by ID in the Integrations app with cookie_consent enabled (or, for a tag the catalog lacks, cookie_consent.scripts_optional). In a migration, never copy the source site\'s plugin, theme or WordPress scripts here.'),
|
|
68
|
+
scripts_body_end: z.string().optional().describe('Raw HTML injected just before </body> on every page, before consent. Use for small site-owned scripts. Not for analytics or advertising tags (Integrations app with cookie_consent enabled) or for scripts copied from the source site in a migration.'),
|
|
69
69
|
custom_css: z.string().optional().describe('Global CSS in a <style> in <head>, after block CSS and before the page\'s own custom CSS and the template base stylesheet (reset + global). Base rules for :root tokens (spacing, radius, container widths), body font and line-height load after it, so override those with a more specific selector (for example html:root or body.page) rather than a bare :root/body rule. Preview uses the same order. Lets you define site-wide design tokens (CSS variables, @media queries, :hover states) without inlining on every element. Prefer named styles (create_style) for anything a style can express. Syntax errors (unbalanced braces, unclosed comments or strings, expression()/javascript:) refuse the write; selectors on platform markup ([data-block], .block-*) come back as warnings because they can change between render versions — target s-<style> classes or block custom_class instead.'),
|
|
70
70
|
default_og_image: z.string().optional().describe('Site-wide fallback og:image URL for pages without their own social image. Empty string clears.'),
|
|
71
71
|
twitter_handle: z.string().optional().describe('Twitter/X handle for twitter:site, with or without the leading @ (stored without it). Empty string clears.'),
|
package/dist/tools/sites.js
CHANGED
|
@@ -53,7 +53,7 @@ export const siteTools = [
|
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
name: 'get_site_insights',
|
|
56
|
-
description: 'Read Analytics insights over the last 7, 30, or 90 days: traffic, AI-assistant referrals, and first-party conversion events grouped by event, destination, source, and campaign. Traffic is powered by Cloudflare Web Analytics; conversions come from validated attribution-funnel click events. The response carries a traffic `status`: "ok" with numbers, or "app_disabled" / "not_configured" / "no_site_tag" / "
|
|
56
|
+
description: 'Read Analytics insights over the last 7, 30, or 90 days: traffic, AI-assistant referrals, and first-party conversion events grouped by event, destination, source, and campaign. Traffic is powered by Cloudflare Web Analytics; conversions come from validated attribution-funnel click events. The response carries a traffic `status`: "ok" with numbers, "no_data" for a linked site without traffic, or "app_disabled" / "not_configured" / "no_site_tag" / "connection_required" / "access_required" / "account_mismatch" / "provider_error" with a `reason` ({code, message, next_step, settings_url}). Traffic is read from the same Cloudflare account the beacon belongs to (`account`): the site\'s Hosting Group account for customer publishing, the installation\'s account otherwise. Conversion data can still be present when the traffic provider is unavailable.',
|
|
57
57
|
inputSchema: {
|
|
58
58
|
days: z.union([z.literal(7), z.literal(30), z.literal(90)]).optional().describe('Look-back window in days (default 30).'),
|
|
59
59
|
},
|
package/dist/tools/workflows.js
CHANGED
|
@@ -51,7 +51,7 @@ export const workflowTools = [
|
|
|
51
51
|
{
|
|
52
52
|
name: 'create_site_and_plan',
|
|
53
53
|
noSite: true,
|
|
54
|
-
description: 'Create a new Site in the Organization and start AI site planning from a business description, like "Plan a new site with AI" on the New site page. Requires an organization API key. The plan pauses for review: read it with get_workflow and approve_workflow only after the user agrees.',
|
|
54
|
+
description: 'Create a new Site in the Organization and start AI site planning from a business description, like "Plan a new site with AI" on the New site page. Requires an organization API key. Needs the Organization\'s own AI key with site access "all" (read_organization_ai_settings); otherwise it answers 403 ai_not_enabled and creates nothing — then create a blank Site and do the planning yourself, or ask an owner or admin to enable it. The plan pauses for review: read it with get_workflow and approve_workflow only after the user agrees.',
|
|
55
55
|
inputSchema: {
|
|
56
56
|
name: z.string().min(1),
|
|
57
57
|
business_description: z.string().min(1).describe('What the business does, for whom, and what sets it apart.'),
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeroll/mcp-server",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.65",
|
|
4
4
|
"description": "Typeroll CMS MCP server – connect AI agents to the REST API to manage content and publish static websites.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"typescript": "^5.6.0",
|
|
50
|
-
"vitest": "^4.1.
|
|
50
|
+
"vitest": "^4.1.12"
|
|
51
51
|
},
|
|
52
52
|
"publishConfig": {
|
|
53
53
|
"access": "public"
|
package/skills/tr-import-url.md
CHANGED
|
@@ -5,6 +5,10 @@ description: Use when the user wants to import or migrate content from a non-Wor
|
|
|
5
5
|
|
|
6
6
|
# Import content from a non-WordPress site
|
|
7
7
|
|
|
8
|
+
**Who runs this:** this recipe is for the user's own AI agent (Claude Code, Codex or
|
|
9
|
+
another MCP client) working through MCP and the public API. The portal's built-in
|
|
10
|
+
AI assistant has no migration tools: it does not import, convert or verify sites.
|
|
11
|
+
|
|
8
12
|
**Before bulk conversion:** read `tr-migration-evidence` with `read_skill`.
|
|
9
13
|
Complete its source baseline and one verified prototype per active template.
|
|
10
14
|
Read target content to protect edits, not to justify accidental target defaults.
|
|
@@ -194,8 +198,11 @@ get_deploy_status job_id=<id>
|
|
|
194
198
|
several. Fix it.
|
|
195
199
|
- **Squarespace/Wix forms.** They won't work after import — the backend
|
|
196
200
|
is vendor-locked. Create a Typeroll form instead: `create_form`.
|
|
197
|
-
- **
|
|
198
|
-
|
|
201
|
+
- **Scripts and tracking.** Never copy the source's JavaScript. Carry
|
|
202
|
+
analytics and advertising tags (GA4, GTM, Meta Pixel and others) over by
|
|
203
|
+
ID in the Integrations app, with the cookie banner enabled so they wait for
|
|
204
|
+
consent; never paste the vendor snippet into `scripts_head`. See
|
|
205
|
+
tr-migration-evidence, "Scripts, tracking and consent".
|
|
199
206
|
- **Videos.** YouTube/Vimeo embeds are fine (`<iframe>` is allowed).
|
|
200
207
|
Hosted MP4s need re-uploading if the source URL won't persist.
|
|
201
208
|
|
|
@@ -5,6 +5,10 @@ description: Migrate an existing Astro site, including Astro Content Collections
|
|
|
5
5
|
|
|
6
6
|
# Migrate an Astro site
|
|
7
7
|
|
|
8
|
+
**Who runs this:** this recipe is for the user's own AI agent (Claude Code, Codex or
|
|
9
|
+
another MCP client) working through MCP and the public API. The portal's built-in
|
|
10
|
+
AI assistant has no migration tools: it does not import, convert or verify sites.
|
|
11
|
+
|
|
8
12
|
**Before bulk conversion:** read `tr-migration-evidence` with `read_skill`.
|
|
9
13
|
Complete its source baseline and one verified prototype per active template.
|
|
10
14
|
Read target content to protect edits, not to justify accidental target defaults.
|
|
@@ -50,6 +54,9 @@ assume that a filename alone determines the production URL.
|
|
|
50
54
|
- Browser scripts, integrations and server-rendered endpoints need separate
|
|
51
55
|
review. A static Page cannot inherit a private server runtime by copying its
|
|
52
56
|
source text. Configure the supported Forms/Extension runtime explicitly.
|
|
57
|
+
Do not copy client scripts; analytics and advertising tags move to the
|
|
58
|
+
Integrations app behind the cookie banner (tr-migration-evidence, "Scripts,
|
|
59
|
+
tracking and consent").
|
|
53
60
|
|
|
54
61
|
Create templates with `create_page_template`, types with `create_content_type`,
|
|
55
62
|
then one `create_page` per source record. Use returned Page IDs, not source IDs
|
|
@@ -5,6 +5,10 @@ description: Use when migrating SEVERAL sites at once — a WordPress multisite
|
|
|
5
5
|
|
|
6
6
|
# Migrate a multisite / multi-domain family to Typeroll
|
|
7
7
|
|
|
8
|
+
**Who runs this:** this recipe is for the user's own AI agent (Claude Code, Codex or
|
|
9
|
+
another MCP client) working through MCP and the public API. The portal's built-in
|
|
10
|
+
AI assistant has no migration tools: it does not import, convert or verify sites.
|
|
11
|
+
|
|
8
12
|
**Before bulk conversion:** read `tr-migration-evidence` with `read_skill`.
|
|
9
13
|
Complete its source baseline and one verified prototype per active template.
|
|
10
14
|
Read target content to protect edits, not to justify accidental target defaults.
|
|
@@ -163,6 +167,11 @@ language and attribution. Use authorized synthetic tests with a safe recipient;
|
|
|
163
167
|
record delivery UNVERIFIED when it cannot be tested safely. Never create real
|
|
164
168
|
leads or send mail to third parties as a routine migration test.
|
|
165
169
|
|
|
170
|
+
Scripts follow tr-migration-evidence, "Scripts, tracking and consent": copy no
|
|
171
|
+
source JavaScript, and carry each market's own analytics and advertising IDs
|
|
172
|
+
over in that site's Integrations app with its cookie banner enabled. Never
|
|
173
|
+
reuse one market's tracking IDs on another.
|
|
174
|
+
|
|
166
175
|
## Phase 5 — Redirects for everything you didn't preserve
|
|
167
176
|
|
|
168
177
|
Work the coverage report, not your memory:
|
|
@@ -271,6 +280,7 @@ new canonical build and is not an inventory-only operation):
|
|
|
271
280
|
- [ ] Complete `verify_migration_urls` → 0 `missing`, 0 `broken_redirect`, 0 `error`, 0 untested variants
|
|
272
281
|
- [ ] `record_migration_seo_acceptance` is accepted for the latest deploy
|
|
273
282
|
- [ ] `get_migration_launch_report` → `launch_ready: true`
|
|
283
|
+
- [ ] No source JavaScript copied; analytics/advertising via the Integrations app with the cookie banner on (`scripts.status: "clean"` in the launch report)
|
|
274
284
|
- [ ] hreflang cluster written on both/all sides, absolute, final domains
|
|
275
285
|
- [ ] `language` set on the site; `<html lang>` correct in the deployed HTML
|
|
276
286
|
- [ ] Internal links rewritten (no lingering absolute links to the old domain)
|
package/skills/tr-migrate-wp.md
CHANGED
|
@@ -5,6 +5,10 @@ description: Use when the user asks to migrate a WordPress site to Typeroll, men
|
|
|
5
5
|
|
|
6
6
|
# Migrate from WordPress to Typeroll
|
|
7
7
|
|
|
8
|
+
**Who runs this:** this recipe is for the user's own AI agent (Claude Code, Codex or
|
|
9
|
+
another MCP client) working through MCP and the public API. The portal's built-in
|
|
10
|
+
AI assistant has no migration tools: it does not import, convert or verify sites.
|
|
11
|
+
|
|
8
12
|
**Before bulk conversion:** read `tr-migration-evidence` with `read_skill`.
|
|
9
13
|
Complete its source baseline and one verified prototype per active template.
|
|
10
14
|
Read target content to protect edits, not to justify accidental target defaults.
|
|
@@ -146,6 +150,9 @@ a. Fetch from WP. Prefer the helper plugin's authenticated endpoint
|
|
|
146
150
|
b. Clean the HTML. Strip Elementor / Gutenberg / Breakdance class
|
|
147
151
|
soup. Drop empty `<div>` and `<span>` wrappers. Keep semantic tags,
|
|
148
152
|
tables, iframes from known hosts (YouTube / Vimeo / Calendly).
|
|
153
|
+
Remove every `<script>`: plugin and theme JavaScript is never copied.
|
|
154
|
+
Analytics and advertising tags move to the Integrations app behind the
|
|
155
|
+
cookie banner (tr-migration-evidence, "Scripts, tracking and consent").
|
|
149
156
|
|
|
150
157
|
c. Migrate referenced images with `upload_media_from_url`. The customer's
|
|
151
158
|
transfer Worker copies and verifies them directly in R2. Use the returned
|
|
@@ -291,6 +298,11 @@ get_deploy_status job_id=<id> # poll
|
|
|
291
298
|
- **Image bandwidth.** R2 upload is metered. Use `find_pages_matching`
|
|
292
299
|
contains="<old-domain>" on already-imported content to spot images
|
|
293
300
|
that weren't transferred.
|
|
301
|
+
- **Plugin JavaScript.** Never copy scripts from plugins, the theme or
|
|
302
|
+
WordPress core into settings, pages, partials or block types, and never
|
|
303
|
+
copy the cookie plugin. Carry analytics and advertising over by ID in the
|
|
304
|
+
Integrations app with `cookie_consent.enabled: true`. The launch report
|
|
305
|
+
fails on copied source scripts and on tracking outside consent.
|
|
294
306
|
- **WP-specific JSON-LD** (Yoast, Rank Math) is usually wrong after a
|
|
295
307
|
redesign because it references old URLs. Strip it; let Typeroll
|
|
296
308
|
emit fresh Article/Page schemas via `kind: 'article'` + `author`.
|
|
@@ -5,6 +5,10 @@ description: Source-first design baseline, native capability mapping and evidenc
|
|
|
5
5
|
|
|
6
6
|
# Migration evidence contract
|
|
7
7
|
|
|
8
|
+
**Who runs this:** this recipe is for the user's own AI agent (Claude Code, Codex or
|
|
9
|
+
another MCP client) working through MCP and the public API. The portal's built-in
|
|
10
|
+
AI assistant has no migration tools: it does not import, convert or verify sites.
|
|
11
|
+
|
|
8
12
|
Use this shared contract with WordPress, Astro, URL and multisite recipes.
|
|
9
13
|
Default to preserving the source appearance and behavior. A redesign requires
|
|
10
14
|
explicit scope. A partly migrated target is not the design reference.
|
|
@@ -115,6 +119,33 @@ interaction and conversion separately; only use an authorized synthetic mode and
|
|
|
115
119
|
safe recipient. Unavailable delivery remains UNVERIFIED. No real leads or mail
|
|
116
120
|
to third parties merely to fill a checklist.
|
|
117
121
|
|
|
122
|
+
## Scripts, tracking and consent
|
|
123
|
+
|
|
124
|
+
Do not copy JavaScript from the source site. WordPress plugins and themes
|
|
125
|
+
inject their own runtime: jQuery, sliders, lightboxes, form and cookie
|
|
126
|
+
plugins, page builders (Elementor, Breakdance, Divi), emoji and AJAX calls to
|
|
127
|
+
`admin-ajax.php` or `/wp-json/`. It loads from, or posts to, a site that is
|
|
128
|
+
about to be switched off. Inventory every `<script>` on representative source
|
|
129
|
+
pages (head and body, inline and external, and which plugin or theme it
|
|
130
|
+
belongs to), then decide each one:
|
|
131
|
+
|
|
132
|
+
| Source script | Decision |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| WordPress core, theme or plugin runtime | Never copy. Rebuild what it did with native blocks, Forms or settings, or report a platform gap (see "Report platform gaps"). |
|
|
135
|
+
| Cookie plugin (CookieYes, Complianz, Cookiebot, Borlabs, …) | Never copy. Enable the site's cookie banner (`cookie_consent.enabled: true`) with Swedish or local text and the privacy policy URL. |
|
|
136
|
+
| Analytics or advertising tag in the Integrations catalog (GA4, GTM, Meta Pixel, LinkedIn, TikTok, Hotjar, Clarity, Pinterest, Snap, Reddit, X, Microsoft Ads, HubSpot, Plausible, Fathom, Matomo) | Carry over the ID only: enable the Integrations app with the source's ID (`list_apps`, `update_app`). Enable the cookie banner; analytics and advertising integrations wait for the visitor's consent. Never paste the vendor snippet. |
|
|
137
|
+
| Tracking tag the catalog does not cover | Only with the owner's approval, in `cookie_consent.scripts_optional` with the banner enabled. Never in `scripts_head`, `scripts_body_end`, `scripts_necessary`, pages or partials. |
|
|
138
|
+
| Partner widget or embed the page needs to work | Prefer a native block or an iframe from an allowlisted host. Keep custom code minimal, record why, and do not load it from the source site. |
|
|
139
|
+
|
|
140
|
+
Ask the owner to confirm the analytics IDs; never invent or reuse one from
|
|
141
|
+
another market. Before launch, `get_migration_launch_report` must show
|
|
142
|
+
`scripts.status: "clean"`: it fails on copied source-platform scripts
|
|
143
|
+
(`source_runtime_script`), tracking outside consent
|
|
144
|
+
(`tracking_outside_consent`) and analytics or advertising integrations without
|
|
145
|
+
the banner (`tracking_without_consent_banner`). In the hosted preview, check
|
|
146
|
+
that no request goes to the source host or `/wp-content/`, no tracking request
|
|
147
|
+
fires before consent, and the tags fire after "accept all".
|
|
148
|
+
|
|
118
149
|
## Evidence record (repeat per recipe, viewport and state)
|
|
119
150
|
|
|
120
151
|
- Source URL/date and target URL; Page/template IDs; exact publication ID.
|