@damusix/ghost-mcp 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.
- package/README.md +18 -0
- package/dist/index.mjs +51 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/skills/ghost-writing/SKILL.md +40 -0
- package/skills/ghost-writing/references/blocks.md +68 -0
- package/skills/ghost-writing/references/workflows.md +68 -0
- package/skills/ghost-writing/references/writing.md +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@damusix/ghost-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MCP server for Ghost CMS — manage content, members, newsletters, and more via LLMs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"bin",
|
|
23
|
-
"dist"
|
|
23
|
+
"dist",
|
|
24
|
+
"skills"
|
|
24
25
|
],
|
|
25
26
|
"type": "module",
|
|
26
27
|
"main": "./dist/index.mjs",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ghost-writing
|
|
3
|
+
description: 'Use when writing, editing, or publishing Ghost blog posts with the ghost-mcp server. Covers compose_post vs use_ghost_api, the full Koenig block catalog, and blog-writing best practices.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Ghost Writing
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
compose_post {
|
|
11
|
+
"title": "We shipped the editor",
|
|
12
|
+
"status": "draft",
|
|
13
|
+
"blocks": [
|
|
14
|
+
{ "type": "heading", "level": 2, "text": "What changed" },
|
|
15
|
+
{ "type": "paragraph", "text": "Prose with **bold** and [links](https://ghost.org)." },
|
|
16
|
+
{ "type": "callout", "emoji": "🚀", "color": "green", "text": "Live now" }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
## Critical Rules
|
|
21
|
+
|
|
22
|
+
1. **Never push raw HTML** — compose posts from Koenig blocks via `compose_post`. The `html` block is a last resort when no native block fits.
|
|
23
|
+
2. **Draft first** — create with `status: "draft"`, report the post URL, publish only when asked.
|
|
24
|
+
3. **Long posts use `blockFile`** — write blocks JSON to an absolute path, validate with `compose_lexical`, pass the path to `compose_post`.
|
|
25
|
+
4. **Updates replace the whole body** — `compose_post` with `id` needs the current `updated_at` (via `posts.read`) and full blocks; keep the blockFile as source of truth when iterating.
|
|
26
|
+
5. **Don't guess block fields** — run `koenig_help` (list) or `koenig_help { "block": "callout" }` (fields + example) before composing.
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
1. Outline: one goal per post, working title, section list — see [writing](references/writing.md)
|
|
31
|
+
2. Pick blocks per section: prose vs cards — see [blocks](references/blocks.md)
|
|
32
|
+
3. Compose: `compose_post` with inline `blocks`, or `blockFile` for long posts
|
|
33
|
+
4. Review: re-read for rhythm, set excerpt/tags/feature image
|
|
34
|
+
5. Publish or schedule via `use_ghost_api` — see [workflows](references/workflows.md)
|
|
35
|
+
|
|
36
|
+
## References
|
|
37
|
+
|
|
38
|
+
- [Blocks](references/blocks.md) — every block type, its purpose, and how to choose between similar ones
|
|
39
|
+
- [Writing](references/writing.md) — structure, rhythm, metadata, and voice practices for good posts
|
|
40
|
+
- [Workflows](references/workflows.md) — tool map and end-to-end recipes: create, edit, publish, newsletter, members-only
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Koenig block catalog
|
|
2
|
+
|
|
3
|
+
Every block `compose_post` / `compose_lexical` accepts, grouped as `koenig_help` reports them. This page tells you **when to reach for each block**; run `koenig_help { "block": "<type>" }` for exact fields and a JSON example — never guess field names.
|
|
4
|
+
|
|
5
|
+
## Prose blocks
|
|
6
|
+
|
|
7
|
+
Native Lexical nodes. Their `text` (or `items`) accepts inline markdown: `**bold**`, `_italic_`, `` `code` ``, `[links](url)`.
|
|
8
|
+
|
|
9
|
+
| Block | Purpose | Reach for it when |
|
|
10
|
+
| ----------- | ---------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
11
|
+
| `paragraph` | A text paragraph | The default. Most of every post should be paragraphs. |
|
|
12
|
+
| `heading` | Section heading, `level` 1–6 (default 2) | Starting a new section. Use level 2 for top sections — the post title is the h1. |
|
|
13
|
+
| `list` | Bullet or numbered list (`style`: `bullet`/`number`) | 3+ parallel items; steps in order → `number`. |
|
|
14
|
+
| `quote` | Blockquote | Quoting a person or source. Attribution goes in a following paragraph. |
|
|
15
|
+
| `aside` | Pull-quote / aside | Restating your own key line for emphasis, or a tangential remark. |
|
|
16
|
+
|
|
17
|
+
## Media cards
|
|
18
|
+
|
|
19
|
+
| Block | Purpose | Reach for it when |
|
|
20
|
+
| --------- | -------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
21
|
+
| `image` | Single image with caption, alt text, optional link | One illustrative image. Always set `alt`. |
|
|
22
|
+
| `gallery` | Grid of images (`images` array) | 3+ related photos that belong together; beats stacking `image` blocks. |
|
|
23
|
+
| `video` | Video file (`src`, `thumbnailSrc` poster) | Self-hosted video files. For YouTube/Vimeo use `embed` instead. |
|
|
24
|
+
| `audio` | Audio file with title | Podcast episodes, audio clips. |
|
|
25
|
+
| `file` | Downloadable file card | PDFs, zips — anything the reader should download. |
|
|
26
|
+
|
|
27
|
+
## Embed cards
|
|
28
|
+
|
|
29
|
+
| Block | Purpose | Reach for it when |
|
|
30
|
+
| ----------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
31
|
+
| `bookmark` | Rich link preview (title, description, thumbnail) | Featuring a link as a visual card — recommended reading, source articles. |
|
|
32
|
+
| `embed` | External embed (YouTube, X/Twitter, etc.) via `url` + embed `html` | Third-party content with a player/widget. |
|
|
33
|
+
| `codeblock` | Syntax-highlighted code block | Any multi-line code. Set `language`. Inline code stays in paragraph backticks. |
|
|
34
|
+
| `markdown` | Markdown rendered as one opaque unit | Avoid for prose — it edits as one blob. Only for markdown-specific needs (e.g. tables). |
|
|
35
|
+
| `html` | Raw HTML passthrough | Last resort when no native block fits. Not granularly editable in Ghost. |
|
|
36
|
+
|
|
37
|
+
## Layout cards
|
|
38
|
+
|
|
39
|
+
| Block | Purpose | Reach for it when |
|
|
40
|
+
| --------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
41
|
+
| `callout` | Highlighted box with emoji and background color | Warnings, tips, key takeaways — content the reader must not skim past. |
|
|
42
|
+
| `toggle` | Collapsible accordion (`content` is HTML; no-op in email) | FAQs, optional detail. Don't hide essential content in it. |
|
|
43
|
+
| `button` | Call-to-action button | The post's one primary action. More than ~2 buttons dilutes all of them. |
|
|
44
|
+
| `header` | Large hero header with optional background image and button | Big visual section break in landing-page-style posts. Regular posts use `heading`. |
|
|
45
|
+
| `cta` | Call-to-action card with text, optional image and button | A richer pitch than a bare `button` — sponsorships, product plugs. |
|
|
46
|
+
| `product` | Product card with image, star rating, and button | Product recommendations and reviews. |
|
|
47
|
+
|
|
48
|
+
## Membership cards
|
|
49
|
+
|
|
50
|
+
| Block | Purpose | Reach for it when |
|
|
51
|
+
| --------- | ----------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| `signup` | Member signup form (no-op in email) | Converting anonymous readers — typically near the end, once. |
|
|
53
|
+
| `paywall` | Free/paid split marker | Everything after it is members-only. Place it after the hook so free readers get real value first. |
|
|
54
|
+
|
|
55
|
+
## Structure & email cards
|
|
56
|
+
|
|
57
|
+
| Block | Purpose | Reach for it when |
|
|
58
|
+
| ----------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
59
|
+
| `divider` | Horizontal rule | Scene change within a section. Between sections, a `heading` already breaks the page. |
|
|
60
|
+
| `email` | Content shown ONLY in the email newsletter (`html`, supports `{first_name, "fallback"}`) | Personal newsletter greetings/postscripts. Invisible on web. |
|
|
61
|
+
| `email-cta` | Newsletter-only CTA targeting a member segment | Upgrade pitches to free subscribers in the email version. |
|
|
62
|
+
|
|
63
|
+
## Choosing between similar blocks
|
|
64
|
+
|
|
65
|
+
- **`quote` vs `aside` vs `callout`** — someone else's words → `quote`; your own line, amplified → `aside`; must-see info with visual weight → `callout`.
|
|
66
|
+
- **`bookmark` vs `embed` vs `button`** — preview a link → `bookmark`; play third-party content inline → `embed`; ask the reader to act → `button`.
|
|
67
|
+
- **`heading` vs `header`** — document structure → `heading`; full-width visual hero → `header`.
|
|
68
|
+
- **prose blocks vs `markdown` vs `html`** — native blocks edit granularly in Ghost's editor; `markdown` and `html` become single opaque cards. Prefer native, always.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Tool map and workflows
|
|
2
|
+
|
|
3
|
+
## Which tool when
|
|
4
|
+
|
|
5
|
+
| Tool | Use it for |
|
|
6
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------ |
|
|
7
|
+
| `koenig_help` | Discover block types, or fields + JSON example for one block. Call before composing. |
|
|
8
|
+
| `compose_post` | Create or update a post from blocks. The default write path — never hand-write `lexical` or push HTML. |
|
|
9
|
+
| `compose_lexical` | Compile blocks to Lexical JSON _without_ touching the site — validate a `blockFile`, or preview. |
|
|
10
|
+
| `use_ghost_api` | Everything else: publish/schedule, tags, images, members, newsletters, settings. |
|
|
11
|
+
| `ghost_api_help` | List available actions, or the payload schema for one (`{ "action": "posts.edit" }`). |
|
|
12
|
+
| `ghost_docs` | Search Ghost's official docs (`{ "search": "..." }`) for platform questions the tools don't answer. |
|
|
13
|
+
|
|
14
|
+
The server runs in **admin** mode (full read/write) or **content** mode (read-only). In content mode every write is rejected — surface that to the user instead of retrying.
|
|
15
|
+
|
|
16
|
+
## Create a post
|
|
17
|
+
|
|
18
|
+
1. `koenig_help` — refresh the block list if unsure.
|
|
19
|
+
2. Build the `blocks` array ([blocks.md](blocks.md)), following [writing.md](writing.md).
|
|
20
|
+
3. `compose_post { "title": ..., "status": "draft", "blocks": [...], "excerpt": ..., "tags": [{ "name": ... }] }`.
|
|
21
|
+
4. Report the returned post `url` and `id` so the user can review the draft.
|
|
22
|
+
|
|
23
|
+
Stay in `draft` until the user asks to publish. `visibility` controls access: `public`, `members`, `paid`, or `tiers`.
|
|
24
|
+
|
|
25
|
+
## Long posts: `blockFile`
|
|
26
|
+
|
|
27
|
+
For posts beyond ~15 blocks, don't inline the array — write it to a file and iterate there:
|
|
28
|
+
|
|
29
|
+
1. Write the blocks JSON (bare `[...]` or `{ "blocks": [...] }`) to an **absolute** path, e.g. under the project's `tmp/`.
|
|
30
|
+
2. Validate: `compose_lexical { "blockFile": "/abs/path/tmp/post.json" }` — composition errors list the offending block index.
|
|
31
|
+
3. Create: `compose_post { "title": ..., "status": "draft", "blockFile": "/abs/path/tmp/post.json" }`.
|
|
32
|
+
4. To revise, edit the file and re-run — the file is the source of truth; you never re-send the array.
|
|
33
|
+
|
|
34
|
+
## Edit an existing post
|
|
35
|
+
|
|
36
|
+
`compose_post` with an `id` **replaces the entire body** — there is no partial patch:
|
|
37
|
+
|
|
38
|
+
1. `use_ghost_api { "action": "posts.read", "payload": { "id": ..., "formats": "lexical" } }` — get current content and `updated_at`.
|
|
39
|
+
2. Rebuild the full blocks array (from your blockFile if you authored the post; otherwise reconstruct from the fetched content).
|
|
40
|
+
3. `compose_post { "id": ..., "updated_at": "<current value>", "blocks": [...] }` — a stale `updated_at` is rejected as a collision; re-read and retry.
|
|
41
|
+
|
|
42
|
+
For metadata-only changes (title, tags, status, feature image) skip composition and call `posts.edit` directly with `id` + `updated_at`.
|
|
43
|
+
|
|
44
|
+
## Publish or schedule
|
|
45
|
+
|
|
46
|
+
- Publish now: `posts.edit` with `{ "status": "published" }`.
|
|
47
|
+
- Schedule: `posts.edit` with `{ "status": "scheduled", "published_at": "<future ISO 8601>" }`.
|
|
48
|
+
- Both need `id` + current `updated_at`. Confirm with the user before either — publishing can trigger the newsletter send.
|
|
49
|
+
|
|
50
|
+
## Images
|
|
51
|
+
|
|
52
|
+
Ghost only renders images it can reach by URL:
|
|
53
|
+
|
|
54
|
+
1. Local file → `images.upload` (multipart upload) → returns the hosted URL.
|
|
55
|
+
2. Use that URL in `image` / `gallery` blocks or as `feature_image`.
|
|
56
|
+
3. External URLs work directly but break if the host removes them — prefer uploading.
|
|
57
|
+
|
|
58
|
+
## Newsletter posts
|
|
59
|
+
|
|
60
|
+
- Email-specific content: `email` and `email-cta` blocks ([blocks.md](blocks.md) — email-only group).
|
|
61
|
+
- Audience and sending are controlled at publish time by newsletter settings, not by blocks; check `newsletters.browse` for what exists and `ghost_docs { "search": "newsletter" }` for sending mechanics.
|
|
62
|
+
- Members-only content: `paywall` block for in-post splits, `visibility` for whole-post gating.
|
|
63
|
+
|
|
64
|
+
## When something fails
|
|
65
|
+
|
|
66
|
+
- Composition errors from `compose_post`/`compose_lexical` return `issues` with block indexes — fix the named block, don't fall back to `html`.
|
|
67
|
+
- API errors come back verbatim from Ghost; `ghost_api_help { "action": ... }` shows the expected payload shape.
|
|
68
|
+
- Unknown platform behavior (routing, themes, member tiers): `ghost_docs` before guessing.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Writing good posts
|
|
2
|
+
|
|
3
|
+
Block mechanics live in [blocks.md](blocks.md); this page is the craft — what makes a post worth reading and how to shape it with the blocks you have.
|
|
4
|
+
|
|
5
|
+
## One post, one goal
|
|
6
|
+
|
|
7
|
+
Before composing anything, answer: _what should the reader know or do after this post?_ If you have two answers, you have two posts. The goal decides the title, the structure, and the single CTA at the end.
|
|
8
|
+
|
|
9
|
+
## Structure
|
|
10
|
+
|
|
11
|
+
- **Hook first.** The opening paragraph earns the rest of the read: state the problem, the payoff, or the surprising claim. No throat-clearing ("In today's fast-paced world...").
|
|
12
|
+
- **Sections with `heading` level 2.** The post title is the h1 — body headings start at level 2, with level 3 for subsections. Never skip levels.
|
|
13
|
+
- **A heading every 3–6 paragraphs.** Readers scan before they read; headings are the scan path. A heading should make sense out of context.
|
|
14
|
+
- **End with a conclusion and one CTA.** Summarize the takeaway in a paragraph, then one `button`, `signup`, or `cta` — not all three.
|
|
15
|
+
|
|
16
|
+
## Rhythm and scannability
|
|
17
|
+
|
|
18
|
+
- Keep paragraphs to 2–4 sentences. A wall of `paragraph` blocks reads as work; break long chains with a `list`, `image`, `callout`, or `divider`.
|
|
19
|
+
- Use `list` whenever items are parallel — three comma-separated clauses in a sentence usually want to be bullets.
|
|
20
|
+
- Bold sparingly: **key terms** the scanner's eye should catch, not whole sentences.
|
|
21
|
+
- Make link text descriptive: `[the Koenig card reference](url)`, never `[click here](url)`.
|
|
22
|
+
- One `callout` per section at most. When everything is highlighted, nothing is.
|
|
23
|
+
|
|
24
|
+
## Metadata
|
|
25
|
+
|
|
26
|
+
Set these on every post — they control how the post looks in lists, search, and shares:
|
|
27
|
+
|
|
28
|
+
- **`title`** — under ~60 characters so it doesn't truncate in search results. Specific beats clever.
|
|
29
|
+
- **`excerpt`** — 1–2 sentences selling the post; shows in post lists and social cards. Don't let Ghost auto-truncate the first paragraph.
|
|
30
|
+
- **`tags`** — the first tag is the primary tag and drives theme layout/routing. Reuse existing tags (check `tags.browse`) before inventing new ones.
|
|
31
|
+
- **`feature_image`** — posts without one look broken in most themes' index pages. Upload via `images.upload` if needed.
|
|
32
|
+
- **`slug`** — short and stable; leave it derived from the title unless the title is long.
|
|
33
|
+
- **`meta_title` / `meta_description`** — only when the SEO framing should differ from the display title/excerpt (set via `use_ghost_api posts.edit`).
|
|
34
|
+
|
|
35
|
+
## Voice
|
|
36
|
+
|
|
37
|
+
- Plain, precise language. Name things what they are; cut adjectives that don't discriminate.
|
|
38
|
+
- Active voice, present tense where possible. "Ghost renders the card" over "the card is rendered".
|
|
39
|
+
- Concrete beats abstract: an example, a number, or a before/after every few paragraphs.
|
|
40
|
+
- Cut the last 10%: filler intros, hedges ("arguably", "quite"), and restating what the heading already said.
|
|
41
|
+
|
|
42
|
+
## Newsletter and member awareness
|
|
43
|
+
|
|
44
|
+
If the post goes out as a newsletter or sits behind membership, shape it for both renderings:
|
|
45
|
+
|
|
46
|
+
- `toggle` and `signup` do nothing in email; don't put essential content or the only CTA in them.
|
|
47
|
+
- `email` / `email-cta` blocks are invisible on web — use them for the personal greeting or subscriber-only pitch, never for the body.
|
|
48
|
+
- Place `paywall` after the hook and first real insight, so free readers get value and a reason to upgrade.
|