@typeroll/mcp-server 0.45.76 → 0.45.79

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.
Files changed (43) hide show
  1. package/AGENTS.md +8 -2
  2. package/README.md +18 -0
  3. package/dist/bundled-content.js +35 -10
  4. package/dist/init.js +1 -1
  5. package/dist/tools/deploy.js +2 -1
  6. package/dist/tools/media.js +1 -1
  7. package/dist/tools/migration.js +4 -2
  8. package/dist/tools/pages.js +1 -1
  9. package/dist/tools/preview.js +1 -1
  10. package/dist/tools/working-copy.js +1 -1
  11. package/dist/version.js +1 -1
  12. package/dist/workspace.js +34 -10
  13. package/package.json +3 -2
  14. package/skills/tr-header-footer.md +95 -41
  15. package/skills/tr-import-url.md +6 -6
  16. package/skills/tr-migrate-astro.md +3 -3
  17. package/skills/tr-migrate-multisite.md +4 -4
  18. package/skills/tr-migrate-wp.md +28 -12
  19. package/skills/tr-migration-evidence.md +35 -23
  20. package/skills/tr-new-site.md +5 -7
  21. package/workspace-templates/brief/interview.md +18 -0
  22. package/workspace-templates/brief/migration.md +42 -0
  23. package/workspace-templates/qa/checklist.md +35 -0
  24. package/workspace-templates/qa/typeroll-platform-feedback.md +11 -0
  25. package/workspace-templates/scripts/README.md +85 -0
  26. package/workspace-templates/scripts/typeroll/browser.cjs +50 -0
  27. package/workspace-templates/scripts/typeroll/call.py +12 -0
  28. package/workspace-templates/scripts/typeroll/capture_text.cjs +17 -0
  29. package/workspace-templates/scripts/typeroll/card-stacking-check.cjs +27 -0
  30. package/workspace-templates/scripts/typeroll/check_assets.py +72 -0
  31. package/workspace-templates/scripts/typeroll/check_unsaved.py +13 -0
  32. package/workspace-templates/scripts/typeroll/check_url_parity.py +74 -0
  33. package/workspace-templates/scripts/typeroll/compare_text.py +59 -0
  34. package/workspace-templates/scripts/typeroll/contact_sheet.py +20 -0
  35. package/workspace-templates/scripts/typeroll/layout_trace.cjs +33 -0
  36. package/workspace-templates/scripts/typeroll/shoot_hosted.cjs +26 -0
  37. package/workspace-templates/scripts/typeroll/shootframe.cjs +16 -0
  38. package/workspace-templates/scripts/typeroll/sweep_blocks.example.py +23 -0
  39. package/workspace-templates/scripts/typeroll/trlib.py +212 -0
  40. package/workspace-templates/workflows/design.md +30 -0
  41. package/workspace-templates/workflows/interview.md +72 -0
  42. package/workspace-templates/workflows/migration.md +84 -0
  43. package/workspace-templates/workflows/new-site.md +17 -0
