@latellu/atlas-agent-skills 0.4.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas",
3
- "version": "0.4.0",
3
+ "version": "0.5.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.4.0",
3
+ "version": "0.5.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)",
@@ -82,8 +82,7 @@ types, then build the page"), route each sub-task to its own single reference.
82
82
 
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
- code that outlives the session (migrations, sync jobs, backends) and for anything MCP
86
- lacks (bulk, scheduling, page unpublish/archive, block reorder, media alt-text edits).
85
+ code that outlives the session (migrations, sync jobs, backends) and for structured per-field error objects.
87
86
 
88
87
  ## Hard capability boundaries (don't go looking for these)
89
88
 
@@ -1,9 +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
4
+ This file describes `@latellu/atlas-mcp` **≥ 1.2.0** (the bundled `.mcp.json` uses
5
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.
6
+ `seo_translations`, per-field error detail, the schema `id`/`is_block` fields, page/entry lifecycle tools (unpublish/archive/duplicate/schedule), bulk operations, reorder tools, media alt-text editing, list meta+cursor pagination, automatic 429 retry, and pre-parsed block data in get_page.
7
7
 
8
8
  **Environment** — read tools need `ATLAS_LIVE_API_KEY`, write tools need
9
9
  `ATLAS_MGMT_API_KEY`; with only one set, the other half of the tools fails client-side
@@ -29,32 +29,42 @@ errors).
29
29
 
30
30
  | Tool | Params | Notes |
31
31
  |---|---|---|
32
- | `list_entries` | `content_type`, `page?`=1, `limit?`=20, `status?` | `status` is free text and **cannot** reveal drafts to a live key |
33
- | `get_entry` | `content_type`, `slug` | resolves by `slug` alone — `content_type` is accepted but not sent |
32
+ | `list_entries` | `content_type`, `page?`=1, `limit?`=20, `status?`, `cursor?` | `status` is enum (draft \| published \| archived \| scheduled); live key **cannot** reveal drafts. Returns `{ data, meta }` where `meta` has `total_data`, `total_pages` (offset mode) or `next_cursor` (cursor mode). Use `cursor: meta.next_cursor` to page past the 1000-page limit. |
33
+ | `get_entry` | `content_type`, `slug` | resolves by `slug` alone — `content_type` is accepted but errors clearly if the slug belongs to a different content type |
34
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
35
  | `update_entry` | `content_type`, `slug`, `data`, `translations?` | **full replace** of `data` (and per-locale `translations`), not a patch |
36
36
  | `publish_entry` / `unpublish_entry` / `archive_entry` | `content_type`, `slug` | need the `content:publish` scope |
37
37
  | `duplicate_entry` | `content_type`, `slug` | copies to a new draft |
38
- | `delete_entry` | `content_type`, `slug` | returns plain text, not JSON |
38
+ | `schedule_entry` | `content_type`, `slug`, `publish_at` | schedules publish for a future time (ISO 8601 UTC); needs `content:publish` scope |
39
+ | `bulk_entries` | `content_type`, `operations` | batch publish/archive/schedule/delete by entry UUIDs; each op in array: `{ op, id, publish_at? }` |
40
+ | `bulk_delete_entries` | `content_type`, `ids` | batch delete by entry UUIDs |
41
+ | `reorder_entries` | `content_type`, `ids` | reorder entries; pass the complete ordered list of entry UUIDs |
42
+ | `delete_entry` | `content_type`, `slug` | returns JSON `{ deleted: true, ... }` |
39
43
 
40
44
  ### Pages — read: live key · write: mgmt key
41
45
 
42
46
  | Tool | Params | Notes |
43
47
  |---|---|---|
44
- | `list_pages` | `page?`=1, `limit?`=20 | summaries, no blocks |
45
- | `get_page` | `slug` | block tree — each block's `data` is a **JSON string**, parse it |
48
+ | `list_pages` | `page?`=1, `limit?`=20, `cursor?` | summaries, no blocks. Returns `{ data, meta }` with cursor pagination option (same as `list_entries`). |
49
+ | `get_page` | `slug` | block tree — each block's `data` is already parsed to an object (no JSON.parse needed); nested children and translations included |
46
50
  | `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
51
  | `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 |
49
- | `delete_page` | `slug` | plain-text result |
52
+ | `publish_page` | `slug` | publish a page; fails on blockless pages |
53
+ | `unpublish_page` | `slug` | unpublish a page; needs `content:publish` scope |
54
+ | `archive_page` | `slug` | archive a page; needs `content:publish` scope |
55
+ | `duplicate_page` | `slug` | copy to a new draft |
56
+ | `schedule_page` | `slug`, `publish_at` | schedule publish for a future time (ISO 8601 UTC); needs `content:publish` scope |
57
+ | `reorder_page_blocks` | `slug`, `block_ids` | reorder blocks; pass the complete ordered list of block IDs |
58
+ | `delete_page` | `slug` | returns JSON `{ deleted: true, ... }` |
50
59
 
51
60
  ### Media
52
61
 
53
62
  | Tool | Params | Notes |
