@jgardner04/ghost-mcp-server 1.10.0 → 1.12.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/package.json +1 -1
- package/src/__tests__/mcp_server.test.js +10 -4
- package/src/__tests__/mcp_server_improved.test.js +192 -149
- package/src/__tests__/mcp_server_pages.test.js +72 -68
- package/src/errors/__tests__/index.test.js +70 -0
- package/src/errors/index.js +10 -0
- package/src/mcp_server.js +9 -19
- package/src/mcp_server_improved.js +815 -424
- package/src/schemas/__tests__/common.test.js +84 -0
- package/src/schemas/common.js +50 -3
- package/src/services/__tests__/ghostServiceImproved.members.test.js +12 -61
- package/src/services/__tests__/ghostServiceImproved.tiers.test.js +392 -0
- package/src/services/__tests__/postService.test.js +7 -99
- package/src/services/__tests__/tierService.test.js +372 -0
- package/src/services/ghostServiceImproved.js +140 -21
- package/src/services/postService.js +4 -30
- package/src/services/tierService.js +304 -0
- package/src/utils/__tests__/tempFileManager.test.js +316 -0
- package/src/utils/__tests__/validation.test.js +163 -0
- package/src/utils/tempFileManager.js +113 -0
- package/src/utils/validation.js +28 -0
|
@@ -8,6 +8,31 @@ import fs from 'fs';
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import crypto from 'crypto';
|
|
11
|
+
import { ValidationError } from './errors/index.js';
|
|
12
|
+
import { validateToolInput } from './utils/validation.js';
|
|
13
|
+
import { trackTempFile, cleanupTempFiles } from './utils/tempFileManager.js';
|
|
14
|
+
import {
|
|
15
|
+
createTagSchema,
|
|
16
|
+
updateTagSchema,
|
|
17
|
+
tagQuerySchema,
|
|
18
|
+
ghostIdSchema,
|
|
19
|
+
emailSchema,
|
|
20
|
+
createPostSchema,
|
|
21
|
+
updatePostSchema,
|
|
22
|
+
postQuerySchema,
|
|
23
|
+
createMemberSchema,
|
|
24
|
+
updateMemberSchema,
|
|
25
|
+
memberQuerySchema,
|
|
26
|
+
createTierSchema,
|
|
27
|
+
updateTierSchema,
|
|
28
|
+
tierQuerySchema,
|
|
29
|
+
createNewsletterSchema,
|
|
30
|
+
updateNewsletterSchema,
|
|
31
|
+
newsletterQuerySchema,
|
|
32
|
+
createPageSchema,
|
|
33
|
+
updatePageSchema,
|
|
34
|
+
pageQuerySchema,
|
|
35
|
+
} from './schemas/index.js';
|
|
11
36
|
|
|
12
37
|
// Load environment variables
|
|
13
38
|
dotenv.config();
|
|
@@ -51,26 +76,37 @@ const server = new McpServer({
|
|
|
51
76
|
|
|
52
77
|
// --- Register Tools ---
|
|
53
78
|
|
|
79
|
+
// --- Schema Definitions for Tools ---
|
|
80
|
+
const getTagsSchema = tagQuerySchema.partial();
|
|
81
|
+
const getTagSchema = z.object({
|
|
82
|
+
id: ghostIdSchema.optional().describe('The ID of the tag to retrieve.'),
|
|
83
|
+
slug: z.string().optional().describe('The slug of the tag to retrieve.'),
|
|
84
|
+
include: z.string().optional().describe('Additional resources to include (e.g., "count.posts").'),
|
|
85
|
+
});
|
|
86
|
+
const updateTagInputSchema = updateTagSchema.extend({ id: ghostIdSchema });
|
|
87
|
+
const deleteTagSchema = z.object({ id: ghostIdSchema });
|
|
88
|
+
|
|
54
89
|
// Get Tags Tool
|
|
55
90
|
server.tool(
|
|
56
91
|
'ghost_get_tags',
|
|
57
92
|
'Retrieves a list of tags from Ghost CMS. Can optionally filter by tag name.',
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
getTagsSchema,
|
|
94
|
+
async (rawInput) => {
|
|
95
|
+
const validation = validateToolInput(getTagsSchema, rawInput, 'ghost_get_tags');
|
|
96
|
+
if (!validation.success) {
|
|
97
|
+
return validation.errorResponse;
|
|
98
|
+
}
|
|
99
|
+
const input = validation.data;
|
|
100
|
+
|
|
65
101
|
console.error(`Executing tool: ghost_get_tags`);
|
|
66
102
|
try {
|
|
67
103
|
await loadServices();
|
|
68
104
|
const tags = await ghostService.getTags();
|
|
69
105
|
let result = tags;
|
|
70
106
|
|
|
71
|
-
if (name) {
|
|
72
|
-
result = tags.filter((tag) => tag.name.toLowerCase() === name.toLowerCase());
|
|
73
|
-
console.error(`Filtered tags by name "${name}". Found ${result.length} match(es).`);
|
|
107
|
+
if (input.name) {
|
|
108
|
+
result = tags.filter((tag) => tag.name.toLowerCase() === input.name.toLowerCase());
|
|
109
|
+
console.error(`Filtered tags by name "${input.name}". Found ${result.length} match(es).`);
|
|
74
110
|
} else {
|
|
75
111
|
console.error(`Retrieved ${tags.length} tags from Ghost.`);
|
|
76
112
|
}
|
|
@@ -80,6 +116,13 @@ server.tool(
|
|
|
80
116
|
};
|
|
81
117
|
} catch (error) {
|
|
82
118
|
console.error(`Error in ghost_get_tags:`, error);
|
|
119
|
+
if (error.name === 'ZodError') {
|
|
120
|
+
const validationError = ValidationError.fromZod(error, 'Tags retrieval');
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
123
|
+
isError: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
83
126
|
return {
|
|
84
127
|
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
85
128
|
isError: true,
|
|
@@ -92,21 +135,18 @@ server.tool(
|
|
|
92
135
|
server.tool(
|
|
93
136
|
'ghost_create_tag',
|
|
94
137
|
'Creates a new tag in Ghost CMS.',
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
async ({ name, description, slug }) => {
|
|
106
|
-
console.error(`Executing tool: ghost_create_tag with name: ${name}`);
|
|
138
|
+
createTagSchema,
|
|
139
|
+
async (rawInput) => {
|
|
140
|
+
const validation = validateToolInput(createTagSchema, rawInput, 'ghost_create_tag');
|
|
141
|
+
if (!validation.success) {
|
|
142
|
+
return validation.errorResponse;
|
|
143
|
+
}
|
|
144
|
+
const input = validation.data;
|
|
145
|
+
|
|
146
|
+
console.error(`Executing tool: ghost_create_tag with name: ${input.name}`);
|
|
107
147
|
try {
|
|
108
148
|
await loadServices();
|
|
109
|
-
const createdTag = await ghostService.createTag(
|
|
149
|
+
const createdTag = await ghostService.createTag(input);
|
|
110
150
|
console.error(`Tag created successfully. Tag ID: ${createdTag.id}`);
|
|
111
151
|
|
|
112
152
|
return {
|
|
@@ -114,6 +154,13 @@ server.tool(
|
|
|
114
154
|
};
|
|
115
155
|
} catch (error) {
|
|
116
156
|
console.error(`Error in ghost_create_tag:`, error);
|
|
157
|
+
if (error.name === 'ZodError') {
|
|
158
|
+
const validationError = ValidationError.fromZod(error, 'Tag creation');
|
|
159
|
+
return {
|
|
160
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
161
|
+
isError: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
117
164
|
return {
|
|
118
165
|
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
119
166
|
isError: true,
|
|
@@ -126,15 +173,14 @@ server.tool(
|
|
|
126
173
|
server.tool(
|
|
127
174
|
'ghost_get_tag',
|
|
128
175
|
'Retrieves a single tag from Ghost CMS by ID or slug.',
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
async ({ id, slug, include }) => {
|
|
176
|
+
getTagSchema,
|
|
177
|
+
async (rawInput) => {
|
|
178
|
+
const validation = validateToolInput(getTagSchema, rawInput, 'ghost_get_tag');
|
|
179
|
+
if (!validation.success) {
|
|
180
|
+
return validation.errorResponse;
|
|
181
|
+
}
|
|
182
|
+
const { id, slug, include } = validation.data;
|
|
183
|
+
|
|
138
184
|
console.error(`Executing tool: ghost_get_tag`);
|
|
139
185
|
try {
|
|
140
186
|
if (!id && !slug) {
|
|
@@ -156,6 +202,13 @@ server.tool(
|
|
|
156
202
|
};
|
|
157
203
|
} catch (error) {
|
|
158
204
|
console.error(`Error in ghost_get_tag:`, error);
|
|
205
|
+
if (error.name === 'ZodError') {
|
|
206
|
+
const validationError = ValidationError.fromZod(error, 'Tag retrieval');
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
209
|
+
isError: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
159
212
|
return {
|
|
160
213
|
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
161
214
|
isError: true,
|
|
@@ -168,32 +221,24 @@ server.tool(
|
|
|
168
221
|
server.tool(
|
|
169
222
|
'ghost_update_tag',
|
|
170
223
|
'Updates an existing tag in Ghost CMS.',
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
async ({ id, name, slug, description, feature_image, meta_title, meta_description }) => {
|
|
181
|
-
console.error(`Executing tool: ghost_update_tag for ID: ${id}`);
|
|
224
|
+
updateTagInputSchema,
|
|
225
|
+
async (rawInput) => {
|
|
226
|
+
const validation = validateToolInput(updateTagInputSchema, rawInput, 'ghost_update_tag');
|
|
227
|
+
if (!validation.success) {
|
|
228
|
+
return validation.errorResponse;
|
|
229
|
+
}
|
|
230
|
+
const input = validation.data;
|
|
231
|
+
|
|
232
|
+
console.error(`Executing tool: ghost_update_tag for ID: ${input.id}`);
|
|
182
233
|
try {
|
|
183
|
-
if (!id) {
|
|
234
|
+
if (!input.id) {
|
|
184
235
|
throw new Error('Tag ID is required');
|
|
185
236
|
}
|
|
186
237
|
|
|
187
238
|
await loadServices();
|
|
188
239
|
|
|
189
|
-
// Build update data object with only provided fields
|
|
190
|
-
const updateData =
|
|
191
|
-
if (name !== undefined) updateData.name = name;
|
|
192
|
-
if (slug !== undefined) updateData.slug = slug;
|
|
193
|
-
if (description !== undefined) updateData.description = description;
|
|
194
|
-
if (feature_image !== undefined) updateData.feature_image = feature_image;
|
|
195
|
-
if (meta_title !== undefined) updateData.meta_title = meta_title;
|
|
196
|
-
if (meta_description !== undefined) updateData.meta_description = meta_description;
|
|
240
|
+
// Build update data object with only provided fields (exclude id from update data)
|
|
241
|
+
const { id, ...updateData } = input;
|
|
197
242
|
|
|
198
243
|
const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
|
|
199
244
|
const updatedTag = await ghostServiceImproved.updateTag(id, updateData);
|
|
@@ -204,6 +249,13 @@ server.tool(
|
|
|
204
249
|
};
|
|
205
250
|
} catch (error) {
|
|
206
251
|
console.error(`Error in ghost_update_tag:`, error);
|
|
252
|
+
if (error.name === 'ZodError') {
|
|
253
|
+
const validationError = ValidationError.fromZod(error, 'Tag update');
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
256
|
+
isError: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
207
259
|
return {
|
|
208
260
|
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
209
261
|
isError: true,
|
|
@@ -216,10 +268,14 @@ server.tool(
|
|
|
216
268
|
server.tool(
|
|
217
269
|
'ghost_delete_tag',
|
|
218
270
|
'Deletes a tag from Ghost CMS by ID. This operation is permanent.',
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
271
|
+
deleteTagSchema,
|
|
272
|
+
async (rawInput) => {
|
|
273
|
+
const validation = validateToolInput(deleteTagSchema, rawInput, 'ghost_delete_tag');
|
|
274
|
+
if (!validation.success) {
|
|
275
|
+
return validation.errorResponse;
|
|
276
|
+
}
|
|
277
|
+
const { id } = validation.data;
|
|
278
|
+
|
|
223
279
|
console.error(`Executing tool: ghost_delete_tag for ID: ${id}`);
|
|
224
280
|
try {
|
|
225
281
|
if (!id) {
|
|
@@ -237,6 +293,13 @@ server.tool(
|
|
|
237
293
|
};
|
|
238
294
|
} catch (error) {
|
|
239
295
|
console.error(`Error in ghost_delete_tag:`, error);
|
|
296
|
+
if (error.name === 'ZodError') {
|
|
297
|
+
const validationError = ValidationError.fromZod(error, 'Tag deletion');
|
|
298
|
+
return {
|
|
299
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
300
|
+
isError: true,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
240
303
|
return {
|
|
241
304
|
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
242
305
|
isError: true,
|
|
@@ -245,20 +308,27 @@ server.tool(
|
|
|
245
308
|
}
|
|
246
309
|
);
|
|
247
310
|
|
|
311
|
+
// --- Image Schema ---
|
|
312
|
+
const uploadImageSchema = z.object({
|
|
313
|
+
imageUrl: z.string().describe('The publicly accessible URL of the image to upload.'),
|
|
314
|
+
alt: z
|
|
315
|
+
.string()
|
|
316
|
+
.optional()
|
|
317
|
+
.describe('Alt text for the image. If omitted, a default will be generated from the filename.'),
|
|
318
|
+
});
|
|
319
|
+
|
|
248
320
|
// Upload Image Tool
|
|
249
321
|
server.tool(
|
|
250
322
|
'ghost_upload_image',
|
|
251
323
|
'Downloads an image from a URL, processes it, uploads it to Ghost CMS, and returns the final Ghost image URL and alt text.',
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
},
|
|
261
|
-
async ({ imageUrl, alt }) => {
|
|
324
|
+
uploadImageSchema,
|
|
325
|
+
async (rawInput) => {
|
|
326
|
+
const validation = validateToolInput(uploadImageSchema, rawInput, 'ghost_upload_image');
|
|
327
|
+
if (!validation.success) {
|
|
328
|
+
return validation.errorResponse;
|
|
329
|
+
}
|
|
330
|
+
const { imageUrl, alt } = validation.data;
|
|
331
|
+
|
|
262
332
|
console.error(`Executing tool: ghost_upload_image for URL: ${imageUrl}`);
|
|
263
333
|
let downloadedPath = null;
|
|
264
334
|
let processedPath = null;
|
|
@@ -288,10 +358,16 @@ server.tool(
|
|
|
288
358
|
writer.on('finish', resolve);
|
|
289
359
|
writer.on('error', reject);
|
|
290
360
|
});
|
|
361
|
+
// Track temp file for cleanup on process exit
|
|
362
|
+
trackTempFile(downloadedPath);
|
|
291
363
|
console.error(`Downloaded image to temporary path: ${downloadedPath}`);
|
|
292
364
|
|
|
293
365
|
// 3. Process the image
|
|
294
366
|
processedPath = await imageProcessingService.processImage(downloadedPath, tempDir);
|
|
367
|
+
// Track processed file for cleanup on process exit
|
|
368
|
+
if (processedPath !== downloadedPath) {
|
|
369
|
+
trackTempFile(processedPath);
|
|
370
|
+
}
|
|
295
371
|
console.error(`Processed image path: ${processedPath}`);
|
|
296
372
|
|
|
297
373
|
// 4. Determine Alt Text
|
|
@@ -319,63 +395,56 @@ server.tool(
|
|
|
319
395
|
isError: true,
|
|
320
396
|
};
|
|
321
397
|
} finally {
|
|
322
|
-
// Cleanup temporary files
|
|
323
|
-
|
|
324
|
-
fs.unlink(downloadedPath, (err) => {
|
|
325
|
-
if (err) console.error('Error deleting temporary downloaded file:', downloadedPath, err);
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
if (processedPath && processedPath !== downloadedPath) {
|
|
329
|
-
fs.unlink(processedPath, (err) => {
|
|
330
|
-
if (err) console.error('Error deleting temporary processed file:', processedPath, err);
|
|
331
|
-
});
|
|
332
|
-
}
|
|
398
|
+
// Cleanup temporary files with proper async/await
|
|
399
|
+
await cleanupTempFiles([downloadedPath, processedPath], console);
|
|
333
400
|
}
|
|
334
401
|
}
|
|
335
402
|
);
|
|
336
403
|
|
|
404
|
+
// --- Post Schema Definitions ---
|
|
405
|
+
const getPostsSchema = postQuerySchema.extend({
|
|
406
|
+
status: z
|
|
407
|
+
.enum(['published', 'draft', 'scheduled', 'all'])
|
|
408
|
+
.optional()
|
|
409
|
+
.describe('Filter posts by status. Options: published, draft, scheduled, all.'),
|
|
410
|
+
});
|
|
411
|
+
const getPostSchema = z.object({
|
|
412
|
+
id: ghostIdSchema.optional().describe('The ID of the post to retrieve.'),
|
|
413
|
+
slug: z.string().optional().describe('The slug of the post to retrieve.'),
|
|
414
|
+
include: z
|
|
415
|
+
.string()
|
|
416
|
+
.optional()
|
|
417
|
+
.describe('Comma-separated list of relations to include (e.g., "tags,authors").'),
|
|
418
|
+
});
|
|
419
|
+
const searchPostsSchema = z.object({
|
|
420
|
+
query: z.string().min(1).describe('Search query to find in post titles.'),
|
|
421
|
+
status: z
|
|
422
|
+
.enum(['published', 'draft', 'scheduled', 'all'])
|
|
423
|
+
.optional()
|
|
424
|
+
.describe('Filter by post status. Default searches all statuses.'),
|
|
425
|
+
limit: z
|
|
426
|
+
.number()
|
|
427
|
+
.int()
|
|
428
|
+
.min(1)
|
|
429
|
+
.max(50)
|
|
430
|
+
.optional()
|
|
431
|
+
.describe('Maximum number of results (1-50). Default is 15.'),
|
|
432
|
+
});
|
|
433
|
+
const updatePostInputSchema = updatePostSchema.extend({ id: ghostIdSchema });
|
|
434
|
+
const deletePostSchema = z.object({ id: ghostIdSchema });
|
|
435
|
+
|
|
337
436
|
// Create Post Tool
|
|
338
437
|
server.tool(
|
|
339
438
|
'ghost_create_post',
|
|
340
439
|
'Creates a new post in Ghost CMS.',
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
.array(z.string())
|
|
350
|
-
.optional()
|
|
351
|
-
.describe(
|
|
352
|
-
"List of tag names to associate with the post. Tags will be created if they don't exist."
|
|
353
|
-
),
|
|
354
|
-
published_at: z
|
|
355
|
-
.string()
|
|
356
|
-
.optional()
|
|
357
|
-
.describe("ISO 8601 date/time to publish the post. Required if status is 'scheduled'."),
|
|
358
|
-
custom_excerpt: z.string().optional().describe('A custom short summary for the post.'),
|
|
359
|
-
feature_image: z
|
|
360
|
-
.string()
|
|
361
|
-
.optional()
|
|
362
|
-
.describe(
|
|
363
|
-
'URL of the image (e.g., from ghost_upload_image tool) to use as the featured image.'
|
|
364
|
-
),
|
|
365
|
-
feature_image_alt: z.string().optional().describe('Alt text for the featured image.'),
|
|
366
|
-
feature_image_caption: z.string().optional().describe('Caption for the featured image.'),
|
|
367
|
-
meta_title: z
|
|
368
|
-
.string()
|
|
369
|
-
.optional()
|
|
370
|
-
.describe('Custom title for SEO (max 300 chars). Defaults to post title if omitted.'),
|
|
371
|
-
meta_description: z
|
|
372
|
-
.string()
|
|
373
|
-
.optional()
|
|
374
|
-
.describe(
|
|
375
|
-
'Custom description for SEO (max 500 chars). Defaults to excerpt or generated summary if omitted.'
|
|
376
|
-
),
|
|
377
|
-
},
|
|
378
|
-
async (input) => {
|
|
440
|
+
createPostSchema,
|
|
441
|
+
async (rawInput) => {
|
|
442
|
+
const validation = validateToolInput(createPostSchema, rawInput, 'ghost_create_post');
|
|
443
|
+
if (!validation.success) {
|
|
444
|
+
return validation.errorResponse;
|
|
445
|
+
}
|
|
446
|
+
const input = validation.data;
|
|
447
|
+
|
|
379
448
|
console.error(`Executing tool: ghost_create_post with title: ${input.title}`);
|
|
380
449
|
try {
|
|
381
450
|
await loadServices();
|
|
@@ -387,6 +456,13 @@ server.tool(
|
|
|
387
456
|
};
|
|
388
457
|
} catch (error) {
|
|
389
458
|
console.error(`Error in ghost_create_post:`, error);
|
|
459
|
+
if (error.name === 'ZodError') {
|
|
460
|
+
const validationError = ValidationError.fromZod(error, 'Post creation');
|
|
461
|
+
return {
|
|
462
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
463
|
+
isError: true,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
390
466
|
return {
|
|
391
467
|
content: [{ type: 'text', text: `Error creating post: ${error.message}` }],
|
|
392
468
|
isError: true,
|
|
@@ -399,32 +475,14 @@ server.tool(
|
|
|
399
475
|
server.tool(
|
|
400
476
|
'ghost_get_posts',
|
|
401
477
|
'Retrieves a list of posts from Ghost CMS with pagination, filtering, and sorting options.',
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
status: z
|
|
411
|
-
.enum(['published', 'draft', 'scheduled', 'all'])
|
|
412
|
-
.optional()
|
|
413
|
-
.describe('Filter posts by status. Options: published, draft, scheduled, all.'),
|
|
414
|
-
include: z
|
|
415
|
-
.string()
|
|
416
|
-
.optional()
|
|
417
|
-
.describe('Comma-separated list of relations to include (e.g., "tags,authors").'),
|
|
418
|
-
filter: z
|
|
419
|
-
.string()
|
|
420
|
-
.optional()
|
|
421
|
-
.describe('Ghost NQL filter string for advanced filtering (e.g., "featured:true").'),
|
|
422
|
-
order: z
|
|
423
|
-
.string()
|
|
424
|
-
.optional()
|
|
425
|
-
.describe('Sort order for results (e.g., "published_at DESC", "title ASC").'),
|
|
426
|
-
},
|
|
427
|
-
async (input) => {
|
|
478
|
+
getPostsSchema,
|
|
479
|
+
async (rawInput) => {
|
|
480
|
+
const validation = validateToolInput(getPostsSchema, rawInput, 'ghost_get_posts');
|
|
481
|
+
if (!validation.success) {
|
|
482
|
+
return validation.errorResponse;
|
|
483
|
+
}
|
|
484
|
+
const input = validation.data;
|
|
485
|
+
|
|
428
486
|
console.error(`Executing tool: ghost_get_posts`);
|
|
429
487
|
try {
|
|
430
488
|
await loadServices();
|
|
@@ -446,6 +504,13 @@ server.tool(
|
|
|
446
504
|
};
|
|
447
505
|
} catch (error) {
|
|
448
506
|
console.error(`Error in ghost_get_posts:`, error);
|
|
507
|
+
if (error.name === 'ZodError') {
|
|
508
|
+
const validationError = ValidationError.fromZod(error, 'Posts retrieval');
|
|
509
|
+
return {
|
|
510
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
511
|
+
isError: true,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
449
514
|
return {
|
|
450
515
|
content: [{ type: 'text', text: `Error retrieving posts: ${error.message}` }],
|
|
451
516
|
isError: true,
|
|
@@ -458,15 +523,14 @@ server.tool(
|
|
|
458
523
|
server.tool(
|
|
459
524
|
'ghost_get_post',
|
|
460
525
|
'Retrieves a single post from Ghost CMS by ID or slug.',
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
async (input) => {
|
|
526
|
+
getPostSchema,
|
|
527
|
+
async (rawInput) => {
|
|
528
|
+
const validation = validateToolInput(getPostSchema, rawInput, 'ghost_get_post');
|
|
529
|
+
if (!validation.success) {
|
|
530
|
+
return validation.errorResponse;
|
|
531
|
+
}
|
|
532
|
+
const input = validation.data;
|
|
533
|
+
|
|
470
534
|
console.error(`Executing tool: ghost_get_post`);
|
|
471
535
|
try {
|
|
472
536
|
// Validate that at least one of id or slug is provided
|
|
@@ -491,6 +555,13 @@ server.tool(
|
|
|
491
555
|
};
|
|
492
556
|
} catch (error) {
|
|
493
557
|
console.error(`Error in ghost_get_post:`, error);
|
|
558
|
+
if (error.name === 'ZodError') {
|
|
559
|
+
const validationError = ValidationError.fromZod(error, 'Post retrieval');
|
|
560
|
+
return {
|
|
561
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
562
|
+
isError: true,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
494
565
|
return {
|
|
495
566
|
content: [{ type: 'text', text: `Error retrieving post: ${error.message}` }],
|
|
496
567
|
isError: true,
|
|
@@ -503,20 +574,14 @@ server.tool(
|
|
|
503
574
|
server.tool(
|
|
504
575
|
'ghost_search_posts',
|
|
505
576
|
'Search for posts in Ghost CMS by query string with optional status filtering.',
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
.
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
.min(1)
|
|
515
|
-
.max(50)
|
|
516
|
-
.optional()
|
|
517
|
-
.describe('Maximum number of results (1-50). Default is 15.'),
|
|
518
|
-
},
|
|
519
|
-
async (input) => {
|
|
577
|
+
searchPostsSchema,
|
|
578
|
+
async (rawInput) => {
|
|
579
|
+
const validation = validateToolInput(searchPostsSchema, rawInput, 'ghost_search_posts');
|
|
580
|
+
if (!validation.success) {
|
|
581
|
+
return validation.errorResponse;
|
|
582
|
+
}
|
|
583
|
+
const input = validation.data;
|
|
584
|
+
|
|
520
585
|
console.error(`Executing tool: ghost_search_posts with query: ${input.query}`);
|
|
521
586
|
try {
|
|
522
587
|
await loadServices();
|
|
@@ -536,6 +601,13 @@ server.tool(
|
|
|
536
601
|
};
|
|
537
602
|
} catch (error) {
|
|
538
603
|
console.error(`Error in ghost_search_posts:`, error);
|
|
604
|
+
if (error.name === 'ZodError') {
|
|
605
|
+
const validationError = ValidationError.fromZod(error, 'Post search');
|
|
606
|
+
return {
|
|
607
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
608
|
+
isError: true,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
539
611
|
return {
|
|
540
612
|
content: [{ type: 'text', text: `Error searching posts: ${error.message}` }],
|
|
541
613
|
isError: true,
|
|
@@ -548,35 +620,14 @@ server.tool(
|
|
|
548
620
|
server.tool(
|
|
549
621
|
'ghost_update_post',
|
|
550
622
|
'Updates an existing post in Ghost CMS. Can update title, content, status, tags, images, and SEO fields.',
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
tags: z
|
|
560
|
-
.array(z.string())
|
|
561
|
-
.optional()
|
|
562
|
-
.describe('New list of tag names to associate with the post.'),
|
|
563
|
-
feature_image: z.string().optional().describe('New featured image URL.'),
|
|
564
|
-
feature_image_alt: z.string().optional().describe('New alt text for the featured image.'),
|
|
565
|
-
feature_image_caption: z.string().optional().describe('New caption for the featured image.'),
|
|
566
|
-
meta_title: z.string().optional().describe('New custom title for SEO (max 300 chars).'),
|
|
567
|
-
meta_description: z
|
|
568
|
-
.string()
|
|
569
|
-
.optional()
|
|
570
|
-
.describe('New custom description for SEO (max 500 chars).'),
|
|
571
|
-
published_at: z
|
|
572
|
-
.string()
|
|
573
|
-
.optional()
|
|
574
|
-
.describe(
|
|
575
|
-
"New publication date/time in ISO 8601 format. Required if changing status to 'scheduled'."
|
|
576
|
-
),
|
|
577
|
-
custom_excerpt: z.string().optional().describe('New custom short summary for the post.'),
|
|
578
|
-
},
|
|
579
|
-
async (input) => {
|
|
623
|
+
updatePostInputSchema,
|
|
624
|
+
async (rawInput) => {
|
|
625
|
+
const validation = validateToolInput(updatePostInputSchema, rawInput, 'ghost_update_post');
|
|
626
|
+
if (!validation.success) {
|
|
627
|
+
return validation.errorResponse;
|
|
628
|
+
}
|
|
629
|
+
const input = validation.data;
|
|
630
|
+
|
|
580
631
|
console.error(`Executing tool: ghost_update_post for post ID: ${input.id}`);
|
|
581
632
|
try {
|
|
582
633
|
await loadServices();
|
|
@@ -594,6 +645,13 @@ server.tool(
|
|
|
594
645
|
};
|
|
595
646
|
} catch (error) {
|
|
596
647
|
console.error(`Error in ghost_update_post:`, error);
|
|
648
|
+
if (error.name === 'ZodError') {
|
|
649
|
+
const validationError = ValidationError.fromZod(error, 'Post update');
|
|
650
|
+
return {
|
|
651
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
652
|
+
isError: true,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
597
655
|
return {
|
|
598
656
|
content: [{ type: 'text', text: `Error updating post: ${error.message}` }],
|
|
599
657
|
isError: true,
|
|
@@ -606,10 +664,14 @@ server.tool(
|
|
|
606
664
|
server.tool(
|
|
607
665
|
'ghost_delete_post',
|
|
608
666
|
'Deletes a post from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
667
|
+
deletePostSchema,
|
|
668
|
+
async (rawInput) => {
|
|
669
|
+
const validation = validateToolInput(deletePostSchema, rawInput, 'ghost_delete_post');
|
|
670
|
+
if (!validation.success) {
|
|
671
|
+
return validation.errorResponse;
|
|
672
|
+
}
|
|
673
|
+
const { id } = validation.data;
|
|
674
|
+
|
|
613
675
|
console.error(`Executing tool: ghost_delete_post for post ID: ${id}`);
|
|
614
676
|
try {
|
|
615
677
|
await loadServices();
|
|
@@ -624,6 +686,13 @@ server.tool(
|
|
|
624
686
|
};
|
|
625
687
|
} catch (error) {
|
|
626
688
|
console.error(`Error in ghost_delete_post:`, error);
|
|
689
|
+
if (error.name === 'ZodError') {
|
|
690
|
+
const validationError = ValidationError.fromZod(error, 'Post deletion');
|
|
691
|
+
return {
|
|
692
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
693
|
+
isError: true,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
627
696
|
return {
|
|
628
697
|
content: [{ type: 'text', text: `Error deleting post: ${error.message}` }],
|
|
629
698
|
isError: true,
|
|
@@ -637,30 +706,54 @@ server.tool(
|
|
|
637
706
|
// Pages are similar to posts but do NOT support tags
|
|
638
707
|
// =============================================================================
|
|
639
708
|
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
limit: z
|
|
646
|
-
.number()
|
|
647
|
-
.min(1)
|
|
648
|
-
.max(100)
|
|
649
|
-
.optional()
|
|
650
|
-
.describe('Number of pages to retrieve (1-100). Default is 15.'),
|
|
651
|
-
page: z.number().min(1).optional().describe('Page number for pagination. Default is 1.'),
|
|
652
|
-
status: z
|
|
653
|
-
.enum(['published', 'draft', 'scheduled', 'all'])
|
|
654
|
-
.optional()
|
|
655
|
-
.describe('Filter pages by status.'),
|
|
709
|
+
// --- Page Schema Definitions ---
|
|
710
|
+
const getPageSchema = z
|
|
711
|
+
.object({
|
|
712
|
+
id: ghostIdSchema.optional().describe('The ID of the page to retrieve.'),
|
|
713
|
+
slug: z.string().optional().describe('The slug of the page to retrieve.'),
|
|
656
714
|
include: z
|
|
657
715
|
.string()
|
|
658
716
|
.optional()
|
|
659
717
|
.describe('Comma-separated list of relations to include (e.g., "authors").'),
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
718
|
+
})
|
|
719
|
+
.refine((data) => data.id || data.slug, {
|
|
720
|
+
message: 'Either id or slug is required to retrieve a page',
|
|
721
|
+
});
|
|
722
|
+
const updatePageInputSchema = z
|
|
723
|
+
.object({ id: ghostIdSchema.describe('The ID of the page to update.') })
|
|
724
|
+
.merge(updatePageSchema);
|
|
725
|
+
const deletePageSchema = z.object({ id: ghostIdSchema.describe('The ID of the page to delete.') });
|
|
726
|
+
const searchPagesSchema = z.object({
|
|
727
|
+
query: z
|
|
728
|
+
.string()
|
|
729
|
+
.min(1, 'Search query cannot be empty')
|
|
730
|
+
.describe('Search query to find in page titles.'),
|
|
731
|
+
status: z
|
|
732
|
+
.enum(['published', 'draft', 'scheduled', 'all'])
|
|
733
|
+
.optional()
|
|
734
|
+
.describe('Filter by page status. Default searches all statuses.'),
|
|
735
|
+
limit: z
|
|
736
|
+
.number()
|
|
737
|
+
.int()
|
|
738
|
+
.min(1)
|
|
739
|
+
.max(50)
|
|
740
|
+
.default(15)
|
|
741
|
+
.optional()
|
|
742
|
+
.describe('Maximum number of results (1-50). Default is 15.'),
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// Get Pages Tool
|
|
746
|
+
server.tool(
|
|
747
|
+
'ghost_get_pages',
|
|
748
|
+
'Retrieves a list of pages from Ghost CMS with pagination, filtering, and sorting options.',
|
|
749
|
+
pageQuerySchema,
|
|
750
|
+
async (rawInput) => {
|
|
751
|
+
const validation = validateToolInput(pageQuerySchema, rawInput, 'ghost_get_pages');
|
|
752
|
+
if (!validation.success) {
|
|
753
|
+
return validation.errorResponse;
|
|
754
|
+
}
|
|
755
|
+
const input = validation.data;
|
|
756
|
+
|
|
664
757
|
console.error(`Executing tool: ghost_get_pages`);
|
|
665
758
|
try {
|
|
666
759
|
await loadServices();
|
|
@@ -668,9 +761,10 @@ server.tool(
|
|
|
668
761
|
const options = {};
|
|
669
762
|
if (input.limit !== undefined) options.limit = input.limit;
|
|
670
763
|
if (input.page !== undefined) options.page = input.page;
|
|
671
|
-
if (input.status !== undefined) options.status = input.status;
|
|
672
|
-
if (input.include !== undefined) options.include = input.include;
|
|
673
764
|
if (input.filter !== undefined) options.filter = input.filter;
|
|
765
|
+
if (input.include !== undefined) options.include = input.include;
|
|
766
|
+
if (input.fields !== undefined) options.fields = input.fields;
|
|
767
|
+
if (input.formats !== undefined) options.formats = input.formats;
|
|
674
768
|
if (input.order !== undefined) options.order = input.order;
|
|
675
769
|
|
|
676
770
|
const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
|
|
@@ -682,6 +776,13 @@ server.tool(
|
|
|
682
776
|
};
|
|
683
777
|
} catch (error) {
|
|
684
778
|
console.error(`Error in ghost_get_pages:`, error);
|
|
779
|
+
if (error.name === 'ZodError') {
|
|
780
|
+
const validationError = ValidationError.fromZod(error, 'Page query');
|
|
781
|
+
return {
|
|
782
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
783
|
+
isError: true,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
685
786
|
return {
|
|
686
787
|
content: [{ type: 'text', text: `Error retrieving pages: ${error.message}` }],
|
|
687
788
|
isError: true,
|
|
@@ -694,21 +795,16 @@ server.tool(
|
|
|
694
795
|
server.tool(
|
|
695
796
|
'ghost_get_page',
|
|
696
797
|
'Retrieves a single page from Ghost CMS by ID or slug.',
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
async (input) => {
|
|
798
|
+
getPageSchema,
|
|
799
|
+
async (rawInput) => {
|
|
800
|
+
const validation = validateToolInput(getPageSchema, rawInput, 'ghost_get_page');
|
|
801
|
+
if (!validation.success) {
|
|
802
|
+
return validation.errorResponse;
|
|
803
|
+
}
|
|
804
|
+
const input = validation.data;
|
|
805
|
+
|
|
706
806
|
console.error(`Executing tool: ghost_get_page`);
|
|
707
807
|
try {
|
|
708
|
-
if (!input.id && !input.slug) {
|
|
709
|
-
throw new Error('Either id or slug is required to retrieve a page');
|
|
710
|
-
}
|
|
711
|
-
|
|
712
808
|
await loadServices();
|
|
713
809
|
|
|
714
810
|
const options = {};
|
|
@@ -725,6 +821,13 @@ server.tool(
|
|
|
725
821
|
};
|
|
726
822
|
} catch (error) {
|
|
727
823
|
console.error(`Error in ghost_get_page:`, error);
|
|
824
|
+
if (error.name === 'ZodError') {
|
|
825
|
+
const validationError = ValidationError.fromZod(error, 'Get page');
|
|
826
|
+
return {
|
|
827
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
828
|
+
isError: true,
|
|
829
|
+
};
|
|
830
|
+
}
|
|
728
831
|
return {
|
|
729
832
|
content: [{ type: 'text', text: `Error retrieving page: ${error.message}` }],
|
|
730
833
|
isError: true,
|
|
@@ -736,35 +839,15 @@ server.tool(
|
|
|
736
839
|
// Create Page Tool
|
|
737
840
|
server.tool(
|
|
738
841
|
'ghost_create_page',
|
|
739
|
-
'Creates a new page in Ghost CMS. Note: Pages do NOT
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
.
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
published_at: z
|
|
749
|
-
.string()
|
|
750
|
-
.optional()
|
|
751
|
-
.describe("ISO 8601 date/time to publish the page. Required if status is 'scheduled'."),
|
|
752
|
-
custom_excerpt: z.string().optional().describe('A custom short summary for the page.'),
|
|
753
|
-
feature_image: z.string().optional().describe('URL of the image to use as the featured image.'),
|
|
754
|
-
feature_image_alt: z.string().optional().describe('Alt text for the featured image.'),
|
|
755
|
-
feature_image_caption: z.string().optional().describe('Caption for the featured image.'),
|
|
756
|
-
meta_title: z
|
|
757
|
-
.string()
|
|
758
|
-
.optional()
|
|
759
|
-
.describe('Custom title for SEO (max 70 chars). Defaults to page title if omitted.'),
|
|
760
|
-
meta_description: z
|
|
761
|
-
.string()
|
|
762
|
-
.optional()
|
|
763
|
-
.describe(
|
|
764
|
-
'Custom description for SEO (max 160 chars). Defaults to excerpt or generated summary if omitted.'
|
|
765
|
-
),
|
|
766
|
-
},
|
|
767
|
-
async (input) => {
|
|
842
|
+
'Creates a new page in Ghost CMS. Note: Pages do NOT typically use tags (unlike posts).',
|
|
843
|
+
createPageSchema,
|
|
844
|
+
async (rawInput) => {
|
|
845
|
+
const validation = validateToolInput(createPageSchema, rawInput, 'ghost_create_page');
|
|
846
|
+
if (!validation.success) {
|
|
847
|
+
return validation.errorResponse;
|
|
848
|
+
}
|
|
849
|
+
const input = validation.data;
|
|
850
|
+
|
|
768
851
|
console.error(`Executing tool: ghost_create_page with title: ${input.title}`);
|
|
769
852
|
try {
|
|
770
853
|
await loadServices();
|
|
@@ -778,6 +861,13 @@ server.tool(
|
|
|
778
861
|
};
|
|
779
862
|
} catch (error) {
|
|
780
863
|
console.error(`Error in ghost_create_page:`, error);
|
|
864
|
+
if (error.name === 'ZodError') {
|
|
865
|
+
const validationError = ValidationError.fromZod(error, 'Page creation');
|
|
866
|
+
return {
|
|
867
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
868
|
+
isError: true,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
781
871
|
return {
|
|
782
872
|
content: [{ type: 'text', text: `Error creating page: ${error.message}` }],
|
|
783
873
|
isError: true,
|
|
@@ -789,25 +879,15 @@ server.tool(
|
|
|
789
879
|
// Update Page Tool
|
|
790
880
|
server.tool(
|
|
791
881
|
'ghost_update_page',
|
|
792
|
-
'Updates an existing page in Ghost CMS. Can update title, content, status, images, and SEO fields.
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
// NO tags parameter - pages don't support tags
|
|
802
|
-
feature_image: z.string().optional().describe('New featured image URL.'),
|
|
803
|
-
feature_image_alt: z.string().optional().describe('New alt text for the featured image.'),
|
|
804
|
-
feature_image_caption: z.string().optional().describe('New caption for the featured image.'),
|
|
805
|
-
meta_title: z.string().optional().describe('New custom title for SEO.'),
|
|
806
|
-
meta_description: z.string().optional().describe('New custom description for SEO.'),
|
|
807
|
-
published_at: z.string().optional().describe('New publication date/time in ISO 8601 format.'),
|
|
808
|
-
custom_excerpt: z.string().optional().describe('New custom short summary for the page.'),
|
|
809
|
-
},
|
|
810
|
-
async (input) => {
|
|
882
|
+
'Updates an existing page in Ghost CMS. Can update title, content, status, images, and SEO fields.',
|
|
883
|
+
updatePageInputSchema,
|
|
884
|
+
async (rawInput) => {
|
|
885
|
+
const validation = validateToolInput(updatePageInputSchema, rawInput, 'ghost_update_page');
|
|
886
|
+
if (!validation.success) {
|
|
887
|
+
return validation.errorResponse;
|
|
888
|
+
}
|
|
889
|
+
const input = validation.data;
|
|
890
|
+
|
|
811
891
|
console.error(`Executing tool: ghost_update_page for page ID: ${input.id}`);
|
|
812
892
|
try {
|
|
813
893
|
await loadServices();
|
|
@@ -823,6 +903,13 @@ server.tool(
|
|
|
823
903
|
};
|
|
824
904
|
} catch (error) {
|
|
825
905
|
console.error(`Error in ghost_update_page:`, error);
|
|
906
|
+
if (error.name === 'ZodError') {
|
|
907
|
+
const validationError = ValidationError.fromZod(error, 'Page update');
|
|
908
|
+
return {
|
|
909
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
910
|
+
isError: true,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
826
913
|
return {
|
|
827
914
|
content: [{ type: 'text', text: `Error updating page: ${error.message}` }],
|
|
828
915
|
isError: true,
|
|
@@ -835,10 +922,14 @@ server.tool(
|
|
|
835
922
|
server.tool(
|
|
836
923
|
'ghost_delete_page',
|
|
837
924
|
'Deletes a page from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
925
|
+
deletePageSchema,
|
|
926
|
+
async (rawInput) => {
|
|
927
|
+
const validation = validateToolInput(deletePageSchema, rawInput, 'ghost_delete_page');
|
|
928
|
+
if (!validation.success) {
|
|
929
|
+
return validation.errorResponse;
|
|
930
|
+
}
|
|
931
|
+
const { id } = validation.data;
|
|
932
|
+
|
|
842
933
|
console.error(`Executing tool: ghost_delete_page for page ID: ${id}`);
|
|
843
934
|
try {
|
|
844
935
|
await loadServices();
|
|
@@ -852,6 +943,13 @@ server.tool(
|
|
|
852
943
|
};
|
|
853
944
|
} catch (error) {
|
|
854
945
|
console.error(`Error in ghost_delete_page:`, error);
|
|
946
|
+
if (error.name === 'ZodError') {
|
|
947
|
+
const validationError = ValidationError.fromZod(error, 'Page deletion');
|
|
948
|
+
return {
|
|
949
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
950
|
+
isError: true,
|
|
951
|
+
};
|
|
952
|
+
}
|
|
855
953
|
return {
|
|
856
954
|
content: [{ type: 'text', text: `Error deleting page: ${error.message}` }],
|
|
857
955
|
isError: true,
|
|
@@ -864,20 +962,14 @@ server.tool(
|
|
|
864
962
|
server.tool(
|
|
865
963
|
'ghost_search_pages',
|
|
866
964
|
'Search for pages in Ghost CMS by query string with optional status filtering.',
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
.min(1)
|
|
876
|
-
.max(50)
|
|
877
|
-
.optional()
|
|
878
|
-
.describe('Maximum number of results (1-50). Default is 15.'),
|
|
879
|
-
},
|
|
880
|
-
async (input) => {
|
|
965
|
+
searchPagesSchema,
|
|
966
|
+
async (rawInput) => {
|
|
967
|
+
const validation = validateToolInput(searchPagesSchema, rawInput, 'ghost_search_pages');
|
|
968
|
+
if (!validation.success) {
|
|
969
|
+
return validation.errorResponse;
|
|
970
|
+
}
|
|
971
|
+
const input = validation.data;
|
|
972
|
+
|
|
881
973
|
console.error(`Executing tool: ghost_search_pages with query: ${input.query}`);
|
|
882
974
|
try {
|
|
883
975
|
await loadServices();
|
|
@@ -895,6 +987,13 @@ server.tool(
|
|
|
895
987
|
};
|
|
896
988
|
} catch (error) {
|
|
897
989
|
console.error(`Error in ghost_search_pages:`, error);
|
|
990
|
+
if (error.name === 'ZodError') {
|
|
991
|
+
const validationError = ValidationError.fromZod(error, 'Page search');
|
|
992
|
+
return {
|
|
993
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
994
|
+
isError: true,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
898
997
|
return {
|
|
899
998
|
content: [{ type: 'text', text: `Error searching pages: ${error.message}` }],
|
|
900
999
|
isError: true,
|
|
@@ -908,25 +1007,41 @@ server.tool(
|
|
|
908
1007
|
// Member management for Ghost CMS subscribers
|
|
909
1008
|
// =============================================================================
|
|
910
1009
|
|
|
1010
|
+
// --- Member Schema Definitions ---
|
|
1011
|
+
const updateMemberInputSchema = z.object({ id: ghostIdSchema }).merge(updateMemberSchema);
|
|
1012
|
+
const deleteMemberSchema = z.object({ id: ghostIdSchema });
|
|
1013
|
+
const getMembersSchema = memberQuerySchema.omit({ search: true });
|
|
1014
|
+
const getMemberSchema = z
|
|
1015
|
+
.object({
|
|
1016
|
+
id: ghostIdSchema.optional().describe('The ID of the member to retrieve.'),
|
|
1017
|
+
email: emailSchema.optional().describe('The email of the member to retrieve.'),
|
|
1018
|
+
})
|
|
1019
|
+
.refine((data) => data.id || data.email, {
|
|
1020
|
+
message: 'Either id or email must be provided',
|
|
1021
|
+
});
|
|
1022
|
+
const searchMembersSchema = z.object({
|
|
1023
|
+
query: z.string().min(1).describe('Search query to match against member name or email.'),
|
|
1024
|
+
limit: z
|
|
1025
|
+
.number()
|
|
1026
|
+
.int()
|
|
1027
|
+
.min(1)
|
|
1028
|
+
.max(50)
|
|
1029
|
+
.optional()
|
|
1030
|
+
.describe('Maximum number of results to return (1-50). Default is 15.'),
|
|
1031
|
+
});
|
|
1032
|
+
|
|
911
1033
|
// Create Member Tool
|
|
912
1034
|
server.tool(
|
|
913
1035
|
'ghost_create_member',
|
|
914
1036
|
'Creates a new member (subscriber) in Ghost CMS.',
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
.describe('List of newsletter objects with id field to subscribe the member to.'),
|
|
924
|
-
subscribed: z
|
|
925
|
-
.boolean()
|
|
926
|
-
.optional()
|
|
927
|
-
.describe('Whether the member is subscribed to emails. Defaults to true.'),
|
|
928
|
-
},
|
|
929
|
-
async (input) => {
|
|
1037
|
+
createMemberSchema,
|
|
1038
|
+
async (rawInput) => {
|
|
1039
|
+
const validation = validateToolInput(createMemberSchema, rawInput, 'ghost_create_member');
|
|
1040
|
+
if (!validation.success) {
|
|
1041
|
+
return validation.errorResponse;
|
|
1042
|
+
}
|
|
1043
|
+
const input = validation.data;
|
|
1044
|
+
|
|
930
1045
|
console.error(`Executing tool: ghost_create_member with email: ${input.email}`);
|
|
931
1046
|
try {
|
|
932
1047
|
await loadServices();
|
|
@@ -940,6 +1055,13 @@ server.tool(
|
|
|
940
1055
|
};
|
|
941
1056
|
} catch (error) {
|
|
942
1057
|
console.error(`Error in ghost_create_member:`, error);
|
|
1058
|
+
if (error.name === 'ZodError') {
|
|
1059
|
+
const validationError = ValidationError.fromZod(error, 'Member creation');
|
|
1060
|
+
return {
|
|
1061
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1062
|
+
isError: true,
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
943
1065
|
return {
|
|
944
1066
|
content: [{ type: 'text', text: `Error creating member: ${error.message}` }],
|
|
945
1067
|
isError: true,
|
|
@@ -952,21 +1074,14 @@ server.tool(
|
|
|
952
1074
|
server.tool(
|
|
953
1075
|
'ghost_update_member',
|
|
954
1076
|
'Updates an existing member in Ghost CMS. All fields except id are optional.',
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
.describe('New list of label names to assign to the member.'),
|
|
964
|
-
newsletters: z
|
|
965
|
-
.array(z.object({ id: z.string() }))
|
|
966
|
-
.optional()
|
|
967
|
-
.describe('New list of newsletter objects with id field to subscribe the member to.'),
|
|
968
|
-
},
|
|
969
|
-
async (input) => {
|
|
1077
|
+
updateMemberInputSchema,
|
|
1078
|
+
async (rawInput) => {
|
|
1079
|
+
const validation = validateToolInput(updateMemberInputSchema, rawInput, 'ghost_update_member');
|
|
1080
|
+
if (!validation.success) {
|
|
1081
|
+
return validation.errorResponse;
|
|
1082
|
+
}
|
|
1083
|
+
const input = validation.data;
|
|
1084
|
+
|
|
970
1085
|
console.error(`Executing tool: ghost_update_member for member ID: ${input.id}`);
|
|
971
1086
|
try {
|
|
972
1087
|
await loadServices();
|
|
@@ -982,6 +1097,13 @@ server.tool(
|
|
|
982
1097
|
};
|
|
983
1098
|
} catch (error) {
|
|
984
1099
|
console.error(`Error in ghost_update_member:`, error);
|
|
1100
|
+
if (error.name === 'ZodError') {
|
|
1101
|
+
const validationError = ValidationError.fromZod(error, 'Member update');
|
|
1102
|
+
return {
|
|
1103
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1104
|
+
isError: true,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
985
1107
|
return {
|
|
986
1108
|
content: [{ type: 'text', text: `Error updating member: ${error.message}` }],
|
|
987
1109
|
isError: true,
|
|
@@ -994,10 +1116,14 @@ server.tool(
|
|
|
994
1116
|
server.tool(
|
|
995
1117
|
'ghost_delete_member',
|
|
996
1118
|
'Deletes a member from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1119
|
+
deleteMemberSchema,
|
|
1120
|
+
async (rawInput) => {
|
|
1121
|
+
const validation = validateToolInput(deleteMemberSchema, rawInput, 'ghost_delete_member');
|
|
1122
|
+
if (!validation.success) {
|
|
1123
|
+
return validation.errorResponse;
|
|
1124
|
+
}
|
|
1125
|
+
const { id } = validation.data;
|
|
1126
|
+
|
|
1001
1127
|
console.error(`Executing tool: ghost_delete_member for member ID: ${id}`);
|
|
1002
1128
|
try {
|
|
1003
1129
|
await loadServices();
|
|
@@ -1011,6 +1137,13 @@ server.tool(
|
|
|
1011
1137
|
};
|
|
1012
1138
|
} catch (error) {
|
|
1013
1139
|
console.error(`Error in ghost_delete_member:`, error);
|
|
1140
|
+
if (error.name === 'ZodError') {
|
|
1141
|
+
const validationError = ValidationError.fromZod(error, 'Member deletion');
|
|
1142
|
+
return {
|
|
1143
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1144
|
+
isError: true,
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1014
1147
|
return {
|
|
1015
1148
|
content: [{ type: 'text', text: `Error deleting member: ${error.message}` }],
|
|
1016
1149
|
isError: true,
|
|
@@ -1023,25 +1156,14 @@ server.tool(
|
|
|
1023
1156
|
server.tool(
|
|
1024
1157
|
'ghost_get_members',
|
|
1025
1158
|
'Retrieves a list of members (subscribers) from Ghost CMS with optional filtering, pagination, and includes.',
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
.
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
filter: z
|
|
1035
|
-
.string()
|
|
1036
|
-
.optional()
|
|
1037
|
-
.describe('Ghost NQL filter string (e.g., "status:free", "status:paid", "subscribed:true").'),
|
|
1038
|
-
order: z.string().optional().describe('Order string (e.g., "created_at desc", "email asc").'),
|
|
1039
|
-
include: z
|
|
1040
|
-
.string()
|
|
1041
|
-
.optional()
|
|
1042
|
-
.describe('Comma-separated list of related data to include (e.g., "labels,newsletters").'),
|
|
1043
|
-
},
|
|
1044
|
-
async (input) => {
|
|
1159
|
+
getMembersSchema,
|
|
1160
|
+
async (rawInput) => {
|
|
1161
|
+
const validation = validateToolInput(getMembersSchema, rawInput, 'ghost_get_members');
|
|
1162
|
+
if (!validation.success) {
|
|
1163
|
+
return validation.errorResponse;
|
|
1164
|
+
}
|
|
1165
|
+
const input = validation.data;
|
|
1166
|
+
|
|
1045
1167
|
console.error(`Executing tool: ghost_get_members`);
|
|
1046
1168
|
try {
|
|
1047
1169
|
await loadServices();
|
|
@@ -1062,6 +1184,13 @@ server.tool(
|
|
|
1062
1184
|
};
|
|
1063
1185
|
} catch (error) {
|
|
1064
1186
|
console.error(`Error in ghost_get_members:`, error);
|
|
1187
|
+
if (error.name === 'ZodError') {
|
|
1188
|
+
const validationError = ValidationError.fromZod(error, 'Member query');
|
|
1189
|
+
return {
|
|
1190
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1191
|
+
isError: true,
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1065
1194
|
return {
|
|
1066
1195
|
content: [{ type: 'text', text: `Error retrieving members: ${error.message}` }],
|
|
1067
1196
|
isError: true,
|
|
@@ -1074,11 +1203,14 @@ server.tool(
|
|
|
1074
1203
|
server.tool(
|
|
1075
1204
|
'ghost_get_member',
|
|
1076
1205
|
'Retrieves a single member from Ghost CMS by ID or email. Provide either id OR email.',
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1206
|
+
getMemberSchema,
|
|
1207
|
+
async (rawInput) => {
|
|
1208
|
+
const validation = validateToolInput(getMemberSchema, rawInput, 'ghost_get_member');
|
|
1209
|
+
if (!validation.success) {
|
|
1210
|
+
return validation.errorResponse;
|
|
1211
|
+
}
|
|
1212
|
+
const { id, email } = validation.data;
|
|
1213
|
+
|
|
1082
1214
|
console.error(`Executing tool: ghost_get_member for ${id ? `ID: ${id}` : `email: ${email}`}`);
|
|
1083
1215
|
try {
|
|
1084
1216
|
await loadServices();
|
|
@@ -1092,6 +1224,13 @@ server.tool(
|
|
|
1092
1224
|
};
|
|
1093
1225
|
} catch (error) {
|
|
1094
1226
|
console.error(`Error in ghost_get_member:`, error);
|
|
1227
|
+
if (error.name === 'ZodError') {
|
|
1228
|
+
const validationError = ValidationError.fromZod(error, 'Member lookup');
|
|
1229
|
+
return {
|
|
1230
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1231
|
+
isError: true,
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1095
1234
|
return {
|
|
1096
1235
|
content: [{ type: 'text', text: `Error retrieving member: ${error.message}` }],
|
|
1097
1236
|
isError: true,
|
|
@@ -1104,16 +1243,14 @@ server.tool(
|
|
|
1104
1243
|
server.tool(
|
|
1105
1244
|
'ghost_search_members',
|
|
1106
1245
|
'Searches for members by name or email in Ghost CMS.',
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
.
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
},
|
|
1116
|
-
async ({ query, limit }) => {
|
|
1246
|
+
searchMembersSchema,
|
|
1247
|
+
async (rawInput) => {
|
|
1248
|
+
const validation = validateToolInput(searchMembersSchema, rawInput, 'ghost_search_members');
|
|
1249
|
+
if (!validation.success) {
|
|
1250
|
+
return validation.errorResponse;
|
|
1251
|
+
}
|
|
1252
|
+
const { query, limit } = validation.data;
|
|
1253
|
+
|
|
1117
1254
|
console.error(`Executing tool: ghost_search_members with query: ${query}`);
|
|
1118
1255
|
try {
|
|
1119
1256
|
await loadServices();
|
|
@@ -1130,6 +1267,13 @@ server.tool(
|
|
|
1130
1267
|
};
|
|
1131
1268
|
} catch (error) {
|
|
1132
1269
|
console.error(`Error in ghost_search_members:`, error);
|
|
1270
|
+
if (error.name === 'ZodError') {
|
|
1271
|
+
const validationError = ValidationError.fromZod(error, 'Member search');
|
|
1272
|
+
return {
|
|
1273
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1274
|
+
isError: true,
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1133
1277
|
return {
|
|
1134
1278
|
content: [{ type: 'text', text: `Error searching members: ${error.message}` }],
|
|
1135
1279
|
isError: true,
|
|
@@ -1142,27 +1286,32 @@ server.tool(
|
|
|
1142
1286
|
// NEWSLETTER TOOLS
|
|
1143
1287
|
// =============================================================================
|
|
1144
1288
|
|
|
1289
|
+
// --- Newsletter Schema Definitions ---
|
|
1290
|
+
const getNewsletterSchema = z.object({ id: ghostIdSchema });
|
|
1291
|
+
const updateNewsletterInputSchema = z.object({ id: ghostIdSchema }).merge(updateNewsletterSchema);
|
|
1292
|
+
const deleteNewsletterSchema = z.object({ id: ghostIdSchema });
|
|
1293
|
+
|
|
1145
1294
|
// Get Newsletters Tool
|
|
1146
1295
|
server.tool(
|
|
1147
1296
|
'ghost_get_newsletters',
|
|
1148
1297
|
'Retrieves a list of newsletters from Ghost CMS with optional filtering.',
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
.
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
},
|
|
1158
|
-
async (input) => {
|
|
1298
|
+
newsletterQuerySchema,
|
|
1299
|
+
async (rawInput) => {
|
|
1300
|
+
const validation = validateToolInput(newsletterQuerySchema, rawInput, 'ghost_get_newsletters');
|
|
1301
|
+
if (!validation.success) {
|
|
1302
|
+
return validation.errorResponse;
|
|
1303
|
+
}
|
|
1304
|
+
const input = validation.data;
|
|
1305
|
+
|
|
1159
1306
|
console.error(`Executing tool: ghost_get_newsletters`);
|
|
1160
1307
|
try {
|
|
1161
1308
|
await loadServices();
|
|
1162
1309
|
|
|
1163
1310
|
const options = {};
|
|
1164
1311
|
if (input.limit !== undefined) options.limit = input.limit;
|
|
1312
|
+
if (input.page !== undefined) options.page = input.page;
|
|
1165
1313
|
if (input.filter !== undefined) options.filter = input.filter;
|
|
1314
|
+
if (input.order !== undefined) options.order = input.order;
|
|
1166
1315
|
|
|
1167
1316
|
const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
|
|
1168
1317
|
const newsletters = await ghostServiceImproved.getNewsletters(options);
|
|
@@ -1173,6 +1322,13 @@ server.tool(
|
|
|
1173
1322
|
};
|
|
1174
1323
|
} catch (error) {
|
|
1175
1324
|
console.error(`Error in ghost_get_newsletters:`, error);
|
|
1325
|
+
if (error.name === 'ZodError') {
|
|
1326
|
+
const validationError = ValidationError.fromZod(error, 'Newsletter query');
|
|
1327
|
+
return {
|
|
1328
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1329
|
+
isError: true,
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1176
1332
|
return {
|
|
1177
1333
|
content: [{ type: 'text', text: `Error retrieving newsletters: ${error.message}` }],
|
|
1178
1334
|
isError: true,
|
|
@@ -1185,10 +1341,14 @@ server.tool(
|
|
|
1185
1341
|
server.tool(
|
|
1186
1342
|
'ghost_get_newsletter',
|
|
1187
1343
|
'Retrieves a single newsletter from Ghost CMS by ID.',
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1344
|
+
getNewsletterSchema,
|
|
1345
|
+
async (rawInput) => {
|
|
1346
|
+
const validation = validateToolInput(getNewsletterSchema, rawInput, 'ghost_get_newsletter');
|
|
1347
|
+
if (!validation.success) {
|
|
1348
|
+
return validation.errorResponse;
|
|
1349
|
+
}
|
|
1350
|
+
const { id } = validation.data;
|
|
1351
|
+
|
|
1192
1352
|
console.error(`Executing tool: ghost_get_newsletter for ID: ${id}`);
|
|
1193
1353
|
try {
|
|
1194
1354
|
await loadServices();
|
|
@@ -1202,6 +1362,13 @@ server.tool(
|
|
|
1202
1362
|
};
|
|
1203
1363
|
} catch (error) {
|
|
1204
1364
|
console.error(`Error in ghost_get_newsletter:`, error);
|
|
1365
|
+
if (error.name === 'ZodError') {
|
|
1366
|
+
const validationError = ValidationError.fromZod(error, 'Newsletter retrieval');
|
|
1367
|
+
return {
|
|
1368
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1369
|
+
isError: true,
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1205
1372
|
return {
|
|
1206
1373
|
content: [{ type: 'text', text: `Error retrieving newsletter: ${error.message}` }],
|
|
1207
1374
|
isError: true,
|
|
@@ -1214,33 +1381,18 @@ server.tool(
|
|
|
1214
1381
|
server.tool(
|
|
1215
1382
|
'ghost_create_newsletter',
|
|
1216
1383
|
'Creates a new newsletter in Ghost CMS with customizable sender settings and display options.',
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
.describe('Reply-to address setting. Options: newsletter, support.'),
|
|
1230
|
-
subscribe_on_signup: z
|
|
1231
|
-
.boolean()
|
|
1232
|
-
.optional()
|
|
1233
|
-
.describe('Whether new members are automatically subscribed to this newsletter on signup.'),
|
|
1234
|
-
show_header_icon: z
|
|
1235
|
-
.boolean()
|
|
1236
|
-
.optional()
|
|
1237
|
-
.describe('Whether to show the site icon in the newsletter header.'),
|
|
1238
|
-
show_header_title: z
|
|
1239
|
-
.boolean()
|
|
1240
|
-
.optional()
|
|
1241
|
-
.describe('Whether to show the site title in the newsletter header.'),
|
|
1242
|
-
},
|
|
1243
|
-
async (input) => {
|
|
1384
|
+
createNewsletterSchema,
|
|
1385
|
+
async (rawInput) => {
|
|
1386
|
+
const validation = validateToolInput(
|
|
1387
|
+
createNewsletterSchema,
|
|
1388
|
+
rawInput,
|
|
1389
|
+
'ghost_create_newsletter'
|
|
1390
|
+
);
|
|
1391
|
+
if (!validation.success) {
|
|
1392
|
+
return validation.errorResponse;
|
|
1393
|
+
}
|
|
1394
|
+
const input = validation.data;
|
|
1395
|
+
|
|
1244
1396
|
console.error(`Executing tool: ghost_create_newsletter with name: ${input.name}`);
|
|
1245
1397
|
try {
|
|
1246
1398
|
await loadServices();
|
|
@@ -1254,6 +1406,13 @@ server.tool(
|
|
|
1254
1406
|
};
|
|
1255
1407
|
} catch (error) {
|
|
1256
1408
|
console.error(`Error in ghost_create_newsletter:`, error);
|
|
1409
|
+
if (error.name === 'ZodError') {
|
|
1410
|
+
const validationError = ValidationError.fromZod(error, 'Newsletter creation');
|
|
1411
|
+
return {
|
|
1412
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1413
|
+
isError: true,
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1257
1416
|
return {
|
|
1258
1417
|
content: [{ type: 'text', text: `Error creating newsletter: ${error.message}` }],
|
|
1259
1418
|
isError: true,
|
|
@@ -1266,18 +1425,18 @@ server.tool(
|
|
|
1266
1425
|
server.tool(
|
|
1267
1426
|
'ghost_update_newsletter',
|
|
1268
1427
|
'Updates an existing newsletter in Ghost CMS. Can update name, description, sender settings, and display options.',
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
.
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1428
|
+
updateNewsletterInputSchema,
|
|
1429
|
+
async (rawInput) => {
|
|
1430
|
+
const validation = validateToolInput(
|
|
1431
|
+
updateNewsletterInputSchema,
|
|
1432
|
+
rawInput,
|
|
1433
|
+
'ghost_update_newsletter'
|
|
1434
|
+
);
|
|
1435
|
+
if (!validation.success) {
|
|
1436
|
+
return validation.errorResponse;
|
|
1437
|
+
}
|
|
1438
|
+
const input = validation.data;
|
|
1439
|
+
|
|
1281
1440
|
console.error(`Executing tool: ghost_update_newsletter for newsletter ID: ${input.id}`);
|
|
1282
1441
|
try {
|
|
1283
1442
|
await loadServices();
|
|
@@ -1293,6 +1452,13 @@ server.tool(
|
|
|
1293
1452
|
};
|
|
1294
1453
|
} catch (error) {
|
|
1295
1454
|
console.error(`Error in ghost_update_newsletter:`, error);
|
|
1455
|
+
if (error.name === 'ZodError') {
|
|
1456
|
+
const validationError = ValidationError.fromZod(error, 'Newsletter update');
|
|
1457
|
+
return {
|
|
1458
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1459
|
+
isError: true,
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1296
1462
|
return {
|
|
1297
1463
|
content: [{ type: 'text', text: `Error updating newsletter: ${error.message}` }],
|
|
1298
1464
|
isError: true,
|
|
@@ -1305,10 +1471,18 @@ server.tool(
|
|
|
1305
1471
|
server.tool(
|
|
1306
1472
|
'ghost_delete_newsletter',
|
|
1307
1473
|
'Deletes a newsletter from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1474
|
+
deleteNewsletterSchema,
|
|
1475
|
+
async (rawInput) => {
|
|
1476
|
+
const validation = validateToolInput(
|
|
1477
|
+
deleteNewsletterSchema,
|
|
1478
|
+
rawInput,
|
|
1479
|
+
'ghost_delete_newsletter'
|
|
1480
|
+
);
|
|
1481
|
+
if (!validation.success) {
|
|
1482
|
+
return validation.errorResponse;
|
|
1483
|
+
}
|
|
1484
|
+
const { id } = validation.data;
|
|
1485
|
+
|
|
1312
1486
|
console.error(`Executing tool: ghost_delete_newsletter for newsletter ID: ${id}`);
|
|
1313
1487
|
try {
|
|
1314
1488
|
await loadServices();
|
|
@@ -1322,6 +1496,13 @@ server.tool(
|
|
|
1322
1496
|
};
|
|
1323
1497
|
} catch (error) {
|
|
1324
1498
|
console.error(`Error in ghost_delete_newsletter:`, error);
|
|
1499
|
+
if (error.name === 'ZodError') {
|
|
1500
|
+
const validationError = ValidationError.fromZod(error, 'Newsletter deletion');
|
|
1501
|
+
return {
|
|
1502
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1503
|
+
isError: true,
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1325
1506
|
return {
|
|
1326
1507
|
content: [{ type: 'text', text: `Error deleting newsletter: ${error.message}` }],
|
|
1327
1508
|
isError: true,
|
|
@@ -1330,6 +1511,215 @@ server.tool(
|
|
|
1330
1511
|
}
|
|
1331
1512
|
);
|
|
1332
1513
|
|
|
1514
|
+
// --- Tier Tools ---
|
|
1515
|
+
|
|
1516
|
+
// --- Tier Schema Definitions ---
|
|
1517
|
+
const getTierSchema = z.object({ id: ghostIdSchema });
|
|
1518
|
+
const updateTierInputSchema = z.object({ id: ghostIdSchema }).merge(updateTierSchema);
|
|
1519
|
+
const deleteTierSchema = z.object({ id: ghostIdSchema });
|
|
1520
|
+
|
|
1521
|
+
// Get Tiers Tool
|
|
1522
|
+
server.tool(
|
|
1523
|
+
'ghost_get_tiers',
|
|
1524
|
+
'Retrieves a list of tiers (membership levels) from Ghost CMS with optional filtering by type (free/paid).',
|
|
1525
|
+
tierQuerySchema,
|
|
1526
|
+
async (rawInput) => {
|
|
1527
|
+
const validation = validateToolInput(tierQuerySchema, rawInput, 'ghost_get_tiers');
|
|
1528
|
+
if (!validation.success) {
|
|
1529
|
+
return validation.errorResponse;
|
|
1530
|
+
}
|
|
1531
|
+
const input = validation.data;
|
|
1532
|
+
|
|
1533
|
+
console.error(`Executing tool: ghost_get_tiers`);
|
|
1534
|
+
try {
|
|
1535
|
+
await loadServices();
|
|
1536
|
+
|
|
1537
|
+
const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
|
|
1538
|
+
const tiers = await ghostServiceImproved.getTiers(input);
|
|
1539
|
+
console.error(`Retrieved ${tiers.length} tiers`);
|
|
1540
|
+
|
|
1541
|
+
return {
|
|
1542
|
+
content: [{ type: 'text', text: JSON.stringify(tiers, null, 2) }],
|
|
1543
|
+
};
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
console.error(`Error in ghost_get_tiers:`, error);
|
|
1546
|
+
if (error.name === 'ZodError') {
|
|
1547
|
+
const validationError = ValidationError.fromZod(error, 'Tier query');
|
|
1548
|
+
return {
|
|
1549
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1550
|
+
isError: true,
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
return {
|
|
1554
|
+
content: [{ type: 'text', text: `Error getting tiers: ${error.message}` }],
|
|
1555
|
+
isError: true,
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
);
|
|
1560
|
+
|
|
1561
|
+
// Get Tier Tool
|
|
1562
|
+
server.tool(
|
|
1563
|
+
'ghost_get_tier',
|
|
1564
|
+
'Retrieves a single tier (membership level) from Ghost CMS by ID.',
|
|
1565
|
+
getTierSchema,
|
|
1566
|
+
async (rawInput) => {
|
|
1567
|
+
const validation = validateToolInput(getTierSchema, rawInput, 'ghost_get_tier');
|
|
1568
|
+
if (!validation.success) {
|
|
1569
|
+
return validation.errorResponse;
|
|
1570
|
+
}
|
|
1571
|
+
const { id } = validation.data;
|
|
1572
|
+
|
|
1573
|
+
console.error(`Executing tool: ghost_get_tier for tier ID: ${id}`);
|
|
1574
|
+
try {
|
|
1575
|
+
await loadServices();
|
|
1576
|
+
|
|
1577
|
+
const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
|
|
1578
|
+
const tier = await ghostServiceImproved.getTier(id);
|
|
1579
|
+
console.error(`Tier retrieved successfully. Tier ID: ${tier.id}`);
|
|
1580
|
+
|
|
1581
|
+
return {
|
|
1582
|
+
content: [{ type: 'text', text: JSON.stringify(tier, null, 2) }],
|
|
1583
|
+
};
|
|
1584
|
+
} catch (error) {
|
|
1585
|
+
console.error(`Error in ghost_get_tier:`, error);
|
|
1586
|
+
if (error.name === 'ZodError') {
|
|
1587
|
+
const validationError = ValidationError.fromZod(error, 'Tier retrieval');
|
|
1588
|
+
return {
|
|
1589
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1590
|
+
isError: true,
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
return {
|
|
1594
|
+
content: [{ type: 'text', text: `Error getting tier: ${error.message}` }],
|
|
1595
|
+
isError: true,
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
);
|
|
1600
|
+
|
|
1601
|
+
// Create Tier Tool
|
|
1602
|
+
server.tool(
|
|
1603
|
+
'ghost_create_tier',
|
|
1604
|
+
'Creates a new tier (membership level) in Ghost CMS with pricing and benefits.',
|
|
1605
|
+
createTierSchema,
|
|
1606
|
+
async (rawInput) => {
|
|
1607
|
+
const validation = validateToolInput(createTierSchema, rawInput, 'ghost_create_tier');
|
|
1608
|
+
if (!validation.success) {
|
|
1609
|
+
return validation.errorResponse;
|
|
1610
|
+
}
|
|
1611
|
+
const input = validation.data;
|
|
1612
|
+
|
|
1613
|
+
console.error(`Executing tool: ghost_create_tier`);
|
|
1614
|
+
try {
|
|
1615
|
+
await loadServices();
|
|
1616
|
+
|
|
1617
|
+
const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
|
|
1618
|
+
const tier = await ghostServiceImproved.createTier(input);
|
|
1619
|
+
console.error(`Tier created successfully. Tier ID: ${tier.id}`);
|
|
1620
|
+
|
|
1621
|
+
return {
|
|
1622
|
+
content: [{ type: 'text', text: JSON.stringify(tier, null, 2) }],
|
|
1623
|
+
};
|
|
1624
|
+
} catch (error) {
|
|
1625
|
+
console.error(`Error in ghost_create_tier:`, error);
|
|
1626
|
+
if (error.name === 'ZodError') {
|
|
1627
|
+
const validationError = ValidationError.fromZod(error, 'Tier creation');
|
|
1628
|
+
return {
|
|
1629
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1630
|
+
isError: true,
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
return {
|
|
1634
|
+
content: [{ type: 'text', text: `Error creating tier: ${error.message}` }],
|
|
1635
|
+
isError: true,
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
);
|
|
1640
|
+
|
|
1641
|
+
// Update Tier Tool
|
|
1642
|
+
server.tool(
|
|
1643
|
+
'ghost_update_tier',
|
|
1644
|
+
'Updates an existing tier (membership level) in Ghost CMS. Can update pricing, benefits, and other tier properties.',
|
|
1645
|
+
updateTierInputSchema,
|
|
1646
|
+
async (rawInput) => {
|
|
1647
|
+
const validation = validateToolInput(updateTierInputSchema, rawInput, 'ghost_update_tier');
|
|
1648
|
+
if (!validation.success) {
|
|
1649
|
+
return validation.errorResponse;
|
|
1650
|
+
}
|
|
1651
|
+
const input = validation.data;
|
|
1652
|
+
|
|
1653
|
+
console.error(`Executing tool: ghost_update_tier for tier ID: ${input.id}`);
|
|
1654
|
+
try {
|
|
1655
|
+
await loadServices();
|
|
1656
|
+
|
|
1657
|
+
const { id, ...updateData } = input;
|
|
1658
|
+
|
|
1659
|
+
const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
|
|
1660
|
+
const updatedTier = await ghostServiceImproved.updateTier(id, updateData);
|
|
1661
|
+
console.error(`Tier updated successfully. Tier ID: ${updatedTier.id}`);
|
|
1662
|
+
|
|
1663
|
+
return {
|
|
1664
|
+
content: [{ type: 'text', text: JSON.stringify(updatedTier, null, 2) }],
|
|
1665
|
+
};
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
console.error(`Error in ghost_update_tier:`, error);
|
|
1668
|
+
if (error.name === 'ZodError') {
|
|
1669
|
+
const validationError = ValidationError.fromZod(error, 'Tier update');
|
|
1670
|
+
return {
|
|
1671
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1672
|
+
isError: true,
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
return {
|
|
1676
|
+
content: [{ type: 'text', text: `Error updating tier: ${error.message}` }],
|
|
1677
|
+
isError: true,
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
);
|
|
1682
|
+
|
|
1683
|
+
// Delete Tier Tool
|
|
1684
|
+
server.tool(
|
|
1685
|
+
'ghost_delete_tier',
|
|
1686
|
+
'Deletes a tier (membership level) from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
1687
|
+
deleteTierSchema,
|
|
1688
|
+
async (rawInput) => {
|
|
1689
|
+
const validation = validateToolInput(deleteTierSchema, rawInput, 'ghost_delete_tier');
|
|
1690
|
+
if (!validation.success) {
|
|
1691
|
+
return validation.errorResponse;
|
|
1692
|
+
}
|
|
1693
|
+
const { id } = validation.data;
|
|
1694
|
+
|
|
1695
|
+
console.error(`Executing tool: ghost_delete_tier for tier ID: ${id}`);
|
|
1696
|
+
try {
|
|
1697
|
+
await loadServices();
|
|
1698
|
+
|
|
1699
|
+
const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
|
|
1700
|
+
await ghostServiceImproved.deleteTier(id);
|
|
1701
|
+
console.error(`Tier deleted successfully. Tier ID: ${id}`);
|
|
1702
|
+
|
|
1703
|
+
return {
|
|
1704
|
+
content: [{ type: 'text', text: `Tier ${id} has been successfully deleted.` }],
|
|
1705
|
+
};
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
console.error(`Error in ghost_delete_tier:`, error);
|
|
1708
|
+
if (error.name === 'ZodError') {
|
|
1709
|
+
const validationError = ValidationError.fromZod(error, 'Tier deletion');
|
|
1710
|
+
return {
|
|
1711
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
1712
|
+
isError: true,
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
return {
|
|
1716
|
+
content: [{ type: 'text', text: `Error deleting tier: ${error.message}` }],
|
|
1717
|
+
isError: true,
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
);
|
|
1722
|
+
|
|
1333
1723
|
// --- Main Entry Point ---
|
|
1334
1724
|
|
|
1335
1725
|
async function main() {
|
|
@@ -1344,7 +1734,8 @@ async function main() {
|
|
|
1344
1734
|
'ghost_create_post, ghost_get_posts, ghost_get_post, ghost_search_posts, ghost_update_post, ghost_delete_post, ' +
|
|
1345
1735
|
'ghost_get_pages, ghost_get_page, ghost_create_page, ghost_update_page, ghost_delete_page, ghost_search_pages, ' +
|
|
1346
1736
|
'ghost_create_member, ghost_update_member, ghost_delete_member, ghost_get_members, ghost_get_member, ghost_search_members, ' +
|
|
1347
|
-
'ghost_get_newsletters, ghost_get_newsletter, ghost_create_newsletter, ghost_update_newsletter, ghost_delete_newsletter'
|
|
1737
|
+
'ghost_get_newsletters, ghost_get_newsletter, ghost_create_newsletter, ghost_update_newsletter, ghost_delete_newsletter, ' +
|
|
1738
|
+
'ghost_get_tiers, ghost_get_tier, ghost_create_tier, ghost_update_tier, ghost_delete_tier'
|
|
1348
1739
|
);
|
|
1349
1740
|
}
|
|
1350
1741
|
|