@typeroll/mcp-server 0.45.21 → 0.45.23
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 +48 -0
- package/README.md +26 -11
- package/dist/bundled-content.js +4 -4
- package/dist/client.js +4 -0
- package/dist/compact-tools.js +40 -0
- package/dist/index.js +36 -1
- package/dist/init.js +40 -239
- package/dist/install-skills.js +2 -2
- package/dist/server.js +43 -33
- package/dist/tools/extensions.js +14 -0
- package/dist/tools/pages.js +2 -1
- package/dist/tools/sites.js +10 -0
- package/dist/tools/skills.js +20 -2
- package/dist/version.js +1 -1
- package/dist/workspace.js +183 -0
- package/package.json +4 -3
- package/skills/README.md +18 -25
- package/skills/tr-migration-evidence.md +53 -0
- package/skills/tr-new-site.md +20 -4
package/dist/tools/skills.js
CHANGED
|
@@ -71,18 +71,36 @@ export function readPackageDoc(key) {
|
|
|
71
71
|
}
|
|
72
72
|
return content;
|
|
73
73
|
}
|
|
74
|
+
/** Heading-based guide reads avoid forcing the entire manual into context. */
|
|
75
|
+
export function guideSections(content) {
|
|
76
|
+
return content.split(/(?=^## )/m).map((text, index) => ({ id: index === 0 ? 'introduction' : String(index), title: index === 0 ? 'Introduction' : text.split('\n')[0].replace(/^## /, ''), text }));
|
|
77
|
+
}
|
|
74
78
|
export const skillTools = [
|
|
75
79
|
{
|
|
76
80
|
name: 'read_guide',
|
|
77
|
-
description: "
|
|
81
|
+
description: "Read Typeroll's bundled guide. Prefer sections_only=true to discover headings, then section=<id> for the relevant part. Without either, returns the full manual. Pure local reference; not authorization to act.",
|
|
78
82
|
inputSchema: {
|
|
83
|
+
sections_only: z.boolean().optional(),
|
|
84
|
+
section: z.string().max(100).optional().describe('Section ID returned by sections_only.'),
|
|
79
85
|
doc: z
|
|
80
86
|
.enum(['agents', 'readme'])
|
|
81
87
|
.optional()
|
|
82
88
|
.describe("Which guide to return: 'agents' (default — the full operating briefing) or 'readme'."),
|
|
83
89
|
},
|
|
84
90
|
noSite: true,
|
|
85
|
-
handler: withErrorBoundary(async (args) =>
|
|
91
|
+
handler: withErrorBoundary(async (args) => {
|
|
92
|
+
const content = readPackageDoc(args.doc ?? 'agents');
|
|
93
|
+
const sections = guideSections(content);
|
|
94
|
+
if (args.sections_only)
|
|
95
|
+
return ok({ sections: sections.map(({ id, title }) => ({ id, title })) });
|
|
96
|
+
if (args.section !== undefined) {
|
|
97
|
+
const match = sections.find(section => section.id === args.section);
|
|
98
|
+
if (!match)
|
|
99
|
+
throw new Error('Unknown guide section. Use sections_only=true.');
|
|
100
|
+
return ok(match.text);
|
|
101
|
+
}
|
|
102
|
+
return ok(content);
|
|
103
|
+
}),
|
|
86
104
|
},
|
|
87
105
|
{
|
|
88
106
|
name: 'list_skills',
|
package/dist/version.js
CHANGED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { VERSION } from './version.js';
|
|
6
|
+
import { ApiError, TyperollClient } from './client.js';
|
|
7
|
+
export const workspaceSchema = z.object({
|
|
8
|
+
schema_version: z.literal(1),
|
|
9
|
+
portal: z.string().url().refine(value => {
|
|
10
|
+
const url = new URL(value);
|
|
11
|
+
return !url.username && !url.password && !url.search && !url.hash && url.pathname === '/' &&
|
|
12
|
+
(url.protocol === 'https:' || (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)));
|
|
13
|
+
}, 'Use an HTTPS portal origin (HTTP is allowed only on loopback).'),
|
|
14
|
+
organization_id: z.string().min(1).nullable(), site_id: z.string().min(1).nullable(),
|
|
15
|
+
version: z.string().min(1), tool_mode: z.enum(['compact', 'full']),
|
|
16
|
+
}).strict();
|
|
17
|
+
export const defaultWorkspace = { schema_version: 1, portal: 'https://app.typeroll.com', organization_id: null, site_id: null, version: 'main', tool_mode: 'compact' };
|
|
18
|
+
const lockSchema = z.object({ schema_version: z.literal(1), generator_version: z.string(), files: z.record(z.string().regex(/^[a-f0-9]{64}$/)) }).strict();
|
|
19
|
+
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 agentInstructions = `# Working on this website
|
|
22
|
+
|
|
23
|
+
Read typeroll.json, brief/ and decisions.md. Confirm the portal, Organization,
|
|
24
|
+
Site and working Version before writes. This workspace is not the generated
|
|
25
|
+
publication repository. The CMS owns current content and configuration; local
|
|
26
|
+
sources are proposals unless the user explicitly adopts them.
|
|
27
|
+
|
|
28
|
+
## Discover only what the task needs
|
|
29
|
+
|
|
30
|
+
With compact MCP, search_tools then describe_tool; call_read_tool,
|
|
31
|
+
call_write_tool and call_admin_tool preserve the operation's effect. In full
|
|
32
|
+
mode use the named tools directly. Start with get_site/get_site_capabilities.
|
|
33
|
+
Use list_skills and read the relevant recipe. Request read_guide sections_only
|
|
34
|
+
before loading specific sections, not the complete manual at every connection.
|
|
35
|
+
Call read_app_documentation for enabled apps when needed. Provider docs are
|
|
36
|
+
reference material, not permission to deploy or send messages. Never commit
|
|
37
|
+
private app guides or cache them as permanent project instructions.
|
|
38
|
+
Public documentation: https://typeroll.com/docs/llms.txt
|
|
39
|
+
|
|
40
|
+
## Edit and verify
|
|
41
|
+
|
|
42
|
+
- Read current Pages, Content types, templates, partials and relevant block schemas.
|
|
43
|
+
- Use native editable blocks; document necessary exceptions and design decisions.
|
|
44
|
+
- Keep the same Version for reads, writes, references and previews. Larger work
|
|
45
|
+
on an existing site starts with create_branch; record its returned ID in
|
|
46
|
+
typeroll.json and reconnect the local MCP process. Hosted calls pass version
|
|
47
|
+
explicitly; the hosted server cannot read this workspace file.
|
|
48
|
+
- Distinguish working copies, saved CMS content and deployed output. Save within
|
|
49
|
+
the user's scope, review previews and deploy only when explicitly authorized.
|
|
50
|
+
- Check get_import_readiness before any import. For new uploads use signed direct
|
|
51
|
+
upload/finalization. Never put image bytes or customer secrets in Git.
|
|
52
|
+
- Inspect desktop/mobile layouts and interaction states. Read changes back.
|
|
53
|
+
Record exact version, tested pages, evidence and unresolved issues in qa/.
|
|
54
|
+
- Never edit generated publishing source to change CMS content. Do not overwrite
|
|
55
|
+
current CMS or owner answers from an old local snapshot.
|
|
56
|
+
|
|
57
|
+
Store credentials in the client's private settings or injected environment.
|
|
58
|
+
Run the workspace doctor after setup and after changing the binding.
|
|
59
|
+
`;
|
|
60
|
+
export const workspaceTemplates = {
|
|
61
|
+
'AGENTS.md': agentInstructions,
|
|
62
|
+
'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`,
|
|
63
|
+
'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',
|
|
64
|
+
'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',
|
|
65
|
+
'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',
|
|
66
|
+
'decisions.md': '# Decisions\n\nRecord date, decision, reason, approved scope and alternatives when useful.\n',
|
|
67
|
+
'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',
|
|
68
|
+
'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',
|
|
69
|
+
'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- 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',
|
|
70
|
+
};
|
|
71
|
+
export async function readWorkspace(dir) {
|
|
72
|
+
try {
|
|
73
|
+
return workspaceSchema.parse(JSON.parse(await fs.readFile(path.join(dir, 'typeroll.json'), 'utf8')));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new Error('Invalid or missing typeroll.json. Run init and use its documented non-secret fields.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Reject linked destinations, including linked ancestor folders inside the workspace. */
|
|
80
|
+
export async function safeDestination(dir, relative) {
|
|
81
|
+
if (path.isAbsolute(relative) || relative.split(/[\\/]/).some(part => part === '..'))
|
|
82
|
+
throw new Error('Unsafe workspace path');
|
|
83
|
+
let target = dir;
|
|
84
|
+
for (const part of relative.split('/')) {
|
|
85
|
+
target = path.join(target, part);
|
|
86
|
+
const stat = await fs.lstat(target).catch(error => { if (error.code !== 'ENOENT')
|
|
87
|
+
throw error; return null; });
|
|
88
|
+
if (stat?.isSymbolicLink())
|
|
89
|
+
throw new Error(`Refusing linked workspace path: ${relative}`);
|
|
90
|
+
}
|
|
91
|
+
return target;
|
|
92
|
+
}
|
|
93
|
+
export async function writeWorkspaceFiles(dir, templates, update) {
|
|
94
|
+
await fs.mkdir(dir, { recursive: true });
|
|
95
|
+
const root = await fs.realpath(dir);
|
|
96
|
+
const lockFile = await safeDestination(root, 'typeroll.lock.json');
|
|
97
|
+
let previous = { schema_version: 1, generator_version: VERSION, files: {} };
|
|
98
|
+
try {
|
|
99
|
+
previous = lockSchema.parse(JSON.parse(await fs.readFile(lockFile, 'utf8')));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error.code !== 'ENOENT')
|
|
103
|
+
throw new Error('Invalid typeroll.lock.json; restore it before updating.');
|
|
104
|
+
}
|
|
105
|
+
// Validate every destination before performing any writes.
|
|
106
|
+
for (const name of [...Object.keys(templates), '.gitignore'])
|
|
107
|
+
await safeDestination(root, name);
|
|
108
|
+
const results = [];
|
|
109
|
+
const files = { ...previous.files };
|
|
110
|
+
for (const [name, content] of Object.entries(templates)) {
|
|
111
|
+
const target = path.join(root, name);
|
|
112
|
+
const existing = await fs.readFile(target, 'utf8').catch(error => { if (error.code !== 'ENOENT')
|
|
113
|
+
throw error; return null; });
|
|
114
|
+
if (existing !== null && existing !== content && (!update || hash(existing) !== previous.files[name])) {
|
|
115
|
+
results.push({ path: name, action: 'modified' });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (existing !== content) {
|
|
119
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
120
|
+
await fs.writeFile(target, content, { mode: 0o600 });
|
|
121
|
+
}
|
|
122
|
+
files[name] = hash(content);
|
|
123
|
+
results.push({ path: name, action: existing === content ? 'kept' : existing === null ? 'created' : 'updated' });
|
|
124
|
+
}
|
|
125
|
+
const ignoreFile = path.join(root, '.gitignore');
|
|
126
|
+
const current = await fs.readFile(ignoreFile, 'utf8').catch(error => { if (error.code !== 'ENOENT')
|
|
127
|
+
throw error; return ''; });
|
|
128
|
+
const missing = ignores.filter(rule => !current.split(/\r?\n/).includes(rule));
|
|
129
|
+
if (missing.length)
|
|
130
|
+
await fs.writeFile(ignoreFile, current + (current && !current.endsWith('\n') ? '\n' : '') + '\n# Typeroll workspace private files\n' + missing.join('\n') + '\n');
|
|
131
|
+
// Config is always user-owned, never replaced by an update.
|
|
132
|
+
delete files['typeroll.json'];
|
|
133
|
+
await fs.writeFile(lockFile, JSON.stringify({ schema_version: 1, generator_version: VERSION, files }, null, 2) + '\n');
|
|
134
|
+
return results;
|
|
135
|
+
}
|
|
136
|
+
export function workspaceClient(config, env, fetchImpl) {
|
|
137
|
+
if (!env.TYPEROLL_API_KEY)
|
|
138
|
+
throw new Error('Inject TYPEROLL_API_KEY through private client settings or the environment.');
|
|
139
|
+
if (env.TYPEROLL_API_URL && env.TYPEROLL_API_URL.replace(/\/$/, '') !== config.portal.replace(/\/$/, ''))
|
|
140
|
+
throw new Error('TYPEROLL_API_URL conflicts with typeroll.json.');
|
|
141
|
+
if (env.TYPEROLL_SITE_ID && env.TYPEROLL_SITE_ID !== config.site_id)
|
|
142
|
+
throw new Error('TYPEROLL_SITE_ID conflicts with typeroll.json.');
|
|
143
|
+
return new TyperollClient({ baseUrl: config.portal, apiKey: env.TYPEROLL_API_KEY, defaultVersion: config.version, fetchImpl });
|
|
144
|
+
}
|
|
145
|
+
export async function verifyWorkspaceBinding(config, client) {
|
|
146
|
+
if (!config.site_id || !config.organization_id)
|
|
147
|
+
throw new Error('Set site_id and organization_id before starting a bound workspace connection.');
|
|
148
|
+
const site = await client.get(config.site_id, '');
|
|
149
|
+
if (site.id !== config.site_id || site.version_id !== config.version || site.organization_id !== config.organization_id)
|
|
150
|
+
throw new Error('Portal Site, Organization or Version does not match typeroll.json. Run doctor before editing.');
|
|
151
|
+
}
|
|
152
|
+
export async function doctor(dir, offline, env = process.env, fetchImpl) {
|
|
153
|
+
const checks = [];
|
|
154
|
+
const config = await readWorkspace(dir);
|
|
155
|
+
checks.push({ check: 'workspace', status: 'passed', message: 'Valid non-secret workspace configuration.' });
|
|
156
|
+
const ignore = await fs.readFile(path.join(dir, '.gitignore'), 'utf8').catch(() => '');
|
|
157
|
+
checks.push({ check: 'ignore_entries', status: ignores.every(rule => ignore.split(/\r?\n/).includes(rule)) ? 'passed' : 'required', message: 'Checks required .gitignore entries only. Run init if missing; inspect overriding rules and already tracked files separately.' });
|
|
158
|
+
if (!config.site_id)
|
|
159
|
+
checks.push({ check: 'site', status: 'required', message: 'Create/select a Site through hosted MCP or the portal, then set site_id in typeroll.json.' });
|
|
160
|
+
if (offline)
|
|
161
|
+
checks.push({ check: 'connection', status: 'not_checked', message: 'Offline check: authentication, permissions, Version and readiness were not checked.' });
|
|
162
|
+
else {
|
|
163
|
+
try {
|
|
164
|
+
const client = workspaceClient(config, env, fetchImpl);
|
|
165
|
+
await client.rootGet('sites');
|
|
166
|
+
checks.push({ check: 'authentication', status: 'passed', message: 'The portal accepted the credential.' });
|
|
167
|
+
if (config.site_id) {
|
|
168
|
+
const site = await client.get(config.site_id, '');
|
|
169
|
+
if (site.id !== config.site_id || site.version_id !== config.version)
|
|
170
|
+
throw new Error('Site or Version differs from the workspace binding.');
|
|
171
|
+
checks.push({ check: 'site_version', status: 'passed', message: 'Site and Version match the workspace.' });
|
|
172
|
+
checks.push({ check: 'organization', status: config.organization_id && config.organization_id === site.organization_id ? 'passed' : 'required', message: site.organization_id ? `Confirm organization_id matches ${site.organization_id}.` : 'This portal does not return the Site Organization ID; verify it before writes.' });
|
|
173
|
+
await client.get(config.site_id, 'capabilities');
|
|
174
|
+
checks.push({ check: 'capabilities', status: 'passed', message: 'Capabilities are readable. Read relevant block schemas and app documentation on demand.' });
|
|
175
|
+
}
|
|
176
|
+
checks.push({ check: 'writes_and_publishing', status: 'not_checked', message: 'Read-only doctor. No edits, media transfer, app activation or publication was attempted; check publishing/import readiness for those tasks.' });
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
checks.push({ check: 'connection', status: 'required', message: error instanceof ApiError ? `Portal HTTP ${error.status}. Check credential scope, portal and Site/Version.` : 'Could not verify the connection. Check injected credentials, matching environment, network and Site/Version.' });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { passed: !checks.some(check => check.status === 'required'), generator_version: VERSION, checks };
|
|
183
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeroll/mcp-server",
|
|
3
|
-
"version": "0.45.
|
|
4
|
-
"description": "Typeroll CMS MCP server
|
|
3
|
+
"version": "0.45.23",
|
|
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": {
|
|
7
7
|
"type": "git",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
45
|
-
"zod": "^3.23.0"
|
|
45
|
+
"zod": "^3.23.0",
|
|
46
|
+
"zod-to-json-schema": "3.25.2"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"typescript": "^5.6.0",
|
package/skills/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Typeroll CMS skills for AI agents
|
|
2
2
|
|
|
3
|
-
Boilerplate skills that pair with [`@typeroll/mcp-server`](../
|
|
3
|
+
Boilerplate skills that pair with [`@typeroll/mcp-server`](../README.md).
|
|
4
4
|
Each one is a self-contained markdown file the agent reads when its
|
|
5
5
|
description matches the user's request. Recipes call MCP tools; the agent
|
|
6
6
|
adapts them to the specific job.
|
|
@@ -12,27 +12,21 @@ save first.
|
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
Prefer `list_skills` and `read_skill` over loading all recipes into context.
|
|
16
|
+
For optional local reference copies in an agent-neutral workspace:
|
|
17
17
|
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
cp skills/*.md .claude/skills/
|
|
22
|
-
|
|
23
|
-
# or user-scoped (available in every project)
|
|
24
|
-
cp skills/*.md ~/.claude/skills/
|
|
18
|
+
```sh
|
|
19
|
+
npx @typeroll/mcp-server@0.45.23 init ./my-site --recipes
|
|
20
|
+
npx @typeroll/mcp-server@0.45.23 init ./my-site --recipes --update
|
|
25
21
|
```
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
ln -s "$PWD/skills/tr-migrate-wp.md" ~/.claude/skills/
|
|
31
|
-
```
|
|
23
|
+
The files live in `typeroll-skills/`. Updates preserve user edits. Copying Markdown
|
|
24
|
+
is not a guarantee of automatic skill loading in any client; follow its current
|
|
25
|
+
instructions. Read the relevant recipe when the task calls for it.
|
|
32
26
|
|
|
33
27
|
## The skills
|
|
34
28
|
|
|
35
|
-
###
|
|
29
|
+
### Build and design
|
|
36
30
|
|
|
37
31
|
| File | When it triggers | What it does |
|
|
38
32
|
|---|---|---|
|
|
@@ -42,7 +36,7 @@ ln -s "$PWD/skills/tr-migrate-wp.md" ~/.claude/skills/
|
|
|
42
36
|
| `tr-content-write.md` | "write a page about…", "draft copy for…" | Discovery first (settings + sample pages), then drafts in the site's voice, previews, iterates. |
|
|
43
37
|
| `tr-images.md` | "make an image / hero / illustration", media uploads | Generates locally → signed upload URL → metadata patch → embed. |
|
|
44
38
|
|
|
45
|
-
###
|
|
39
|
+
### Features
|
|
46
40
|
|
|
47
41
|
| File | When it triggers | What it does |
|
|
48
42
|
|---|---|---|
|
|
@@ -51,7 +45,7 @@ ln -s "$PWD/skills/tr-migrate-wp.md" ~/.claude/skills/
|
|
|
51
45
|
| `tr-page-template.md` | Reusable Page templates: audio players, chapter lists, guest cards, image galleries — anything needing loops/nested data | Compose native blocks around `template_content_slot`; bind Page metadata, structured fields and references. |
|
|
52
46
|
| `tr-seo.md` | "SEO", "meta descriptions", "structured data" | Audit → fix titles/descriptions → OG images → JSON-LD → robots.txt → deploy. |
|
|
53
47
|
|
|
54
|
-
###
|
|
48
|
+
### Import content
|
|
55
49
|
|
|
56
50
|
| File | When it triggers | What it does |
|
|
57
51
|
|---|---|---|
|
|
@@ -63,14 +57,13 @@ ln -s "$PWD/skills/tr-migrate-wp.md" ~/.claude/skills/
|
|
|
63
57
|
|
|
64
58
|
## Prerequisites for every skill
|
|
65
59
|
|
|
66
|
-
1.
|
|
67
|
-
|
|
68
|
-
2. The
|
|
69
|
-
`
|
|
70
|
-
reference it directly).
|
|
60
|
+
1. A working hosted or local Typeroll MCP connection for API operations. Local
|
|
61
|
+
stdio uses an injected API key; hosted clients may use OAuth or bearer headers.
|
|
62
|
+
2. The relevant guide sections and the selected Site/Version's capabilities.
|
|
63
|
+
Use `read_guide sections_only=true` before requesting individual sections.
|
|
71
64
|
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
Recipe discovery and reads are local reference operations; they do not require a
|
|
66
|
+
Site. API operations enforce authentication, scope and setup independently.
|
|
74
67
|
|
|
75
68
|
## Authoring more
|
|
76
69
|
|
|
@@ -105,3 +105,56 @@ never attach old evidence to a new publication. `record_migration_seo_acceptance
|
|
|
105
105
|
booleans are reviewer attestations, not automated visual proof. Do not set them
|
|
106
106
|
from HTTP status, native-block count or overflow alone. Report a completed
|
|
107
107
|
pre-cutover scope honestly when DNS or integrations remain out of scope.
|
|
108
|
+
|
|
109
|
+
## Complete census and visual signoff
|
|
110
|
+
|
|
111
|
+
The initial route census must combine source sitemaps, internal links, archive
|
|
112
|
+
pagination, CMS inventory and known utility routes. Include homepage, archives,
|
|
113
|
+
404/search behavior and routes outside the sitemap where present. Do not invent
|
|
114
|
+
features absent from the source. Assign every route a recipe, template override,
|
|
115
|
+
composition or documented exception. Inventory the whole shell: content outside
|
|
116
|
+
`main`, header, footer, service navigation and archive heroes are not optional.
|
|
117
|
+
|
|
118
|
+
Derive required evidence from that census. Pair every in-scope source/target route
|
|
119
|
+
at mobile and desktop; add each unique component, breakpoint and interaction
|
|
120
|
+
state. Inspect page-specific media, lists, tables, embeds, CTAs, anchors and related
|
|
121
|
+
content. One representative article cannot attest every article's content.
|
|
122
|
+
|
|
123
|
+
A coverage row records route, recipe, component, state, actual viewport dimensions,
|
|
124
|
+
source revision, target content/config/Core/publication revision, paired screenshot
|
|
125
|
+
references, reviewer, review time and result. All rows start `UNVERIFIED`.
|
|
126
|
+
Keep independent results for content, links, list cardinality, full membership,
|
|
127
|
+
sequence, appearance, keyboard behavior and delivery. Screenshots must be inspected;
|
|
128
|
+
image diffs and contact sheets assist review and do not prove aesthetics.
|
|
129
|
+
|
|
130
|
+
For menus capture source and target closed, open and scrolled states, nested groups
|
|
131
|
+
if present, short landscape, focus order, Escape, link activation and breakpoint
|
|
132
|
+
transitions. For TOCs capture top/middle/bottom and anchor landings. Include tabs,
|
|
133
|
+
accordions and empty/loading/error/success states where applicable. Separate mobile
|
|
134
|
+
and desktop menu compositions require separate evidence; shared links may be reused
|
|
135
|
+
but one variant's screenshots do not attest the other.
|
|
136
|
+
|
|
137
|
+
Report denominators separately: routes/content, paired visual pages, recipes,
|
|
138
|
+
components and states. Missing an archive, open menu, footer or a unique embed leaves
|
|
139
|
+
coverage incomplete even if all captured screenshots pass. Shared block, theme,
|
|
140
|
+
partial or template changes invalidate every dependent row; content changes
|
|
141
|
+
invalidate that route and dependent listings. An old revision cannot approve a new
|
|
142
|
+
one. Source availability failures remain `UNVERIFIED`, not silently excluded.
|
|
143
|
+
|
|
144
|
+
Visual signoff requires zero uncovered required surfaces and zero unexplained
|
|
145
|
+
regressions. `FAIL` or `UNVERIFIED` blocks that signoff, even when a separately scoped
|
|
146
|
+
technical publication has approval. An intentional deviation needs exact before/
|
|
147
|
+
after evidence, specific user benefit and accepted scope; “cleaner” is not a reason.
|
|
148
|
+
|
|
149
|
+
Examples from a moving-services migration:
|
|
150
|
+
|
|
151
|
+
| Surface | Passing evidence | Separate visual result |
|
|
152
|
+
| --- | --- | --- |
|
|
153
|
+
| Mobile menu | Keyboard focus and Escape work | FAIL: header moves, source service group is missing, open-menu composition differs |
|
|
154
|
+
| Category archive | Every destination URL exists | FAIL: source sequence, card anatomy and columns differ |
|
|
155
|
+
| Article body | All text imported | UNVERIFIED: unique embed and footer were never compared |
|
|
156
|
+
|
|
157
|
+
Do not report “all design checked” from a viewport or recipe sample. Keep source
|
|
158
|
+
link-order comparisons and paired contact sheets with the report. The same contract
|
|
159
|
+
applies to WordPress, URL, Astro and multisite migration; do not create separate,
|
|
160
|
+
weaker checklists for individual import mechanisms.
|
package/skills/tr-new-site.md
CHANGED
|
@@ -23,6 +23,21 @@ structured, per-field editing, native full-bleed sections, responsive
|
|
|
23
23
|
breakpoints, and templates. HTML mode is the secondary path for
|
|
24
24
|
hand-crafted one-offs and migrated content (section at the end).
|
|
25
25
|
|
|
26
|
+
## Workspace and context
|
|
27
|
+
|
|
28
|
+
An optional agent-neutral workspace stores the brief, decisions and QA. It does
|
|
29
|
+
not create a Site or replace CMS content. See the public Agent workspace guide.
|
|
30
|
+
For compact MCP: search_tools, describe_tool, then the appropriate read/write/admin
|
|
31
|
+
wrapper. Load only the relevant guide section and recipe. Discover site apps with
|
|
32
|
+
read_app_documentation when needed; do not bundle private app guides locally.
|
|
33
|
+
|
|
34
|
+
No Site yet? Use hosted MCP with an Organization key and create_site, or create it
|
|
35
|
+
in the portal. Read back its ID before editing. Use one explicit Site/Version for
|
|
36
|
+
related operations. Larger changes to an existing site should use a branch.
|
|
37
|
+
|
|
38
|
+
Before importing content or a media library, require get_import_readiness to pass.
|
|
39
|
+
Publishing accounts may remain unconfigured while authoring new drafts and previews.
|
|
40
|
+
|
|
26
41
|
## Preconditions
|
|
27
42
|
|
|
28
43
|
- `@typeroll/mcp-server` configured with a valid `TYPEROLL_API_KEY`.
|
|
@@ -196,9 +211,9 @@ truth):
|
|
|
196
211
|
- **`core/heading`** decouples `level` (h1–h6, semantics) from `size`
|
|
197
212
|
(visual) — exactly one `level: h1` per page.
|
|
198
213
|
- **Images:** `core/image` with an uploaded media URL. The build pipeline
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
214
|
+
emits responsive output from prepared media. Upload through a signed direct
|
|
215
|
+
grant, finalize it, and use returned IDs/URLs. Follow media preparation status;
|
|
216
|
+
do not routinely regenerate unchanged images or use legacy maintenance tools. Use the
|
|
202
217
|
`radius` field for rounded corners.
|
|
203
218
|
- **Repeaters/listings:** `core/page_list`, `gallery`,
|
|
204
219
|
`feature_grid` etc. — alias blocks over `core/repeater`. Use these for
|
|
@@ -302,7 +317,8 @@ edits, not full-page rewrites.
|
|
|
302
317
|
|
|
303
318
|
### 7. Deploy
|
|
304
319
|
|
|
305
|
-
When the user approves
|
|
320
|
+
When the user approves, verify read_publishing_readiness and save the intended
|
|
321
|
+
Version before deploying. A published CMS status is not a live deployment.
|
|
306
322
|
```
|
|
307
323
|
trigger_deploy
|
|
308
324
|
get_deploy_status job_id=<id>
|