@jgardner04/ghost-mcp-server 1.13.4 → 1.13.5
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 +68 -0
- package/package.json +7 -3
- package/src/__tests__/helpers/testUtils.js +15 -1
- package/src/__tests__/mcp_server.test.js +69 -1
- package/src/__tests__/mcp_server_pages.test.js +23 -6
- package/src/mcp_server.js +393 -1143
- package/src/services/__tests__/createResourceService.test.js +468 -0
- package/src/services/__tests__/ghostServiceImproved.members.test.js +0 -3
- package/src/services/__tests__/ghostServiceImproved.newsletters.test.js +2 -2
- package/src/services/__tests__/ghostServiceImproved.pages.test.js +0 -3
- package/src/services/__tests__/ghostServiceImproved.posts.test.js +0 -1
- package/src/services/__tests__/ghostServiceImproved.tags.test.js +0 -3
- package/src/services/__tests__/ghostServiceImproved.tiers.test.js +3 -5
- package/src/services/createResourceService.js +138 -0
- package/src/services/ghostApiClient.js +240 -0
- package/src/services/ghostServiceImproved.js +76 -915
- package/src/services/images.js +27 -0
- package/src/services/members.js +127 -0
- package/src/services/newsletters.js +63 -0
- package/src/services/pages.js +116 -0
- package/src/services/posts.js +116 -0
- package/src/services/tags.js +118 -0
- package/src/services/tiers.js +72 -0
- package/src/services/validators.js +218 -0
package/README.md
CHANGED
|
@@ -278,6 +278,74 @@ For development, the following scripts are available:
|
|
|
278
278
|
| `npm run lint` | Check code for linting errors |
|
|
279
279
|
| `npm run lint:fix` | Auto-fix linting errors |
|
|
280
280
|
|
|
281
|
+
## MCP Client Configuration
|
|
282
|
+
|
|
283
|
+
The Ghost MCP Server works with any MCP-compatible client. Below are quickstart configurations for the most common clients. For a complete guide including WebSocket and HTTP/SSE transports, see [docs/MCP_CLIENT_SETUP.md](docs/MCP_CLIENT_SETUP.md).
|
|
284
|
+
|
|
285
|
+
### Claude Code (including the Sidedoc project)
|
|
286
|
+
|
|
287
|
+
Create a `.mcp.json` file at the root of your project (e.g., the [Sidedoc repository](https://github.com/jgardner04/sidedoc)):
|
|
288
|
+
|
|
289
|
+
```json
|
|
290
|
+
{
|
|
291
|
+
"mcpServers": {
|
|
292
|
+
"ghost": {
|
|
293
|
+
"command": "npx",
|
|
294
|
+
"args": ["-y", "@jgardner04/ghost-mcp-server"],
|
|
295
|
+
"env": {
|
|
296
|
+
"GHOST_ADMIN_API_URL": "https://your-ghost-site.com",
|
|
297
|
+
"GHOST_ADMIN_API_KEY": "your_admin_api_key"
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Claude Code will automatically detect this file and make all 34 Ghost MCP tools available within that project. You can also register the server globally:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
claude mcp add ghost \
|
|
308
|
+
--scope user \
|
|
309
|
+
--command npx \
|
|
310
|
+
--args "-y @jgardner04/ghost-mcp-server" \
|
|
311
|
+
--env GHOST_ADMIN_API_URL=https://your-ghost-site.com \
|
|
312
|
+
--env GHOST_ADMIN_API_KEY=your_admin_api_key
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
> **Tip:** Do not commit `.mcp.json` files that contain real API keys. Add `.mcp.json` to your `.gitignore` or source credentials from a `.env` file.
|
|
316
|
+
|
|
317
|
+
### Claude Desktop
|
|
318
|
+
|
|
319
|
+
Add the server to your Claude Desktop configuration file:
|
|
320
|
+
|
|
321
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
322
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"mcpServers": {
|
|
327
|
+
"ghost": {
|
|
328
|
+
"command": "npx",
|
|
329
|
+
"args": ["-y", "@jgardner04/ghost-mcp-server"],
|
|
330
|
+
"env": {
|
|
331
|
+
"GHOST_ADMIN_API_URL": "https://your-ghost-site.com",
|
|
332
|
+
"GHOST_ADMIN_API_KEY": "your_admin_api_key"
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Restart Claude Desktop after saving the file.
|
|
340
|
+
|
|
341
|
+
### Cursor
|
|
342
|
+
|
|
343
|
+
Open **Cursor Settings → Features → MCP Servers**, click **Add Server**, and provide:
|
|
344
|
+
|
|
345
|
+
- **Name:** `ghost`
|
|
346
|
+
- **Command:** `npx -y @jgardner04/ghost-mcp-server`
|
|
347
|
+
- **Environment Variables:** `GHOST_ADMIN_API_URL` and `GHOST_ADMIN_API_KEY`
|
|
348
|
+
|
|
281
349
|
## Development Setup
|
|
282
350
|
|
|
283
351
|
For contributors or advanced users who want to modify the source code:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgardner04/ghost-mcp-server",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.5",
|
|
4
4
|
"description": "A Model Context Protocol (MCP) server for interacting with Ghost CMS via the Admin API",
|
|
5
5
|
"author": "Jonathan Gardner",
|
|
6
6
|
"type": "module",
|
|
@@ -43,12 +43,13 @@
|
|
|
43
43
|
"lint:fix": "eslint . --fix",
|
|
44
44
|
"format": "prettier --write \"**/*.{js,json,md}\"",
|
|
45
45
|
"format:check": "prettier --check \"**/*.{js,json,md}\"",
|
|
46
|
-
"prepare": "husky"
|
|
46
|
+
"prepare": "husky",
|
|
47
|
+
"prepublishOnly": "chmod +x src/mcp_server.js"
|
|
47
48
|
},
|
|
48
49
|
"dependencies": {
|
|
49
50
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
50
51
|
"@tryghost/admin-api": "^1.13.12",
|
|
51
|
-
"axios": "^1.
|
|
52
|
+
"axios": "^1.15.0",
|
|
52
53
|
"chalk": "^5.3.0",
|
|
53
54
|
"cli-table3": "^0.6.3",
|
|
54
55
|
"dotenv": "^17.0.0",
|
|
@@ -86,6 +87,9 @@
|
|
|
86
87
|
"prettier --write"
|
|
87
88
|
]
|
|
88
89
|
},
|
|
90
|
+
"overrides": {
|
|
91
|
+
"axios": "^1.15.0"
|
|
92
|
+
},
|
|
89
93
|
"devDependencies": {
|
|
90
94
|
"@eslint/js": "^10.0.0",
|
|
91
95
|
"@vitest/coverage-v8": "^4.0.15",
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
import { vi } from 'vitest';
|
|
1
|
+
import { expect, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Asserts that a value is a ZodObject exposing a `.shape` property.
|
|
5
|
+
*
|
|
6
|
+
* Uses `expect` so Vitest reports a clean named assertion failure, and
|
|
7
|
+
* `schema?.shape` so a null/undefined `schema` is handled without a secondary
|
|
8
|
+
* TypeError — the exact opaque failure mode this helper eliminates.
|
|
9
|
+
*
|
|
10
|
+
* @param {unknown} schema - Value expected to be a ZodObject
|
|
11
|
+
* @param {string} toolName - Tool name included in the failure message
|
|
12
|
+
*/
|
|
13
|
+
export function assertZodShape(schema, toolName) {
|
|
14
|
+
expect(schema?.shape, `${toolName}: schema is not a ZodObject (missing .shape)`).toBeDefined();
|
|
15
|
+
}
|
|
2
16
|
|
|
3
17
|
/**
|
|
4
18
|
* Creates a mock environment variable configuration.
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest';
|
|
2
|
+
// zod/v4-mini is a subpath export of zod@^4 — used here because the MCP SDK's
|
|
3
|
+
// internal JSON Schema converter (zod-json-schema-compat.js) uses this same module.
|
|
4
|
+
import * as z4mini from 'zod/v4-mini';
|
|
5
|
+
import { assertZodShape } from './helpers/testUtils.js';
|
|
2
6
|
|
|
3
7
|
// Mock the McpServer to capture tool registrations
|
|
4
8
|
const mockTools = new Map();
|
|
@@ -212,6 +216,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
212
216
|
expect(tool.description).toContain('posts');
|
|
213
217
|
expect(tool.schema).toBeDefined();
|
|
214
218
|
// Zod schemas store field definitions in schema.shape
|
|
219
|
+
assertZodShape(tool.schema, 'ghost_get_posts');
|
|
215
220
|
expect(tool.schema.shape.limit).toBeDefined();
|
|
216
221
|
expect(tool.schema.shape.page).toBeDefined();
|
|
217
222
|
expect(tool.schema.shape.status).toBeDefined();
|
|
@@ -250,6 +255,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
250
255
|
it('should validate limit is between 1 and 100', () => {
|
|
251
256
|
const tool = mockTools.get('ghost_get_posts');
|
|
252
257
|
// Zod schemas store field definitions in schema.shape
|
|
258
|
+
assertZodShape(tool.schema, 'ghost_get_posts');
|
|
253
259
|
const shape = tool.schema.shape;
|
|
254
260
|
|
|
255
261
|
// Test that limit schema exists and has proper validation
|
|
@@ -262,6 +268,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
262
268
|
it('should validate page is at least 1', () => {
|
|
263
269
|
const tool = mockTools.get('ghost_get_posts');
|
|
264
270
|
// Zod schemas store field definitions in schema.shape
|
|
271
|
+
assertZodShape(tool.schema, 'ghost_get_posts');
|
|
265
272
|
const shape = tool.schema.shape;
|
|
266
273
|
|
|
267
274
|
expect(shape.page).toBeDefined();
|
|
@@ -282,6 +289,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
282
289
|
it('should validate status enum values', () => {
|
|
283
290
|
const tool = mockTools.get('ghost_get_posts');
|
|
284
291
|
// Zod schemas store field definitions in schema.shape
|
|
292
|
+
assertZodShape(tool.schema, 'ghost_get_posts');
|
|
285
293
|
const shape = tool.schema.shape;
|
|
286
294
|
|
|
287
295
|
expect(shape.status).toBeDefined();
|
|
@@ -449,6 +457,7 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
449
457
|
expect(tool.description).toContain('tags');
|
|
450
458
|
expect(tool.schema).toBeDefined();
|
|
451
459
|
// Zod schemas store field definitions in schema.shape
|
|
460
|
+
assertZodShape(tool.schema, 'ghost_get_tags');
|
|
452
461
|
expect(tool.schema.shape.limit).toBeDefined();
|
|
453
462
|
expect(tool.schema.shape.page).toBeDefined();
|
|
454
463
|
expect(tool.schema.shape.order).toBeDefined();
|
|
@@ -683,6 +692,7 @@ describe('mcp_server - ghost_get_post tool', () => {
|
|
|
683
692
|
expect(tool.description).toContain('post');
|
|
684
693
|
expect(tool.schema).toBeDefined();
|
|
685
694
|
// In Zod v4, refined schemas expose .shape directly
|
|
695
|
+
assertZodShape(tool.schema, 'ghost_get_post');
|
|
686
696
|
const shape = tool.schema.shape;
|
|
687
697
|
expect(shape.id).toBeDefined();
|
|
688
698
|
expect(shape.slug).toBeDefined();
|
|
@@ -849,6 +859,7 @@ describe('mcp_server - ghost_update_post tool', () => {
|
|
|
849
859
|
expect(tool.description).toContain('Updates an existing post');
|
|
850
860
|
expect(tool.schema).toBeDefined();
|
|
851
861
|
// Zod schemas store field definitions in schema.shape
|
|
862
|
+
assertZodShape(tool.schema, 'ghost_update_post');
|
|
852
863
|
expect(tool.schema.shape.id).toBeDefined();
|
|
853
864
|
expect(tool.schema.shape.title).toBeDefined();
|
|
854
865
|
expect(tool.schema.shape.html).toBeDefined();
|
|
@@ -1120,6 +1131,7 @@ describe('mcp_server - ghost_delete_post tool', () => {
|
|
|
1120
1131
|
expect(tool.description).toContain('permanent');
|
|
1121
1132
|
expect(tool.schema).toBeDefined();
|
|
1122
1133
|
// Zod schemas store field definitions in schema.shape
|
|
1134
|
+
assertZodShape(tool.schema, 'ghost_delete_post');
|
|
1123
1135
|
expect(tool.schema.shape.id).toBeDefined();
|
|
1124
1136
|
});
|
|
1125
1137
|
|
|
@@ -1199,6 +1211,7 @@ describe('mcp_server - ghost_search_posts tool', () => {
|
|
|
1199
1211
|
expect(tool.description).toContain('Search');
|
|
1200
1212
|
expect(tool.schema).toBeDefined();
|
|
1201
1213
|
// Zod schemas store field definitions in schema.shape
|
|
1214
|
+
assertZodShape(tool.schema, 'ghost_search_posts');
|
|
1202
1215
|
expect(tool.schema.shape.query).toBeDefined();
|
|
1203
1216
|
expect(tool.schema.shape.status).toBeDefined();
|
|
1204
1217
|
expect(tool.schema.shape.limit).toBeDefined();
|
|
@@ -1247,6 +1260,7 @@ describe('mcp_server - ghost_search_posts tool', () => {
|
|
|
1247
1260
|
it('should validate limit is between 1 and 50', () => {
|
|
1248
1261
|
const tool = mockTools.get('ghost_search_posts');
|
|
1249
1262
|
// Zod schemas store field definitions in schema.shape
|
|
1263
|
+
assertZodShape(tool.schema, 'ghost_search_posts');
|
|
1250
1264
|
const shape = tool.schema.shape;
|
|
1251
1265
|
|
|
1252
1266
|
expect(shape.limit).toBeDefined();
|
|
@@ -1258,6 +1272,7 @@ describe('mcp_server - ghost_search_posts tool', () => {
|
|
|
1258
1272
|
it('should validate status enum values', () => {
|
|
1259
1273
|
const tool = mockTools.get('ghost_search_posts');
|
|
1260
1274
|
// Zod schemas store field definitions in schema.shape
|
|
1275
|
+
assertZodShape(tool.schema, 'ghost_search_posts');
|
|
1261
1276
|
const shape = tool.schema.shape;
|
|
1262
1277
|
|
|
1263
1278
|
expect(shape.status).toBeDefined();
|
|
@@ -1356,6 +1371,7 @@ describe('ghost_get_tag', () => {
|
|
|
1356
1371
|
it('should have correct schema with id and slug as optional', () => {
|
|
1357
1372
|
const tool = mockTools.get('ghost_get_tag');
|
|
1358
1373
|
// In Zod v4, refined schemas expose .shape directly
|
|
1374
|
+
assertZodShape(tool.schema, 'ghost_get_tag');
|
|
1359
1375
|
const shape = tool.schema.shape;
|
|
1360
1376
|
expect(shape.id).toBeDefined();
|
|
1361
1377
|
expect(shape.slug).toBeDefined();
|
|
@@ -1456,6 +1472,7 @@ describe('ghost_update_tag', () => {
|
|
|
1456
1472
|
it('should have correct schema with all update fields', () => {
|
|
1457
1473
|
const tool = mockTools.get('ghost_update_tag');
|
|
1458
1474
|
// Zod schemas store field definitions in schema.shape
|
|
1475
|
+
assertZodShape(tool.schema, 'ghost_update_tag');
|
|
1459
1476
|
expect(tool.schema.shape.id).toBeDefined();
|
|
1460
1477
|
expect(tool.schema.shape.name).toBeDefined();
|
|
1461
1478
|
expect(tool.schema.shape.slug).toBeDefined();
|
|
@@ -1602,6 +1619,7 @@ describe('ghost_delete_tag', () => {
|
|
|
1602
1619
|
it('should have correct schema with id field', () => {
|
|
1603
1620
|
const tool = mockTools.get('ghost_delete_tag');
|
|
1604
1621
|
// Zod schemas store field definitions in schema.shape
|
|
1622
|
+
assertZodShape(tool.schema, 'ghost_delete_tag');
|
|
1605
1623
|
expect(tool.schema.shape.id).toBeDefined();
|
|
1606
1624
|
});
|
|
1607
1625
|
|
|
@@ -1644,3 +1662,53 @@ describe('ghost_delete_tag', () => {
|
|
|
1644
1662
|
expect(result.content[0].text).toContain('Failed to delete tag');
|
|
1645
1663
|
});
|
|
1646
1664
|
});
|
|
1665
|
+
|
|
1666
|
+
// --- JSON Schema regression tests (JON-103) ---
|
|
1667
|
+
// Verifies that every registered MCP tool exposes a non-empty JSON Schema
|
|
1668
|
+
// to clients. Uses zod/v4-mini's toJSONSchema — the same converter the
|
|
1669
|
+
// MCP SDK calls internally (see zod-json-schema-compat.js).
|
|
1670
|
+
describe('tool schema JSON Schema output', () => {
|
|
1671
|
+
// Matches the MCP SDK's internal conversion options (see zod-json-schema-compat.js)
|
|
1672
|
+
const JSON_SCHEMA_OPTS = { target: 'draft-7', io: 'input' };
|
|
1673
|
+
|
|
1674
|
+
beforeAll(async () => {
|
|
1675
|
+
if (mockTools.size === 0) {
|
|
1676
|
+
await import('../mcp_server.js');
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
it('should produce non-empty properties for every registered tool', () => {
|
|
1681
|
+
expect(mockTools.size).toBeGreaterThan(0);
|
|
1682
|
+
|
|
1683
|
+
for (const [name, tool] of mockTools) {
|
|
1684
|
+
const schema = tool.schema;
|
|
1685
|
+
|
|
1686
|
+
// Schema must be a Zod object with a shape
|
|
1687
|
+
assertZodShape(schema, name);
|
|
1688
|
+
expect(Object.keys(schema.shape).length, `${name}: shape has no keys`).toBeGreaterThan(0);
|
|
1689
|
+
|
|
1690
|
+
// Convert via the same path the MCP SDK uses
|
|
1691
|
+
const jsonSchema = z4mini.toJSONSchema(schema, JSON_SCHEMA_OPTS);
|
|
1692
|
+
|
|
1693
|
+
expect(jsonSchema.type, `${name}: type should be 'object'`).toBe('object');
|
|
1694
|
+
expect(
|
|
1695
|
+
Object.keys(jsonSchema.properties || {}).length,
|
|
1696
|
+
`${name}: JSON Schema properties is empty`
|
|
1697
|
+
).toBeGreaterThan(0);
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
it('should declare title and html as required for ghost_create_post and ghost_create_page', () => {
|
|
1702
|
+
for (const toolName of ['ghost_create_post', 'ghost_create_page']) {
|
|
1703
|
+
const tool = mockTools.get(toolName);
|
|
1704
|
+
expect(tool, `${toolName}: tool not found in registry`).toBeDefined();
|
|
1705
|
+
const jsonSchema = z4mini.toJSONSchema(tool.schema, JSON_SCHEMA_OPTS);
|
|
1706
|
+
|
|
1707
|
+
expect(jsonSchema.properties, `${toolName}: properties missing`).toBeDefined();
|
|
1708
|
+
expect(jsonSchema.required, `${toolName}: title not required`).toContain('title');
|
|
1709
|
+
expect(jsonSchema.required, `${toolName}: html not required`).toContain('html');
|
|
1710
|
+
expect(jsonSchema.properties.title.type, `${toolName}: title type`).toBe('string');
|
|
1711
|
+
expect(jsonSchema.properties.html.type, `${toolName}: html type`).toBe('string');
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { assertZodShape } from './helpers/testUtils.js';
|
|
2
3
|
|
|
3
4
|
// Mock the McpServer to capture tool registrations
|
|
4
5
|
const mockTools = new Map();
|
|
@@ -158,6 +159,7 @@ describe('mcp_server - ghost_get_pages tool', () => {
|
|
|
158
159
|
expect(tool).toBeDefined();
|
|
159
160
|
expect(tool.description).toContain('pages');
|
|
160
161
|
expect(tool.schema).toBeDefined();
|
|
162
|
+
assertZodShape(tool.schema, 'ghost_get_pages');
|
|
161
163
|
expect(tool.schema.shape.limit).toBeDefined();
|
|
162
164
|
expect(tool.schema.shape.page).toBeDefined();
|
|
163
165
|
expect(tool.schema.shape.include).toBeDefined();
|
|
@@ -194,6 +196,7 @@ describe('mcp_server - ghost_get_pages tool', () => {
|
|
|
194
196
|
const tool = mockTools.get('ghost_get_pages');
|
|
195
197
|
const schema = tool.schema;
|
|
196
198
|
|
|
199
|
+
assertZodShape(schema, 'ghost_get_pages');
|
|
197
200
|
expect(schema.shape.limit).toBeDefined();
|
|
198
201
|
expect(() => schema.shape.limit.parse(0)).toThrow();
|
|
199
202
|
expect(() => schema.shape.limit.parse(101)).toThrow();
|
|
@@ -219,7 +222,7 @@ describe('mcp_server - ghost_get_pages tool', () => {
|
|
|
219
222
|
const result = await tool.handler({});
|
|
220
223
|
|
|
221
224
|
expect(result.isError).toBe(true);
|
|
222
|
-
expect(result.content[0].text).toContain('Error
|
|
225
|
+
expect(result.content[0].text).toContain('Error in ghost_get_pages');
|
|
223
226
|
});
|
|
224
227
|
});
|
|
225
228
|
|
|
@@ -239,6 +242,7 @@ describe('mcp_server - ghost_get_page tool', () => {
|
|
|
239
242
|
const tool = mockTools.get('ghost_get_page');
|
|
240
243
|
expect(tool).toBeDefined();
|
|
241
244
|
// In Zod v4, refined schemas expose .shape directly
|
|
245
|
+
assertZodShape(tool.schema, 'ghost_get_page');
|
|
242
246
|
const shape = tool.schema.shape;
|
|
243
247
|
expect(shape.id).toBeDefined();
|
|
244
248
|
expect(shape.slug).toBeDefined();
|
|
@@ -279,6 +283,14 @@ describe('mcp_server - ghost_get_page tool', () => {
|
|
|
279
283
|
expect(() => tool.schema.parse({ slug: 'about-us' })).not.toThrow();
|
|
280
284
|
});
|
|
281
285
|
|
|
286
|
+
it('should return validation error when neither id nor slug provided', async () => {
|
|
287
|
+
const tool = mockTools.get('ghost_get_page');
|
|
288
|
+
const result = await tool.handler({});
|
|
289
|
+
|
|
290
|
+
expect(result.isError).toBe(true);
|
|
291
|
+
expect(result.content[0].text).toContain('Either id or slug is required');
|
|
292
|
+
});
|
|
293
|
+
|
|
282
294
|
it('should handle errors gracefully', async () => {
|
|
283
295
|
mockGetPage.mockRejectedValue(new Error('Page not found'));
|
|
284
296
|
|
|
@@ -286,7 +298,7 @@ describe('mcp_server - ghost_get_page tool', () => {
|
|
|
286
298
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
287
299
|
|
|
288
300
|
expect(result.isError).toBe(true);
|
|
289
|
-
expect(result.content[0].text).toContain('Error
|
|
301
|
+
expect(result.content[0].text).toContain('Error in ghost_get_page');
|
|
290
302
|
});
|
|
291
303
|
});
|
|
292
304
|
|
|
@@ -306,6 +318,7 @@ describe('mcp_server - ghost_create_page tool', () => {
|
|
|
306
318
|
const tool = mockTools.get('ghost_create_page');
|
|
307
319
|
expect(tool).toBeDefined();
|
|
308
320
|
expect(tool.description).toContain('page');
|
|
321
|
+
assertZodShape(tool.schema, 'ghost_create_page');
|
|
309
322
|
expect(tool.schema.shape.title).toBeDefined();
|
|
310
323
|
expect(tool.schema.shape.html).toBeDefined();
|
|
311
324
|
expect(tool.schema.shape.status).toBeDefined();
|
|
@@ -361,7 +374,7 @@ describe('mcp_server - ghost_create_page tool', () => {
|
|
|
361
374
|
const result = await tool.handler({ title: 'Test', html: '<p>Content</p>' });
|
|
362
375
|
|
|
363
376
|
expect(result.isError).toBe(true);
|
|
364
|
-
expect(result.content[0].text).toContain('Error
|
|
377
|
+
expect(result.content[0].text).toContain('Error in ghost_create_page');
|
|
365
378
|
});
|
|
366
379
|
});
|
|
367
380
|
|
|
@@ -381,6 +394,7 @@ describe('mcp_server - ghost_update_page tool', () => {
|
|
|
381
394
|
const tool = mockTools.get('ghost_update_page');
|
|
382
395
|
expect(tool).toBeDefined();
|
|
383
396
|
expect(tool.description).toContain('page');
|
|
397
|
+
assertZodShape(tool.schema, 'ghost_update_page');
|
|
384
398
|
expect(tool.schema.shape.id).toBeDefined();
|
|
385
399
|
expect(tool.schema.shape.title).toBeDefined();
|
|
386
400
|
expect(tool.schema.shape.html).toBeDefined();
|
|
@@ -433,7 +447,7 @@ describe('mcp_server - ghost_update_page tool', () => {
|
|
|
433
447
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439099', title: 'Test' });
|
|
434
448
|
|
|
435
449
|
expect(result.isError).toBe(true);
|
|
436
|
-
expect(result.content[0].text).toContain('Error
|
|
450
|
+
expect(result.content[0].text).toContain('Error in ghost_update_page');
|
|
437
451
|
});
|
|
438
452
|
});
|
|
439
453
|
|
|
@@ -452,6 +466,7 @@ describe('mcp_server - ghost_delete_page tool', () => {
|
|
|
452
466
|
it('should have correct schema with id required', () => {
|
|
453
467
|
const tool = mockTools.get('ghost_delete_page');
|
|
454
468
|
expect(tool).toBeDefined();
|
|
469
|
+
assertZodShape(tool.schema, 'ghost_delete_page');
|
|
455
470
|
expect(tool.schema.shape.id).toBeDefined();
|
|
456
471
|
expect(tool.description).toContain('permanent');
|
|
457
472
|
});
|
|
@@ -473,7 +488,7 @@ describe('mcp_server - ghost_delete_page tool', () => {
|
|
|
473
488
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
474
489
|
|
|
475
490
|
expect(result.isError).toBe(true);
|
|
476
|
-
expect(result.content[0].text).toContain('Error
|
|
491
|
+
expect(result.content[0].text).toContain('Error in ghost_delete_page');
|
|
477
492
|
});
|
|
478
493
|
});
|
|
479
494
|
|
|
@@ -492,6 +507,7 @@ describe('mcp_server - ghost_search_pages tool', () => {
|
|
|
492
507
|
it('should have correct schema with query required', () => {
|
|
493
508
|
const tool = mockTools.get('ghost_search_pages');
|
|
494
509
|
expect(tool).toBeDefined();
|
|
510
|
+
assertZodShape(tool.schema, 'ghost_search_pages');
|
|
495
511
|
expect(tool.schema.shape.query).toBeDefined();
|
|
496
512
|
expect(tool.schema.shape.status).toBeDefined();
|
|
497
513
|
expect(tool.schema.shape.limit).toBeDefined();
|
|
@@ -535,6 +551,7 @@ describe('mcp_server - ghost_search_pages tool', () => {
|
|
|
535
551
|
const tool = mockTools.get('ghost_search_pages');
|
|
536
552
|
const schema = tool.schema;
|
|
537
553
|
|
|
554
|
+
assertZodShape(schema, 'ghost_search_pages');
|
|
538
555
|
expect(schema.shape.limit).toBeDefined();
|
|
539
556
|
expect(() => schema.shape.limit.parse(0)).toThrow();
|
|
540
557
|
expect(() => schema.shape.limit.parse(51)).toThrow();
|
|
@@ -548,6 +565,6 @@ describe('mcp_server - ghost_search_pages tool', () => {
|
|
|
548
565
|
const result = await tool.handler({ query: 'test' });
|
|
549
566
|
|
|
550
567
|
expect(result.isError).toBe(true);
|
|
551
|
-
expect(result.content[0].text).toContain('Error
|
|
568
|
+
expect(result.content[0].text).toContain('Error in ghost_search_pages');
|
|
552
569
|
});
|
|
553
570
|
});
|