@jgardner04/ghost-mcp-server 1.12.2 → 1.12.3

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