@latellu/atlas-agent-skills 0.3.1 → 0.4.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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Work with Atlas CMS from Claude Code: bundles the Atlas MCP server (read/write content via tools) and the atlas-cms skill that teaches agents when and how to use the MCP tools, the @latellu/atlas-sdk delivery/management clients, and the @latellu/atlas-cli type generator.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Latellu",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@latellu/atlas-agent-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Agent skill + MCP configuration for Atlas CMS. Works as a Claude Code plugin and as plain markdown guidance for any AI coding agent (Cursor, Codex, opencode, Gemini CLI, ...).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Latellu (https://github.com/tenriajeng)",
|
|
@@ -83,8 +83,7 @@ types, then build the page"), route each sub-task to its own single reference.
|
|
|
83
83
|
Choosing between MCP and the management SDK for writes: MCP is for operating content
|
|
84
84
|
interactively in an agent session (one-off edits, content entry, audits); the SDK is for
|
|
85
85
|
code that outlives the session (migrations, sync jobs, backends) and for anything MCP
|
|
86
|
-
lacks (bulk, scheduling, page unpublish/archive,
|
|
87
|
-
per-field validation errors).
|
|
86
|
+
lacks (bulk, scheduling, page unpublish/archive, block reorder, media alt-text edits).
|
|
88
87
|
|
|
89
88
|
## Hard capability boundaries (don't go looking for these)
|
|
90
89
|
|
|
@@ -71,9 +71,9 @@ Rules that will bite you if unknown:
|
|
|
71
71
|
});
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
- **Via MCP
|
|
75
|
-
`
|
|
76
|
-
|
|
74
|
+
- **Via MCP (≥ 1.1.0)**: pass the same `translations` map as a tool argument on
|
|
75
|
+
`create_entry`/`update_entry`. Older MCP servers silently drop it — if a
|
|
76
|
+
translation write appears to succeed but nothing changes, check the server version.
|
|
77
77
|
|
|
78
78
|
## Writing pages and blocks
|
|
79
79
|
|
|
@@ -104,19 +104,17 @@ The real write shape (`POST/PUT /pages`) differs from what the reading API shows
|
|
|
104
104
|
|
|
105
105
|
- `update` with `blocks` replaces the block list; reordering alone has a dedicated
|
|
106
106
|
endpoint (`blocksReorder(slug, ids)` in the SDK).
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
- MCP `create_page`/`update_page`
|
|
114
|
-
|
|
115
|
-
`
|
|
107
|
+
- **Finding `block_type_id`: read the workspace schema.** Block types are content
|
|
108
|
+
types with `is_block: true`; `GET /api/v1/public/schema` (or the MCP
|
|
109
|
+
`get_workspace_schema` tool) returns each content type's `id`, `is_block`, and field
|
|
110
|
+
list. Use the `id` of an `is_block: true` type as `block_type_id`, and compose the
|
|
111
|
+
block's `data` against its fields exactly like an entry. (Older backends omit
|
|
112
|
+
`id`/`is_block` from the schema — there, block-type UUIDs are dashboard-only.)
|
|
113
|
+
- MCP `create_page`/`update_page` caveats (≥ 1.1.0): `title` is an alias mapped to
|
|
114
|
+
`seo.title` (on update, `seo` is replaced wholesale — title alone drops the other seo
|
|
115
|
+
fields), and `seo_translations`/per-block `translations` are regular arguments.
|
|
116
116
|
- **A page without blocks cannot be published** — publish returns an error requiring at
|
|
117
|
-
least one block.
|
|
118
|
-
workflow can create and edit pages but **never publish a new one**; publishing needs
|
|
119
|
-
someone to add a block in the dashboard first (or block-type UUIDs supplied to you).
|
|
117
|
+
least one block. Compose at least one block (see above) before `publish_page`.
|
|
120
118
|
- **The write plane has no reads**, and write responses don't echo `seo_translations` —
|
|
121
119
|
you cannot fully verify a translation write without a live key for the same workspace.
|
|
122
120
|
Trick: `PUT /pages/:slug` with body `{}` is a safe no-op that returns the page's
|
|
@@ -128,6 +126,7 @@ The real write shape (`POST/PUT /pages`) differs from what the reading API shows
|
|
|
128
126
|
2. Uploads first → collect media UUIDs.
|
|
129
127
|
3. Compose `data` per the table above; localizable required fields go in `translations`.
|
|
130
128
|
4. Create (draft) → read back → verify → publish.
|
|
131
|
-
5. On `VALIDATION_ERROR
|
|
132
|
-
|
|
133
|
-
markdown in richtext, and
|
|
129
|
+
5. On `VALIDATION_ERROR`: the error text carries per-field detail (MCP ≥ 1.1.0 and the
|
|
130
|
+
SDK both surface it) — fix the named field. The usual culprits are
|
|
131
|
+
slug-instead-of-UUID in relation/image, markdown in richtext, and
|
|
132
|
+
required-localizable fields sitting in `data`.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Atlas MCP tools
|
|
2
2
|
|
|
3
3
|
The `@latellu/atlas-mcp` server (stdio) exposes Atlas content operations as agent tools.
|
|
4
|
+
This file describes `@latellu/atlas-mcp` **≥ 1.1.0** (the bundled `.mcp.json` uses
|
|
5
|
+
`npx -y`, so it always runs the latest). Older servers lack `translations`,
|
|
6
|
+
`seo_translations`, per-field error detail, and the schema `id`/`is_block` fields.
|
|
4
7
|
|
|
5
8
|
**Environment** — read tools need `ATLAS_LIVE_API_KEY`, write tools need
|
|
6
9
|
`ATLAS_MGMT_API_KEY`; with only one set, the other half of the tools fails client-side
|
|
@@ -18,7 +21,7 @@ errors).
|
|
|
18
21
|
|
|
19
22
|
| Tool | Params | Returns |
|
|
20
23
|
|---|---|---|
|
|
21
|
-
| `get_workspace_schema` | — | `{ workspace: { slug, name, locales, default_locale }, content_types: [...] }` |
|
|
24
|
+
| `get_workspace_schema` | — | `{ workspace: { slug, name, locales, default_locale }, content_types: [...] }` — each content type carries `id` and `is_block`; types with `is_block: true` are the valid `block_type_id` values for page blocks |
|
|
22
25
|
| `list_content_types` | — | `content_types` array (client-side view of the schema) |
|
|
23
26
|
| `get_content_type` | `content_type` | one content type with its full field list; error text if the slug doesn't exist |
|
|
24
27
|
|
|
@@ -28,8 +31,8 @@ errors).
|
|
|
28
31
|
|---|---|---|
|
|
29
32
|
| `list_entries` | `content_type`, `page?`=1, `limit?`=20, `status?` | `status` is free text and **cannot** reveal drafts to a live key |
|
|
30
33
|
| `get_entry` | `content_type`, `slug` | resolves by `slug` alone — `content_type` is accepted but not sent |
|
|
31
|
-
| `create_entry` | `content_type`, `data`, `slug?` | creates a **draft**; slug auto-generated when omitted |
|
|
32
|
-
| `update_entry` | `content_type`, `slug`, `data` | **full replace** of `data
|
|
34
|
+
| `create_entry` | `content_type`, `data`, `slug?`, `translations?` | creates a **draft**; slug auto-generated when omitted; `translations` = `{locale: {data: {...}}}` (required+localizable fields go HERE) |
|
|
35
|
+
| `update_entry` | `content_type`, `slug`, `data`, `translations?` | **full replace** of `data` (and per-locale `translations`), not a patch |
|
|
33
36
|
| `publish_entry` / `unpublish_entry` / `archive_entry` | `content_type`, `slug` | need the `content:publish` scope |
|
|
34
37
|
| `duplicate_entry` | `content_type`, `slug` | copies to a new draft |
|
|
35
38
|
| `delete_entry` | `content_type`, `slug` | returns plain text, not JSON |
|
|
@@ -40,9 +43,9 @@ errors).
|
|
|
40
43
|
|---|---|---|
|
|
41
44
|
| `list_pages` | `page?`=1, `limit?`=20 | summaries, no blocks |
|
|
42
45
|
| `get_page` | `slug` | block tree — each block's `data` is a **JSON string**, parse it |
|
|
43
|
-
| `create_page` | `
|
|
44
|
-
| `update_page` | `slug`, `title?`, `blocks?`, `seo?` | |
|
|
45
|
-
| `publish_page` | `slug` | the ONLY page lifecycle tool — see gaps below |
|
|
46
|
+
| `create_page` | `slug`, `title?`, `blocks?`, `seo?`, `seo_translations?` | draft; `title` is an alias for `seo.title`; `seo` accepts `title`/`description`/`keywords`/`og_image`/`canonical`; blocks = `[{block_type_id, parent_id?, position, data, translations?}]` |
|
|
47
|
+
| `update_page` | `slug`, `title?`, `blocks?`, `seo?`, `seo_translations?` | passing `blocks` replaces the whole list; `seo` replaces wholesale (title alone drops other seo fields) |
|
|
48
|
+
| `publish_page` | `slug` | the ONLY page lifecycle tool — see gaps below; fails on blockless pages |
|
|
46
49
|
| `delete_page` | `slug` | plain-text result |
|
|
47
50
|
|
|
48
51
|
### Media
|
|
@@ -51,14 +54,16 @@ errors).
|
|
|
51
54
|
|---|---|---|
|
|
52
55
|
| `get_media` | `id` | live key |
|
|
53
56
|
| `upload_media` | `file_path` (absolute), `folder?`="/", `alt_text?` | mgmt key; reads the local file, multipart upload |
|
|
57
|
+
| `delete_media` | `id` | mgmt key; plain-text result |
|
|
54
58
|
|
|
55
59
|
## Recommended write workflow
|
|
56
60
|
|
|
57
61
|
1. **Schema first.** Call `get_content_type` (or `get_workspace_schema`) before any
|
|
58
62
|
`create_entry`/`update_entry`. The `data` argument is an unvalidated map; the backend
|
|
59
|
-
rejects wrong shapes with 400
|
|
60
|
-
|
|
61
|
-
field names/types up front is the difference between one
|
|
63
|
+
rejects wrong shapes with 400. Error results include per-field detail
|
|
64
|
+
(`title: title is required`) and a `traceId` — read them instead of guessing.
|
|
65
|
+
Knowing the exact field names/types up front is still the difference between one
|
|
66
|
+
call and five.
|
|
62
67
|
2. **Read-modify-write for updates.** `update_entry` replaces `data` wholesale:
|
|
63
68
|
`get_entry` → merge your change into the full object → `update_entry` with everything.
|
|
64
69
|
3. **Create → verify → publish.** `create_entry` makes a draft; `publish_entry` needs
|
|
@@ -74,25 +79,15 @@ errors).
|
|
|
74
79
|
These exist in the backend/SDK but have **no MCP tool**; say so and point to the
|
|
75
80
|
dashboard or the management SDK (`sdk-management.md`) rather than improvising:
|
|
76
81
|
|
|
77
|
-
- **Translations cannot be written.** `create_entry`/`update_entry` forward only
|
|
78
|
-
`slug`/`data` — a `translations` key is dropped. "Translate this entry" is an SDK or
|
|
79
|
-
dashboard job (see `authoring.md`), and note that a required+localizable field can
|
|
80
|
-
make MCP creates fail outright (its value must be in `translations`).
|
|
81
82
|
- Page lifecycle beyond publish: **no `unpublish_page`, `archive_page`,
|
|
82
83
|
`duplicate_page`**.
|
|
83
84
|
- **No** bulk operations, entry/page **scheduling**, or block **reorder** tools.
|
|
84
|
-
- **No `
|
|
85
|
-
only through MCP.
|
|
85
|
+
- **No `update_media`** (alt-text edits) — media is upload/read/delete through MCP.
|
|
86
86
|
- Pagination is offset-only (`page`/`limit`); there is no cursor option, so listings
|
|
87
87
|
past page 1000 (`PAGE_TOO_DEEP`) are unreachable via MCP.
|
|
88
|
-
- Composing page **blocks** needs `block_type_id` UUIDs that no read surface exposes
|
|
89
|
-
(dashboard-only knowledge) — prefer entries for agent-authored content. Also:
|
|
90
|
-
`create_page`'s `title` argument is ignored by the backend (the title is `seo.title`),
|
|
91
|
-
and its `seo` accepts only `title`/`description`.
|
|
92
88
|
|
|
93
89
|
Before composing any `data` payload, read `authoring.md` — it defines the exact value
|
|
94
|
-
format per field type (richtext = Tiptap HTML, relation/image = UUIDs, etc.)
|
|
95
|
-
matters double here because MCP swallows per-field validation errors.
|
|
90
|
+
format per field type (richtext = Tiptap HTML, relation/image = UUIDs, etc.).
|
|
96
91
|
|
|
97
92
|
## Gotchas
|
|
98
93
|
|
|
@@ -113,8 +113,8 @@ try {
|
|
|
113
113
|
if (e instanceof AtlasError) {
|
|
114
114
|
e.status; // 400
|
|
115
115
|
e.code; // "VALIDATION_ERROR"
|
|
116
|
-
e.errors; // [{ field: "title", message: "must be a string" }, ...] ← per-field detail
|
|
117
|
-
} //
|
|
116
|
+
e.errors; // [{ field: "title", message: "must be a string" }, ...] ← per-field detail
|
|
117
|
+
} // (structured here; MCP ≥ 1.1.0 appends it to the error text) — surface it to users
|
|
118
118
|
}
|
|
119
119
|
```
|
|
120
120
|
|