@damusix/ghost-mcp 0.3.0 → 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.
package/README.md CHANGED
@@ -62,9 +62,58 @@ claude mcp add ghost \
62
62
 
63
63
  ## Tools
64
64
 
65
+ ### `compose_post`
66
+
67
+ Create or update a post from structured Koenig content blocks. Prefer this over
68
+ pushing raw HTML or hand-writing Lexical — it produces clean, natively-editable
69
+ posts. Prose blocks (`paragraph`, `heading`, `list`, `quote`) accept inline
70
+ markdown (`**bold**`, `_italic_`, `` `code` ``, `[links](url)`); rich features
71
+ are cards (`callout`, `image`, `button`, `bookmark`, `codeblock`, `toggle`,
72
+ `gallery`, and more). Omit `id` to create; set `id` + `updated_at` to update.
73
+
74
+ ```json
75
+ {
76
+ "title": "We shipped the editor",
77
+ "status": "draft",
78
+ "blocks": [
79
+ { "type": "heading", "level": 2, "text": "What's new" },
80
+ { "type": "paragraph", "text": "Try the **new** [editor](https://ghost.org)." },
81
+ { "type": "list", "style": "bullet", "items": ["Clean blocks", "Native editing"] },
82
+ { "type": "callout", "emoji": "🚀", "color": "green", "text": "Live now" },
83
+ { "type": "button", "text": "Get started", "url": "https://ghost.org" }
84
+ ]
85
+ }
86
+ ```
87
+
88
+ Each block becomes a native Lexical node, so the post stays granularly editable
89
+ in Ghost — not a single opaque HTML block. See
90
+ [docs/koenig-cards.md](docs/koenig-cards.md) for every block type and field.
91
+
92
+ For long posts, write the blocks to a JSON file (a bare `[...]` array or
93
+ `{ "blocks": [...] }`) and pass its **absolute** path as `blockFile` instead of
94
+ `blocks` — useful when iterating, so you edit the file rather than re-sending the
95
+ whole array each time. Validate the file first with `compose_lexical`.
96
+
97
+ ```json
98
+ { "title": "Long post", "status": "draft", "blockFile": "/abs/path/tmp/post.json" }
99
+ ```
100
+
101
+ ### `compose_lexical`
102
+
103
+ Compile the same `blocks` into a Lexical JSON string without creating a post —
104
+ useful for preview or feeding into `use_ghost_api` yourself.
105
+
106
+ ### `koenig_help`
107
+
108
+ List all block types, or get the fields and a JSON example for one block.
109
+
110
+ ```json
111
+ { "block": "callout" }
112
+ ```
113
+
65
114
  ### `use_ghost_api`
66
115
 
67
- Execute Ghost API actions. Supports full CRUD on posts, pages, tags, members, newsletters, offers, tiers, users, webhooks, images, themes, and site settings.
116
+ Execute Ghost API actions. Supports full CRUD on posts, pages, tags, members, newsletters, offers, tiers, users, webhooks, images, themes, and site settings. For post bodies, prefer `compose_post` over passing raw `lexical`/`html` here.
68
117
 
69
118
  ```json
70
119
  {
@@ -109,6 +158,17 @@ Search Ghost documentation via `docs.ghost.org/llms.txt`.
109
158
  - **Admin mode** (default): Full access to all Ghost Admin API and Content API actions. Requires `GHOST_ADMIN_API_KEY`.
110
159
  - **Content mode**: Read-only access to Content API actions only. Requires `GHOST_CONTENT_API_KEY`. Admin actions are rejected with a clear error.
111
160
 
161
+ ## Development
162
+
163
+ Spin up a throwaway Ghost 6 + MySQL 8 stack to exercise the server against a
164
+ live API, and inspect Ghost's real schema with noorm. See
165
+ [docs/experimentation.md](docs/experimentation.md).
166
+
167
+ ```bash
168
+ docker compose up -d
169
+ bin/ghost-keys.sh --write # writes .env pointed at the local Ghost
170
+ ```
171
+
112
172
  ## License
113
173
 
114
174
  MIT
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ # Derive Ghost API keys for the MCP straight from the dockerized DB via noorm.
3
+ #
4
+ # Reads the admin + content keys for a Ghost integration and prints them as
5
+ # env lines (or writes them to .env with --write). The Admin API key is the
6
+ # Ghost `{key_id}:{secret}` form; the Content API key is the bare secret.
7
+ #
8
+ # bin/ghost-keys.sh # print keys for the "MCP Lab" integration
9
+ # bin/ghost-keys.sh "Zapier" # a different integration by name
10
+ # bin/ghost-keys.sh --write # write/refresh .env for the MCP
11
+ #
12
+ # Requires: the docker stack up (docker compose up -d) and noorm installed.
13
+ set -euo pipefail
14
+
15
+ cd "$(dirname "$0")/.."
16
+
17
+ WRITE=0
18
+ INTEGRATION="MCP Lab"
19
+ for arg in "$@"; do
20
+ case "$arg" in
21
+ --write) WRITE=1 ;;
22
+ *) INTEGRATION="$arg" ;;
23
+ esac
24
+ done
25
+
26
+ # shellcheck disable=SC1091
27
+ set -a; source .noorm/dev.env; set +a
28
+
29
+ row() {
30
+ noorm --json sql "SELECT ak.secret AS secret, ak.id AS key_id FROM api_keys ak JOIN integrations i ON i.id = ak.integration_id WHERE i.name = '${INTEGRATION}' AND ak.type = '$1'" 2>/dev/null | grep '^{' | head -1
31
+ }
32
+
33
+ ADMIN_ROW=$(row admin)
34
+ CONTENT_ROW=$(row content)
35
+
36
+ if [[ -z "$ADMIN_ROW" ]]; then
37
+ echo "No admin key found for integration '${INTEGRATION}'." >&2
38
+ echo "Is the stack up, and does that integration exist? (Ghost Admin > Settings > Integrations)" >&2
39
+ exit 1
40
+ fi
41
+
42
+ ADMIN_KEY="$(jq -r '.key_id' <<<"$ADMIN_ROW"):$(jq -r '.secret' <<<"$ADMIN_ROW")"
43
+ CONTENT_KEY="$(jq -r '.secret' <<<"$CONTENT_ROW")"
44
+
45
+ if [[ "$WRITE" == "1" ]]; then
46
+ cat > .env <<EOF
47
+ # Local MCP -> dockerized Ghost (docker-compose.yml). Gitignored; throwaway creds.
48
+ # Regenerate with: bin/ghost-keys.sh --write
49
+ GHOST_URL=http://localhost:2368
50
+ GHOST_API_VERSION=v6.0
51
+ GHOST_ADMIN_API_KEY=${ADMIN_KEY}
52
+ GHOST_CONTENT_API_KEY=${CONTENT_KEY}
53
+ EOF
54
+ echo "Wrote .env for integration '${INTEGRATION}'."
55
+ else
56
+ echo "GHOST_ADMIN_API_KEY=${ADMIN_KEY}"
57
+ echo "GHOST_CONTENT_API_KEY=${CONTENT_KEY}"
58
+ fi