54
63
  |---|---|---|
55
64
  | `get_media` | `id` | live key |
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 |
65
+ | `upload_media` | `file_path` (absolute), `folder?`="/", `alt_text?` | mgmt key; reads the local file, multipart upload. Pre-validates client-side: ≤10 MB, extension allowlist (.jpg .jpeg .png .gif .webp .mp4 .webm .mov .pdf). |
66
+ | `update_media` | `id`, `alt_text?`, `folder?` | mgmt key; edit metadata (alt text and/or folder). |
67
+ | `delete_media` | `id` | mgmt key; returns JSON `{ deleted: true, ... }` |
58
68
 
59
69
  ## Recommended write workflow
60
70
 
@@ -68,23 +78,20 @@ errors).
68
78
  `get_entry` → merge your change into the full object → `update_entry` with everything.
69
79
  3. **Create → verify → publish.** `create_entry` makes a draft; `publish_entry` needs
70
80
  the `content:publish` scope (403 without it, with a clear message).
71
- 4. **Before `upload_media`**: check the file is ≤ 10 MB and has a known extension.
72
- Validation is entirely server-side; MIME is inferred from the extension and unknown
73
- extensions upload as `application/octet-stream`, which the backend always rejects
74
- (so `.svg`, `.heic` fail). Allowed: `image/*`, `video/*`, `application/pdf` (i.e.
75
- `.jpg .jpeg .png .gif .webp .mp4 .webm .mov .pdf`).
81
+ 4. **Before `upload_media`**: the tool pre-validates client-side (≤ 10 MB, extension
82
+ allowlist), so failures are caught before upload. Allowed extensions:
83
+ `.jpg .jpeg .png .gif .webp .mp4 .webm .mov .pdf`.
76
84
 
77
85
  ## Capability gaps — route elsewhere instead of retrying
78
86
 
79
- These exist in the backend/SDK but have **no MCP tool**; say so and point to the
80
- dashboard or the management SDK (`sdk-management.md`) rather than improvising:
87
+ The MCP manage plane is now fully covered. These gaps remain:
81
88
 
82
- - Page lifecycle beyond publish: **no `unpublish_page`, `archive_page`,
83
- `duplicate_page`**.
84
- - **No** bulk operations, entry/page **scheduling**, or block **reorder** tools.
85
- - **No `update_media`** (alt-text edits) media is upload/read/delete through MCP.
86
- - Pagination is offset-only (`page`/`limit`); there is no cursor option, so listings
87
- past page 1000 (`PAGE_TOO_DEEP`) are unreachable via MCP.
89
+ - **No automatic merge on `update_entry`** — the tool replaces `data` wholesale, so you
90
+ must read-modify-write yourself: `get_entry` → merge your changes → `update_entry`.
91
+ The management SDK offers an `update` overload that accepts a patch, but MCP does not.
92
+ - **Schema is dashboard-only.** Creating content types, fields, or block types is not
93
+ exposed via MCP, SDK, or API. "Create a content type" = ask the user to do it in the
94
+ dashboard, then continue.
88
95
 
89
96
  Before composing any `data` payload, read `authoring.md` — it defines the exact value
90
97
  format per field type (richtext = Tiptap HTML, relation/image = UUIDs, etc.).
@@ -93,11 +100,8 @@ format per field type (richtext = Tiptap HTML, relation/image = UUIDs, etc.).
93
100
 
94
101
  - **Drafts are invisible to live keys** — `list_entries(status="draft")` returning
95
102
  empty means key scoping, not an empty workspace.
96
- - **`get_page` block `data` needs `JSON.parse`** per block, or blocks look empty.
97
- - **`get_entry` ignores `content_type`** slugs shared across types resolve to
98
- whatever the backend finds by slug.
99
- - **No client-side 429 retry** — on `Atlas API error: 429`, back off exponentially and
100
- retry the tool call yourself.
103
+ - **`get_entry` errors if the slug belongs to a different content type** — content types
104
+ used to share slug namespaces, now you get a clear error instead of silent mismatch.
101
105
  - Every write sends a fresh auto-generated `Idempotency-Key` per call, so re-issuing a
102
106
  failed create as a *new tool call* is a new operation — check whether the first call
103
107
  actually landed (e.g. `get_entry`) before retrying creates.
@@ -69,7 +69,7 @@ await client.pages.create({ slug: "landing", seo: { title: "Landing" }, blocks:
69
69
  await client.pages.update("landing", { seo: { title: "Landing v2" } });
70
70
  await client.pages.publish("landing"); // fails unless the page has ≥1 block — see authoring.md
71
71
  await client.pages.unpublish("landing"); // pages have the FULL lifecycle here
72
- await client.pages.archive("landing"); // (unlike the MCP tools)
72
+ await client.pages.archive("landing"); // (MCP also has parity since v1.2.0)
73
73
  await client.pages.schedule("landing", "2026-08-01T09:00:00Z");
74
74
  await client.pages.delete("landing");
75
75
  await client.pages.blocksReorder("landing", ["block-id-2", "block-id-1", "block-id-3"]);