@misarblog/mcp 1.0.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 ADDED
@@ -0,0 +1,590 @@
1
+ # Misar.Blog MCP Server
2
+
3
+ Connect Claude Code, Cursor, Windsurf, or any MCP-compatible AI assistant to your
4
+ [Misar.Blog](https://www.misar.blog) account. Publish articles, manage drafts, generate
5
+ cover images, and pull analytics — all from your AI coding environment.
6
+
7
+ Two runtimes are available — choose based on what you already have installed:
8
+
9
+ | Runtime | Requires | Best for |
10
+ | ------- | -------- | -------- |
11
+ | **Python** (recommended) | Python 3.11+ · stdlib only | Claude Code, any lightweight setup |
12
+ | **npm / npx** | Node.js 18+ | Node-first workflows, CI/CD |
13
+
14
+ ---
15
+
16
+ ## Contents
17
+
18
+ - [Quick Start](#quick-start)
19
+ - [Option A — Python](#option-a--python-no-dependencies)
20
+ - [Option B — npm / npx](#option-b--npm--npx)
21
+ - [Client Setup](#client-setup)
22
+ - [Claude Code](#claude-code)
23
+ - [Cursor](#cursor)
24
+ - [Windsurf](#windsurf)
25
+ - [VS Code (Copilot)](#vs-code-copilot)
26
+ - [Any other MCP client](#any-other-mcp-client)
27
+ - [Authentication](#authentication)
28
+ - [API Key (recommended)](#api-key-recommended)
29
+ - [Browser login (no copy-paste)](#browser-login-no-copy-paste)
30
+ - [Tools reference](#tools-reference)
31
+ - [Usage examples](#usage-examples)
32
+ - [Self-hosted Misar.Blog](#self-hosted-misarblog)
33
+ - [Troubleshooting](#troubleshooting)
34
+
35
+ ---
36
+
37
+ ## Quick Start
38
+
39
+ **Fastest path — Python + Claude Code:**
40
+
41
+ ```bash
42
+ # 1. Copy the server script
43
+ cp packages/mcp/misarblog-mcp.py ~/.claude/scripts/misarblog-mcp.py
44
+ chmod +x ~/.claude/scripts/misarblog-mcp.py
45
+
46
+ # 2. Add to Claude Code MCP settings (see below)
47
+
48
+ # 3. Run misarblog_login in Claude Code to authenticate via browser
49
+ ```
50
+
51
+ **Fastest path — npx + any MCP client:**
52
+
53
+ ```bash
54
+ # No install needed — just add the config below and run misarblog_login
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Option A — Python (no dependencies)
60
+
61
+ Uses Python's standard library only. No `pip install` required. Works on macOS, Linux, and Windows (with Python 3.11+).
62
+
63
+ ### 1 · Download the script
64
+
65
+ **If you cloned the MisarBlog repo:**
66
+
67
+ ```bash
68
+ cp packages/mcp/misarblog-mcp.py ~/.claude/scripts/misarblog-mcp.py
69
+ chmod +x ~/.claude/scripts/misarblog-mcp.py
70
+ ```
71
+
72
+ **Direct download (one-liner):**
73
+
74
+ ```bash
75
+ mkdir -p ~/.claude/scripts
76
+ curl -fsSL https://www.misar.blog/mcp/misarblog-mcp.py -o ~/.claude/scripts/misarblog-mcp.py
77
+ chmod +x ~/.claude/scripts/misarblog-mcp.py
78
+ ```
79
+
80
+ ### 2 · Verify Python version
81
+
82
+ ```bash
83
+ python3 --version # must be 3.11 or later
84
+ ```
85
+
86
+ If you're on macOS with an older system Python, use Homebrew: `brew install python`.
87
+
88
+ ### 3 · Add to your MCP client config
89
+
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "misarblog": {
94
+ "command": "python3",
95
+ "args": ["~/.claude/scripts/misarblog-mcp.py"],
96
+ "env": {
97
+ "MISARBLOG_API_KEY": "mbk_your_key_here"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ > You can omit `MISARBLOG_API_KEY` if you plan to use the `misarblog_login` browser flow.
105
+
106
+ ---
107
+
108
+ ## Option B — npm / npx
109
+
110
+ Uses Node.js 18+ with the `@modelcontextprotocol/sdk`. `npx` fetches and caches the package on first run — no manual install needed.
111
+
112
+ ### Option B1 — Zero-install via npx (recommended)
113
+
114
+ ```json
115
+ {
116
+ "mcpServers": {
117
+ "misarblog": {
118
+ "command": "npx",
119
+ "args": ["-y", "@misarblog/mcp"],
120
+ "env": {
121
+ "MISARBLOG_API_KEY": "mbk_your_key_here"
122
+ }
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### Option B2 — Global install
129
+
130
+ ```bash
131
+ npm install -g @misarblog/mcp
132
+ ```
133
+
134
+ Then use `misarblog-mcp` as the command:
135
+
136
+ ```json
137
+ {
138
+ "mcpServers": {
139
+ "misarblog": {
140
+ "command": "misarblog-mcp",
141
+ "env": {
142
+ "MISARBLOG_API_KEY": "mbk_your_key_here"
143
+ }
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Option B3 — pnpm / yarn
150
+
151
+ ```bash
152
+ pnpm add -g @misarblog/mcp
153
+ # or
154
+ yarn global add @misarblog/mcp
155
+ ```
156
+
157
+ ### Verify Node version
158
+
159
+ ```bash
160
+ node --version # must be v18 or later
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Client Setup
166
+
167
+ ### Claude Code
168
+
169
+ Claude Code stores MCP server config in `~/.claude/settings.json`.
170
+
171
+ **Edit the file:**
172
+
173
+ ```bash
174
+ # Open in your editor
175
+ code ~/.claude/settings.json
176
+ ```
177
+
178
+ **Add the `mcpServers` block** (create `settings.json` if it doesn't exist):
179
+
180
+ ```json
181
+ {
182
+ "mcpServers": {
183
+ "misarblog": {
184
+ "command": "python3",
185
+ "args": ["~/.claude/scripts/misarblog-mcp.py"],
186
+ "env": {
187
+ "MISARBLOG_API_KEY": "mbk_your_key_here"
188
+ }
189
+ }
190
+ }
191
+ }
192
+ ```
193
+
194
+ **Reload Claude Code** — MCP servers start automatically on the next session. You'll see
195
+ `misarblog` listed when you run `/mcp` in any Claude Code session.
196
+
197
+ **Verify the connection:**
198
+
199
+ ```text
200
+ > call misarblog_get_profile
201
+ ```
202
+
203
+ Claude Code should return your username, display name, and account status.
204
+
205
+ ---
206
+
207
+ ### Cursor
208
+
209
+ Cursor stores MCP config at `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` inside a
210
+ project (project-scoped, takes priority).
211
+
212
+ **Global config** (`~/.cursor/mcp.json`):
213
+
214
+ ```json
215
+ {
216
+ "mcpServers": {
217
+ "misarblog": {
218
+ "command": "npx",
219
+ "args": ["-y", "@misarblog/mcp"],
220
+ "env": {
221
+ "MISARBLOG_API_KEY": "mbk_your_key_here"
222
+ }
223
+ }
224
+ }
225
+ }
226
+ ```
227
+
228
+ **Alternative — via Cursor Settings UI:**
229
+
230
+ 1. Open Cursor → **Settings** → **MCP**
231
+ 2. Click **+ Add new MCP server**
232
+ 3. Fill in:
233
+ - **Name**: `misarblog`
234
+ - **Command**: `npx`
235
+ - **Args**: `-y @misarblog/mcp`
236
+ - **Env**: `MISARBLOG_API_KEY=mbk_your_key_here`
237
+ 4. Click **Save** — Cursor restarts the MCP daemon automatically.
238
+
239
+ Verify: open Cursor Agent mode → type `use misarblog_get_profile` — the tool card should appear.
240
+
241
+ ---
242
+
243
+ ### Windsurf
244
+
245
+ Windsurf reads MCP config from `~/.codeium/windsurf/mcp_config.json`.
246
+
247
+ ```json
248
+ {
249
+ "mcpServers": {
250
+ "misarblog": {
251
+ "command": "npx",
252
+ "args": ["-y", "@misarblog/mcp"],
253
+ "env": {
254
+ "MISARBLOG_API_KEY": "mbk_your_key_here"
255
+ }
256
+ }
257
+ }
258
+ }
259
+ ```
260
+
261
+ Reload Windsurf after saving. The MCP tools appear under the **Cascade** panel → **Tools**.
262
+
263
+ ---
264
+
265
+ ### VS Code (Copilot)
266
+
267
+ VS Code reads MCP config from `.vscode/mcp.json` in the workspace root, or from
268
+ **User Settings** (`settings.json`) under `"mcp"`.
269
+
270
+ **Workspace config** (`.vscode/mcp.json`):
271
+
272
+ ```json
273
+ {
274
+ "servers": {
275
+ "misarblog": {
276
+ "type": "stdio",
277
+ "command": "npx",
278
+ "args": ["-y", "@misarblog/mcp"],
279
+ "env": {
280
+ "MISARBLOG_API_KEY": "mbk_your_key_here"
281
+ }
282
+ }
283
+ }
284
+ }
285
+ ```
286
+
287
+ **User settings** (`settings.json`):
288
+
289
+ ```json
290
+ {
291
+ "mcp": {
292
+ "servers": {
293
+ "misarblog": {
294
+ "type": "stdio",
295
+ "command": "npx",
296
+ "args": ["-y", "@misarblog/mcp"],
297
+ "env": {
298
+ "MISARBLOG_API_KEY": "mbk_your_key_here"
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+ ```
305
+
306
+ Restart VS Code after saving. The `misarblog_*` tools appear in GitHub Copilot Chat when
307
+ you enable **Agent mode** (the `@` icon in the chat panel).
308
+
309
+ ---
310
+
311
+ ### Any other MCP client
312
+
313
+ Any MCP-compatible client that supports stdio servers works with the same pattern:
314
+
315
+ - **Command**: `python3` (Python) or `npx` (npm)
316
+ - **Args**: `["~/.claude/scripts/misarblog-mcp.py"]` or `["-y", "@misarblog/mcp"]`
317
+ - **Transport**: `stdio`
318
+ - **Env**: `MISARBLOG_API_KEY=mbk_...` (or use `misarblog_login` after connecting)
319
+
320
+ ---
321
+
322
+ ## Authentication
323
+
324
+ ### API Key (recommended)
325
+
326
+ 1. Go to **Misar.Blog → Dashboard → Settings → API Keys**
327
+ 2. Click **Generate API Key** — your key starts with `mbk_`
328
+ 3. Copy it and paste into the `MISARBLOG_API_KEY` env var in your MCP config
329
+
330
+ Keys have a 100 req/min rate limit. You can revoke and regenerate at any time from the settings page.
331
+
332
+ **Precedence order:**
333
+
334
+ ```text
335
+ MISARBLOG_API_KEY env var → ~/.misarblog/config.json → prompt to run misarblog_login
336
+ ```
337
+
338
+ ### Browser login (no copy-paste)
339
+
340
+ If you'd rather not handle the key manually, omit `MISARBLOG_API_KEY` from the config and run
341
+ `misarblog_login` as your first tool call. The flow:
342
+
343
+ 1. The MCP server starts a temporary HTTP listener on `127.0.0.1` (random port 9001–9099)
344
+ 2. Your default browser opens to `https://www.misar.blog/dashboard/settings/api?mcp_port=<port>`
345
+ 3. You click **Authorize MCP Access** — you must be logged in to Misar.Blog
346
+ 4. The page sends your API key directly to the local listener
347
+ 5. The key is saved to `~/.misarblog/config.json` — no clipboard involved
348
+ 6. All subsequent tool calls use this saved key automatically
349
+
350
+ The listener accepts connections from `127.0.0.1` only and shuts down after 120 seconds.
351
+
352
+ **Example prompt:**
353
+
354
+ ```text
355
+ Connect my Misar.Blog account using misarblog_login
356
+ ```
357
+
358
+ Claude will call the tool, open your browser, and confirm once you've authorized.
359
+
360
+ ---
361
+
362
+ ## Tools reference
363
+
364
+ | Tool | Description | Required params |
365
+ | ---- | ----------- | --------------- |
366
+ | `misarblog_login` | Browser-based auth — saves key to `~/.misarblog/config.json` | — |
367
+ | `misarblog_get_profile` | Username, display name, bio, subscriber count, Stripe status | — |
368
+ | `misarblog_list_articles` | List your articles by status | — |
369
+ | `misarblog_get_article` | Fetch a single article with full body | `slug` |
370
+ | `misarblog_publish_article` | Publish now or schedule via ISO 8601 datetime | `title`, `body_markdown` |
371
+ | `misarblog_create_draft` | Save a draft for review in the web editor | `title`, `body_markdown` |
372
+ | `misarblog_upload_image` | Upload a local file to the Misar.Blog CDN | `file_path` |
373
+ | `misarblog_generate_image` | Generate an AI cover image from a text prompt | `prompt` |
374
+ | `misarblog_list_series` | List all article series on your account | — |
375
+ | `misarblog_create_series` | Create a new series | `title` |
376
+ | `misarblog_add_to_series` | Add an article to a series at a given position | `series_slug`, `article_slug` |
377
+ | `misarblog_get_analytics` | Views, revenue, and subscriber trends (up to 365 days) | — |
378
+
379
+ ### Tool parameters
380
+
381
+ #### `misarblog_login`
382
+
383
+ | Param | Type | Default | Description |
384
+ | ----- | ---- | ------- | ----------- |
385
+ | `port` | number | random 9001–9099 | Local callback port |
386
+ | `base_url` | string | `https://www.misar.blog` | Override for self-hosted instances |
387
+
388
+ #### `misarblog_list_articles`
389
+
390
+ | Param | Type | Default | Description |
391
+ | ----- | ---- | ------- | ----------- |
392
+ | `status` | string | `published` | One of: `draft` · `published` · `scheduled` · `archived` |
393
+ | `limit` | number | `20` | Max results, 1–100 |
394
+
395
+ #### `misarblog_publish_article`
396
+
397
+ | Param | Type | Required | Description |
398
+ | ----- | ---- | -------- | ----------- |
399
+ | `title` | string | yes | Article title (max 250 chars) |
400
+ | `body_markdown` | string | yes | Full article content in Markdown |
401
+ | `tags` | string[] | no | Up to 10 tags |
402
+ | `cover_image_url` | string | no | HTTPS URL of cover image |
403
+ | `schedule_at` | string | no | ISO 8601 datetime to publish later (e.g. `2025-06-01T09:00:00Z`) |
404
+ | `visibility` | string | no | `public` · `subscribers` · `paid` · `private` (default: `public`) |
405
+
406
+ #### `misarblog_create_draft`
407
+
408
+ | Param | Type | Required | Description |
409
+ | ----- | ---- | -------- | ----------- |
410
+ | `title` | string | yes | Draft title |
411
+ | `body_markdown` | string | yes | Content in Markdown |
412
+ | `tags` | string[] | no | Up to 10 tags |
413
+
414
+ #### `misarblog_upload_image`
415
+
416
+ | Param | Type | Required | Description |
417
+ | ----- | ---- | -------- | ----------- |
418
+ | `file_path` | string | yes | Absolute local path (jpg, png, webp, gif, svg) |
419
+
420
+ #### `misarblog_generate_image`
421
+
422
+ | Param | Type | Default | Description |
423
+ | ----- | ---- | ------- | ----------- |
424
+ | `prompt` | string | — | Image description (max 1000 chars) |
425
+ | `size` | string | `1024x1024` | `1024x1024` · `1792x1024` · `1024x1792` |
426
+
427
+ #### `misarblog_create_series`
428
+
429
+ | Param | Type | Required | Description |
430
+ | ----- | ---- | -------- | ----------- |
431
+ | `title` | string | yes | Series name |
432
+ | `description` | string | no | Short description |
433
+
434
+ #### `misarblog_add_to_series`
435
+
436
+ | Param | Type | Required | Description |
437
+ | ----- | ---- | -------- | ----------- |
438
+ | `series_slug` | string | yes | URL slug of the series |
439
+ | `article_slug` | string | yes | URL slug of the article |
440
+ | `position` | number | no | 1-indexed position; appends to end if omitted |
441
+
442
+ #### `misarblog_get_analytics`
443
+
444
+ | Param | Type | Default | Description |
445
+ | ----- | ---- | ------- | ----------- |
446
+ | `days` | number | `30` | Look-back window, 1–365 |
447
+
448
+ ---
449
+
450
+ ## Usage examples
451
+
452
+ These are prompts you can send directly in Claude Code or Cursor Agent mode:
453
+
454
+ **Publish a new article:**
455
+
456
+ ```text
457
+ Write a 1000-word article about "Why AI-first blogging changes SEO forever"
458
+ and publish it on my Misar.Blog with tags ["AI", "SEO", "blogging"].
459
+ ```
460
+
461
+ **Draft with a generated cover image:**
462
+
463
+ ```text
464
+ Generate a dark, futuristic cover image for an article titled "Building with MCP".
465
+ Then create a draft with that image as the cover.
466
+ ```
467
+
468
+ **Check performance:**
469
+
470
+ ```text
471
+ Show me my analytics for the last 90 days.
472
+ ```
473
+
474
+ **Publish on a schedule:**
475
+
476
+ ```text
477
+ Write a short announcement post and schedule it to publish tomorrow at 9am UTC.
478
+ ```
479
+
480
+ **Organize a series:**
481
+
482
+ ```text
483
+ List my articles with status "published", then create a series called "AI Writing Guide"
484
+ and add the last 3 articles to it in chronological order.
485
+ ```
486
+
487
+ ---
488
+
489
+ ## Self-hosted Misar.Blog
490
+
491
+ If you run your own Misar.Blog instance, set `MISARBLOG_BASE_URL` to your domain:
492
+
493
+ ```json
494
+ {
495
+ "mcpServers": {
496
+ "misarblog": {
497
+ "command": "python3",
498
+ "args": ["~/.claude/scripts/misarblog-mcp.py"],
499
+ "env": {
500
+ "MISARBLOG_API_KEY": "mbk_your_key_here",
501
+ "MISARBLOG_BASE_URL": "https://blog.yourdomain.com"
502
+ }
503
+ }
504
+ }
505
+ }
506
+ ```
507
+
508
+ `MISARBLOG_BASE_URL` can also be stored in `~/.misarblog/config.json` (written by `misarblog_login`):
509
+
510
+ ```json
511
+ {
512
+ "api_key": "mbk_...",
513
+ "username": "yourname",
514
+ "base_url": "https://blog.yourdomain.com"
515
+ }
516
+ ```
517
+
518
+ ---
519
+
520
+ ## Troubleshooting
521
+
522
+ ### "Not configured" on every tool call
523
+
524
+ The server can't find your API key. Either:
525
+
526
+ - Set `MISARBLOG_API_KEY` in the MCP config env block, or
527
+ - Run `misarblog_login` once to save it to `~/.misarblog/config.json`
528
+
529
+ ### "API key invalid or expired"
530
+
531
+ Your key was revoked. Go to **Dashboard → Settings → API Keys** and generate a new one,
532
+ or run `misarblog_login` again to get a fresh key via the browser flow.
533
+
534
+ ### "Rate limited (100 req/min)"
535
+
536
+ You've exceeded the API rate limit. Wait 60 seconds and retry. If you're running automated
537
+ pipelines, add a short delay between tool calls.
538
+
539
+ ### Browser doesn't open during `misarblog_login`
540
+
541
+ The server prints the URL to stderr when `webbrowser.open()` fails. Copy and open it manually:
542
+
543
+ ```text
544
+ Open this URL in your browser:
545
+ https://www.misar.blog/dashboard/settings/api?mcp_port=9042
546
+ ```
547
+
548
+ You have 120 seconds from when the tool runs to click **Authorize MCP Access**.
549
+
550
+ ### `python3: command not found`
551
+
552
+ - macOS: `brew install python` or install from [python.org](https://www.python.org)
553
+ - Linux: `sudo apt install python3` / `sudo dnf install python3`
554
+ - Windows: Install from [python.org](https://www.python.org) and ensure `python3` is in `PATH`
555
+
556
+ Alternatively, switch to the [npm/npx option](#option-b--npm--npx) — it only requires Node.js.
557
+
558
+ ### `npx` is slow on first run
559
+
560
+ `npx -y @misarblog/mcp` downloads the package on first run and caches it locally. Subsequent
561
+ starts are instant. If startup time matters, use `npm install -g @misarblog/mcp` instead.
562
+
563
+ ### MCP server doesn't appear in Claude Code
564
+
565
+ Run `/mcp` in a Claude Code session to list active servers. If `misarblog` is missing:
566
+
567
+ 1. Check `~/.claude/settings.json` — ensure the `mcpServers.misarblog` block is valid JSON
568
+ 2. Verify the script path: `ls -la ~/.claude/scripts/misarblog-mcp.py`
569
+ 3. Test the server directly:
570
+
571
+ ```bash
572
+ printf '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}\n' \
573
+ | MISARBLOG_API_KEY=mbk_test python3 ~/.claude/scripts/misarblog-mcp.py
574
+ ```
575
+
576
+ You should see a JSON response listing 12 tools.
577
+
578
+ ### Connection refused on `misarblog_login` callback
579
+
580
+ The local HTTP server binds to `127.0.0.1`. If your browser opens on a different machine
581
+ (e.g. remote VS Code over SSH), the callback won't reach the MCP server. In that case,
582
+ use the [API Key method](#api-key-recommended) instead.
583
+
584
+ ---
585
+
586
+ ## Requirements
587
+
588
+ - **Python runtime**: Python 3.11+ · no external packages
589
+ - **npm runtime**: Node.js 18+ · package fetched automatically via npx
590
+ - **Account**: Misar.Blog creator account ([sign up free](https://www.misar.blog))
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerArticleTools } from "./tools/articles.js";
5
+ import { registerImageTools } from "./tools/images.js";
6
+ import { registerSeriesTools } from "./tools/series.js";
7
+ import { registerProfileTools } from "./tools/profile.js";
8
+ import { registerLoginTool } from "./tools/login.js";
9
+ const server = new McpServer({
10
+ name: "misarblog",
11
+ version: "1.0.0",
12
+ });
13
+ registerLoginTool(server);
14
+ registerProfileTools(server);
15
+ registerArticleTools(server);
16
+ registerImageTools(server);
17
+ registerSeriesTools(server);
18
+ async function main() {
19
+ const transport = new StdioServerTransport();
20
+ await server.connect(transport);
21
+ }
22
+ main().catch((err) => {
23
+ console.error("MCP server error:", err);
24
+ process.exit(1);
25
+ });
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAC3B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAE5B,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;IACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function apiFetch<T>(path: string, options?: RequestInit): Promise<T>;
2
+ export declare function apiUpload<T>(path: string, form: FormData): Promise<T>;
3
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAGA,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,CAAC,CAAC,CAeZ;AAED,wBAAsB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAc3E"}
@@ -0,0 +1,34 @@
1
+ import { getApiKey, getBaseUrl } from "./auth.js";
2
+ import { apiError } from "./errors.js";
3
+ export async function apiFetch(path, options = {}) {
4
+ const key = getApiKey();
5
+ const res = await fetch(`${getBaseUrl()}${path}`, {
6
+ ...options,
7
+ headers: {
8
+ "Content-Type": "application/json",
9
+ Authorization: `Bearer ${key}`,
10
+ "X-MCP-Source": "mcp_claude",
11
+ ...options.headers,
12
+ },
13
+ });
14
+ const body = await res.json().catch(() => ({}));
15
+ if (!res.ok)
16
+ throw apiError(res.status, body);
17
+ return body;
18
+ }
19
+ export async function apiUpload(path, form) {
20
+ const key = getApiKey();
21
+ const res = await fetch(`${getBaseUrl()}${path}`, {
22
+ method: "POST",
23
+ headers: {
24
+ Authorization: `Bearer ${key}`,
25
+ "X-MCP-Source": "mcp_claude",
26
+ },
27
+ body: form,
28
+ });
29
+ const body = await res.json().catch(() => ({}));
30
+ if (!res.ok)
31
+ throw apiError(res.status, body);
32
+ return body;
33
+ }
34
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,UAAuB,EAAE;IAEzB,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,UAAU,EAAE,GAAG,IAAI,EAAE,EAAE;QAChD,GAAG,OAAO;QACV,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,GAAG,EAAE;YAC9B,cAAc,EAAE,YAAY;YAC5B,GAAG,OAAO,CAAC,OAAO;SACnB;KACF,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9C,OAAO,IAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,IAAY,EAAE,IAAc;IAC7D,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,UAAU,EAAE,GAAG,IAAI,EAAE,EAAE;QAChD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,GAAG,EAAE;YAC9B,cAAc,EAAE,YAAY;SAC7B;QACD,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9C,OAAO,IAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,11 @@
1
+ interface MisarConfig {
2
+ api_key?: string;
3
+ username?: string;
4
+ base_url?: string;
5
+ }
6
+ export declare function saveConfig(config: MisarConfig): void;
7
+ export declare function getApiKey(): string;
8
+ export declare function tryGetApiKey(): string | null;
9
+ export declare function getBaseUrl(): string;
10
+ export {};
11
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAMA,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAUD,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAIpD;AAED,wBAAgB,SAAS,IAAI,MAAM,CAUlC;AAED,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAM5C;AAED,wBAAgB,UAAU,IAAI,MAAM,CAQnC"}
@@ -0,0 +1,44 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ const CONFIG_PATH = join(homedir(), ".misarblog", "config.json");
5
+ function loadConfig() {
6
+ try {
7
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
8
+ }
9
+ catch {
10
+ return {};
11
+ }
12
+ }
13
+ export function saveConfig(config) {
14
+ mkdirSync(join(homedir(), ".misarblog"), { recursive: true });
15
+ const existing = loadConfig();
16
+ writeFileSync(CONFIG_PATH, JSON.stringify({ ...existing, ...config }, null, 2), "utf8");
17
+ }
18
+ export function getApiKey() {
19
+ const envKey = process.env.MISARBLOG_API_KEY?.trim();
20
+ if (envKey)
21
+ return envKey;
22
+ const cfg = loadConfig();
23
+ if (cfg.api_key)
24
+ return cfg.api_key;
25
+ throw new Error("Not configured. Run the login tool to connect via browser, or set MISARBLOG_API_KEY.");
26
+ }
27
+ export function tryGetApiKey() {
28
+ try {
29
+ return getApiKey();
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ export function getBaseUrl() {
36
+ const envUrl = (process.env.MISARBLOG_BASE_URL ?? "").trim();
37
+ if (envUrl)
38
+ return envUrl.replace(/\/$/, "") + "/api/v1";
39
+ const cfg = loadConfig();
40
+ if (cfg.base_url)
41
+ return cfg.base_url.replace(/\/$/, "") + "/api/v1";
42
+ return "https://www.misar.blog/api/v1";
43
+ }
44
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;AAQjE,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAgB,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAmB;IAC5C,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;IAC9B,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,GAAG,CAAC,OAAO;QAAE,OAAO,GAAG,CAAC,OAAO,CAAC;IAEpC,MAAM,IAAI,KAAK,CACb,sFAAsF,CACvF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC;QACH,OAAO,SAAS,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7D,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC;IAEzD,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,GAAG,CAAC,QAAQ;QAAE,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC;IAErE,OAAO,+BAA+B,CAAC;AACzC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function formatError(error: unknown): string;
2
+ export declare function apiError(status: number, body: unknown): Error;
3
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIlD;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,KAAK,CAM7D"}
@@ -0,0 +1,14 @@
1
+ export function formatError(error) {
2
+ if (error instanceof Error)
3
+ return error.message;
4
+ if (typeof error === "string")
5
+ return error;
6
+ return "An unknown error occurred";
7
+ }
8
+ export function apiError(status, body) {
9
+ const msg = typeof body === "object" && body !== null && "error" in body
10
+ ? String(body.error)
11
+ : `HTTP ${status}`;
12
+ return new Error(msg);
13
+ }
14
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,2BAA2B,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,IAAa;IACpD,MAAM,GAAG,GACP,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,IAAI;QAC1D,CAAC,CAAC,MAAM,CAAE,IAA2B,CAAC,KAAK,CAAC;QAC5C,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC;IACvB,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerArticleTools(server: McpServer): void;
3
+ //# sourceMappingURL=articles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"articles.d.ts","sourceRoot":"","sources":["../../src/tools/articles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkBpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,QAsGrD"}
@@ -0,0 +1,89 @@
1
+ import { z } from "zod";
2
+ import { apiFetch } from "../lib/api-client.js";
3
+ import { formatError } from "../lib/errors.js";
4
+ export function registerArticleTools(server) {
5
+ server.tool("list_my_articles", "List your articles on Misar.Blog", {
6
+ status: z
7
+ .enum(["draft", "published", "scheduled", "archived"])
8
+ .optional()
9
+ .describe("Filter by status (omit for all published)"),
10
+ limit: z.number().int().min(1).max(100).default(20).describe("Number of articles to return"),
11
+ }, async ({ status, limit }) => {
12
+ try {
13
+ const qs = new URLSearchParams({ limit: String(limit) });
14
+ if (status)
15
+ qs.set("status", status);
16
+ const data = await apiFetch(`/articles?${qs}`);
17
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
18
+ }
19
+ catch (err) {
20
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
21
+ }
22
+ });
23
+ server.tool("get_article", "Get a single article by slug, including full markdown content", { slug: z.string().describe("The article slug") }, async ({ slug }) => {
24
+ try {
25
+ const article = await apiFetch(`/articles/${encodeURIComponent(slug)}`);
26
+ return { content: [{ type: "text", text: JSON.stringify(article, null, 2) }] };
27
+ }
28
+ catch (err) {
29
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
30
+ }
31
+ });
32
+ server.tool("publish_article", "Publish a new article (or schedule it) on Misar.Blog", {
33
+ title: z.string().min(1).max(250).describe("Article title"),
34
+ body_markdown: z.string().min(1).describe("Full article body in Markdown"),
35
+ tags: z.array(z.string()).max(10).optional().describe("Up to 10 tags"),
36
+ cover_image_url: z.string().url().optional().describe("URL of the cover image"),
37
+ schedule_at: z
38
+ .string()
39
+ .optional()
40
+ .describe("ISO 8601 timestamp to schedule. Omit to publish immediately."),
41
+ visibility: z
42
+ .enum(["public", "subscribers", "paid", "private"])
43
+ .default("public")
44
+ .describe("Who can read this article"),
45
+ }, async (args) => {
46
+ try {
47
+ const article = await apiFetch("/articles", {
48
+ method: "POST",
49
+ body: JSON.stringify(args),
50
+ });
51
+ const action = args.schedule_at ? "scheduled" : "published";
52
+ return {
53
+ content: [
54
+ {
55
+ type: "text",
56
+ text: `Article ${action} successfully!\n\nURL: ${article.url}\nEditor: ${article.editor_url}\n\n${JSON.stringify(article, null, 2)}`,
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ catch (err) {
62
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
63
+ }
64
+ });
65
+ server.tool("create_draft", "Save an article as a draft on Misar.Blog (for review before publishing)", {
66
+ title: z.string().min(1).describe("Draft title"),
67
+ body_markdown: z.string().min(1).describe("Full article body in Markdown"),
68
+ tags: z.array(z.string()).optional().describe("Tags for the draft"),
69
+ }, async (args) => {
70
+ try {
71
+ const draft = await apiFetch("/drafts", {
72
+ method: "POST",
73
+ body: JSON.stringify(args),
74
+ });
75
+ return {
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: `Draft saved!\n\nEditor: ${draft.editor_url}\n\n${JSON.stringify(draft, null, 2)}`,
80
+ },
81
+ ],
82
+ };
83
+ }
84
+ catch (err) {
85
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
86
+ }
87
+ });
88
+ }
89
+ //# sourceMappingURL=articles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"articles.js","sourceRoot":"","sources":["../../src/tools/articles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerImageTools(server: McpServer): void;
3
+ //# sourceMappingURL=images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/tools/images.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,QAyDnD"}
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import { apiFetch, apiUpload } from "../lib/api-client.js";
3
+ import { formatError } from "../lib/errors.js";
4
+ import { readFile } from "fs/promises";
5
+ export function registerImageTools(server) {
6
+ server.tool("upload_image", "Upload a local image file to the Misar.Blog CDN", {
7
+ file_path: z.string().describe("Absolute path to the image file (JPEG, PNG, WebP, or GIF)"),
8
+ }, async ({ file_path }) => {
9
+ try {
10
+ const { Blob } = await import("buffer");
11
+ const data = await readFile(file_path);
12
+ const ext = file_path.split(".").pop()?.toLowerCase() ?? "jpg";
13
+ const mimeMap = {
14
+ jpg: "image/jpeg",
15
+ jpeg: "image/jpeg",
16
+ png: "image/png",
17
+ webp: "image/webp",
18
+ gif: "image/gif",
19
+ };
20
+ const mime = mimeMap[ext] ?? "image/jpeg";
21
+ const blob = new Blob([data], { type: mime });
22
+ const form = new FormData();
23
+ form.append("file", blob, `upload.${ext}`);
24
+ const result = await apiUpload("/images/upload", form);
25
+ return { content: [{ type: "text", text: `Image uploaded!\n\nURL: ${result.url}` }] };
26
+ }
27
+ catch (err) {
28
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
29
+ }
30
+ });
31
+ server.tool("generate_cover_image", "Generate a cover image using AI and upload it to the Misar.Blog CDN", {
32
+ prompt: z.string().min(1).max(1000).describe("Description of the image to generate"),
33
+ size: z
34
+ .enum(["1024x1024", "1792x1024", "1024x1792"])
35
+ .default("1792x1024")
36
+ .describe("Image dimensions (default: landscape 1792x1024)"),
37
+ }, async ({ prompt, size }) => {
38
+ try {
39
+ const result = await apiFetch("/images/generate", {
40
+ method: "POST",
41
+ body: JSON.stringify({ prompt, size }),
42
+ });
43
+ return {
44
+ content: [
45
+ { type: "text", text: `Cover image generated!\n\nURL: ${result.url}\n\nUse this as cover_image_url when publishing.` },
46
+ ],
47
+ };
48
+ }
49
+ catch (err) {
50
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
51
+ }
52
+ });
53
+ }
54
+ //# sourceMappingURL=images.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.js","sourceRoot":"","sources":["../../src/tools/images.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerLoginTool(server: McpServer): void;
3
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/tools/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,QAgFlD"}
@@ -0,0 +1,93 @@
1
+ import { z } from "zod";
2
+ import { createServer } from "http";
3
+ import { execFileSync } from "child_process";
4
+ import { saveConfig, getBaseUrl } from "../lib/auth.js";
5
+ // Opens URL in the system browser — uses execFileSync with separate args (no shell, no injection)
6
+ function openBrowser(url) {
7
+ try {
8
+ if (process.platform === "darwin")
9
+ execFileSync("open", [url], { stdio: "ignore" });
10
+ else if (process.platform === "win32")
11
+ execFileSync("cmd.exe", ["/c", "start", "", url], { stdio: "ignore" });
12
+ else
13
+ execFileSync("xdg-open", [url], { stdio: "ignore" });
14
+ }
15
+ catch {
16
+ process.stderr.write(`Open this URL in your browser:\n ${url}\n`);
17
+ }
18
+ }
19
+ function pickPort(preferred) {
20
+ if (preferred && preferred >= 9001 && preferred <= 9099)
21
+ return preferred;
22
+ return 9001 + Math.floor(Math.random() * 99);
23
+ }
24
+ export function registerLoginTool(server) {
25
+ server.tool("login", "Authenticate with your Misar.Blog account via browser — no API key copy-paste needed. Opens your browser to the settings page where you click 'Authorize MCP Access'. Your API key is saved to ~/.misarblog/config.json.", {
26
+ port: z.number().int().min(9001).max(9099).optional().describe("Local callback port (9001–9099). Random by default."),
27
+ base_url: z.string().url().optional().describe("Misar.Blog base URL for self-hosted instances."),
28
+ }, async ({ port: preferredPort, base_url }) => {
29
+ const _port = pickPort(preferredPort);
30
+ const _baseUrl = (base_url ?? getBaseUrl().replace(/\/api\/v1$/, "")).replace(/\/$/, "");
31
+ const _settingsUrl = `${_baseUrl}/dashboard/settings/api?mcp_port=${_port}`;
32
+ return new Promise((resolve) => {
33
+ let _resolved = false;
34
+ const _srv = createServer((req, res) => {
35
+ // Only accept callbacks from loopback
36
+ const _remote = req.socket.remoteAddress ?? "";
37
+ if (_remote !== "127.0.0.1" && _remote !== "::1" && _remote !== "::ffff:127.0.0.1") {
38
+ res.writeHead(403).end();
39
+ return;
40
+ }
41
+ if (req.method !== "POST" || req.url !== "/token") {
42
+ res.writeHead(404).end();
43
+ return;
44
+ }
45
+ let _body = "";
46
+ req.on("data", (chunk) => { _body += chunk.toString(); });
47
+ req.on("end", () => {
48
+ try {
49
+ const _data = JSON.parse(_body);
50
+ const _gotKey = (_data.api_key ?? "").trim();
51
+ const _gotUser = (_data.username ?? "").trim();
52
+ if (!_gotKey.startsWith("mbk_")) {
53
+ res.writeHead(400).end(JSON.stringify({ error: "invalid key" }));
54
+ return;
55
+ }
56
+ res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ ok: true }));
57
+ saveConfig({ api_key: _gotKey, username: _gotUser, ...(base_url ? { base_url } : {}) });
58
+ if (!_resolved) {
59
+ _resolved = true;
60
+ _srv.close();
61
+ resolve({
62
+ content: [{
63
+ type: "text",
64
+ text: `Connected as @${_gotUser}! API key saved to ~/.misarblog/config.json.\n\nYou can now use all Misar.Blog tools without setting MISARBLOG_API_KEY.`,
65
+ }],
66
+ });
67
+ }
68
+ }
69
+ catch {
70
+ res.writeHead(400).end();
71
+ }
72
+ });
73
+ });
74
+ _srv.listen(_port, "127.0.0.1", () => {
75
+ openBrowser(_settingsUrl);
76
+ });
77
+ // 120s timeout
78
+ setTimeout(() => {
79
+ if (!_resolved) {
80
+ _resolved = true;
81
+ _srv.close();
82
+ resolve({
83
+ content: [{
84
+ type: "text",
85
+ text: `Login timed out after 120 seconds. Open this URL manually and click 'Authorize MCP Access':\n\n${_settingsUrl}`,
86
+ }],
87
+ });
88
+ }
89
+ }, 120_000);
90
+ });
91
+ });
92
+ }
93
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/tools/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerProfileTools(server: McpServer): void;
3
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/tools/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,QAmCrD"}
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+ import { apiFetch } from "../lib/api-client.js";
3
+ import { formatError } from "../lib/errors.js";
4
+ export function registerProfileTools(server) {
5
+ server.tool("get_profile", "Get your Misar.Blog creator profile", {}, async () => {
6
+ try {
7
+ const profile = await apiFetch("/me");
8
+ return {
9
+ content: [
10
+ {
11
+ type: "text",
12
+ text: JSON.stringify(profile, null, 2),
13
+ },
14
+ ],
15
+ };
16
+ }
17
+ catch (err) {
18
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
19
+ }
20
+ });
21
+ server.tool("get_analytics_summary", "Get analytics summary (views, revenue, subscribers) for a time period", { days: z.number().int().min(1).max(365).default(30).describe("Number of days to look back (default: 30, max: 365)") }, async ({ days }) => {
22
+ try {
23
+ const data = await apiFetch(`/analytics?days=${days}`);
24
+ const revenueUsd = (data.revenue_net_cents / 100).toFixed(2);
25
+ const summary = {
26
+ ...data,
27
+ revenue_usd: `$${revenueUsd}`,
28
+ };
29
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
30
+ }
31
+ catch (err) {
32
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
33
+ }
34
+ });
35
+ }
36
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/tools/profile.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmB/C,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,qCAAqC,EAAE,EAAE,EAAE,KAAK,IAAI,EAAE;QAC/E,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAkB,KAAK,CAAC,CAAC;YACvD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;qBACvC;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5F,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CACT,uBAAuB,EACvB,uEAAuE,EACvE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,qDAAqD,CAAC,EAAE,EACtH,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACjB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAoB,mBAAmB,IAAI,EAAE,CAAC,CAAC;YAC1E,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,iBAAiB,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC7D,MAAM,OAAO,GAAG;gBACd,GAAG,IAAI;gBACP,WAAW,EAAE,IAAI,UAAU,EAAE;aAC9B,CAAC;YACF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACjF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5F,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSeriesTools(server: McpServer): void;
3
+ //# sourceMappingURL=series.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"series.d.ts","sourceRoot":"","sources":["../../src/tools/series.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAcpE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,QA2DpD"}
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ import { apiFetch } from "../lib/api-client.js";
3
+ import { formatError } from "../lib/errors.js";
4
+ export function registerSeriesTools(server) {
5
+ server.tool("get_series", "List all your series on Misar.Blog", {}, async () => {
6
+ try {
7
+ const data = await apiFetch("/series");
8
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
9
+ }
10
+ catch (err) {
11
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
12
+ }
13
+ });
14
+ server.tool("create_series", "Create a new series to group related articles", {
15
+ title: z.string().min(1).describe("Series title"),
16
+ description: z.string().optional().describe("Short description of the series"),
17
+ }, async ({ title, description }) => {
18
+ try {
19
+ const series = await apiFetch("/series", {
20
+ method: "POST",
21
+ body: JSON.stringify({ title, description }),
22
+ });
23
+ return {
24
+ content: [
25
+ { type: "text", text: `Series created!\n\nURL: ${series.url}\nSlug: ${series.slug}\n\n${JSON.stringify(series, null, 2)}` },
26
+ ],
27
+ };
28
+ }
29
+ catch (err) {
30
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
31
+ }
32
+ });
33
+ server.tool("add_to_series", "Add an existing article to a series", {
34
+ series_slug: z.string().describe("The series slug"),
35
+ article_slug: z.string().describe("The article slug to add"),
36
+ position: z.number().int().min(1).optional().describe("Position in the series (optional, appends if omitted)"),
37
+ }, async ({ series_slug, article_slug, position }) => {
38
+ try {
39
+ const result = await apiFetch(`/series/${encodeURIComponent(series_slug)}/articles`, {
40
+ method: "POST",
41
+ body: JSON.stringify({ article_slug, position }),
42
+ });
43
+ return { content: [{ type: "text", text: `Article added to series successfully.` }] };
44
+ }
45
+ catch (err) {
46
+ return { content: [{ type: "text", text: `Error: ${formatError(err)}` }], isError: true };
47
+ }
48
+ });
49
+ }
50
+ //# sourceMappingURL=series.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"series.js","sourceRoot":"","sources":["../../src/tools/series.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAW/C,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,IAAI,CACT,YAAY,EACZ,oCAAoC,EACpC,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAsC,SAAS,CAAC,CAAC;YAC5E,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5F,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,eAAe,EACf,+CAA+C,EAC/C;QACE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;QACjD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;KAC/E,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAS,SAAS,EAAE;gBAC/C,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;aAC7C,CAAC,CAAC;YACH,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,MAAM,CAAC,GAAG,WAAW,MAAM,CAAC,IAAI,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;iBAC5H;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5F,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,eAAe,EACf,qCAAqC,EACrC;QACE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QACnD,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;QAC5D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;KAC/G,EACD,KAAK,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,EAAE;QAChD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAkB,WAAW,kBAAkB,CAAC,WAAW,CAAC,WAAW,EAAE;gBACpG,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;aACjD,CAAC,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,uCAAuC,EAAE,CAAC,EAAE,CAAC;QACxF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5F,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@misarblog/mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Misar.Blog — publish blog posts, manage drafts, generate AI cover images, and access analytics from Claude Code, Cursor & Windsurf.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "misarblog-mcp": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc --watch",
16
+ "start": "node dist/index.js",
17
+ "prepublishOnly": "pnpm build"
18
+ },
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.10.2",
21
+ "zod": "^3.24.2"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "typescript": "^5.7.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "keywords": [
31
+ "mcp",
32
+ "mcp-server",
33
+ "model-context-protocol",
34
+ "blogging",
35
+ "blog",
36
+ "content-publishing",
37
+ "markdown",
38
+ "ai-writing",
39
+ "ai-content",
40
+ "draft-management",
41
+ "analytics",
42
+ "claude-code",
43
+ "cursor",
44
+ "windsurf",
45
+ "misarblog",
46
+ "publishing-platform",
47
+ "seo",
48
+ "aeo",
49
+ "writer-tools",
50
+ "editorial"
51
+ ],
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://git.misar.io/misaradmin/MisarBlog"
56
+ },
57
+ "homepage": "https://www.misar.blog"
58
+ }