@typeroll/mcp-server 0.44.74 → 0.45.1
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 +63 -74
- package/README.md +32 -18
- package/dist/bundled-content.js +15 -16
- package/dist/init.js +6 -0
- package/dist/server.js +15 -7
- package/dist/tools/bulk.js +4 -5
- package/dist/tools/content-types.js +28 -0
- package/dist/tools/deploy.js +1 -1
- package/dist/tools/migration.js +3 -4
- package/dist/tools/page-blocks.js +2 -2
- package/dist/tools/page-templates.js +18 -0
- package/dist/tools/pages.js +16 -3
- package/dist/tools/preview.js +1 -3
- package/dist/tools/sites.js +1 -1
- package/dist/tools/working-copy.js +2 -8
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/README.md +6 -7
- package/skills/tr-blog.md +46 -130
- package/skills/tr-brand.md +2 -2
- package/skills/tr-content-write.md +1 -1
- package/skills/tr-directory.md +46 -271
- package/skills/tr-import-url.md +1 -1
- package/skills/tr-migrate-astro.md +65 -293
- package/skills/tr-migrate-multisite.md +1 -1
- package/skills/tr-migrate-wp.md +2 -2
- package/skills/tr-new-site.md +9 -9
- package/skills/tr-page-template.md +53 -164
- package/skills/tr-redesign-branch.md +2 -2
- package/skills/tr-responsive.md +2 -2
- package/skills/tr-seo.md +1 -1
- package/dist/tools/collections.js +0 -242
- package/skills/tr-collection-template.md +0 -305
|
@@ -1,168 +1,57 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tr-page-template
|
|
3
|
-
description:
|
|
3
|
+
description: Create or edit a reusable Page template for articles, checklists, products, directory entries or other Pages that share a layout.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Share
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
</style>"
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Same for `cta-newsletter` and `footer-testimonials`.
|
|
62
|
-
|
|
63
|
-
The partial-id is what `<x-include>` references — keep it kebab-case and descriptive.
|
|
64
|
-
|
|
65
|
-
#### 3. Build each category page using the partials
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
create_page title="Tjänster för bostadsrätter" slug="brf" content_mode="html"
|
|
69
|
-
html_content="<section class=\"hero hero--brf\">
|
|
70
|
-
<div class=\"container\">
|
|
71
|
-
<h1>Tjänster för bostadsrättsföreningar</h1>
|
|
72
|
-
<p class=\"hero__tagline\">Trygg förvaltning, helt utan överraskningar.</p>
|
|
73
|
-
</div>
|
|
74
|
-
</section>
|
|
75
|
-
|
|
76
|
-
<section class=\"intro\">
|
|
77
|
-
<div class=\"container\">
|
|
78
|
-
<p>Vi har förvaltat över 200 BRF:er i Stockholmsområdet sedan 2005.</p>
|
|
79
|
-
</div>
|
|
80
|
-
</section>
|
|
81
|
-
|
|
82
|
-
<x-include name=\"why-choose-us\" />
|
|
83
|
-
<x-include name=\"cta-newsletter\" />
|
|
84
|
-
<x-include name=\"footer-testimonials\" />"
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Repeat for the other 6 categories, varying only the `hero` + `intro` sections.
|
|
88
|
-
|
|
89
|
-
#### 4. Edit once, propagate everywhere
|
|
90
|
-
|
|
91
|
-
Adding a fourth "Why us" tile? Edit `why-choose-us` once:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
replace_partial partial_id="why-choose-us" html_content="<section class=\"why\">
|
|
95
|
-
...four tiles instead of three...
|
|
96
|
-
</section>"
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
All 7 pages pick up the change on the next deploy. No multi-page diff.
|
|
100
|
-
|
|
101
|
-
### Edge cases
|
|
102
|
-
|
|
103
|
-
- **`<x-include>` self-closes or has an explicit close.** Both forms work: `<x-include name="x" />` and `<x-include name="x"></x-include>`.
|
|
104
|
-
- **Nested includes are NOT supported.** If `partial-a` references `<x-include name="partial-b" />`, the inner reference is not expanded. Flatten the hierarchy at design time.
|
|
105
|
-
- **Unknown partial → silently empty.** A typo in the `name` attribute removes the tag at expand time with no warning. Verify the partial id matches one returned by `list_partials`.
|
|
106
|
-
- **Partial content is sanitised on save.** `<script>` tags get stripped at partial creation, then the inlined HTML is sanitised again when the page renders. Two passes; both intentional.
|
|
107
|
-
|
|
108
|
-
## Pattern B — Formal PageTemplate (block mode)
|
|
109
|
-
|
|
110
|
-
Available when the site is in block mode (`supports_blocks_mode=true` and the page's `content_mode='blocks'`). A PageTemplate is a `Block[]` tree with one or more `template_content_slot` blocks marking where the page's own blocks go.
|
|
111
|
-
|
|
112
|
-
### Recipe
|
|
113
|
-
|
|
114
|
-
#### 1. List existing templates
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
list_page_templates
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Returns templates already defined on the site.
|
|
121
|
-
|
|
122
|
-
#### 2. Create / pick a template
|
|
123
|
-
|
|
124
|
-
Templates live as `Block[]` trees under `paths.pageTemplate(orgId, siteId, templateId)`. They're created via the portal UI for now (no dedicated MCP `create_page_template` tool yet — the chat-tool surface in `anthropic.ts` exposes list + set, not create).
|
|
125
|
-
|
|
126
|
-
If the template you want doesn't exist, ask the user to create it via **/app/sites/{id}/templates** in the portal, or use the block-mode-aware AI chat in the portal which knows how to write template trees.
|
|
127
|
-
|
|
128
|
-
#### 3. Assign it to a page
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
set_page_template page_id="brf" template_id="category-landing"
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
The renderer composes the template's blocks with the page's blocks at build time via `composePageWithTemplate` (replacing each `template_content_slot` with the page's own block tree). The page's CSS / theme / SEO settings are unchanged.
|
|
135
|
-
|
|
136
|
-
#### 4. Editing the template propagates to every page
|
|
137
|
-
|
|
138
|
-
The same template id can be set on multiple pages. Editing the template updates them all on next deploy.
|
|
139
|
-
|
|
140
|
-
### When B beats A
|
|
141
|
-
|
|
142
|
-
- Block-mode editor surfaces template assignment as a dropdown in the page settings.
|
|
143
|
-
- Templates compose with the block tree, so the page author can drop content blocks into named slots rather than writing HTML.
|
|
144
|
-
- The renderer is aware of block CSS / JS bundling — assets per block type are aggregated automatically.
|
|
145
|
-
|
|
146
|
-
### When A still beats B even in block mode
|
|
147
|
-
|
|
148
|
-
- Need a small reusable HTML chunk in the middle of a block-mode page → still use `<x-include>` (block-mode bodies can contain free HTML blocks that include partials).
|
|
149
|
-
- One-page change wanted without affecting siblings → just edit the page's blocks; templates are all-or-nothing.
|
|
150
|
-
|
|
151
|
-
## Refactor: 7 already-existing pages into a shared partial
|
|
152
|
-
|
|
153
|
-
If the 7 pages already exist as separate HTML bodies with duplicated chunks:
|
|
154
|
-
|
|
155
|
-
1. `read_page page_id=<one of them>` and identify the shared HTML literally — character-for-character chunks that repeat across all 7.
|
|
156
|
-
2. `create_partial partial_id=<descriptive-name> html_content="<the shared chunk>"`.
|
|
157
|
-
3. For each of the 7 pages, `update_page` with the shared chunk replaced by `<x-include name="<name>" />`.
|
|
158
|
-
4. Deploy. The rendered output should be byte-identical to before; only the source pages got shorter.
|
|
159
|
-
|
|
160
|
-
Don't try to abstract first time and hand-write the partials. Refactor from real, working duplication.
|
|
161
|
-
|
|
162
|
-
## Pitfalls
|
|
163
|
-
|
|
164
|
-
- **Don't put hero / page-specific content into a shared partial.** A partial is for things that are *truly identical* across uses. The moment you want it to vary by page, lift the differing parts back into the page body.
|
|
165
|
-
- **Header and footer already use partials.** Don't recreate them inside a category-page shared partial — they're injected by the layout, not by the page body.
|
|
166
|
-
- **CSS scoping.** Partials carrying `<style>` blocks merge into every page that includes them. If two partials define `.tile` differently, the last one wins. Either namespace classes per partial (`.why__tile`, `.feature__tile`) or move shared styles into the site's global CSS via `update_site_settings` → `custom_css`.
|
|
167
|
-
- **Don't migrate to block-mode templates just because you can.** Phase 1 sites are HTML-mode by design; partials + includes give you 90% of the value with zero risk.
|
|
168
|
-
- **`<x-include>` in a partial body referencing another partial.** Won't expand (see edge cases). If you find yourself wanting this, you're building a layout system inside the partial system — at that point the site probably wants block mode.
|
|
6
|
+
# Share a layout across Pages
|
|
7
|
+
|
|
8
|
+
Read capabilities, available block types, content types and Page templates first.
|
|
9
|
+
Use a site version for substantial design changes. The Page owns its body blocks;
|
|
10
|
+
the template owns the shared layout around that body. The content type selects a
|
|
11
|
+
default template, and an individual Page can override it.
|
|
12
|
+
|
|
13
|
+
1. `create_page_template name="article-layout" label="Article" starter="article" status="published"`.
|
|
14
|
+
Alternatively supply `blocks` instead of `starter`. Presets include article,
|
|
15
|
+
blog, checklist, team, events, products and custom.
|
|
16
|
+
2. `update_content_type name="articles" patch={template:"article-layout"}`.
|
|
17
|
+
For a single-page override, `update_page page_id=... patch={template:"article-layout"} save=true`.
|
|
18
|
+
3. Read and edit the template through `read_page_template` or block tools with
|
|
19
|
+
`target: { kind: "template", id: "article-layout" }`. Template edits save
|
|
20
|
+
immediately; content-type and template changes are isolated by `version`.
|
|
21
|
+
4. Keep a `template_content_slot` where the Page's body belongs. Its `max_width`
|
|
22
|
+
can be full, narrow, normal or wide. Use native columns/containers for layout.
|
|
23
|
+
5. Metadata blocks include `template/page_title`, `template/page_date`,
|
|
24
|
+
`template/page_featured_image`, `template/page_breadcrumbs` and
|
|
25
|
+
`template/page_navigation`. The outline block reads the rendered body headings.
|
|
26
|
+
6. Use `{{page.title}}` or `{{page.custom_field}}` in template bindings.
|
|
27
|
+
A repeater's children use `{{item.title}}` for the current listed Page.
|
|
28
|
+
Custom fields can be typed objects, arrays or Page references; preserve those
|
|
29
|
+
structures instead of storing hand-generated list HTML.
|
|
30
|
+
7. Preview several Pages using the template: long title, short body, missing
|
|
31
|
+
optional image, populated reference list. Inspect desktop and mobile output.
|
|
32
|
+
Verify the authorized static build as well as the live database preview.
|
|
33
|
+
|
|
34
|
+
Prefer native image, table, list, heading and content blocks. A rare specialized
|
|
35
|
+
widget can use a reviewed HTML/embed block. Reusable custom behavior belongs in
|
|
36
|
+
one custom block type rather than repeated HTML bodies. Existing HTML-mode pages
|
|
37
|
+
may share fragments through partials and `<x-include>`, but use block templates
|
|
38
|
+
for new content families.
|
|
39
|
+
|
|
40
|
+
Header/footer partials are global and remain separate from content types. Keep
|
|
41
|
+
site-level design tokens in settings and shared layout changes in the template.
|
|
42
|
+
Page body edits stay specific to that Page. Read the template before mutation;
|
|
43
|
+
never replace it with a starter merely because an import is being retried.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Content types also own `sort_field`/`sort_dir` and optional `allowed_templates`.
|
|
47
|
+
Types define content; templates define presentation. Multiple compatible Page
|
|
48
|
+
templates can be allowed, with `template` selecting the default. Null/absent
|
|
49
|
+
`allowed_templates` is unrestricted, `[]` allows none, and a configured default
|
|
50
|
+
must be in an explicit allowed list. Page overrides must be allowed; null/empty
|
|
51
|
+
`Page.template` restores inheritance. Page template changes use Save/Discard.
|
|
52
|
+
|
|
53
|
+
Listings inherit type sorting unless explicitly overridden. `Page.sort_order`
|
|
54
|
+
is the manual numeric order; null clears it. Missing sort values come last and
|
|
55
|
+
IDs break ties. Explicit ID lists keep their order. Typed `list_pages` queries
|
|
56
|
+
inherit type sorting and accept `sort_by`/`sort_order`; unfiltered API lists
|
|
57
|
+
default to stable IDs. See the public Content types guide for editor steps.
|
|
@@ -6,7 +6,7 @@ description: Use when the user asks to redesign, modernize, or restructure a Typ
|
|
|
6
6
|
# Redesign a site without breaking the live one
|
|
7
7
|
|
|
8
8
|
> **The buffer model (draft writes).** Every content write in this recipe
|
|
9
|
-
> (pages, blocks
|
|
9
|
+
> (pages, blocks and partials) lands in an unsaved per-doc
|
|
10
10
|
> DRAFT — deploys and plain previews only see SAVED content. For recipe-style
|
|
11
11
|
> build work, pass `save: true` on write calls (the work is pre-approved by
|
|
12
12
|
> the task itself), or run `commit_working_copy` per doc before any
|
|
@@ -59,7 +59,7 @@ read_partial partial_id="footer"
|
|
|
59
59
|
list_pages limit=20
|
|
60
60
|
batch_read_pages page_ids=[<top 3-5 pages>] # see actual conventions
|
|
61
61
|
list_partials # what free blocks exist
|
|
62
|
-
|
|
62
|
+
list_content_types # any data we need to consider
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
Write the user a short read-back: *"This is a 12-page agency site
|
package/skills/tr-responsive.md
CHANGED
|
@@ -25,7 +25,7 @@ Use `set_block_responsive` (or pass the object form directly in `add_block` /
|
|
|
25
25
|
|
|
26
26
|
The breakpoint object belongs on the responsive field inside `block.data`,
|
|
27
27
|
for example `data.cols`. Do not add a top-level `block.responsive` object: it
|
|
28
|
-
is not a rendered field, and page, partial and
|
|
28
|
+
is not a rendered field, and page, partial and Page-template writes
|
|
29
29
|
reject it instead of silently storing an inert value.
|
|
30
30
|
|
|
31
31
|
```
|
|
@@ -46,7 +46,7 @@ Pass a scalar to collapse a field back to one value everywhere.
|
|
|
46
46
|
iPad, 1/row icon-on-top on a phone":
|
|
47
47
|
|
|
48
48
|
1. `core/grid` containing `core/icon_box` cards (or a `core/repeater` with
|
|
49
|
-
`item_block: core/icon_box` for a
|
|
49
|
+
`item_block: core/icon_box` for a Page-driven list).
|
|
50
50
|
2. On the grid: `cols = { mobile: 1, tablet: 2, desktop: 4 }`.
|
|
51
51
|
3. On each icon_box (or the repeater's item defaults):
|
|
52
52
|
`layout = { mobile: "icon-top", tablet: "icon-left", desktop: "icon-top" }`.
|
package/skills/tr-seo.md
CHANGED
|
@@ -6,7 +6,7 @@ description: Use when the user asks to improve SEO, fix meta tags, add structure
|
|
|
6
6
|
# SEO audit and improvements for a Typeroll site
|
|
7
7
|
|
|
8
8
|
> **The buffer model (draft writes).** Every content write in this recipe
|
|
9
|
-
> (pages, blocks
|
|
9
|
+
> (pages, blocks and partials) lands in an unsaved per-doc
|
|
10
10
|
> DRAFT — deploys and plain previews only see SAVED content. For recipe-style
|
|
11
11
|
> build work, pass `save: true` on write calls (the work is pre-approved by
|
|
12
12
|
> the task itself), or run `commit_working_copy` per doc before any
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
// Collections + items (blog, team, events, products, custom).
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { ok, withErrorBoundary, versionParam } from './helpers.js';
|
|
4
|
-
function v(version) {
|
|
5
|
-
return version ? { version } : undefined;
|
|
6
|
-
}
|
|
7
|
-
function existingCollectionArg(args) {
|
|
8
|
-
const value = args.collection ?? args.name;
|
|
9
|
-
if (!value)
|
|
10
|
-
throw new Error('collection is required');
|
|
11
|
-
return value;
|
|
12
|
-
}
|
|
13
|
-
export const collectionTools = [
|
|
14
|
-
{
|
|
15
|
-
name: 'create_collection',
|
|
16
|
-
description: 'Create a new content collection with a field schema and (optionally) per-item URLs. Pass `route_template` (e.g. "/restaurants/{slug}") to give each published item its own URL — omit to default to "/{name}/{slug}", set to "" to disable per-item URLs entirely. Pass template_kind for a polished native item template, or item_template_blocks for a custom block tree.',
|
|
17
|
-
inputSchema: {
|
|
18
|
-
name: z.string().regex(/^[a-z][a-z0-9_-]{0,62}$/),
|
|
19
|
-
label_singular: z.string().min(1),
|
|
20
|
-
label_plural: z.string().min(1),
|
|
21
|
-
icon: z.string().optional(),
|
|
22
|
-
fields: z
|
|
23
|
-
.array(z.object({
|
|
24
|
-
name: z.string(),
|
|
25
|
-
label: z.string(),
|
|
26
|
-
type: z.string(),
|
|
27
|
-
required: z.boolean().optional(),
|
|
28
|
-
default: z.unknown().optional(),
|
|
29
|
-
options: z.array(z.string()).optional(),
|
|
30
|
-
}).passthrough())
|
|
31
|
-
.min(1),
|
|
32
|
-
slug_field: z.string().optional(),
|
|
33
|
-
sort_field: z.string().optional(),
|
|
34
|
-
sort_dir: z.enum(['asc', 'desc']).optional(),
|
|
35
|
-
route_template: z.string().optional(),
|
|
36
|
-
item_template_html: z.string().optional(),
|
|
37
|
-
template_kind: z
|
|
38
|
-
.enum(['blog', 'article', 'checklist', 'team', 'events', 'products', 'custom'])
|
|
39
|
-
.optional()
|
|
40
|
-
.describe('Native starter for item pages. article includes breadcrumbs + body outline; checklist includes optional PDF CTA + explicit previous/next fields.'),
|
|
41
|
-
item_template_blocks: z.array(z.any()).optional().describe('Explicit item-template block tree. Overrides template_kind.'),
|
|
42
|
-
version: versionParam,
|
|
43
|
-
},
|
|
44
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
45
|
-
const { version, ...body } = args;
|
|
46
|
-
const res = await client.post(siteId, 'collections', body, v(version));
|
|
47
|
-
return ok(res);
|
|
48
|
-
}),
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: 'update_collection_schema',
|
|
52
|
-
description: 'PATCH a collection\'s schema or routing. `collection` is required (`name` remains a deprecated compatibility alias). Pass only the fields you want to change. Renaming a field would orphan existing item data — adding new fields is safe, removing fields is silently allowed but item docs keep the dropped data.',
|
|
53
|
-
inputSchema: {
|
|
54
|
-
collection: z.string().optional().describe('Collection machine name. Preferred argument name.'),
|
|
55
|
-
name: z.string().optional().describe('Deprecated alias for collection.'),
|
|
56
|
-
patch: z
|
|
57
|
-
.object({
|
|
58
|
-
label_singular: z.string().optional(),
|
|
59
|
-
label_plural: z.string().optional(),
|
|
60
|
-
icon: z.string().optional(),
|
|
61
|
-
fields: z.array(z.record(z.unknown())).optional(),
|
|
62
|
-
slug_field: z.string().optional(),
|
|
63
|
-
sort_field: z.string().optional(),
|
|
64
|
-
sort_dir: z.enum(['asc', 'desc']).optional(),
|
|
65
|
-
route_template: z.string().optional(),
|
|
66
|
-
item_template_html: z.string().optional(),
|
|
67
|
-
schema_type: z
|
|
68
|
-
.string()
|
|
69
|
-
.optional()
|
|
70
|
-
.describe('Free-form Schema.org type for auto JSON-LD on every item route ("BlogPosting", "PodcastEpisode", "Product", "Event", "Recipe", "Course", …). Pair with schema_field_map when item field names differ from the schema property names.'),
|
|
71
|
-
schema_field_map: z
|
|
72
|
-
.record(z.string())
|
|
73
|
-
.optional()
|
|
74
|
-
.describe('Override the default field→schema-property mapping. Example: { "audio_url": "contentUrl", "show_notes": "description" }. Values use camelCase (Schema.org convention).'),
|
|
75
|
-
})
|
|
76
|
-
.passthrough(),
|
|
77
|
-
version: versionParam,
|
|
78
|
-
},
|
|
79
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
80
|
-
const collection = existingCollectionArg(args);
|
|
81
|
-
const res = await client.patch(siteId, `collections/${encodeURIComponent(collection)}`, { patch: args.patch }, v(args.version));
|
|
82
|
-
return ok(res);
|
|
83
|
-
}),
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'delete_collection',
|
|
87
|
-
description: 'Delete a collection AND every item in it. `collection` is required (`name` remains a deprecated compatibility alias). Destructive; requires `confirm: true`. Get-the-user-to-confirm workflow recommended.',
|
|
88
|
-
inputSchema: {
|
|
89
|
-
collection: z.string().optional().describe('Collection machine name. Preferred argument name.'),
|
|
90
|
-
name: z.string().optional().describe('Deprecated alias for collection.'),
|
|
91
|
-
confirm: z.literal(true).describe('Must be true to actually delete.'),
|
|
92
|
-
version: versionParam,
|
|
93
|
-
},
|
|
94
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
95
|
-
const collection = existingCollectionArg(args);
|
|
96
|
-
const res = await client.del(siteId, `collections/${encodeURIComponent(collection)}`, { confirm: 'true', ...v(args.version) });
|
|
97
|
-
return ok(res);
|
|
98
|
-
}),
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: 'list_collections',
|
|
102
|
-
description: 'List content collections on the site (name, label, icon, field schemas).',
|
|
103
|
-
inputSchema: { version: versionParam },
|
|
104
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
105
|
-
const res = await client.get(siteId, 'collections', v(args.version));
|
|
106
|
-
return ok(res);
|
|
107
|
-
}),
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: 'read_collection',
|
|
111
|
-
description: 'Fetch one collection\'s schema (label, fields, icon). `collection` is required; `name` remains a deprecated compatibility alias.',
|
|
112
|
-
inputSchema: {
|
|
113
|
-
collection: z.string().optional().describe('Collection machine name. Preferred argument name.'),
|
|
114
|
-
name: z.string().optional().describe('Deprecated alias for collection.'),
|
|
115
|
-
version: versionParam,
|
|
116
|
-
},
|
|
117
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
118
|
-
const collection = existingCollectionArg(args);
|
|
119
|
-
const res = await client.get(siteId, `collections/${encodeURIComponent(collection)}`, v(args.version));
|
|
120
|
-
return ok(res);
|
|
121
|
-
}),
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
name: 'collection_completeness',
|
|
125
|
-
description: "Which records in a collection need work, worst first. Returns per-field gap counts across the whole collection plus the N worst items (missing fields, never-verified fields, and fields whose last write is older than the staleness window). Use this INSTEAD of paging every item and diffing client-side — it's the intended entry point for an enrichment pass. Computed at read time, so it's never stale. By default only fields an API key may actually write are counted, since a gap you're forbidden to close is noise; pass all_fields: true for a human audit.",
|
|
126
|
-
inputSchema: {
|
|
127
|
-
collection: z.string(),
|
|
128
|
-
limit: z.number().int().min(1).max(500).optional().describe('Worst-N items to return. Default 50.'),
|
|
129
|
-
stale_after_days: z.number().int().min(1).optional().describe('A field untouched for longer than this is reported stale. Default 180.'),
|
|
130
|
-
all_fields: z.boolean().optional().describe('Include fields no API key may write. Default false.'),
|
|
131
|
-
version: versionParam,
|
|
132
|
-
},
|
|
133
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
134
|
-
const { collection, version, ...query } = args;
|
|
135
|
-
const res = await client.get(siteId, `collections/${encodeURIComponent(collection)}/completeness`, { ...query, ...v(version) });
|
|
136
|
-
return ok(res);
|
|
137
|
-
}),
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
name: 'list_collection_items',
|
|
141
|
-
description: 'List items in a collection with status filter + cursor pagination (cap 200 per page). Defaults to summary mode (richtext/textarea fields replaced with a byte-count hint) so a 200-item directory listing stays compact; pass include_richtext: true if you actually need full bodies.',
|
|
142
|
-
inputSchema: {
|
|
143
|
-
collection: z.string(),
|
|
144
|
-
status: z.enum(['draft', 'published', 'all']).optional(),
|
|
145
|
-
limit: z.number().int().min(1).max(200).optional(),
|
|
146
|
-
cursor: z.string().optional(),
|
|
147
|
-
include_richtext: z.boolean().optional().describe('Include full richtext/textarea field values inline. Default false.'),
|
|
148
|
-
version: versionParam,
|
|
149
|
-
},
|
|
150
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
151
|
-
const { collection, version, include_richtext, ...query } = args;
|
|
152
|
-
const res = await client.get(siteId, `collections/${encodeURIComponent(collection)}/items`, { ...query, ...(include_richtext ? {} : { summary: 'true' }), ...v(version) });
|
|
153
|
-
return ok(res);
|
|
154
|
-
}),
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: 'batch_read_collection_items',
|
|
158
|
-
description: 'Read up to 200 collection items in one call. Returns per-id found/not-found.',
|
|
159
|
-
inputSchema: {
|
|
160
|
-
collection: z.string(),
|
|
161
|
-
item_ids: z.array(z.string()).min(1).max(200),
|
|
162
|
-
version: versionParam,
|
|
163
|
-
},
|
|
164
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
165
|
-
const res = await client.post(siteId, `collections/${encodeURIComponent(args.collection)}/items/batch-read`, { item_ids: args.item_ids }, v(args.version));
|
|
166
|
-
return ok(res);
|
|
167
|
-
}),
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
name: 'read_collection_item',
|
|
171
|
-
description: 'Fetch a single item with all fields. item_id may be the internal id or the value of the collection\'s slug_field.',
|
|
172
|
-
inputSchema: { collection: z.string(), item_id: z.string(), version: versionParam },
|
|
173
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
174
|
-
const res = await client.get(siteId, `collections/${encodeURIComponent(args.collection)}/items/${encodeURIComponent(args.item_id)}`, v(args.version));
|
|
175
|
-
return ok(res);
|
|
176
|
-
}),
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
name: 'create_collection_item',
|
|
180
|
-
description: 'Create an item. Fields outside the collection schema are silently dropped — call list_collections first if you\'re unsure what fields exist.',
|
|
181
|
-
inputSchema: {
|
|
182
|
-
collection: z.string(),
|
|
183
|
-
fields: z.record(z.unknown()),
|
|
184
|
-
status: z.enum(['draft', 'published']).optional(),
|
|
185
|
-
version: versionParam,
|
|
186
|
-
},
|
|
187
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
188
|
-
const { collection, version, ...body } = args;
|
|
189
|
-
const res = await client.post(siteId, `collections/${encodeURIComponent(collection)}/items`, body, v(version));
|
|
190
|
-
return ok(res);
|
|
191
|
-
}),
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
name: 'update_collection_item',
|
|
195
|
-
description: 'Update a collection item by internal id or slug_field value. Fields outside the schema are dropped. BUFFER MODEL: field values land in the item\'s unsaved DRAFT (status applies immediately); pass save:true to commit in the same call, or commit_working_copy later. Scheduled publishing (0.29.0+): pass publish_at/unpublish_at INSIDE `fields` — ISO datetime at which the platform flips status (+ deploys the site); null clears; applies immediately like status.',
|
|
196
|
-
inputSchema: {
|
|
197
|
-
collection: z.string(),
|
|
198
|
-
item_id: z.string(),
|
|
199
|
-
fields: z.record(z.unknown()).optional(),
|
|
200
|
-
status: z.enum(['draft', 'published']).optional(),
|
|
201
|
-
save: z.boolean().optional().describe('Also SAVE (commit) the draft in the same call.'),
|
|
202
|
-
version: versionParam,
|
|
203
|
-
},
|
|
204
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
205
|
-
const { collection, item_id, version, ...body } = args;
|
|
206
|
-
const res = await client.patch(siteId, `collections/${encodeURIComponent(collection)}/items/${encodeURIComponent(item_id)}`, body, v(version));
|
|
207
|
-
return ok(res);
|
|
208
|
-
}),
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: 'regenerate_collection_listing',
|
|
212
|
-
description: 'Refresh a hand-managed listing of collection items on a page. The agent inserts a marker pair `<!-- typeroll:listing:{collection} -->` ... `<!-- /typeroll:listing:{collection} -->` into a page body once; from then on this tool fills the section between the markers with the current published items, sorted by the collection\'s sort_field. Pass `item_template` to customize per-item HTML — {{field}} substitutes HTML-escaped, {{{field}}} raw, {{url}} resolves through the collection\'s route_template. Defaults to a basic <ul> of links.',
|
|
213
|
-
inputSchema: {
|
|
214
|
-
collection: z.string(),
|
|
215
|
-
page_id: z.string(),
|
|
216
|
-
item_template: z.string().optional()
|
|
217
|
-
.describe('Per-item HTML template. Default: <li><a href="{{url}}">{{title}}</a></li>'),
|
|
218
|
-
wrap_open: z.string().optional().describe('Override the opening wrap element. Default: <ul class="tr-listing">'),
|
|
219
|
-
wrap_close: z.string().optional().describe('Override the closing wrap element. Default: </ul>'),
|
|
220
|
-
empty_html: z.string().optional().describe('Rendered when no items exist.'),
|
|
221
|
-
sort_field: z.string().optional().describe('Override the collection\'s default sort_field.'),
|
|
222
|
-
sort_dir: z.enum(['asc', 'desc']).optional(),
|
|
223
|
-
status_filter: z.enum(['published', 'all']).optional().describe('Default: published.'),
|
|
224
|
-
limit: z.number().int().min(1).max(500).optional(),
|
|
225
|
-
version: versionParam,
|
|
226
|
-
},
|
|
227
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
228
|
-
const { collection, version, ...body } = args;
|
|
229
|
-
const res = await client.post(siteId, `collections/${encodeURIComponent(collection)}/regenerate-listing`, body, v(version));
|
|
230
|
-
return ok(res);
|
|
231
|
-
}),
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: 'delete_collection_item',
|
|
235
|
-
description: 'Delete a collection item.',
|
|
236
|
-
inputSchema: { collection: z.string(), item_id: z.string(), version: versionParam },
|
|
237
|
-
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
238
|
-
const res = await client.del(siteId, `collections/${encodeURIComponent(args.collection)}/items/${encodeURIComponent(args.item_id)}`, v(args.version));
|
|
239
|
-
return ok(res);
|
|
240
|
-
}),
|
|
241
|
-
},
|
|
242
|
-
];
|