@timelesscms-com/mcp-server 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +30 -10
- package/dist/tools/block-types.js +12 -4
- package/dist/tools/pages.js +9 -1
- package/dist/tools/settings.js +13 -5
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -68,11 +68,17 @@ maps to one HTTP endpoint; the actual logic runs in the customer's portal
|
|
|
68
68
|
the page's own `blocks`. Set `Page.template = "<template-id>"` to
|
|
69
69
|
apply a template to a page.
|
|
70
70
|
|
|
71
|
-
- **Block types.**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
- **Block types.** A site has three sources of block types:
|
|
72
|
+
- **Core** (origin: 'core', ids like `core/section`) — shipped in
|
|
73
|
+
the platform, always available.
|
|
74
|
+
- **User** (origin: 'user') — created in the portal's block-types UI.
|
|
75
|
+
- **Third-party** (origin: 'third_party') — imported from .tcblocks
|
|
76
|
+
packages via `import_block_types`.
|
|
77
|
+
|
|
78
|
+
`list_block_types` returns ALL of them in one list, with each entry's
|
|
79
|
+
full schema (field names, types, defaults). Always call this FIRST
|
|
80
|
+
before working with blocks — never hardcode block ids or field names,
|
|
81
|
+
the available set is per-site.
|
|
76
82
|
|
|
77
83
|
- **Redirects.** `from_path → to_path` with status code 301 / 302.
|
|
78
84
|
Auto-created when you change a page's slug.
|
|
@@ -108,8 +114,11 @@ goes through the MCP:
|
|
|
108
114
|
`include_content: true` if you actually need the bodies inline.
|
|
109
115
|
5. `list_collections` — what content types exist + their schemas +
|
|
110
116
|
`route_template` (so you know if items have URLs).
|
|
111
|
-
6. `list_block_types` —
|
|
112
|
-
|
|
117
|
+
6. `list_block_types` — every block type usable on this site: core
|
|
118
|
+
(always available, ids like `core/section`), custom (origin: 'user'),
|
|
119
|
+
and third-party (origin: 'third_party'). Each entry includes the
|
|
120
|
+
full schema so you know what `data.X` fields each block accepts.
|
|
121
|
+
7. `list_page_templates` — PageTemplate docs that wrap pages.
|
|
113
122
|
|
|
114
123
|
You usually want at least #1 + #2 + a sampling from #3 before
|
|
115
124
|
proposing any design change, so you mirror the conventions in use.
|
|
@@ -182,13 +191,20 @@ Edits to the block update every page that includes it. Use
|
|
|
182
191
|
### "Build a page using blocks (the default for new pages)"
|
|
183
192
|
|
|
184
193
|
New pages default to `content_mode='blocks'` with a seeded heading +
|
|
185
|
-
prose block.
|
|
194
|
+
prose block. Discover-then-build:
|
|
186
195
|
|
|
187
196
|
```
|
|
197
|
+
list_block_types
|
|
198
|
+
# → [{ id: "core/section", category: "layout", container: true, schema: [{ name: "width", type: "select", options: ["narrow","normal","wide","full"] }, …] },
|
|
199
|
+
# { id: "core/columns", container: "slots", slot_count: 2, slot_labels: ["Left","Right"], schema: [...] },
|
|
200
|
+
# { id: "hero_bold", origin: "user", schema: [...] }, ← any custom blocks on this site
|
|
201
|
+
# …]
|
|
202
|
+
|
|
188
203
|
get_page_blocks page_id=home
|
|
189
|
-
#
|
|
204
|
+
# → { content_mode: 'blocks', blocks: [...] }
|
|
205
|
+
|
|
190
206
|
add_block page_id=home block={ type: 'core/section', data: { width: 'wide' } }
|
|
191
|
-
#
|
|
207
|
+
# → { added_id: 'blk_xyz', blocks: [...] }
|
|
192
208
|
add_block page_id=home parent_id="blk_xyz" block={
|
|
193
209
|
type: 'core/heading', data: { text: 'Pricing', level: 'h2' }
|
|
194
210
|
}
|
|
@@ -197,6 +213,10 @@ add_block page_id=home parent_id="blk_xyz" block={
|
|
|
197
213
|
}
|
|
198
214
|
```
|
|
199
215
|
|
|
216
|
+
For an unfamiliar custom block, `read_block_type id="..."` gives the
|
|
217
|
+
full field list (types, defaults, required) so you don't ship invalid
|
|
218
|
+
`data`.
|
|
219
|
+
|
|
200
220
|
Updating, moving, removing blocks: `update_block`, `move_block`,
|
|
201
221
|
`remove_block` (all by `block_id`).
|
|
202
222
|
|
|
@@ -44,16 +44,24 @@ export const blockTypeTools = [
|
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
name: 'list_block_types',
|
|
47
|
-
description: '
|
|
48
|
-
inputSchema: {
|
|
47
|
+
description: 'Discover every block type usable on this site. Returns ALL of them in one list: core blocks (id like "core/section", always available), custom blocks (origin: "user", created in the portal), and third-party blocks (origin: "third_party", imported from .tcblocks packages). Each entry includes id, label, category, container/slot info, origin, and the full schema (fields with name/type/label/required/options/default). Call this FIRST when starting block-mode work — never hardcode block ids; the available set is per-site.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
include_core: z.boolean().optional().describe('Default true. Set false to get only custom + third-party (rarely needed).'),
|
|
50
|
+
version: versionParam,
|
|
51
|
+
},
|
|
49
52
|
handler: withErrorBoundary(async (args, { client, siteId }) => {
|
|
50
|
-
const
|
|
53
|
+
const query = {};
|
|
54
|
+
if (args.version)
|
|
55
|
+
query.version = args.version;
|
|
56
|
+
if (args.include_core === false)
|
|
57
|
+
query.include_core = 'false';
|
|
58
|
+
const res = await client.get(siteId, 'block-types', query);
|
|
51
59
|
return ok(res);
|
|
52
60
|
}),
|
|
53
61
|
},
|
|
54
62
|
{
|
|
55
63
|
name: 'read_block_type',
|
|
56
|
-
description:
|
|
64
|
+
description: "Full schema for one block type. Returns the BlockType doc with template, styles, optional script, and the field-definitions array. Use this when list_block_types' summary isn't enough — e.g. before add_block on a custom block whose data fields you haven't seen.",
|
|
57
65
|
inputSchema: {
|
|
58
66
|
type_id: z.string(),
|
|
59
67
|
version: versionParam,
|
package/dist/tools/pages.js
CHANGED
|
@@ -151,9 +151,17 @@ export const pageTools = [
|
|
|
151
151
|
// 2. Build the new page body. Title + slug from args, everything else
|
|
152
152
|
// copied — except the new page always starts as a draft so cloning
|
|
153
153
|
// a published page doesn't accidentally publish a half-edited copy.
|
|
154
|
+
// Critical: preserve the source's content_mode exactly. Without
|
|
155
|
+
// this, create_page falls back to its blocks-default and seeds a
|
|
156
|
+
// fresh heading+prose tree, leaving the clone with TWO content
|
|
157
|
+
// sources (the copied html_content AND a default blocks[]). Bug
|
|
158
|
+
// surfaced in MCP-FEEDBACK-2.md from the Sundsvallsflytt demo build.
|
|
159
|
+
const srcMode = src.content_mode ?? 'html';
|
|
154
160
|
const body = {
|
|
155
161
|
title: args.title,
|
|
156
|
-
|
|
162
|
+
content_mode: srcMode,
|
|
163
|
+
html_content: srcMode === 'html' ? src.html_content : '',
|
|
164
|
+
blocks: srcMode === 'blocks' ? (src.blocks ?? []) : undefined,
|
|
157
165
|
seo_title: src.seo_title,
|
|
158
166
|
seo_description: src.seo_description,
|
|
159
167
|
og_image: src.og_image,
|
package/dist/tools/settings.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
// Settings tools. scripts_head, scripts_body_end, and custom_css
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Settings tools. scripts_head, scripts_body_end, and custom_css ARE
|
|
2
|
+
// writable via the public API (as of mcp-server 0.4.x) — the v1 route
|
|
3
|
+
// accepts them and they round-trip through read_site_settings. Same
|
|
4
|
+
// trust model as user-authored block-type JS: a bearer-token caller
|
|
5
|
+
// takes responsibility for what they ship. The portal chat AI still
|
|
6
|
+
// doesn't expose these fields, so conversation-driven assistants can't
|
|
7
|
+
// smuggle scripts in.
|
|
4
8
|
import { z } from 'zod';
|
|
5
9
|
import { ok, withErrorBoundary } from './helpers.js';
|
|
6
10
|
export const settingsTools = [
|
|
7
11
|
{
|
|
8
12
|
name: 'read_site_settings',
|
|
9
|
-
description:
|
|
13
|
+
description: "Read every site setting: name, tagline, logo, favicon, colors, fonts, contact info, social links, default SEO suffix, language, robots_txt, plus the scriptable surfaces scripts_head, scripts_body_end, and custom_css.",
|
|
10
14
|
handler: withErrorBoundary(async (_args, { client, siteId }) => {
|
|
11
15
|
const res = await client.get(siteId, 'settings');
|
|
12
16
|
return ok(res);
|
|
@@ -14,7 +18,7 @@ export const settingsTools = [
|
|
|
14
18
|
},
|
|
15
19
|
{
|
|
16
20
|
name: 'update_site_settings',
|
|
17
|
-
description: 'Patch site settings. Only the fields you pass change. Nested objects (colors, fonts, contact, social) are shallow-merged.
|
|
21
|
+
description: 'Patch site settings. Only the fields you pass change. Nested objects (colors, fonts, contact, social) are shallow-merged. scripts_head / scripts_body_end / custom_css ARE writable here — useful for a global stylesheet across all pages. Whatever you ship in those fields runs verbatim on the rendered site, so treat them as code, not data.',
|
|
18
22
|
inputSchema: {
|
|
19
23
|
site_name: z.string().optional(),
|
|
20
24
|
tagline: z.string().optional(),
|
|
@@ -23,6 +27,10 @@ export const settingsTools = [
|
|
|
23
27
|
default_seo_suffix: z.string().optional(),
|
|
24
28
|
language: z.string().optional().describe('BCP-47 tag (e.g. "en", "sv", "en-GB"). Drives <html lang> on the rendered site.'),
|
|
25
29
|
robots_txt: z.string().optional(),
|
|
30
|
+
// Scriptable surfaces. Trusted because the caller has an API key.
|
|
31
|
+
scripts_head: z.string().optional().describe('Raw HTML injected into <head> on every page. Use for analytics, fonts, third-party CSS links.'),
|
|
32
|
+
scripts_body_end: z.string().optional().describe('Raw HTML injected just before </body> on every page. Use for chat widgets, deferred analytics.'),
|
|
33
|
+
custom_css: z.string().optional().describe('Global CSS shipped in <style> at the end of <head>. Lets you define site-wide design tokens (CSS variables, @media queries, :hover states) without inlining on every element.'),
|
|
26
34
|
colors: z.record(z.string()).optional(),
|
|
27
35
|
fonts: z.record(z.unknown()).optional(),
|
|
28
36
|
contact: z
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timelesscms-com/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Model Context Protocol server for the TimelessCMS public API. Use with Claude Code or any MCP-compatible client to manage a TimelessCMS site.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|