@motiblog/mcp 0.1.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 +236 -0
- package/dist/http.d.ts +31 -0
- package/dist/http.js +1109 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1034 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +266 -0
- package/dist/lib.js +1085 -0
- package/dist/lib.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# MotiBlog MCP server
|
|
2
|
+
|
|
3
|
+
Agent-first control surface over the MotiBlog API ([`docs/product/agent-first-direction.md`](../../docs/product/agent-first-direction.md), Direction B). Any MCP-capable agent — Claude Code, Cursor, Codex — can run a whole blog: propose topics, generate, review fact-check reports, approve through the governed gate, publish to targets, or **export content to deploy into its own codebase**.
|
|
4
|
+
|
|
5
|
+
## Quick start — hosted (what a customer uses)
|
|
6
|
+
|
|
7
|
+
The transport is mounted inside the API process, so the deployed API *is* the
|
|
8
|
+
MCP endpoint. No install, no repository checkout, no separate service:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# 1. Get a per-project API key (dashboard: Project → Blog API → copy/rotate)
|
|
12
|
+
# 2. Point any MCP-capable agent at the hosted endpoint:
|
|
13
|
+
claude mcp add --transport http motiblog https://api.motiblog.ai/mcp \
|
|
14
|
+
--header "x-api-key: <your-key>"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Verify by hand:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
curl -s https://api.motiblog.ai/mcp \
|
|
21
|
+
-H 'content-type: application/json' \
|
|
22
|
+
-H 'accept: application/json, text/event-stream' \
|
|
23
|
+
-H 'x-api-key: <your-key>' \
|
|
24
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start — local stdio (development)
|
|
28
|
+
|
|
29
|
+
Running from the repository, for work on the tool layer itself:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude mcp add motiblog \
|
|
33
|
+
--env MOTIBLOG_API_KEY=<your-key> \
|
|
34
|
+
--env MOTIBLOG_PROJECT_ID=<optional-default-project> \
|
|
35
|
+
-- pnpm --filter @motiblog/mcp start
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The package is not published to npm and does not need to be: the hosted
|
|
39
|
+
endpoint covers every customer path, and stdio is for people who have the
|
|
40
|
+
repository anyway.
|
|
41
|
+
|
|
42
|
+
Or use the committed [`.mcp.json`](../../.mcp.json): set `MOTIBLOG_API_KEY` in your environment and start Claude Code in the repo root.
|
|
43
|
+
|
|
44
|
+
| Env var | Required | Meaning |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `MOTIBLOG_API_KEY` | yes | Per-project key (`Project.blogApiKey`). Rotatable from dashboard. |
|
|
47
|
+
| `MOTIBLOG_API_URL` | no | API base URL. Default `http://localhost:3001`. |
|
|
48
|
+
| `MOTIBLOG_PROJECT_ID` | no | Default project; tools still accept explicit `project_id`. |
|
|
49
|
+
|
|
50
|
+
## Remote agents: streamable HTTP
|
|
51
|
+
|
|
52
|
+
### How it is served in production
|
|
53
|
+
|
|
54
|
+
The handler (`src/http-handler.ts`) is mounted **inside the API process** as
|
|
55
|
+
Express middleware — `apps/api/src/mcp/mcp.middleware.ts`, registered from
|
|
56
|
+
`apps/api/src/main.ts`. Consequences worth knowing:
|
|
57
|
+
|
|
58
|
+
- `POST api.motiblog.ai/mcp` works with the ordinary API deploy. There is no
|
|
59
|
+
second container and no extra reverse-proxy rule — which is exactly why the
|
|
60
|
+
endpoint was unreachable before: the code existed, the route did not.
|
|
61
|
+
- It is registered with `app.use()` *before* `app.listen()`, so it sits ahead
|
|
62
|
+
of Nest's router. The global `ValidationPipe` and `TransformInterceptor`
|
|
63
|
+
never see it; if they did, they would rewrap the JSON-RPC envelope in the
|
|
64
|
+
API's `{ success, data }` shape and no client could parse it.
|
|
65
|
+
- Nest's body-parser has already drained the request stream by then, so the
|
|
66
|
+
parsed body is handed to the transport explicitly. Skip that and the
|
|
67
|
+
transport waits on a stream that will never emit again.
|
|
68
|
+
- Tool calls loop back to the same API over loopback, so an agent's key passes
|
|
69
|
+
the same guards an external caller meets. Being mounted in-process buys no
|
|
70
|
+
privileged shortcut. Override the loopback base with `MOTIBLOG_MCP_API_URL`.
|
|
71
|
+
|
|
72
|
+
Covered by `apps/api/src/mcp/mcp.middleware.spec.ts`, which boots a real Nest
|
|
73
|
+
app with those globals installed and asserts a full handshake survives.
|
|
74
|
+
|
|
75
|
+
### Standalone server
|
|
76
|
+
|
|
77
|
+
For local work, or to serve MCP from somewhere other than the API:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pnpm --filter @motiblog/mcp start:http
|
|
81
|
+
# → http://127.0.0.1:3021/mcp (POST, JSON-RPC; stateless)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| Env var | Default | Meaning |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `MOTIBLOG_HTTP_PORT` | `3021` | Listen port |
|
|
87
|
+
| `MOTIBLOG_HTTP_HOST` | `127.0.0.1` | Bind host (use `0.0.0.0` behind a proxy) |
|
|
88
|
+
|
|
89
|
+
Every request must carry the key (`x-api-key` or `Authorization: Bearer`); requests are authenticated and served independently (stateless — no session affinity).
|
|
90
|
+
|
|
91
|
+
OAuth lands later — per-project keys are the MVP contract per `docs/product/agent-first-direction.md`.
|
|
92
|
+
|
|
93
|
+
## Publishing to npm
|
|
94
|
+
|
|
95
|
+
The package is publish-ready but **not yet published**. Until it is, leave the
|
|
96
|
+
"no published npm package" line on `/mcp` and in `llms.txt` alone — an agent
|
|
97
|
+
that reads an install instruction and hits a 404 is worse off than one told
|
|
98
|
+
the truth.
|
|
99
|
+
|
|
100
|
+
Prerequisites, all of which are a human's job:
|
|
101
|
+
|
|
102
|
+
1. An npm account, and membership of an org named `motiblog` (the `@motiblog`
|
|
103
|
+
scope). A free org is enough — scoped packages publish publicly with
|
|
104
|
+
`access: public`, already set in `publishConfig`.
|
|
105
|
+
2. `npm login` on the machine doing the publish.
|
|
106
|
+
|
|
107
|
+
Then, from `apps/mcp`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pnpm publish --access public # runs tsup via prepublishOnly
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Verify before you push the button:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm pack # writes motiblog-mcp-<version>.tgz
|
|
117
|
+
tar -tzf motiblog-mcp-*.tgz # dist/ + README.md, nothing else
|
|
118
|
+
tar -xzOf motiblog-mcp-*.tgz package/package.json | grep -E '"main"|"bin"'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Two things that must stay true, and both are easy to break:
|
|
122
|
+
|
|
123
|
+
- **`@motiblog/shared` is bundled, not depended on.** It is a workspace
|
|
124
|
+
package that will never exist on npm, so `tsup.config.ts` lists it under
|
|
125
|
+
`noExternal` and the manifest keeps it in `devDependencies`. Move it back to
|
|
126
|
+
`dependencies` and every install resolves fine right up until the first
|
|
127
|
+
`export_blog` call. `grep -c '@motiblog/shared' dist/*.js` must print 0.
|
|
128
|
+
- **`main` differs between the workspace and the tarball.** The repo consumes
|
|
129
|
+
raw TypeScript (`src/lib.ts`) because `apps/api` imports this package in
|
|
130
|
+
process; npm consumers get `dist/lib.js`. `publishConfig` performs that swap
|
|
131
|
+
at publish time, so neither side needs the other's layout.
|
|
132
|
+
|
|
133
|
+
Smoke-test the tarball the way a customer meets it:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
mkdir /tmp/t && cd /tmp/t && npm init -y
|
|
137
|
+
npm install /path/to/motiblog-mcp-<version>.tgz
|
|
138
|
+
node -e "console.log(Object.keys(require('@motiblog/mcp')))"
|
|
139
|
+
MOTIBLOG_HTTP_PORT=3098 node_modules/.bin/motiblog-mcp-http
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Entry points
|
|
143
|
+
|
|
144
|
+
| File | Shape | Use |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `src/lib.ts` | pure exports, no side effects | what `main` points at; what the API imports |
|
|
147
|
+
| `src/index.ts` | executes on import | stdio server (`pnpm start`) |
|
|
148
|
+
| `src/http.ts` | executes on import | standalone HTTP server (`pnpm start:http`) |
|
|
149
|
+
|
|
150
|
+
Import the library surface, never an entry point — importing `index.ts` or
|
|
151
|
+
`http.ts` starts a server as a side effect of the import.
|
|
152
|
+
|
|
153
|
+
## The agent loop
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
list_projects → suggest_topics → approve_content_plan → generate_article
|
|
157
|
+
→ list_review_queue → get_article (factCheckReport, seoScore)
|
|
158
|
+
→ update_article (fix flagged claims / supply_product_fact)
|
|
159
|
+
→ approve_publication → publish_to_integration …or… export_blog
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`get_pipeline_logs` diagnoses failures; `list_refresh_suggestions` finds decaying published posts worth refreshing; `start_pipeline` runs the fully autonomous crawl→plan→generate loop when you want hands-off operation.
|
|
163
|
+
|
|
164
|
+
## Tools
|
|
165
|
+
|
|
166
|
+
### Discovery & system
|
|
167
|
+
| Tool | Notes |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `list_projects` | All accessible projects + approval/quota settings |
|
|
170
|
+
| `get_project` | Full pipeline config, positioning inputs |
|
|
171
|
+
| `start_pipeline` | Autonomous crawl→plan→generate run |
|
|
172
|
+
| `get_pipeline_status` | Run state + steps |
|
|
173
|
+
|
|
174
|
+
### Topics & planning
|
|
175
|
+
| Tool | Notes |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `suggest_topics` | Add topic(s) to content plan (DRAFT entries) |
|
|
178
|
+
| `list_content_plans` | Plan queue with statuses |
|
|
179
|
+
| `approve_content_plan` | Clear a plan entry for generation |
|
|
180
|
+
| `regenerate_content_plan` | AI-rewrite of a plan entry |
|
|
181
|
+
| `generate_article` | Full pipeline from APPROVED plan |
|
|
182
|
+
| `get_calendar` | Scheduled entries between two ISO dates |
|
|
183
|
+
|
|
184
|
+
### Review loop (the governance core)
|
|
185
|
+
| Tool | Notes |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `list_review_queue` | Articles by lifecycle status (default REVIEW) |
|
|
188
|
+
| `get_article` | Full markdown + `factCheckReport`, `topicGate`, seoScore, optional per-phase logs |
|
|
189
|
+
| `update_article` | Edit content/title/meta/status — how agents answer fact-check flags |
|
|
190
|
+
| `approve_publication` | Governed approval gate; `publish_now=true` publishes immediately |
|
|
191
|
+
| `schedule_publication` | Set/clear scheduled publish time |
|
|
192
|
+
|
|
193
|
+
### Regeneration ops
|
|
194
|
+
| Tool | Notes |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `regenerate_article` | Full rerun (destructive) |
|
|
197
|
+
| `regenerate_chapter` | One section only (safe) |
|
|
198
|
+
| `get_pipeline_logs` | Phase telemetry: status, tokens, USD cost |
|
|
199
|
+
| `list_refresh_suggestions` | GSC-driven decay candidates across projects |
|
|
200
|
+
|
|
201
|
+
### Publishing targets
|
|
202
|
+
| Tool | Notes |
|
|
203
|
+
|---|---|
|
|
204
|
+
| `list_integrations` | WEBHOOK/WORDPRESS/GHOST/WEBFLOW/SHOPIFY/DEVTO/SANITY/CUSTOM_API |
|
|
205
|
+
| `create_webhook_integration` | Push target for your own infra; deliveries signed `X-Signature: sha256=HMAC(secret)` |
|
|
206
|
+
| `test_integration` | `ping` or `full` connection test |
|
|
207
|
+
| `publish_to_integration` | APPROVED article → PUBLISHING → PUBLISHED |
|
|
208
|
+
| `retry_publish` | Retry failed attempt |
|
|
209
|
+
| `list_publish_logs` | Success/failure history with provider errors |
|
|
210
|
+
|
|
211
|
+
### Agent-supplied knowledge
|
|
212
|
+
| Tool | Notes |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `add_keyword` / `list_keywords` | SEO keywords driving topic relevance |
|
|
215
|
+
| `supply_product_fact` / `list_product_facts` | Ground truth enforced by strict fact-checking — supply BEFORE generation |
|
|
216
|
+
|
|
217
|
+
### Export (deploy it yourself)
|
|
218
|
+
| Tool | Notes |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `export_blog` | Writes `{out_dir}/{slug}/index.md` (+ YAML frontmatter incl. `motiblogArticleId`) and `manifest.json`. Portable GFM: single H1, `<img>`→markdown images, iframes→watch links. Drop into Astro/Next/Jekyll/Hugo/plain git and deploy anywhere. |
|
|
221
|
+
|
|
222
|
+
## Security model
|
|
223
|
+
|
|
224
|
+
- The API key authenticates as the **project owner** on every existing route — ownership checks are unchanged; there is no privilege escalation.
|
|
225
|
+
- **Admin routes refuse API keys** (`AdminGuard` rejects machine callers), even if the owner is an admin.
|
|
226
|
+
- Publication always passes the typed approval gate with provenance — MCP adds no bypass.
|
|
227
|
+
- Keys are per-project and rotatable; rotate immediately if exposed.
|
|
228
|
+
- Rate limiting: global ThrottlerGuard applies unchanged.
|
|
229
|
+
|
|
230
|
+
## Development
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
pnpm --filter @motiblog/mcp test # jest unit tests (client envelope handling, export format)
|
|
234
|
+
pnpm --filter @motiblog/mcp lint # tsc --noEmit
|
|
235
|
+
pnpm --filter @motiblog/mcp dev # tsx watch (restart your MCP client after edits)
|
|
236
|
+
```
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MotiBlog MCP server — standalone streamable HTTP entrypoint.
|
|
6
|
+
*
|
|
7
|
+
* This is the local/self-hosted way to serve remote agents. In production the
|
|
8
|
+
* same handler is mounted inside the API process instead, so the endpoint is
|
|
9
|
+
* `POST api.motiblog.ai/mcp` with no extra service and no extra proxy rule —
|
|
10
|
+
* see apps/api/src/mcp/mcp.middleware.ts.
|
|
11
|
+
*
|
|
12
|
+
* Stateless: every request authenticates with its own per-project API key and
|
|
13
|
+
* gets a fresh server + transport (no session affinity needed).
|
|
14
|
+
*
|
|
15
|
+
* Env:
|
|
16
|
+
* MOTIBLOG_API_URL — API base for tool calls (default http://localhost:3001)
|
|
17
|
+
* MOTIBLOG_HTTP_PORT — listen port (default 3021)
|
|
18
|
+
* MOTIBLOG_HTTP_HOST — bind host (default 127.0.0.1; use 0.0.0.0 behind a proxy)
|
|
19
|
+
* MOTIBLOG_PROJECT_ID — optional default project
|
|
20
|
+
*
|
|
21
|
+
* Auth: `x-api-key: <key>` or `Authorization: Bearer <key>` on every request.
|
|
22
|
+
*/
|
|
23
|
+
interface HttpServerOptions {
|
|
24
|
+
port: number;
|
|
25
|
+
host: string;
|
|
26
|
+
apiBaseUrl: string;
|
|
27
|
+
defaultProjectId?: string;
|
|
28
|
+
}
|
|
29
|
+
declare function startHttpServer(opts: HttpServerOptions): ReturnType<typeof createServer>;
|
|
30
|
+
|
|
31
|
+
export { type HttpServerOptions, startHttpServer };
|