package/dist/init.js CHANGED
@@ -48,7 +48,7 @@ export async function runInitCli(args) {
48
48
  try {
49
49
  const result = await runInit({ dir, update, recipes, client });
50
50
  console.log(JSON.stringify(result, null, 2));
51
- console.log('Next: fill brief/, connect hosted MCP at https://app.typeroll.com/api/mcp?tools=compact, create/select your Site, record its IDs/Version in typeroll.json, then run typeroll doctor. Local MCP uses an injected TYPEROLL_API_KEY; no key is stored by init. See README.md.');
51
+ console.log('Next: read AGENTS.md. For guidance, start/resume workflows/interview.md; otherwise use workflows/new-site.md or workflows/migration.md. Record decisions in brief/, connect hosted MCP at https://app.typeroll.com/api/mcp?tools=compact, create/select your Site, record its IDs/Version in typeroll.json, then run typeroll doctor. Local MCP uses an injected TYPEROLL_API_KEY; no key is stored by init. See README.md.');
52
52
  return 0;
53
53
  }
54
54
  catch {
@@ -15,6 +15,7 @@ export const deployTools = [
15
15
  description: "Enqueue a deploy (static-page build → hosting) of the currently active version. Returns a job_id immediately; poll get_deploy_status until the status leaves queued/running. NOT for previewing iterative design/content edits — that's what get_preview_link is for (it renders live from the DB with no build). Reserve trigger_deploy for publishing, a stakeholder link to the compiled site, or pre-merge validation. Deploys build SAVED content only — unsaved drafts (working copies) are excluded, so commit_working_copy (or save:true on your writes) before deploying, or your changes won't ship. Customer publishing freezes a consistent saved source, then resumes source/Git/build-dispatch checkpoints without recapturing later edits. GitHub or Cloudflare performs rendering and media work. With dry_run:true, customer publishing validates frozen source without a Git push or external build; it does not prove an Astro build succeeds. Other self-hosted adapters may build locally without upload. Check the reported phase.",
16
16
  inputSchema: {
17
17
  environment: z.enum(['production', 'staging']).optional(),
18
+ allow_unsaved: z.boolean().optional().describe('Explicitly publish saved content while leaving the reported unsaved Pages/partials untouched. Only after owner confirmation.'),
18
19
  dry_run: z.boolean().optional().describe('Validate without publishing. Customer publishing checks frozen source only; other adapters may build locally without upload.'),
19
20
  version: versionParam,
20
21
  },
@@ -40,7 +41,7 @@ export const deployTools = [
40
41
  },
41
42
  {
42
43
  name: 'get_deploy_status',
43
- description: "Status of a single deploy job. Returns { job, queued_for_seconds, queue_timeout_seconds }. job.status is one of queued | running | succeeded | failed. On a failed build, job.error names the failed step and the cause the step printed, job.failure.code is the reported code (e.g. media_build_process_exit_1) and job.failure.diagnostic holds { cause, lines } (the last 40 lines that step printed, credentials removed). On the Cloudflare build engine job.failure.provider_log also holds { step, build_id, summary, lines } from the provider's build log (last 60 lines). Read them before retrying; retrying does not fix a cause that is in the content or the build engine. Polling cadence: ~5s for queued/running. job.render_report, when present, reports actual HTML mode (full/partial), rendered, reused, total and removed route counts plus a fallback reason. From Core 0.2.17, custom blocks and consumed image metadata are tracked per route; older caches require one full build. It does not measure media or provider startup savings. job.coordinator, when present, reports the last completed CMS phase and duration_ms for that attempt; external build waiting is excluded. Keep observing the same job across checkpoints; do not trigger duplicate deploys. A job stuck in 'queued' for longer than queue_timeout_seconds (default 300s) auto-flips to failed with phase='queue_timeout' — so a single poll past that timestamp returns the terminal state and you can stop polling.",
44
+ description: "Status of a single deploy job. Optional top-level build_progress reports the exact task status, attempt, step and completed/total files; not an overall percentage or ETA. Returns { job, queued_for_seconds, queue_timeout_seconds }. job.status is one of queued | running | succeeded | failed. On a failed build, job.error names the failed step and the cause the step printed, job.failure.code is the reported code (e.g. media_build_process_exit_1) and job.failure.diagnostic holds { cause, lines } (the last 40 lines that step printed, credentials removed). On the Cloudflare build engine job.failure.provider_log also holds { step, build_id, summary, lines } from the provider's build log (last 60 lines). Read them before retrying; retrying does not fix a cause that is in the content or the build engine. Polling cadence: ~5s for queued/running. job.render_report, when present, reports actual HTML mode (full/partial), rendered, reused, total and removed route counts plus a fallback reason. From Core 0.2.17, custom blocks and consumed image metadata are tracked per route; older caches require one full build. It does not measure media or provider startup savings. job.coordinator, when present, reports the last completed CMS phase and duration_ms for that attempt; external build waiting is excluded. Keep observing the same job across checkpoints; do not trigger duplicate deploys. A job stuck in 'queued' for longer than queue_timeout_seconds (default 300s) auto-flips to failed with phase='queue_timeout' — so a single poll past that timestamp returns the terminal state and you can stop polling.",
44
45
  inputSchema: { job_id: z.string(), include_provider: z.boolean().optional().describe('Also read the exact Git commit and Cloudflare build stages for a customer Git publication. Does not return build secrets.') },
45
46
  handler: withErrorBoundary(async (args, { client, siteId }) => {
46
47
  const res = await client.get(siteId, `deploys/${encodeURIComponent(args.job_id)}`);
@@ -206,7 +206,7 @@ export const mediaTools = [
206
206
  },
207
207
  {
208
208
  name: 'finalize_media',
209
- description: 'Post-upload finalize for a single image: (1) applies immutable Cache-Control (public, max-age=31536000) to the R2 original via in-place CopyObject so the CDN caches it for a year, and (2) generates AVIF/WebP responsive variants if not already present. Call this immediately after a successful upload (the presigned PUT does NOT set cache headers — without finalize, Cloudflare defaults to ~4h and every visit re-fetches the original). Idempotent: safe to call twice. Skips variant work when variants already exist on the doc. INTEGRITY: the result always includes the stored original\'s sha256 + size_bytes — compare against your source file to catch transit corruption before referencing the asset. Better: pass expected_sha256 (hash the file yourself first, e.g. `shasum -a 256 file`) and the server rejects a mismatch with a 422 so corrupted bytes never get blessed with immutable cache headers. Truncated SVGs are rejected outright.',
209
+ description: 'Post-upload finalize for a single image: (1) applies immutable Cache-Control (public, max-age=31536000) to the R2 original via in-place CopyObject so the CDN caches it for a year, and (2) generates AVIF/WebP responsive variants if not already present. Call this immediately after a successful upload (the presigned PUT does NOT set cache headers — without finalize, Cloudflare defaults to ~4h and every visit re-fetches the original). Idempotent: safe to call twice. Skips variant work when variants already exist on the doc. INTEGRITY: the response is { ok, result: { sha256, size_bytes, ... } }; read result.sha256 and result.size_bytes for the stored original — compare against your source file to catch transit corruption before referencing the asset. Better: pass expected_sha256 (hash the file yourself first, e.g. `shasum -a 256 file`) and the server rejects a mismatch with a 422 so corrupted bytes never get blessed with immutable cache headers. Truncated SVGs are rejected outright.',
210
210
  inputSchema: {
211
211
  media_id: z.string(),
212
212
  expected_sha256: z.string().regex(/^[0-9a-fA-F]{64}$/).optional()
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { ok, withErrorBoundary, versionParam } from './helpers.js';
3
- const STATUS = z.enum(['migrated', 'redirected', 'excluded', 'unhandled']);
3
+ const STATUS = z.enum(['migrated', 'redirected', 'excluded', 'unhandled', 'matched_draft']);
4
4
  const PLAIN_TEXT_REPAIR_FIELD = z.enum(['title', 'seo_title', 'seo_description', 'excerpt']);
5
5
  export const migrationTools = [
6
6
  {
@@ -8,6 +8,8 @@ export const migrationTools = [
8
8
  description: 'Import an explicit sitemap URL into the migration URL inventory. Sitemap indexes are followed recursively; URLs outside source_origin are rejected and reported.',
9
9
  inputSchema: {
10
10
  url: z.string().url().describe('Absolute URL of a sitemap or sitemap index.'),
11
+ include_wordpress: z.boolean().optional().describe('Also enumerate public WordPress REST pages/posts/types. No helper installation required; incomplete discovery is reported.'),
12
+ crawl_pages: z.number().int().min(0).max(50).optional().describe('With include_wordpress, crawl up to this many source pages for same-origin legacy links. Default 0; bounded discovery is not an authoritative old-slug export.'),
11
13
  source_origin: z.string().url().optional().describe('Expected legacy-site origin. Defaults to the sitemap origin.'),
12
14
  },
13
15
  handler: withErrorBoundary(async (args, { client, siteId }) => {
@@ -69,7 +71,7 @@ export const migrationTools = [
69
71
  },
70
72
  {
71
73
  name: 'list_migration_urls',
72
- description: "The legacy site's URL inventory with LIVE coverage status. Every entry is classified on read against the site's current pages + redirects: `migrated` (a Page answers at that path), `redirected` (a redirect rule covers it), `excluded` (signed off as an intentional 404), `unhandled` (nothing covers it — the work list). Returns a summary over the whole inventory plus a page of entries, sorted worst-first then by GSC clicks. Use `status: \"unhandled\"` to get exactly what's left to do before cutover. Coverage is computed, never stored, so it's current the moment you create a redirect.",
74
+ description: "The legacy site's URL inventory with LIVE coverage status. Every entry is classified on read against the site's current pages + redirects: `migrated` (a Page answers at that path), `redirected` (a redirect rule covers it), `excluded` (signed off as an intentional 404), `matched_draft` (a saved but unpublished Page owns the path; not launch-ready), `unhandled` (nothing covers it — the work list). Returns a summary over the whole inventory plus a page of entries, sorted worst-first then by GSC clicks. Use `status: \"unhandled\"` to get exactly what's left to do before cutover. Coverage is computed, never stored, so it's current the moment you create a redirect.",
73
75
  inputSchema: {
74
76
  status: STATUS.optional().describe('Only return entries with this coverage status.'),
75
77
  limit: z.number().int().positive().max(1000).optional().describe('Default 200.'),
@@ -223,7 +223,7 @@ export const pageTools = [
223
223
  },
224
224
  {
225
225
  name: 'batch_update_pages',
226
- description: 'Apply per-page patches in one call (up to 200 entries). Each entry is { page_id, patch, save?, answer_sources? }; failures are reported per-row, the rest still apply. BUFFER MODEL: content patches land in each page\'s unsaved draft; per-entry save:true (or the top-level save flag) commits — typical for a user-approved sweep. content_mode is rejected per-row; switch each page with set_page_mode.',
226
+ description: 'Apply per-page patches in one call (up to 200 entries). Each entry is { page_id, patch, save?, answer_sources? }; failures are reported per-row, the rest still apply. BUFFER MODEL: content patches land in each page\'s unsaved draft; per-entry save:true (or the top-level save flag) commits — typical for a user-approved sweep. Returns results with saved (whether this patch reached saved storage), staged (draft field names), and applied_immediately (status/schedule field names). A status-only patch is saved immediately and does not commit earlier drafts. content_mode is rejected per-row; switch each page with set_page_mode.',
227
227
  inputSchema: {
228
228
  updates: z
229
229
  .array(z.object({
@@ -17,7 +17,7 @@ export const previewTools = [
17
17
  },
18
18
  {
19
19
  name: 'get_preview_link',
20
- description: 'Mint a signed URL the user (or your own browser tool) can open to SEE the rendered preview. Renders LIVE from the database with NO build — this is the PREFERRED way to preview design/content changes as you iterate; reach for this, not trigger_deploy, when previewing. BUFFER MODEL: your edits are unsaved drafts, so for the iteration loop mint the link with include_working_copy:true (drafts visible); a plain link shows SAVED content only — right for stakeholders reviewing what will deploy. Mint ONCE and REUSE that single URL across edits — internal links keep the token so it navigates the whole branch, and it stays valid until the TTL lapses (24h default and max). Target a page (page_id), or a raw slug; omit all for the home page. Forms on the page can be clicked through end to end — validation, steps, success message or the named redirect target — without creating submissions or running actions ("Preview – nothing is sent"). (For a permanent public link, complete Publishing setup and wait for a verified deployment on the organization or site domain.)',
20
+ description: 'Returns url (normal wrapper) and frame_url (signed sandboxed frame for rendering-only QA; use the wrapper for Extension host interactions). Mint a signed URL the user (or your own browser tool) can open to SEE the rendered preview. Renders LIVE from the database with NO build — this is the PREFERRED way to preview design/content changes as you iterate; reach for this, not trigger_deploy, when previewing. BUFFER MODEL: your edits are unsaved drafts, so for the iteration loop mint the link with include_working_copy:true (drafts visible); a plain link shows SAVED content only — right for stakeholders reviewing what will deploy. Mint ONCE and REUSE that single URL across edits — internal links keep the token so it navigates the whole branch, and it stays valid until the TTL lapses (24h default and max). Target a page (page_id), or a raw slug; omit all for the home page. Forms on the page can be clicked through end to end — validation, steps, success message or the named redirect target — without creating submissions or running actions ("Preview – nothing is sent"). (For a permanent public link, complete Publishing setup and wait for a verified deployment on the organization or site domain.)',
21
21
  inputSchema: {
22
22
  page_id: z.string().optional(),
23
23
  slug: z.string().optional(),
@@ -29,7 +29,7 @@ function wcPath(t) {
29
29
  export const workingCopyTools = [
30
30
  {
31
31
  name: 'read_working_copy',
32
- description: 'Read a doc\'s unsaved draft (working copy) as a raw field diff — { working_copy: null } means everything is saved. read_page/get_page_blocks already return the draft VIEW; use this when you need to know exactly WHICH fields are unsaved, e.g. before discarding (the draft may hold the user\'s in-progress editor work, not just yours).',
32
+ description: 'Templates save immediately and do not use this tool. Use target: {kind: page|partial, id: ...}, not page_id. Read a doc\'s unsaved draft (working copy) as a raw field diff — { working_copy: null } means everything is saved. read_page/get_page_blocks already return the draft VIEW; use this when you need to know exactly WHICH fields are unsaved, e.g. before discarding (the draft may hold the user\'s in-progress editor work, not just yours).',
33
33
  inputSchema: {
34
34
  target: wcTargetSchema,
35
35
  version: versionParam,
package/dist/version.js CHANGED
@@ -8,4 +8,4 @@
8
8
  //
9
9
  // Keep it in lockstep with package.json: tests/version.test.ts asserts
10
10
  // VERSION === package.json.version, so a bump that forgets this line fails CI.
11
- export const VERSION = '0.45.76';
11
+ export const VERSION = '0.45.79';
package/dist/workspace.js CHANGED
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { z } from 'zod';
5
5
  import { VERSION } from './version.js';
6
+ import { BUNDLED_WORKSPACE_TEMPLATES } from './bundled-content.js';
6
7
  import { ApiError, TyperollClient } from './client.js';
7
8
  export const workspaceSchema = z.object({
8
9
  schema_version: z.literal(1),
@@ -17,14 +18,24 @@ export const workspaceSchema = z.object({
17
18
  export const defaultWorkspace = { schema_version: 1, portal: 'https://app.typeroll.com', organization_id: null, site_id: null, version: 'main', tool_mode: 'compact' };
18
19
  const lockSchema = z.object({ schema_version: z.literal(1), generator_version: z.string(), files: z.record(z.string(), z.string().regex(/^[a-f0-9]{64}$/)) }).strict();
19
20
  const hash = (value) => createHash('sha256').update(value).digest('hex');
20
- export const ignores = ['.typeroll/', '.env', '.env.*', '!.env.example', '.mcp.json', '.cursor/mcp.json', '.vscode/mcp.json', 'qa/reports/', 'sources/private/'];
21
+ export const ignores = ['.typeroll/', '.env', '.env.*', '!.env.example', '.mcp.json', '.cursor/mcp.json', '.vscode/mcp.json', 'qa/reports/', 'sources/private/', '**/__pycache__/', 'scripts/typeroll/node_modules/'];
21
22
  export const agentInstructions = `# Working on this website
22
23
 
23
- Read typeroll.json, brief/ and decisions.md. Confirm the portal, Organization,
24
+ Read typeroll.json, brief/site.md, brief/rules.md and decisions.md first. Read
25
+ other brief/ files and workflows/ only when relevant. Confirm the portal, Organization,
24
26
  Site and working Version before writes. This workspace is not the generated
25
27
  publication repository. The CMS owns current content and configuration; local
26
28
  sources are proposals unless the user explicitly adopts them.
27
29
 
30
+ ## Guided start
31
+
32
+ When the owner needs guidance or a material decision is missing, start or resume
33
+ workflows/interview.md. Read brief/interview.md and existing answers first; ask
34
+ one short question at a time and save progress. Do not repeat confirmed questions
35
+ or treat silence as approval. For a complete brief, proceed directly to
36
+ workflows/new-site.md or workflows/migration.md. All kickoff prompts live here
37
+ in the workspace; public docs explain the flow and link to these source files.
38
+
28
39
  ## Discover only what the task needs
29
40
 
30
41
  With compact MCP, search_tools then describe_tool; call_read_tool,
@@ -60,12 +71,24 @@ Public documentation: https://typeroll.com/docs/llms.txt
60
71
  ## Migrations
61
72
 
62
73
  Read tr-migration-evidence, then the source-specific recipe (tr-migrate-wp,
63
- tr-import-url, tr-migrate-astro or tr-migrate-multisite). Record preserve or renew
64
- intent in brief/migration.md; ask if unclear. Standard blocks first, reviewed
74
+ tr-import-url, tr-migrate-astro or tr-migrate-multisite). Record text_intent and design_intent separately as preserve or renew
75
+ in brief/migration.md; design includes images. Ask if either is unclear. Standard blocks first, reviewed
65
76
  custom types next, documented HTML exceptions last. Preserve or meaningfully
66
77
  redirect every old public URL. Import completion is not block reconstruction
67
- or launch acceptance. Compare preserve work to the source, renew work to the
68
- approved design/content brief. Discover tools and recipes on demand.
78
+ or launch acceptance. Compare text and design/images separately: preserved dimensions to the source,
79
+ renewed dimensions to their approved brief. New design never permits rewriting preserved text. Start with workflows/migration.md for the kickoff
80
+ prompt. The agent rebuilds imported HTML using ordinary block tools; the owner
81
+ does not need to manually convert each Page. No automatic converter is available.
82
+ Discover tools and recipes on demand. Read scripts/README.md for version-bound
83
+ pre-deploy and hosted QA tools. Follow workflows/design.md for layout defaults.
84
+
85
+ ## Project rules and search strategy
86
+
87
+ Record the owner's preferences and durable constraints in brief/rules.md; put
88
+ approved search intent, topic coverage and URL/indexing strategy in brief/seo.md.
89
+ These are project decisions, not CMS settings or permission to publish. If a
90
+ request conflicts with an agreed rule, ask and record the resulting decision.
91
+ Use workflows/README.md to find the relevant routine, not load every recipe.
69
92
 
70
93
  ## Everyday requests
71
94
 
@@ -82,16 +105,17 @@ Store credentials in the client's private settings or injected environment.
82
105
  Run the workspace doctor after setup and after changing the binding.
83
106
  `;
84
107
  export const workspaceTemplates = {
108
+ ...BUNDLED_WORKSPACE_TEMPLATES,
85
109
  'AGENTS.md': agentInstructions,
86
- 'README.md': `# Website workspace\n\nKeep project intent here; edit the website through Typeroll CMS.\n\n1. Fill in brief/site.md and brief/brand.md.\n2. Connect hosted MCP at https://app.typeroll.com/api/mcp?tools=compact.\n3. Use an Organization key to create a site, or select an existing one.\n4. Record its IDs and working version in typeroll.json. No secrets belong there.\n5. Run npx @typeroll/mcp-server doctor . (inject TYPEROLL_API_KEY privately).\n\nStart guide: https://typeroll.com/docs/getting-started/agent-workspace/\n\nUpdate the generated scaffold with npx @typeroll/mcp-server init . --update.\nEdited files are preserved and reported for manual reconciliation. Optional local\nrecipes: init . --recipes. They are reference Markdown, not automatically loaded.\n`,
110
+ 'README.md': `# Website workspace\n\nKeep project intent here; edit the website through Typeroll CMS.\n\n1. Read AGENTS.md. For guidance, ask your agent to start or resume workflows/interview.md. Otherwise use workflows/new-site.md or workflows/migration.md. Save confirmed goals, rules and SEO strategy in brief/; record text and design/image choices separately for migrations.\n2. Connect hosted MCP at https://app.typeroll.com/api/mcp?tools=compact.\n3. Use an Organization key to create a site, or select an existing one.\n4. Record its IDs and working version in typeroll.json. No secrets belong there.\n5. Run npx @typeroll/mcp-server doctor . (inject TYPEROLL_API_KEY privately).\n\nStart guide: https://typeroll.com/docs/getting-started/agent-workspace/\n\nUpdate the generated scaffold with npx @typeroll/mcp-server init . --update.\nEdited files are preserved and reported for manual reconciliation. Optional local\nrecipes: init . --recipes. They are reference Markdown, not automatically loaded.\n`,
87
111
  'brief/site.md': '# Site brief\n\n## Purpose and audience\n\n## Languages and scope\n\n## Primary user journeys\n\n## Content authority and sources\n\n## Editorial constraints and forbidden internal markers\n',
88
112
  'brief/brand.md': '# Brand\n\n## Voice and tone\n\n## Visual direction and references\n\n## Typography, colors and image policy\n\n## Desktop and mobile navigation\n\nRecord intent here; current design tokens belong in CMS settings.\n',
89
113
  'brief/content-model.md': '# Content model\n\n## Page types and routes\n\n## Fields and references\n\n## Shared templates and partials\n\n## Taxonomy and navigation\n\nUse the current CMS schema. This document describes decisions, not a second schema store.\n',
90
- 'brief/migration.md': '# Migration brief (when applicable)\n\n## Intent: preserve or renew\n\n## Source URLs, date and agreed scope\n\n## Design and content acceptance reference\n\n## Retained facts and approved content changes\n\n## Standard blocks, custom types and HTML exceptions\n\n## Old URL to preserved path or relevant redirect\n\n## Prototype and verification evidence\n\nKeep private extracts in sources/private/ and screenshots in qa/reports/.\n',
114
+ 'brief/rules.md': "# Owner preferences and project rules\n\n## Confirmed rules\n\nRecord each rule, its scope, who approved it and the date. Separate confirmed rules from proposals.\n\n## Writing and factual accuracy\n\nVoice, terminology, prohibited claims, source requirements and legal copy that must not be rewritten.\n\n## Design and editing\n\nBrand constraints, accessibility, editable content, standard/custom blocks and documented HTML exceptions.\n\n## Operations and approval\n\nWho may approve content, publication, DNS changes and messages? Credentials stay outside this repository.\n\n## Exceptions and superseded rules\n\nLink decisions.md; do not silently override a confirmed rule. Project notes do not grant permissions beyond the user's authorization or account access.\n",
115
+ 'brief/seo.md': "# SEO strategy\n\n## Business goals, audience, markets and languages\n\n## Search intent and topic coverage\n\nRecord research sources and dates. Distinguish evidence from assumptions; do not invent traffic or ranking forecasts.\n\n## Page roles and URL plan\n\nContent types, useful landing pages, taxonomy, internal links and routes to preserve or redirect. Avoid near-duplicate pages created only for keyword variants.\n\n## Indexing and canonical policy\n\nWhich pages should be indexable, public noindex, or private? Record deliberate canonical overrides and sitemap policy; actual values live in the CMS.\n\n## Editorial standards and structured data\n\nUnique useful copy, supported claims, descriptive titles and links, accessible image text, schema matching visible content. No fixed title length or word count is a hard SEO rule.\n\n## Measurement and review\n\nBaseline, analytics/consent decisions, measures of success and review dates. Never store credentials or private analytics exports here.\n",
116
+ 'workflows/README.md': "# Project routines\n\nRead only the relevant routine and discover current tool schemas through MCP.\nProject files record intent; the CMS owns current content and configuration.\n\n| Task | Project context | On-demand recipe |\n| --- | --- | --- |\n| Guided start | workflows/interview.md, brief/interview.md | Ask one question at a time; resume confirmed answers |\n| New website | workflows/new-site.md, brief/site.md, brief/brand.md, brief/content-model.md | tr-new-site, tr-starter-design |\n| Migration | workflows/migration.md, brief/migration.md | tr-migration-evidence, then tr-migrate-wp or the matching source recipe |\n| Everyday editing | brief/rules.md and the relevant Page | tr-edit-site |\n| SEO | brief/seo.md and current site settings | tr-seo |\n| New reusable block | brief/brand.md and recorded editing needs | tr-custom-blocks |\n| Verification and publication | qa/checklist.md, decisions.md | Current preview/deploy tools and publishing readiness |\n\nAdd project-specific routines here with inputs, steps, acceptance evidence and approval boundaries. Do not copy the complete Typeroll manual or private app guides. Discover enabled app guidance with read_app_documentation. Load a recipe when needed so instructions stay current and context stays small.\n",
91
117
  'decisions.md': '# Decisions\n\nRecord date, decision, reason, approved scope and alternatives when useful.\n',
92
118
  'sources/README.md': '# Sources\n\nStore research references and copy proposals. Record source URLs and dates.\nPrivate extracts belong in ignored private/ or an approved external store.\nDo not treat local drafts as current CMS content or commit media libraries.\n',
93
- 'scripts/README.md': '# Project automation\n\nKeep repeatable imports and QA scripts here. Use the authenticated REST API\nfor scripted work; keep credentials outside files and inject them at runtime.\nMCP remains the interactive interface. Typeroll currently has no general site CRUD CLI.\n',
94
- 'qa/checklist.md': '# Verification\n\n- Confirm Site, Version and deployed/source identity.\n- Read back saved content and inspect desktop/mobile previews.\n- Exercise menus, keyboard focus, links and enabled app states.\n- Verify routes, metadata, media and publishing readiness before launch.\n- Check that structured data (FAQ, recipe, event) matches what each page shows.\n- Record tested pages, viewports, results and unresolved issues.\n- Store screenshots and private runtime reports in ignored reports/.\n- Publishing validation does not prove visual or editorial completeness.\n',
95
119
  };
96
120
  export async function readWorkspace(dir) {
97
121
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typeroll/mcp-server",
3
- "version": "0.45.76",
3
+ "version": "0.45.79",
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": {
@@ -29,7 +29,8 @@
29
29
  "dist",
30
30
  "AGENTS.md",
31
31
  "README.md",
32
- "skills"
32
+ "skills",
33
+ "workspace-templates"
33
34
  ],
34
35
  "scripts": {
35
36
  "gen": "node scripts/embed-content.mjs",
@@ -5,11 +5,10 @@ description: Build accessible native header and footer partials with the site lo
5
5
 
6
6
  # Native header and footer compositions
7
7
 
8
- Use block-mode partials for new work. Core 0.1.8 / capability contract 0.43.0
9
- ships `template/site_logo`, `core/navigation`, layout primitives, and a
10
- revision-safe partial mode operation. HTML partials remain supported for
11
- legacy markup, but generic navigation and responsive layout no longer require
12
- tenant HTML, CSS, or JavaScript.
8
+ Use block-mode partials for new work. Prefer `get_composition_starter`
9
+ for the current native header tree: `template/site_logo`, `core/navigation_menu`
10
+ and `core/navigation_links`. Use the menu's dialog/drawer rather than an inline
11
+ mobile expansion that moves the logo. Existing `core/navigation` remains supported.
13
12
 
14
13
  ## Preconditions
15
14
 
@@ -31,51 +30,106 @@ read it back. Localize both accessible labels and every link label.
31
30
  [
32
31
  {
33
32
  "type": "core/section",
34
- "data": { "width": "wide", "padding_y": "sm" },
35
- "children": [{
36
- "type": "core/container",
37
- "data": {
38
- "direction": { "mobile": "column", "tablet": "row" },
39
- "align_main": "space-between",
40
- "align_cross": "center",
41
- "gap": "md",
42
- "width": "wide",
43
- "padding_y": "none",
44
- "padding_x": "none"
45
- },
46
- "children": [
47
- { "type": "template/site_logo", "data": { "height": "md", "link_to_home": true } },
48
- {
49
- "type": "core/navigation",
50
- "data": {
51
- "aria_label": "Main navigation",
52
- "menu_label": "Menu",
53
- "links": [
54
- { "label": "Home", "href": "/" },
55
- { "label": "About", "href": "/about/" },
56
- { "label": "Contact", "href": "/contact/" }
33
+ "data": {
34
+ "width": "wide",
35
+ "padding_y": "compact"
36
+ },
37
+ "children": [
38
+ {
39
+ "type": "core/container",
40
+ "data": {
41
+ "direction": "row",
42
+ "wrap": "nowrap",
43
+ "align_main": "space-between",
44
+ "align_cross": "center",
45
+ "width": "full"
46
+ },
47
+ "children": [
48
+ {
49
+ "type": "template/site_logo",
50
+ "data": {
51
+ "height": "md",
52
+ "link_to_home": true
53
+ }
54
+ },
55
+ {
56
+ "type": "core/navigation_menu",
57
+ "data": {
58
+ "aria_label": "Main navigation",
59
+ "menu_label": "Open menu",
60
+ "close_label": "Close menu",
61
+ "collapse_below": "1024"
62
+ },
63
+ "slots": [
64
+ [
65
+ {
66
+ "type": "core/navigation_links",
67
+ "data": {
68
+ "direction": "row",
69
+ "links": [
70
+ {
71
+ "label": "Home",
72
+ "href": "/"
73
+ },
74
+ {
75
+ "label": "About",
76
+ "href": "/about/"
77
+ },
78
+ {
79
+ "label": "Contact",
80
+ "href": "/contact/"
81
+ }
82
+ ]
83
+ }
84
+ }
85
+ ],
86
+ [
87
+ {
88
+ "type": "core/navigation_links",
89
+ "data": {
90
+ "direction": "column",
91
+ "links": [
92
+ {
93
+ "label": "Home",
94
+ "href": "/"
95
+ },
96
+ {
97
+ "label": "About",
98
+ "href": "/about/"
99
+ },
100
+ {
101
+ "label": "Contact",
102
+ "href": "/contact/"
103
+ }
104
+ ]
105
+ }
106
+ }
107
+ ]
57
108
  ]
58
109
  }
59
- }
60
- ]
61
- }]
110
+ ]
111
+ }
112
+ ]
62
113
  }
63
114
  ]
64
115
  ```
65
116
 
66
- `core/navigation` emits a real `nav` and list in initial HTML, marks the
67
- matching page with `aria-current="page"`, and progressively enhances to a
68
- button-controlled mobile menu. Verify Tab and Shift-Tab order, Enter/Space,
69
- Escape returning focus to the disclosure, visible focus, long translated
70
- labels, and no horizontal overflow. Without JavaScript the links remain
71
- visible and usable.
117
+ The first slot is desktop; the second is the optional mobile composition.
118
+ The default is drawer below 1024 px and inline from 1024 px. For responsive
119
+ link fields use matching `responsive_breakpoints` (tablet 768, laptop 1024,
120
+ desktop 1280, wide 1536); do not independently hide all links at the menu boundary.
121
+ Verify 1023/1024 px, Tab/Shift-Tab, Enter/Space, Escape returning focus,
122
+ visible focus, long labels, no-JavaScript links and no horizontal overflow.
72
123
 
73
124
  ## Footer tree and consent
74
125
 
75
- Use the same tree for `footer`, normally with `padding_y: "lg"`, a small site
76
- logo, localized footer navigation, and optional contact/prose blocks. The
77
- footer navigation gets its own accessible label; it is not the main
78
- navigation landmark.
126
+ Use an accessible footer navigation landmark and `core/navigation_links`, not
127
+ a second header drawer. If the footer has running about text, give it a separate,
128
+ wider column beside a nested link grid. An equal four-column grid is rarely a
129
+ good fit for one dense paragraph and three short link lists. Start with
130
+ `core/columns` ratio `1-2`, put flow containers inside the slots, and tune the
131
+ gap and readable text width to the actual content. Stack on mobile. Inspect at
132
+ 390, 1024, 1280 and 1536 px; do not copy one site's pixel values blindly.
79
133
 
80
134
  Cookie consent is a site feature, not a footer widget. Configure it separately:
81
135
 
@@ -10,10 +10,10 @@ another MCP client) working through MCP and the public API. The portal's built-i
10
10
  AI assistant has no migration tools: it does not import, convert or verify sites.
11
11
 
12
12
  **Before import or reconstruction:** read `tr-migration-evidence` with
13
- `read_skill`. Choose preserve (platform change) or renew (new design/content),
14
- record the brief and verify one prototype per active template before bulk writes.
13
+ `read_skill`. Choose preserve or renew independently for text and design/images,
14
+ record text_intent and design_intent in the brief and verify one prototype per active template before bulk writes.
15
15
  Use standard blocks first, reviewed custom types next, rare documented HTML last.
16
- Both paths must preserve or meaningfully redirect every old public URL. That
16
+ All four combinations must preserve or meaningfully redirect every old public URL. That
17
17
  shared contract defines acceptance; this recipe only adds source-specific steps.
18
18
 
19
19
 
@@ -81,7 +81,7 @@ list_pages limit=5
81
81
  ```
82
82
 
83
83
  Read existing target data to avoid overwriting work. Compare prototypes to the
84
- source in preserve mode, or to the agreed new design in renew mode. Validate
84
+ source when design_intent is preserve, or to the agreed new design when it is renew. Review text separately. Validate
85
85
  standard/custom blocks using `tr-migration-evidence` before mass writes.
86
86
 
87
87
  ### 3. Fetch and clean each source page
@@ -123,8 +123,8 @@ omit it. Inventory PDFs, download hrefs, srcsets and backgrounds as well.
123
123
 
124
124
  **d. Adapt to the target's design.**
125
125
  Use standard blocks first, then reusable custom types with editable fields.
126
- Preserve source appearance/text in preserve mode; use the approved new design
127
- and content plan in renew mode. Raw HTML is the documented last resort.
126
+ Review appearance and text independently according to design_intent and text_intent;
127
+ compare each preserved dimension to the source and each renewed dimension to its approved brief. Raw HTML is the documented last resort.
128
128
 
129
129
  ### 4. Create pages as drafts
130
130
 
@@ -10,10 +10,10 @@ another MCP client) working through MCP and the public API. The portal's built-i
10
10
  AI assistant has no migration tools: it does not import, convert or verify sites.
11
11
 
12
12
  **Before import or reconstruction:** read `tr-migration-evidence` with
13
- `read_skill`. Choose preserve (platform change) or renew (new design/content),
14
- record the brief and verify one prototype per active template before bulk writes.
13
+ `read_skill`. Choose preserve or renew independently for text and design/images,
14
+ record text_intent and design_intent in the brief and verify one prototype per active template before bulk writes.
15
15
  Use standard blocks first, reviewed custom types next, rare documented HTML last.
16
- Both paths must preserve or meaningfully redirect every old public URL. That
16
+ All four combinations must preserve or meaningfully redirect every old public URL. That
17
17
  shared contract defines acceptance; this recipe only adds source-specific steps.
18
18
 
19
19
 
@@ -10,10 +10,10 @@ another MCP client) working through MCP and the public API. The portal's built-i
10
10
  AI assistant has no migration tools: it does not import, convert or verify sites.
11
11
 
12
12
  **Before import or reconstruction:** read `tr-migration-evidence` with
13
- `read_skill`. Choose preserve (platform change) or renew (new design/content),
14
- record the brief and verify one prototype per active template before bulk writes.
13
+ `read_skill`. Choose preserve or renew independently for text and design/images,
14
+ record text_intent and design_intent in the brief and verify one prototype per active template before bulk writes.
15
15
  Use standard blocks first, reviewed custom types next, rare documented HTML last.
16
- Both paths must preserve or meaningfully redirect every old public URL. That
16
+ All four combinations must preserve or meaningfully redirect every old public URL. That
17
17
  shared contract defines acceptance; this recipe only adds source-specific steps.
18
18
 
19
19
 
@@ -69,7 +69,7 @@ required before import; GitHub/build/domain readiness is checked separately for
69
69
  publication. Do not infer that every Site shares the same blockers.
70
70
 
71
71
  Ask explicitly:
72
- - Which intent applies per Site: preserve or renew? Which source or approved
72
+ - Which text_intent and design_intent apply per Site, each preserve or renew? Which source or approved
73
73
  prototype is the **design reference**? Build that one properly first.
74
74
  - Are the sites **translations of each other** (same page structure) or
75
75
  independent? This decides whether hreflang clusters are mechanical or
@@ -10,10 +10,10 @@ another MCP client) working through MCP and the public API. The portal's built-i
10
10
  AI assistant has no migration tools: it does not import, convert or verify sites.
11
11
 
12
12
  **Before import or reconstruction:** read `tr-migration-evidence` with
13
- `read_skill`. Choose preserve (platform change) or renew (new design/content),
14
- record the brief and verify one prototype per active template before bulk writes.
13
+ `read_skill`. Choose preserve or renew independently for text and design/images,
14
+ record text_intent and design_intent in the brief and verify one prototype per active template before bulk writes.
15
15
  Use standard blocks first, reviewed custom types next, rare documented HTML last.
16
- Both paths must preserve or meaningfully redirect every old public URL. That
16
+ All four combinations must preserve or meaningfully redirect every old public URL. That
17
17
  shared contract defines acceptance; this recipe only adds source-specific steps.
18
18
 
19
19
 
@@ -80,7 +80,7 @@ Then the ordinary preconditions:
80
80
  REST API disabled" if not — common for hardened hosts).
81
81
  - The target exists. Establish the source design baseline first, then prove
82
82
  representative target templates using `tr-migration-evidence`. Preserve
83
- appearance in preserve mode; use the approved design/content brief in renew mode.
83
+ appearance when design_intent is preserve; use the approved design brief when it is renew. Review text against its separate choice.
84
84
  - If the target already has content, you must NOT clobber it — always
85
85
  `list_pages` first and only write to slugs that don't already exist.
86
86
  Pass the slug explicitly: `create_page` then refuses an address that is
@@ -148,12 +148,28 @@ place. Do not preserve them as classed raw HTML in text blocks.
148
148
 
149
149
  For each source URL:
150
150
 
151
- a. Fetch from WP. Prefer the helper plugin's authenticated endpoint
152
- (`/wp-json/typeroll/v1/...`) if available — it bypasses
153
- `show_in_rest=false` and returns ACF + builder fields. Otherwise
154
- fall back to `/wp-json/wp/v2/<post-type>?slug=<slug>`.
155
-
156
- b. Clean the HTML. Strip Elementor / Gutenberg / Breakdance class
151
+ a. Choose access based on missing evidence, not a mandatory plugin install:
152
+
153
+ | Source requirement | Access path |
154
+ | --- | --- |
155
+ | Public Pages, posts, media and rendered builder content exposed in REST | Public paginated `/wp-json/wp/v2/pages`, `/posts`, `/media` and rendered HTML can suffice |
156
+ | SEO exposed in `yoast_head_json`, visible menus/backgrounds | Public REST + rendered HTML/CSS; verify actual coverage |
157
+ | Private/draft content, REST-hidden types, ACF/custom fields, exact menus | Authenticated helper/export when available and authorized |
158
+ | REST blocked by security policy | Owner-approved helper/export; do not bypass access controls |
159
+ | Authoritative old slugs or plugin redirects | Owner export/helper **if supported**; a public crawl only approximates them |
160
+
161
+ Do not ask the owner to install the helper when public sources cover the scope.
162
+ Inventory both Pages and posts (including sitemap-excluded public records),
163
+ crawl internal links and inspect redirects, menus and CSS background images.
164
+ Do not claim the helper exports a field without reading its current contract.
165
+ The helper is distributed through Typeroll's documented package; do not claim
166
+ WordPress.org availability. Listing it there is separate publication work.
167
+
168
+ b. Clean the HTML. Parse HTML and discard comments before extracting text or
169
+ constructing blocks (comments may contain old phone-plugin metadata). Do not
170
+ use regex tag removal as a text parser. Resolve relative `../` links against
171
+ the source URL; preserve query strings and fragments. Report missing targets
172
+ instead of pointing everything at the home page. Strip Elementor / Gutenberg / Breakdance class
157
173
  soup. Drop empty `<div>` and `<span>` wrappers. Keep semantic tags,
158
174
  tables, iframes from known hosts (YouTube / Vimeo / Calendly).
159
175
  Remove every `<script>`: plugin and theme JavaScript is never copied.
@@ -165,10 +181,10 @@ c. Migrate referenced images with `upload_media_from_url`. The customer's
165
181
  media reference only after verification. Stop and report a failed required
166
182
  transfer; do not silently hotlink the WordPress source.
167
183
 
168
- The managed importer stores cleaned HTML. Typeroll does not convert HTML into blocks, and `set_page_mode` never rewrites HTML. Pages move to blocks only when someone rebuilds them with blocks.
184
+ The managed importer stores cleaned HTML. Typeroll does not convert HTML into blocks, and `set_page_mode` never rewrites HTML. You, the external agent, rebuild the imported content with ordinary block tools. The owner does not need to convert every Page manually. There is no editor conversion preview; it was removed in Core 0.2.58. Preserve the complete source before switching modes and never publish the empty intermediate Page.
169
185
 
170
186
  d. Rebuild into editable blocks according to the selected intent. Preserve exact
171
- wording in preserve mode; track approved changes in renew mode. Map headings,
187
+ wording when text_intent is preserve; track approved changes when it is renew, independently of design. Map headings,
172
188
  prose, images, buttons and layout to standard fields first, then create
173
189
  reviewed custom types where needed. Use raw HTML only for a documented,
174
190
  exceptional fragment. Preserve anchors and map every old URL in either mode.