@jgardner04/ghost-mcp-server 1.13.4 → 1.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mcp_server.js CHANGED
@@ -83,6 +83,49 @@ const escapeNqlValue = (value) => {
83
83
  return value.replace(/'/g, "''");
84
84
  };
85
85
 
86
+ /**
87
+ * Higher-order function that wraps a tool handler with standardized
88
+ * input validation, service loading, and error handling.
89
+ *
90
+ * Note: Each registerTool call passes the schema twice — once as `inputSchema`
91
+ * (MCP protocol metadata exposed to clients) and once here for runtime input
92
+ * validation via validateToolInput. These serve different purposes and both
93
+ * are required.
94
+ *
95
+ * @param {string} toolName - The tool identifier (e.g., 'ghost_get_tags')
96
+ * @param {object} schema - Zod schema for input validation
97
+ * @param {Function} handler - Async function receiving validated input, returns MCP response
98
+ * @returns {Function} Wrapped async handler for server.registerTool
99
+ */
100
+ const withErrorHandling = (toolName, schema, handler) => {
101
+ const zodContext = toolName.replace('ghost_', '').replace(/_/g, ' ');
102
+ return async (rawInput) => {
103
+ console.error(`Executing tool: ${toolName}`);
104
+ const validation = validateToolInput(schema, rawInput, toolName);
105
+ if (!validation.success) {
106
+ return validation.errorResponse;
107
+ }
108
+
109
+ try {
110
+ await loadServices();
111
+ return await handler(validation.data);
112
+ } catch (error) {
113
+ console.error(`Error in ${toolName}:`, error);
114
+ if (error.name === 'ZodError') {
115
+ const validationError = ValidationError.fromZod(error, zodContext);
116
+ return {
117
+ content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
118
+ isError: true,
119
+ };
120
+ }
121
+ return {
122
+ content: [{ type: 'text', text: `Error in ${toolName}: ${error.message}` }],
123
+ isError: true,
124
+ };
125
+ }
126
+ };
127
+ };
128
+
86
129
  // Create server instance with new API
87
130
  const server = new McpServer({
88
131
  name: 'ghost-mcp-server',
@@ -116,58 +159,32 @@ server.registerTool(
116
159
  'Retrieves a list of tags from Ghost CMS with pagination, filtering, sorting, and relation inclusion. Supports filtering by name, slug, visibility, or custom NQL filter expressions.',
117
160
  inputSchema: getTagsSchema,
118
161
  },
119
- async (rawInput) => {
120
- const validation = validateToolInput(getTagsSchema, rawInput, 'ghost_get_tags');
121
- if (!validation.success) {
122
- return validation.errorResponse;
123
- }
124
- const input = validation.data;
125
-
126
- console.error(`Executing tool: ghost_get_tags`);
127
- try {
128
- await loadServices();
129
-
130
- // Build options object with provided parameters
131
- const options = {};
132
- if (input.limit !== undefined) options.limit = input.limit;
133
- if (input.page !== undefined) options.page = input.page;
134
- if (input.order !== undefined) options.order = input.order;
135
- if (input.include !== undefined) options.include = input.include;
136
-
137
- // Build filter string from individual filter parameters
138
- const filters = [];
139
- if (input.name) filters.push(`name:'${escapeNqlValue(input.name)}'`);
140
- if (input.slug) filters.push(`slug:'${escapeNqlValue(input.slug)}'`);
141
- if (input.visibility) filters.push(`visibility:'${input.visibility}'`); // visibility is enum-validated, no escaping needed
142
- if (input.filter) filters.push(input.filter);
143
-
144
- if (filters.length > 0) {
145
- options.filter = filters.join('+');
146
- }
147
-
148
- const tags = await ghostService.getTags(options);
149
- console.error(`Retrieved ${tags.length} tags from Ghost.`);
150
-
151
- const result = tags;
152
-
153
- return {
154
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
155
- };
156
- } catch (error) {
157
- console.error(`Error in ghost_get_tags:`, error);
158
- if (error.name === 'ZodError') {
159
- const validationError = ValidationError.fromZod(error, 'Tags retrieval');
160
- return {
161
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
162
- isError: true,
163
- };
164
- }
165
- return {
166
- content: [{ type: 'text', text: `Error: ${error.message}` }],
167
- isError: true,
168
- };
169
- }
170
- }
162
+ withErrorHandling('ghost_get_tags', getTagsSchema, async (input) => {
163
+ // Build options object with provided parameters
164
+ const options = {};
165
+ if (input.limit !== undefined) options.limit = input.limit;
166
+ if (input.page !== undefined) options.page = input.page;
167
+ if (input.order !== undefined) options.order = input.order;
168
+ if (input.include !== undefined) options.include = input.include;
169
+
170
+ // Build filter string from individual filter parameters
171
+ const filters = [];
172
+ if (input.name) filters.push(`name:'${escapeNqlValue(input.name)}'`);
173
+ if (input.slug) filters.push(`slug:'${escapeNqlValue(input.slug)}'`);
174
+ if (input.visibility) filters.push(`visibility:'${input.visibility}'`); // visibility is enum-validated, no escaping needed
175
+ if (input.filter) filters.push(input.filter);
176
+
177
+ if (filters.length > 0) {
178
+ options.filter = filters.join('+');
179
+ }
180
+
181
+ const tags = await ghostService.getTags(options);
182
+ console.error(`Retrieved ${tags.length} tags from Ghost.`);
183
+
184
+ return {
185
+ content: [{ type: 'text', text: JSON.stringify(tags, null, 2) }],
186
+ };
187
+ })
171
188
  );
172
189
 
173
190
  // Create Tag Tool
@@ -177,37 +194,14 @@ server.registerTool(
177
194
  description: 'Creates a new tag in Ghost CMS.',
178
195
  inputSchema: createTagSchema,
179
196
  },
180
- async (rawInput) => {
181
- const validation = validateToolInput(createTagSchema, rawInput, 'ghost_create_tag');
182
- if (!validation.success) {
183
- return validation.errorResponse;
184
- }
185
- const input = validation.data;
186
-
187
- console.error(`Executing tool: ghost_create_tag with name: ${input.name}`);
188
- try {
189
- await loadServices();
190
- const createdTag = await ghostService.createTag(input);
191
- console.error(`Tag created successfully. Tag ID: ${createdTag.id}`);
197
+ withErrorHandling('ghost_create_tag', createTagSchema, async (input) => {
198
+ const createdTag = await ghostService.createTag(input);
199
+ console.error(`Tag created successfully. Tag ID: ${createdTag.id}`);
192
200
 
193
- return {
194
- content: [{ type: 'text', text: JSON.stringify(createdTag, null, 2) }],
195
- };
196
- } catch (error) {
197
- console.error(`Error in ghost_create_tag:`, error);
198
- if (error.name === 'ZodError') {
199
- const validationError = ValidationError.fromZod(error, 'Tag creation');
200
- return {
201
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
202
- isError: true,
203
- };
204
- }
205
- return {
206
- content: [{ type: 'text', text: `Error: ${error.message}` }],
207
- isError: true,
208
- };
209
- }
210
- }
201
+ return {
202
+ content: [{ type: 'text', text: JSON.stringify(createdTag, null, 2) }],
203
+ };
204
+ })
211
205
  );
212
206
 
213
207
  // Get Tag Tool
@@ -217,42 +211,22 @@ server.registerTool(
217
211
  description: 'Retrieves a single tag from Ghost CMS by ID or slug.',
218
212
  inputSchema: getTagSchema,
219
213
  },
220
- async (rawInput) => {
221
- const validation = validateToolInput(getTagSchema, rawInput, 'ghost_get_tag');
222
- if (!validation.success) {
223
- return validation.errorResponse;
224
- }
225
- const { id, slug, include } = validation.data;
226
-
227
- console.error(`Executing tool: ghost_get_tag`);
228
- try {
229
- await loadServices();
214
+ withErrorHandling('ghost_get_tag', getTagSchema, async (input) => {
215
+ const options = {};
216
+ if (input.include !== undefined) options.include = input.include;
230
217
 
231
- // If slug is provided, use the slug/slug-name format
232
- const identifier = slug ? `slug/${slug}` : id;
233
- const options = include ? { include } : {};
218
+ if (!input.id && !input.slug) {
219
+ throw new Error('Either id or slug is required');
220
+ }
221
+ const identifier = input.id || `slug/${input.slug}`;
234
222
 
235
- const tag = await ghostService.getTag(identifier, options);
236
- console.error(`Tag retrieved successfully. Tag ID: ${tag.id}`);
223
+ const tag = await ghostService.getTag(identifier, options);
224
+ console.error(`Retrieved tag: ${tag.name} (ID: ${tag.id})`);
237
225
 
238
- return {
239
- content: [{ type: 'text', text: JSON.stringify(tag, null, 2) }],
240
- };
241
- } catch (error) {
242
- console.error(`Error in ghost_get_tag:`, error);
243
- if (error.name === 'ZodError') {
244
- const validationError = ValidationError.fromZod(error, 'Tag retrieval');
245
- return {
246
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
247
- isError: true,
248
- };
249
- }
250
- return {
251
- content: [{ type: 'text', text: `Error: ${error.message}` }],
252
- isError: true,
253
- };
254
- }
255
- }
226
+ return {
227
+ content: [{ type: 'text', text: JSON.stringify(tag, null, 2) }],
228
+ };
229
+ })
256
230
  );
257
231
 
258
232
  // Update Tag Tool
@@ -262,90 +236,34 @@ server.registerTool(
262
236
  description: 'Updates an existing tag in Ghost CMS.',
263
237
  inputSchema: updateTagInputSchema,
264
238
  },
265
- async (rawInput) => {
266
- const validation = validateToolInput(updateTagInputSchema, rawInput, 'ghost_update_tag');
267
- if (!validation.success) {
268
- return validation.errorResponse;
269
- }
270
- const input = validation.data;
271
-
272
- console.error(`Executing tool: ghost_update_tag for ID: ${input.id}`);
273
- try {
274
- if (!input.id) {
275
- throw new Error('Tag ID is required');
276
- }
277
-
278
- await loadServices();
279
-
280
- // Build update data object with only provided fields (exclude id from update data)
281
- const { id, ...updateData } = input;
282
-
283
- const updatedTag = await ghostService.updateTag(id, updateData);
284
- console.error(`Tag updated successfully. Tag ID: ${updatedTag.id}`);
285
-
286
- return {
287
- content: [{ type: 'text', text: JSON.stringify(updatedTag, null, 2) }],
288
- };
289
- } catch (error) {
290
- console.error(`Error in ghost_update_tag:`, error);
291
- if (error.name === 'ZodError') {
292
- const validationError = ValidationError.fromZod(error, 'Tag update');
293
- return {
294
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
295
- isError: true,
296
- };
297
- }
298
- return {
299
- content: [{ type: 'text', text: `Error: ${error.message}` }],
300
- isError: true,
301
- };
302
- }
303
- }
239
+ withErrorHandling('ghost_update_tag', updateTagInputSchema, async (input) => {
240
+ const { id, ...updateData } = input;
241
+ const updatedTag = await ghostService.updateTag(id, updateData);
242
+ console.error(`Tag updated successfully. Tag ID: ${updatedTag.id}`);
243
+
244
+ return {
245
+ content: [{ type: 'text', text: JSON.stringify(updatedTag, null, 2) }],
246
+ };
247
+ })
304
248
  );
305
249
 
306
250
  // Delete Tag Tool
307
251
  server.registerTool(
308
252
  'ghost_delete_tag',
309
253
  {
310
- description: 'Deletes a tag from Ghost CMS by ID. This operation is permanent.',
254
+ description:
255
+ 'Deletes a tag from Ghost CMS by ID. This operation is permanent and cannot be undone.',
311
256
  inputSchema: deleteTagSchema,
312
257
  },
313
- async (rawInput) => {
314
- const validation = validateToolInput(deleteTagSchema, rawInput, 'ghost_delete_tag');
315
- if (!validation.success) {
316
- return validation.errorResponse;
317
- }
318
- const { id } = validation.data;
319
-
320
- console.error(`Executing tool: ghost_delete_tag for ID: ${id}`);
321
- try {
322
- if (!id) {
323
- throw new Error('Tag ID is required');
324
- }
325
-
326
- await loadServices();
327
-
328
- await ghostService.deleteTag(id);
329
- console.error(`Tag deleted successfully. Tag ID: ${id}`);
330
-
331
- return {
332
- content: [{ type: 'text', text: `Tag with ID ${id} has been successfully deleted.` }],
333
- };
334
- } catch (error) {
335
- console.error(`Error in ghost_delete_tag:`, error);
336
- if (error.name === 'ZodError') {
337
- const validationError = ValidationError.fromZod(error, 'Tag deletion');
338
- return {
339
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
340
- isError: true,
341
- };
342
- }
343
- return {
344
- content: [{ type: 'text', text: `Error: ${error.message}` }],
345
- isError: true,
346
- };
347
- }
348
- }
258
+ withErrorHandling('ghost_delete_tag', deleteTagSchema, async (input) => {
259
+ const { id } = input;
260
+ await ghostService.deleteTag(id);
261
+ console.error(`Tag deleted successfully. Tag ID: ${id}`);
262
+
263
+ return {
264
+ content: [{ type: 'text', text: `Tag ${id} has been successfully deleted.` }],
265
+ };
266
+ })
349
267
  );
350
268
 
351
269
  // --- Image Schema ---
@@ -357,7 +275,7 @@ const uploadImageSchema = z.object({
357
275
  }),
358
276
  });
359
277
 
360
- // Upload Image Tool
278
+ // Upload Image Tool — unique handler with finally-clause cleanup
361
279
  server.registerTool(
362
280
  'ghost_upload_image',
363
281
  {
@@ -486,37 +404,14 @@ server.registerTool(
486
404
  description: 'Creates a new post in Ghost CMS.',
487
405
  inputSchema: createPostSchema,
488
406
  },
489
- async (rawInput) => {
490
- const validation = validateToolInput(createPostSchema, rawInput, 'ghost_create_post');
491
- if (!validation.success) {
492
- return validation.errorResponse;
493
- }
494
- const input = validation.data;
407
+ withErrorHandling('ghost_create_post', createPostSchema, async (input) => {
408
+ const createdPost = await postService.createPostService(input);
409
+ console.error(`Post created successfully. Post ID: ${createdPost.id}`);
495
410
 
496
- console.error(`Executing tool: ghost_create_post with title: ${input.title}`);
497
- try {
498
- await loadServices();
499
- const createdPost = await postService.createPostService(input);
500
- console.error(`Post created successfully. Post ID: ${createdPost.id}`);
501
-
502
- return {
503
- content: [{ type: 'text', text: JSON.stringify(createdPost, null, 2) }],
504
- };
505
- } catch (error) {
506
- console.error(`Error in ghost_create_post:`, error);
507
- if (error.name === 'ZodError') {
508
- const validationError = ValidationError.fromZod(error, 'Post creation');
509
- return {
510
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
511
- isError: true,
512
- };
513
- }
514
- return {
515
- content: [{ type: 'text', text: `Error creating post: ${error.message}` }],
516
- isError: true,
517
- };
518
- }
519
- }
411
+ return {
412
+ content: [{ type: 'text', text: JSON.stringify(createdPost, null, 2) }],
413
+ };
414
+ })
520
415
  );
521
416
 
522
417
  // Get Posts Tool
@@ -527,49 +422,25 @@ server.registerTool(
527
422
  'Retrieves a list of posts from Ghost CMS with pagination, filtering, and sorting options.',
528
423
  inputSchema: getPostsSchema,
529
424
  },
530
- async (rawInput) => {
531
- const validation = validateToolInput(getPostsSchema, rawInput, 'ghost_get_posts');
532
- if (!validation.success) {
533
- return validation.errorResponse;
534
- }
535
- const input = validation.data;
536
-
537
- console.error(`Executing tool: ghost_get_posts`);
538
- try {
539
- await loadServices();
540
-
541
- // Build options object with provided parameters
542
- const options = {};
543
- if (input.limit !== undefined) options.limit = input.limit;
544
- if (input.page !== undefined) options.page = input.page;
545
- if (input.status !== undefined) options.status = input.status;
546
- if (input.include !== undefined) options.include = input.include;
547
- if (input.filter !== undefined) options.filter = input.filter;
548
- if (input.order !== undefined) options.order = input.order;
549
- if (input.fields !== undefined) options.fields = input.fields;
550
- if (input.formats !== undefined) options.formats = input.formats;
551
-
552
- const posts = await ghostService.getPosts(options);
553
- console.error(`Retrieved ${posts.length} posts from Ghost.`);
554
-
555
- return {
556
- content: [{ type: 'text', text: JSON.stringify(posts, null, 2) }],
557
- };
558
- } catch (error) {
559
- console.error(`Error in ghost_get_posts:`, error);
560
- if (error.name === 'ZodError') {
561
- const validationError = ValidationError.fromZod(error, 'Posts retrieval');
562
- return {
563
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
564
- isError: true,
565
- };
566
- }
567
- return {
568
- content: [{ type: 'text', text: `Error retrieving posts: ${error.message}` }],
569
- isError: true,
570
- };
571
- }
572
- }
425
+ withErrorHandling('ghost_get_posts', getPostsSchema, async (input) => {
426
+ // Build options object with provided parameters
427
+ const options = {};
428
+ if (input.limit !== undefined) options.limit = input.limit;
429
+ if (input.page !== undefined) options.page = input.page;
430
+ if (input.status !== undefined) options.status = input.status;
431
+ if (input.include !== undefined) options.include = input.include;
432
+ if (input.filter !== undefined) options.filter = input.filter;
433
+ if (input.order !== undefined) options.order = input.order;
434
+ if (input.fields !== undefined) options.fields = input.fields;
435
+ if (input.formats !== undefined) options.formats = input.formats;
436
+
437
+ const posts = await ghostService.getPosts(options);
438
+ console.error(`Retrieved ${posts.length} posts from Ghost.`);
439
+
440
+ return {
441
+ content: [{ type: 'text', text: JSON.stringify(posts, null, 2) }],
442
+ };
443
+ })
573
444
  );
574
445
 
575
446
  // Get Post Tool
@@ -579,45 +450,24 @@ server.registerTool(
579
450
  description: 'Retrieves a single post from Ghost CMS by ID or slug.',
580
451
  inputSchema: getPostSchema,
581
452
  },
582
- async (rawInput) => {
583
- const validation = validateToolInput(getPostSchema, rawInput, 'ghost_get_post');
584
- if (!validation.success) {
585
- return validation.errorResponse;
586
- }
587
- const input = validation.data;
588
-
589
- console.error(`Executing tool: ghost_get_post`);
590
- try {
591
- await loadServices();
453
+ withErrorHandling('ghost_get_post', getPostSchema, async (input) => {
454
+ // Build options object
455
+ const options = {};
456
+ if (input.include !== undefined) options.include = input.include;
592
457
 
593
- // Build options object
594
- const options = {};
595
- if (input.include !== undefined) options.include = input.include;
596
-
597
- // Determine identifier (prefer ID over slug)
598
- const identifier = input.id || `slug/${input.slug}`;
458
+ // Determine identifier (prefer ID over slug)
459
+ if (!input.id && !input.slug) {
460
+ throw new Error('Either id or slug is required');
461
+ }
462
+ const identifier = input.id || `slug/${input.slug}`;
599
463
 
600
- const post = await ghostService.getPost(identifier, options);
601
- console.error(`Retrieved post: ${post.title} (ID: ${post.id})`);
464
+ const post = await ghostService.getPost(identifier, options);
465
+ console.error(`Retrieved post: ${post.title} (ID: ${post.id})`);
602
466
 
603
- return {
604
- content: [{ type: 'text', text: JSON.stringify(post, null, 2) }],
605
- };
606
- } catch (error) {
607
- console.error(`Error in ghost_get_post:`, error);
608
- if (error.name === 'ZodError') {
609
- const validationError = ValidationError.fromZod(error, 'Post retrieval');
610
- return {
611
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
612
- isError: true,
613
- };
614
- }
615
- return {
616
- content: [{ type: 'text', text: `Error retrieving post: ${error.message}` }],
617
- isError: true,
618
- };
619
- }
620
- }
467
+ return {
468
+ content: [{ type: 'text', text: JSON.stringify(post, null, 2) }],
469
+ };
470
+ })
621
471
  );
622
472
 
623
473
  // Search Posts Tool
@@ -627,43 +477,19 @@ server.registerTool(
627
477
  description: 'Search for posts in Ghost CMS by query string with optional status filtering.',
628
478
  inputSchema: searchPostsSchema,
629
479
  },
630
- async (rawInput) => {
631
- const validation = validateToolInput(searchPostsSchema, rawInput, 'ghost_search_posts');
632
- if (!validation.success) {
633
- return validation.errorResponse;
634
- }
635
- const input = validation.data;
636
-
637
- console.error(`Executing tool: ghost_search_posts with query: ${input.query}`);
638
- try {
639
- await loadServices();
640
-
641
- // Build options object with provided parameters
642
- const options = {};
643
- if (input.status !== undefined) options.status = input.status;
644
- if (input.limit !== undefined) options.limit = input.limit;
645
-
646
- const posts = await ghostService.searchPosts(input.query, options);
647
- console.error(`Found ${posts.length} posts matching "${input.query}".`);
648
-
649
- return {
650
- content: [{ type: 'text', text: JSON.stringify(posts, null, 2) }],
651
- };
652
- } catch (error) {
653
- console.error(`Error in ghost_search_posts:`, error);
654
- if (error.name === 'ZodError') {
655
- const validationError = ValidationError.fromZod(error, 'Post search');
656
- return {
657
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
658
- isError: true,
659
- };
660
- }
661
- return {
662
- content: [{ type: 'text', text: `Error searching posts: ${error.message}` }],
663
- isError: true,
664
- };
665
- }
666
- }
480
+ withErrorHandling('ghost_search_posts', searchPostsSchema, async (input) => {
481
+ // Build options object with provided parameters
482
+ const options = {};
483
+ if (input.status !== undefined) options.status = input.status;
484
+ if (input.limit !== undefined) options.limit = input.limit;
485
+
486
+ const posts = await ghostService.searchPosts(input.query, options);
487
+ console.error(`Found ${posts.length} posts matching "${input.query}".`);
488
+
489
+ return {
490
+ content: [{ type: 'text', text: JSON.stringify(posts, null, 2) }],
491
+ };
492
+ })
667
493
  );
668
494
 
669
495
  // Update Post Tool
@@ -674,42 +500,18 @@ server.registerTool(
674
500
  'Updates an existing post in Ghost CMS. Can update title, content, status, tags, images, and SEO fields. Only the provided fields are changed; omitted fields remain unchanged. Note: tags and authors arrays are fully replaced, not merged with existing values.',
675
501
  inputSchema: updatePostInputSchema,
676
502
  },
677
- async (rawInput) => {
678
- const validation = validateToolInput(updatePostInputSchema, rawInput, 'ghost_update_post');
679
- if (!validation.success) {
680
- return validation.errorResponse;
681
- }
682
- const input = validation.data;
503
+ withErrorHandling('ghost_update_post', updatePostInputSchema, async (input) => {
504
+ // Extract ID from input and build update data
505
+ const { id, ...updateData } = input;
683
506
 
684
- console.error(`Executing tool: ghost_update_post for post ID: ${input.id}`);
685
- try {
686
- await loadServices();
507
+ const updatedPost = await ghostService.updatePost(id, updateData);
508
+ console.error(`Post updated successfully. Post ID: ${updatedPost.id}`);
687
509
 
688
- // Extract ID from input and build update data
689
- const { id, ...updateData } = input;
690
-
691
- const updatedPost = await ghostService.updatePost(id, updateData);
692
- console.error(`Post updated successfully. Post ID: ${updatedPost.id}`);
693
-
694
- return {
695
- content: [{ type: 'text', text: JSON.stringify(updatedPost, null, 2) }],
696
- };
697
- } catch (error) {
698
- console.error(`Error in ghost_update_post:`, error);
699
- if (error.name === 'ZodError') {
700
- const validationError = ValidationError.fromZod(error, 'Post update');
701
- return {
702
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
703
- isError: true,
704
- };
705
- }
706
- return {
707
- content: [{ type: 'text', text: `Error updating post: ${error.message}` }],
708
- isError: true,
709
- };
710
- }
711
- }
712
- );
510
+ return {
511
+ content: [{ type: 'text', text: JSON.stringify(updatedPost, null, 2) }],
512
+ };
513
+ })
514
+ );
713
515
 
714
516
  // Delete Post Tool
715
517
  server.registerTool(
@@ -719,38 +521,15 @@ server.registerTool(
719
521
  'Deletes a post from Ghost CMS by ID. This operation is permanent and cannot be undone.',
720
522
  inputSchema: deletePostSchema,
721
523
  },
722
- async (rawInput) => {
723
- const validation = validateToolInput(deletePostSchema, rawInput, 'ghost_delete_post');
724
- if (!validation.success) {
725
- return validation.errorResponse;
726
- }
727
- const { id } = validation.data;
728
-
729
- console.error(`Executing tool: ghost_delete_post for post ID: ${id}`);
730
- try {
731
- await loadServices();
732
-
733
- await ghostService.deletePost(id);
734
- console.error(`Post deleted successfully. Post ID: ${id}`);
735
-
736
- return {
737
- content: [{ type: 'text', text: `Post ${id} has been successfully deleted.` }],
738
- };
739
- } catch (error) {
740
- console.error(`Error in ghost_delete_post:`, error);
741
- if (error.name === 'ZodError') {
742
- const validationError = ValidationError.fromZod(error, 'Post deletion');
743
- return {
744
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
745
- isError: true,
746
- };
747
- }
748
- return {
749
- content: [{ type: 'text', text: `Error deleting post: ${error.message}` }],
750
- isError: true,
751
- };
752
- }
753
- }
524
+ withErrorHandling('ghost_delete_post', deletePostSchema, async (input) => {
525
+ const { id } = input;
526
+ await ghostService.deletePost(id);
527
+ console.error(`Post deleted successfully. Post ID: ${id}`);
528
+
529
+ return {
530
+ content: [{ type: 'text', text: `Post ${id} has been successfully deleted.` }],
531
+ };
532
+ })
754
533
  );
755
534
 
756
535
  // =============================================================================
@@ -804,47 +583,23 @@ server.registerTool(
804
583
  'Retrieves a list of pages from Ghost CMS with pagination, filtering, and sorting options.',
805
584
  inputSchema: pageQuerySchema,
806
585
  },
807
- async (rawInput) => {
808
- const validation = validateToolInput(pageQuerySchema, rawInput, 'ghost_get_pages');
809
- if (!validation.success) {
810
- return validation.errorResponse;
811
- }
812
- const input = validation.data;
813
-
814
- console.error(`Executing tool: ghost_get_pages`);
815
- try {
816
- await loadServices();
817
-
818
- const options = {};
819
- if (input.limit !== undefined) options.limit = input.limit;
820
- if (input.page !== undefined) options.page = input.page;
821
- if (input.filter !== undefined) options.filter = input.filter;
822
- if (input.include !== undefined) options.include = input.include;
823
- if (input.fields !== undefined) options.fields = input.fields;
824
- if (input.formats !== undefined) options.formats = input.formats;
825
- if (input.order !== undefined) options.order = input.order;
826
-
827
- const pages = await ghostService.getPages(options);
828
- console.error(`Retrieved ${pages.length} pages from Ghost.`);
829
-
830
- return {
831
- content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
832
- };
833
- } catch (error) {
834
- console.error(`Error in ghost_get_pages:`, error);
835
- if (error.name === 'ZodError') {
836
- const validationError = ValidationError.fromZod(error, 'Page query');
837
- return {
838
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
839
- isError: true,
840
- };
841
- }
842
- return {
843
- content: [{ type: 'text', text: `Error retrieving pages: ${error.message}` }],
844
- isError: true,
845
- };
846
- }
847
- }
586
+ withErrorHandling('ghost_get_pages', pageQuerySchema, async (input) => {
587
+ const options = {};
588
+ if (input.limit !== undefined) options.limit = input.limit;
589
+ if (input.page !== undefined) options.page = input.page;
590
+ if (input.filter !== undefined) options.filter = input.filter;
591
+ if (input.include !== undefined) options.include = input.include;
592
+ if (input.fields !== undefined) options.fields = input.fields;
593
+ if (input.formats !== undefined) options.formats = input.formats;
594
+ if (input.order !== undefined) options.order = input.order;
595
+
596
+ const pages = await ghostService.getPages(options);
597
+ console.error(`Retrieved ${pages.length} pages from Ghost.`);
598
+
599
+ return {
600
+ content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
601
+ };
602
+ })
848
603
  );
849
604
 
850
605
  // Get Page Tool
@@ -854,43 +609,22 @@ server.registerTool(
854
609
  description: 'Retrieves a single page from Ghost CMS by ID or slug.',
855
610
  inputSchema: getPageSchema,
856
611
  },
857
- async (rawInput) => {
858
- const validation = validateToolInput(getPageSchema, rawInput, 'ghost_get_page');
859
- if (!validation.success) {
860
- return validation.errorResponse;
861
- }
862
- const input = validation.data;
863
-
864
- console.error(`Executing tool: ghost_get_page`);
865
- try {
866
- await loadServices();
867
-
868
- const options = {};
869
- if (input.include !== undefined) options.include = input.include;
612
+ withErrorHandling('ghost_get_page', getPageSchema, async (input) => {
613
+ const options = {};
614
+ if (input.include !== undefined) options.include = input.include;
870
615
 
871
- const identifier = input.id || `slug/${input.slug}`;
616
+ if (!input.id && !input.slug) {
617
+ throw new Error('Either id or slug is required');
618
+ }
619
+ const identifier = input.id || `slug/${input.slug}`;
872
620
 
873
- const page = await ghostService.getPage(identifier, options);
874
- console.error(`Retrieved page: ${page.title} (ID: ${page.id})`);
621
+ const page = await ghostService.getPage(identifier, options);
622
+ console.error(`Retrieved page: ${page.title} (ID: ${page.id})`);
875
623
 
876
- return {
877
- content: [{ type: 'text', text: JSON.stringify(page, null, 2) }],
878
- };
879
- } catch (error) {
880
- console.error(`Error in ghost_get_page:`, error);
881
- if (error.name === 'ZodError') {
882
- const validationError = ValidationError.fromZod(error, 'Get page');
883
- return {
884
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
885
- isError: true,
886
- };
887
- }
888
- return {
889
- content: [{ type: 'text', text: `Error retrieving page: ${error.message}` }],
890
- isError: true,
891
- };
892
- }
893
- }
624
+ return {
625
+ content: [{ type: 'text', text: JSON.stringify(page, null, 2) }],
626
+ };
627
+ })
894
628
  );
895
629
 
896
630
  // Create Page Tool
@@ -901,38 +635,14 @@ server.registerTool(
901
635
  'Creates a new page in Ghost CMS. Note: Pages do NOT typically use tags (unlike posts).',
902
636
  inputSchema: createPageSchema,
903
637
  },
904
- async (rawInput) => {
905
- const validation = validateToolInput(createPageSchema, rawInput, 'ghost_create_page');
906
- if (!validation.success) {
907
- return validation.errorResponse;
908
- }
909
- const input = validation.data;
910
-
911
- console.error(`Executing tool: ghost_create_page with title: ${input.title}`);
912
- try {
913
- await loadServices();
914
-
915
- const createdPage = await pageService.createPageService(input);
916
- console.error(`Page created successfully. Page ID: ${createdPage.id}`);
638
+ withErrorHandling('ghost_create_page', createPageSchema, async (input) => {
639
+ const createdPage = await pageService.createPageService(input);
640
+ console.error(`Page created successfully. Page ID: ${createdPage.id}`);
917
641
 
918
- return {
919
- content: [{ type: 'text', text: JSON.stringify(createdPage, null, 2) }],
920
- };
921
- } catch (error) {
922
- console.error(`Error in ghost_create_page:`, error);
923
- if (error.name === 'ZodError') {
924
- const validationError = ValidationError.fromZod(error, 'Page creation');
925
- return {
926
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
927
- isError: true,
928
- };
929
- }
930
- return {
931
- content: [{ type: 'text', text: `Error creating page: ${error.message}` }],
932
- isError: true,
933
- };
934
- }
935
- }
642
+ return {
643
+ content: [{ type: 'text', text: JSON.stringify(createdPage, null, 2) }],
644
+ };
645
+ })
936
646
  );
937
647
 
938
648
  // Update Page Tool
@@ -943,40 +653,16 @@ server.registerTool(
943
653
  'Updates an existing page in Ghost CMS. Can update title, content, status, images, and SEO fields. Only the provided fields are changed; omitted fields remain unchanged.',
944
654
  inputSchema: updatePageInputSchema,
945
655
  },
946
- async (rawInput) => {
947
- const validation = validateToolInput(updatePageInputSchema, rawInput, 'ghost_update_page');
948
- if (!validation.success) {
949
- return validation.errorResponse;
950
- }
951
- const input = validation.data;
952
-
953
- console.error(`Executing tool: ghost_update_page for page ID: ${input.id}`);
954
- try {
955
- await loadServices();
656
+ withErrorHandling('ghost_update_page', updatePageInputSchema, async (input) => {
657
+ const { id, ...updateData } = input;
956
658
 
957
- const { id, ...updateData } = input;
659
+ const updatedPage = await ghostService.updatePage(id, updateData);
660
+ console.error(`Page updated successfully. Page ID: ${updatedPage.id}`);
958
661
 
959
- const updatedPage = await ghostService.updatePage(id, updateData);
960
- console.error(`Page updated successfully. Page ID: ${updatedPage.id}`);
961
-
962
- return {
963
- content: [{ type: 'text', text: JSON.stringify(updatedPage, null, 2) }],
964
- };
965
- } catch (error) {
966
- console.error(`Error in ghost_update_page:`, error);
967
- if (error.name === 'ZodError') {
968
- const validationError = ValidationError.fromZod(error, 'Page update');
969
- return {
970
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
971
- isError: true,
972
- };
973
- }
974
- return {
975
- content: [{ type: 'text', text: `Error updating page: ${error.message}` }],
976
- isError: true,
977
- };
978
- }
979
- }
662
+ return {
663
+ content: [{ type: 'text', text: JSON.stringify(updatedPage, null, 2) }],
664
+ };
665
+ })
980
666
  );
981
667
 
982
668
  // Delete Page Tool
@@ -987,38 +673,15 @@ server.registerTool(
987
673
  'Deletes a page from Ghost CMS by ID. This operation is permanent and cannot be undone.',
988
674
  inputSchema: deletePageSchema,
989
675
  },
990
- async (rawInput) => {
991
- const validation = validateToolInput(deletePageSchema, rawInput, 'ghost_delete_page');
992
- if (!validation.success) {
993
- return validation.errorResponse;
994
- }
995
- const { id } = validation.data;
996
-
997
- console.error(`Executing tool: ghost_delete_page for page ID: ${id}`);
998
- try {
999
- await loadServices();
1000
-
1001
- await ghostService.deletePage(id);
1002
- console.error(`Page deleted successfully. Page ID: ${id}`);
1003
-
1004
- return {
1005
- content: [{ type: 'text', text: `Page ${id} has been successfully deleted.` }],
1006
- };
1007
- } catch (error) {
1008
- console.error(`Error in ghost_delete_page:`, error);
1009
- if (error.name === 'ZodError') {
1010
- const validationError = ValidationError.fromZod(error, 'Page deletion');
1011
- return {
1012
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1013
- isError: true,
1014
- };
1015
- }
1016
- return {
1017
- content: [{ type: 'text', text: `Error deleting page: ${error.message}` }],
1018
- isError: true,
1019
- };
1020
- }
1021
- }
676
+ withErrorHandling('ghost_delete_page', deletePageSchema, async (input) => {
677
+ const { id } = input;
678
+ await ghostService.deletePage(id);
679
+ console.error(`Page deleted successfully. Page ID: ${id}`);
680
+
681
+ return {
682
+ content: [{ type: 'text', text: `Page ${id} has been successfully deleted.` }],
683
+ };
684
+ })
1022
685
  );
1023
686
 
1024
687
  // Search Pages Tool
@@ -1028,42 +691,18 @@ server.registerTool(
1028
691
  description: 'Search for pages in Ghost CMS by query string with optional status filtering.',
1029
692
  inputSchema: searchPagesSchema,
1030
693
  },
1031
- async (rawInput) => {
1032
- const validation = validateToolInput(searchPagesSchema, rawInput, 'ghost_search_pages');
1033
- if (!validation.success) {
1034
- return validation.errorResponse;
1035
- }
1036
- const input = validation.data;
694
+ withErrorHandling('ghost_search_pages', searchPagesSchema, async (input) => {
695
+ const options = {};
696
+ if (input.status !== undefined) options.status = input.status;
697
+ if (input.limit !== undefined) options.limit = input.limit;
1037
698
 
1038
- console.error(`Executing tool: ghost_search_pages with query: ${input.query}`);
1039
- try {
1040
- await loadServices();
1041
-
1042
- const options = {};
1043
- if (input.status !== undefined) options.status = input.status;
1044
- if (input.limit !== undefined) options.limit = input.limit;
1045
-
1046
- const pages = await ghostService.searchPages(input.query, options);
1047
- console.error(`Found ${pages.length} pages matching "${input.query}".`);
699
+ const pages = await ghostService.searchPages(input.query, options);
700
+ console.error(`Found ${pages.length} pages matching "${input.query}".`);
1048
701
 
1049
- return {
1050
- content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
1051
- };
1052
- } catch (error) {
1053
- console.error(`Error in ghost_search_pages:`, error);
1054
- if (error.name === 'ZodError') {
1055
- const validationError = ValidationError.fromZod(error, 'Page search');
1056
- return {
1057
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1058
- isError: true,
1059
- };
1060
- }
1061
- return {
1062
- content: [{ type: 'text', text: `Error searching pages: ${error.message}` }],
1063
- isError: true,
1064
- };
1065
- }
1066
- }
702
+ return {
703
+ content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
704
+ };
705
+ })
1067
706
  );
1068
707
 
1069
708
  // =============================================================================
@@ -1104,38 +743,14 @@ server.registerTool(
1104
743
  description: 'Creates a new member (subscriber) in Ghost CMS.',
1105
744
  inputSchema: createMemberSchema,
1106
745
  },
1107
- async (rawInput) => {
1108
- const validation = validateToolInput(createMemberSchema, rawInput, 'ghost_create_member');
1109
- if (!validation.success) {
1110
- return validation.errorResponse;
1111
- }
1112
- const input = validation.data;
1113
-
1114
- console.error(`Executing tool: ghost_create_member with email: ${input.email}`);
1115
- try {
1116
- await loadServices();
746
+ withErrorHandling('ghost_create_member', createMemberSchema, async (input) => {
747
+ const createdMember = await ghostService.createMember(input);
748
+ console.error(`Member created successfully. Member ID: ${createdMember.id}`);
1117
749
 
1118
- const createdMember = await ghostService.createMember(input);
1119
- console.error(`Member created successfully. Member ID: ${createdMember.id}`);
1120
-
1121
- return {
1122
- content: [{ type: 'text', text: JSON.stringify(createdMember, null, 2) }],
1123
- };
1124
- } catch (error) {
1125
- console.error(`Error in ghost_create_member:`, error);
1126
- if (error.name === 'ZodError') {
1127
- const validationError = ValidationError.fromZod(error, 'Member creation');
1128
- return {
1129
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1130
- isError: true,
1131
- };
1132
- }
1133
- return {
1134
- content: [{ type: 'text', text: `Error creating member: ${error.message}` }],
1135
- isError: true,
1136
- };
1137
- }
1138
- }
750
+ return {
751
+ content: [{ type: 'text', text: JSON.stringify(createdMember, null, 2) }],
752
+ };
753
+ })
1139
754
  );
1140
755
 
1141
756
  // Update Member Tool
@@ -1145,40 +760,16 @@ server.registerTool(
1145
760
  description: 'Updates an existing member in Ghost CMS. All fields except id are optional.',
1146
761
  inputSchema: updateMemberInputSchema,
1147
762
  },
1148
- async (rawInput) => {
1149
- const validation = validateToolInput(updateMemberInputSchema, rawInput, 'ghost_update_member');
1150
- if (!validation.success) {
1151
- return validation.errorResponse;
1152
- }
1153
- const input = validation.data;
1154
-
1155
- console.error(`Executing tool: ghost_update_member for member ID: ${input.id}`);
1156
- try {
1157
- await loadServices();
1158
-
1159
- const { id, ...updateData } = input;
763
+ withErrorHandling('ghost_update_member', updateMemberInputSchema, async (input) => {
764
+ const { id, ...updateData } = input;
1160
765
 
1161
- const updatedMember = await ghostService.updateMember(id, updateData);
1162
- console.error(`Member updated successfully. Member ID: ${updatedMember.id}`);
766
+ const updatedMember = await ghostService.updateMember(id, updateData);
767
+ console.error(`Member updated successfully. Member ID: ${updatedMember.id}`);
1163
768
 
1164
- return {
1165
- content: [{ type: 'text', text: JSON.stringify(updatedMember, null, 2) }],
1166
- };
1167
- } catch (error) {
1168
- console.error(`Error in ghost_update_member:`, error);
1169
- if (error.name === 'ZodError') {
1170
- const validationError = ValidationError.fromZod(error, 'Member update');
1171
- return {
1172
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1173
- isError: true,
1174
- };
1175
- }
1176
- return {
1177
- content: [{ type: 'text', text: `Error updating member: ${error.message}` }],
1178
- isError: true,
1179
- };
1180
- }
1181
- }
769
+ return {
770
+ content: [{ type: 'text', text: JSON.stringify(updatedMember, null, 2) }],
771
+ };
772
+ })
1182
773
  );
1183
774
 
1184
775
  // Delete Member Tool
@@ -1189,38 +780,15 @@ server.registerTool(
1189
780
  'Deletes a member from Ghost CMS by ID. This operation is permanent and cannot be undone.',
1190
781
  inputSchema: deleteMemberSchema,
1191
782
  },
1192
- async (rawInput) => {
1193
- const validation = validateToolInput(deleteMemberSchema, rawInput, 'ghost_delete_member');
1194
- if (!validation.success) {
1195
- return validation.errorResponse;
1196
- }
1197
- const { id } = validation.data;
1198
-
1199
- console.error(`Executing tool: ghost_delete_member for member ID: ${id}`);
1200
- try {
1201
- await loadServices();
1202
-
1203
- await ghostService.deleteMember(id);
1204
- console.error(`Member deleted successfully. Member ID: ${id}`);
1205
-
1206
- return {
1207
- content: [{ type: 'text', text: `Member ${id} has been successfully deleted.` }],
1208
- };
1209
- } catch (error) {
1210
- console.error(`Error in ghost_delete_member:`, error);
1211
- if (error.name === 'ZodError') {
1212
- const validationError = ValidationError.fromZod(error, 'Member deletion');
1213
- return {
1214
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1215
- isError: true,
1216
- };
1217
- }
1218
- return {
1219
- content: [{ type: 'text', text: `Error deleting member: ${error.message}` }],
1220
- isError: true,
1221
- };
1222
- }
1223
- }
783
+ withErrorHandling('ghost_delete_member', deleteMemberSchema, async (input) => {
784
+ const { id } = input;
785
+ await ghostService.deleteMember(id);
786
+ console.error(`Member deleted successfully. Member ID: ${id}`);
787
+
788
+ return {
789
+ content: [{ type: 'text', text: `Member ${id} has been successfully deleted.` }],
790
+ };
791
+ })
1224
792
  );
1225
793
 
1226
794
  // Get Members Tool
@@ -1231,46 +799,22 @@ server.registerTool(
1231
799
  'Retrieves a list of members (subscribers) from Ghost CMS with optional filtering, pagination, and includes.',
1232
800
  inputSchema: getMembersSchema,
1233
801
  },
1234
- async (rawInput) => {
1235
- const validation = validateToolInput(getMembersSchema, rawInput, 'ghost_get_members');
1236
- if (!validation.success) {
1237
- return validation.errorResponse;
1238
- }
1239
- const input = validation.data;
1240
-
1241
- console.error(`Executing tool: ghost_get_members`);
1242
- try {
1243
- await loadServices();
1244
-
1245
- const options = {};
1246
- if (input.limit !== undefined) options.limit = input.limit;
1247
- if (input.page !== undefined) options.page = input.page;
1248
- if (input.filter !== undefined) options.filter = input.filter;
1249
- if (input.order !== undefined) options.order = input.order;
1250
- if (input.include !== undefined) options.include = input.include;
1251
-
1252
- const members = await ghostService.getMembers(options);
1253
- console.error(`Retrieved ${members.length} members from Ghost.`);
1254
-
1255
- return {
1256
- content: [{ type: 'text', text: JSON.stringify(members, null, 2) }],
1257
- };
1258
- } catch (error) {
1259
- console.error(`Error in ghost_get_members:`, error);
1260
- if (error.name === 'ZodError') {
1261
- const validationError = ValidationError.fromZod(error, 'Member query');
1262
- return {
1263
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1264
- isError: true,
1265
- };
1266
- }
1267
- return {
1268
- content: [{ type: 'text', text: `Error retrieving members: ${error.message}` }],
1269
- isError: true,
1270
- };
1271
- }
1272
- }
1273
- );
802
+ withErrorHandling('ghost_get_members', getMembersSchema, async (input) => {
803
+ const options = {};
804
+ if (input.limit !== undefined) options.limit = input.limit;
805
+ if (input.page !== undefined) options.page = input.page;
806
+ if (input.filter !== undefined) options.filter = input.filter;
807
+ if (input.order !== undefined) options.order = input.order;
808
+ if (input.include !== undefined) options.include = input.include;
809
+
810
+ const members = await ghostService.getMembers(options);
811
+ console.error(`Retrieved ${members.length} members from Ghost.`);
812
+
813
+ return {
814
+ content: [{ type: 'text', text: JSON.stringify(members, null, 2) }],
815
+ };
816
+ })
817
+ );
1274
818
 
1275
819
  // Get Member Tool
1276
820
  server.registerTool(
@@ -1280,38 +824,15 @@ server.registerTool(
1280
824
  'Retrieves a single member from Ghost CMS by ID or email. Provide either id OR email.',
1281
825
  inputSchema: getMemberSchema,
1282
826
  },
1283
- async (rawInput) => {
1284
- const validation = validateToolInput(getMemberSchema, rawInput, 'ghost_get_member');
1285
- if (!validation.success) {
1286
- return validation.errorResponse;
1287
- }
1288
- const { id, email } = validation.data;
1289
-
1290
- console.error(`Executing tool: ghost_get_member for ${id ? `ID: ${id}` : `email: ${email}`}`);
1291
- try {
1292
- await loadServices();
1293
-
1294
- const member = await ghostService.getMember({ id, email });
1295
- console.error(`Retrieved member: ${member.email} (ID: ${member.id})`);
1296
-
1297
- return {
1298
- content: [{ type: 'text', text: JSON.stringify(member, null, 2) }],
1299
- };
1300
- } catch (error) {
1301
- console.error(`Error in ghost_get_member:`, error);
1302
- if (error.name === 'ZodError') {
1303
- const validationError = ValidationError.fromZod(error, 'Member lookup');
1304
- return {
1305
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1306
- isError: true,
1307
- };
1308
- }
1309
- return {
1310
- content: [{ type: 'text', text: `Error retrieving member: ${error.message}` }],
1311
- isError: true,
1312
- };
1313
- }
1314
- }
827
+ withErrorHandling('ghost_get_member', getMemberSchema, async (input) => {
828
+ const { id, email } = input;
829
+ const member = await ghostService.getMember({ id, email });
830
+ console.error(`Retrieved member: ${member.email} (ID: ${member.id})`);
831
+
832
+ return {
833
+ content: [{ type: 'text', text: JSON.stringify(member, null, 2) }],
834
+ };
835
+ })
1315
836
  );
1316
837
 
1317
838
  // Search Members Tool
@@ -1321,41 +842,18 @@ server.registerTool(
1321
842
  description: 'Searches for members by name or email in Ghost CMS.',
1322
843
  inputSchema: searchMembersSchema,
1323
844
  },
1324
- async (rawInput) => {
1325
- const validation = validateToolInput(searchMembersSchema, rawInput, 'ghost_search_members');
1326
- if (!validation.success) {
1327
- return validation.errorResponse;
1328
- }
1329
- const { query, limit } = validation.data;
845
+ withErrorHandling('ghost_search_members', searchMembersSchema, async (input) => {
846
+ const { query, limit } = input;
847
+ const options = {};
848
+ if (limit !== undefined) options.limit = limit;
1330
849
 
1331
- console.error(`Executing tool: ghost_search_members with query: ${query}`);
1332
- try {
1333
- await loadServices();
1334
-
1335
- const options = {};
1336
- if (limit !== undefined) options.limit = limit;
850
+ const members = await ghostService.searchMembers(query, options);
851
+ console.error(`Found ${members.length} members matching "${query}".`);
1337
852
 
1338
- const members = await ghostService.searchMembers(query, options);
1339
- console.error(`Found ${members.length} members matching "${query}".`);
1340
-
1341
- return {
1342
- content: [{ type: 'text', text: JSON.stringify(members, null, 2) }],
1343
- };
1344
- } catch (error) {
1345
- console.error(`Error in ghost_search_members:`, error);
1346
- if (error.name === 'ZodError') {
1347
- const validationError = ValidationError.fromZod(error, 'Member search');
1348
- return {
1349
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1350
- isError: true,
1351
- };
1352
- }
1353
- return {
1354
- content: [{ type: 'text', text: `Error searching members: ${error.message}` }],
1355
- isError: true,
1356
- };
1357
- }
1358
- }
853
+ return {
854
+ content: [{ type: 'text', text: JSON.stringify(members, null, 2) }],
855
+ };
856
+ })
1359
857
  );
1360
858
 
1361
859
  // =============================================================================
@@ -1374,44 +872,20 @@ server.registerTool(
1374
872
  description: 'Retrieves a list of newsletters from Ghost CMS with optional filtering.',
1375
873
  inputSchema: newsletterQuerySchema,
1376
874
  },
1377
- async (rawInput) => {
1378
- const validation = validateToolInput(newsletterQuerySchema, rawInput, 'ghost_get_newsletters');
1379
- if (!validation.success) {
1380
- return validation.errorResponse;
1381
- }
1382
- const input = validation.data;
1383
-
1384
- console.error(`Executing tool: ghost_get_newsletters`);
1385
- try {
1386
- await loadServices();
1387
-
1388
- const options = {};
1389
- if (input.limit !== undefined) options.limit = input.limit;
1390
- if (input.page !== undefined) options.page = input.page;
1391
- if (input.filter !== undefined) options.filter = input.filter;
1392
- if (input.order !== undefined) options.order = input.order;
1393
-
1394
- const newsletters = await ghostService.getNewsletters(options);
1395
- console.error(`Retrieved ${newsletters.length} newsletters from Ghost.`);
1396
-
1397
- return {
1398
- content: [{ type: 'text', text: JSON.stringify(newsletters, null, 2) }],
1399
- };
1400
- } catch (error) {
1401
- console.error(`Error in ghost_get_newsletters:`, error);
1402
- if (error.name === 'ZodError') {
1403
- const validationError = ValidationError.fromZod(error, 'Newsletter query');
1404
- return {
1405
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1406
- isError: true,
1407
- };
1408
- }
1409
- return {
1410
- content: [{ type: 'text', text: `Error retrieving newsletters: ${error.message}` }],
1411
- isError: true,
1412
- };
1413
- }
1414
- }
875
+ withErrorHandling('ghost_get_newsletters', newsletterQuerySchema, async (input) => {
876
+ const options = {};
877
+ if (input.limit !== undefined) options.limit = input.limit;
878
+ if (input.page !== undefined) options.page = input.page;
879
+ if (input.filter !== undefined) options.filter = input.filter;
880
+ if (input.order !== undefined) options.order = input.order;
881
+
882
+ const newsletters = await ghostService.getNewsletters(options);
883
+ console.error(`Retrieved ${newsletters.length} newsletters from Ghost.`);
884
+
885
+ return {
886
+ content: [{ type: 'text', text: JSON.stringify(newsletters, null, 2) }],
887
+ };
888
+ })
1415
889
  );
1416
890
 
1417
891
  // Get Newsletter Tool
@@ -1421,38 +895,15 @@ server.registerTool(
1421
895
  description: 'Retrieves a single newsletter from Ghost CMS by ID.',
1422
896
  inputSchema: getNewsletterSchema,
1423
897
  },
1424
- async (rawInput) => {
1425
- const validation = validateToolInput(getNewsletterSchema, rawInput, 'ghost_get_newsletter');
1426
- if (!validation.success) {
1427
- return validation.errorResponse;
1428
- }
1429
- const { id } = validation.data;
1430
-
1431
- console.error(`Executing tool: ghost_get_newsletter for ID: ${id}`);
1432
- try {
1433
- await loadServices();
1434
-
1435
- const newsletter = await ghostService.getNewsletter(id);
1436
- console.error(`Retrieved newsletter: ${newsletter.name} (ID: ${newsletter.id})`);
1437
-
1438
- return {
1439
- content: [{ type: 'text', text: JSON.stringify(newsletter, null, 2) }],
1440
- };
1441
- } catch (error) {
1442
- console.error(`Error in ghost_get_newsletter:`, error);
1443
- if (error.name === 'ZodError') {
1444
- const validationError = ValidationError.fromZod(error, 'Newsletter retrieval');
1445
- return {
1446
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1447
- isError: true,
1448
- };
1449
- }
1450
- return {
1451
- content: [{ type: 'text', text: `Error retrieving newsletter: ${error.message}` }],
1452
- isError: true,
1453
- };
1454
- }
1455
- }
898
+ withErrorHandling('ghost_get_newsletter', getNewsletterSchema, async (input) => {
899
+ const { id } = input;
900
+ const newsletter = await ghostService.getNewsletter(id);
901
+ console.error(`Retrieved newsletter: ${newsletter.name} (ID: ${newsletter.id})`);
902
+
903
+ return {
904
+ content: [{ type: 'text', text: JSON.stringify(newsletter, null, 2) }],
905
+ };
906
+ })
1456
907
  );
1457
908
 
1458
909
  // Create Newsletter Tool
@@ -1463,42 +914,14 @@ server.registerTool(
1463
914
  'Creates a new newsletter in Ghost CMS with customizable sender settings and display options.',
1464
915
  inputSchema: createNewsletterSchema,
1465
916
  },
1466
- async (rawInput) => {
1467
- const validation = validateToolInput(
1468
- createNewsletterSchema,
1469
- rawInput,
1470
- 'ghost_create_newsletter'
1471
- );
1472
- if (!validation.success) {
1473
- return validation.errorResponse;
1474
- }
1475
- const input = validation.data;
1476
-
1477
- console.error(`Executing tool: ghost_create_newsletter with name: ${input.name}`);
1478
- try {
1479
- await loadServices();
1480
-
1481
- const createdNewsletter = await newsletterService.createNewsletterService(input);
1482
- console.error(`Newsletter created successfully. Newsletter ID: ${createdNewsletter.id}`);
917
+ withErrorHandling('ghost_create_newsletter', createNewsletterSchema, async (input) => {
918
+ const createdNewsletter = await newsletterService.createNewsletterService(input);
919
+ console.error(`Newsletter created successfully. Newsletter ID: ${createdNewsletter.id}`);
1483
920
 
1484
- return {
1485
- content: [{ type: 'text', text: JSON.stringify(createdNewsletter, null, 2) }],
1486
- };
1487
- } catch (error) {
1488
- console.error(`Error in ghost_create_newsletter:`, error);
1489
- if (error.name === 'ZodError') {
1490
- const validationError = ValidationError.fromZod(error, 'Newsletter creation');
1491
- return {
1492
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1493
- isError: true,
1494
- };
1495
- }
1496
- return {
1497
- content: [{ type: 'text', text: `Error creating newsletter: ${error.message}` }],
1498
- isError: true,
1499
- };
1500
- }
1501
- }
921
+ return {
922
+ content: [{ type: 'text', text: JSON.stringify(createdNewsletter, null, 2) }],
923
+ };
924
+ })
1502
925
  );
1503
926
 
1504
927
  // Update Newsletter Tool
@@ -1509,44 +932,16 @@ server.registerTool(
1509
932
  'Updates an existing newsletter in Ghost CMS. Can update name, description, sender settings, and display options.',
1510
933
  inputSchema: updateNewsletterInputSchema,
1511
934
  },
1512
- async (rawInput) => {
1513
- const validation = validateToolInput(
1514
- updateNewsletterInputSchema,
1515
- rawInput,
1516
- 'ghost_update_newsletter'
1517
- );
1518
- if (!validation.success) {
1519
- return validation.errorResponse;
1520
- }
1521
- const input = validation.data;
935
+ withErrorHandling('ghost_update_newsletter', updateNewsletterInputSchema, async (input) => {
936
+ const { id, ...updateData } = input;
1522
937
 
1523
- console.error(`Executing tool: ghost_update_newsletter for newsletter ID: ${input.id}`);
1524
- try {
1525
- await loadServices();
1526
-
1527
- const { id, ...updateData } = input;
938
+ const updatedNewsletter = await ghostService.updateNewsletter(id, updateData);
939
+ console.error(`Newsletter updated successfully. Newsletter ID: ${updatedNewsletter.id}`);
1528
940
 
1529
- const updatedNewsletter = await ghostService.updateNewsletter(id, updateData);
1530
- console.error(`Newsletter updated successfully. Newsletter ID: ${updatedNewsletter.id}`);
1531
-
1532
- return {
1533
- content: [{ type: 'text', text: JSON.stringify(updatedNewsletter, null, 2) }],
1534
- };
1535
- } catch (error) {
1536
- console.error(`Error in ghost_update_newsletter:`, error);
1537
- if (error.name === 'ZodError') {
1538
- const validationError = ValidationError.fromZod(error, 'Newsletter update');
1539
- return {
1540
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1541
- isError: true,
1542
- };
1543
- }
1544
- return {
1545
- content: [{ type: 'text', text: `Error updating newsletter: ${error.message}` }],
1546
- isError: true,
1547
- };
1548
- }
1549
- }
941
+ return {
942
+ content: [{ type: 'text', text: JSON.stringify(updatedNewsletter, null, 2) }],
943
+ };
944
+ })
1550
945
  );
1551
946
 
1552
947
  // Delete Newsletter Tool
@@ -1557,42 +952,15 @@ server.registerTool(
1557
952
  'Deletes a newsletter from Ghost CMS by ID. This operation is permanent and cannot be undone.',
1558
953
  inputSchema: deleteNewsletterSchema,
1559
954
  },
1560
- async (rawInput) => {
1561
- const validation = validateToolInput(
1562
- deleteNewsletterSchema,
1563
- rawInput,
1564
- 'ghost_delete_newsletter'
1565
- );
1566
- if (!validation.success) {
1567
- return validation.errorResponse;
1568
- }
1569
- const { id } = validation.data;
1570
-
1571
- console.error(`Executing tool: ghost_delete_newsletter for newsletter ID: ${id}`);
1572
- try {
1573
- await loadServices();
1574
-
1575
- await ghostService.deleteNewsletter(id);
1576
- console.error(`Newsletter deleted successfully. Newsletter ID: ${id}`);
1577
-
1578
- return {
1579
- content: [{ type: 'text', text: `Newsletter ${id} has been successfully deleted.` }],
1580
- };
1581
- } catch (error) {
1582
- console.error(`Error in ghost_delete_newsletter:`, error);
1583
- if (error.name === 'ZodError') {
1584
- const validationError = ValidationError.fromZod(error, 'Newsletter deletion');
1585
- return {
1586
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1587
- isError: true,
1588
- };
1589
- }
1590
- return {
1591
- content: [{ type: 'text', text: `Error deleting newsletter: ${error.message}` }],
1592
- isError: true,
1593
- };
1594
- }
1595
- }
955
+ withErrorHandling('ghost_delete_newsletter', deleteNewsletterSchema, async (input) => {
956
+ const { id } = input;
957
+ await ghostService.deleteNewsletter(id);
958
+ console.error(`Newsletter deleted successfully. Newsletter ID: ${id}`);
959
+
960
+ return {
961
+ content: [{ type: 'text', text: `Newsletter ${id} has been successfully deleted.` }],
962
+ };
963
+ })
1596
964
  );
1597
965
 
1598
966
  // --- Tier Tools ---
@@ -1610,38 +978,14 @@ server.registerTool(
1610
978
  'Retrieves a list of tiers (membership levels) from Ghost CMS with optional filtering by type (free/paid).',
1611
979
  inputSchema: tierQuerySchema,
1612
980
  },
1613
- async (rawInput) => {
1614
- const validation = validateToolInput(tierQuerySchema, rawInput, 'ghost_get_tiers');
1615
- if (!validation.success) {
1616
- return validation.errorResponse;
1617
- }
1618
- const input = validation.data;
981
+ withErrorHandling('ghost_get_tiers', tierQuerySchema, async (input) => {
982
+ const tiers = await ghostService.getTiers(input);
983
+ console.error(`Retrieved ${tiers.length} tiers`);
1619
984
 
1620
- console.error(`Executing tool: ghost_get_tiers`);
1621
- try {
1622
- await loadServices();
1623
-
1624
- const tiers = await ghostService.getTiers(input);
1625
- console.error(`Retrieved ${tiers.length} tiers`);
1626
-
1627
- return {
1628
- content: [{ type: 'text', text: JSON.stringify(tiers, null, 2) }],
1629
- };
1630
- } catch (error) {
1631
- console.error(`Error in ghost_get_tiers:`, error);
1632
- if (error.name === 'ZodError') {
1633
- const validationError = ValidationError.fromZod(error, 'Tier query');
1634
- return {
1635
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1636
- isError: true,
1637
- };
1638
- }
1639
- return {
1640
- content: [{ type: 'text', text: `Error getting tiers: ${error.message}` }],
1641
- isError: true,
1642
- };
1643
- }
1644
- }
985
+ return {
986
+ content: [{ type: 'text', text: JSON.stringify(tiers, null, 2) }],
987
+ };
988
+ })
1645
989
  );
1646
990
 
1647
991
  // Get Tier Tool
@@ -1651,38 +995,15 @@ server.registerTool(
1651
995
  description: 'Retrieves a single tier (membership level) from Ghost CMS by ID.',
1652
996
  inputSchema: getTierSchema,
1653
997
  },
1654
- async (rawInput) => {
1655
- const validation = validateToolInput(getTierSchema, rawInput, 'ghost_get_tier');
1656
- if (!validation.success) {
1657
- return validation.errorResponse;
1658
- }
1659
- const { id } = validation.data;
1660
-
1661
- console.error(`Executing tool: ghost_get_tier for tier ID: ${id}`);
1662
- try {
1663
- await loadServices();
1664
-
1665
- const tier = await ghostService.getTier(id);
1666
- console.error(`Tier retrieved successfully. Tier ID: ${tier.id}`);
1667
-
1668
- return {
1669
- content: [{ type: 'text', text: JSON.stringify(tier, null, 2) }],
1670
- };
1671
- } catch (error) {
1672
- console.error(`Error in ghost_get_tier:`, error);
1673
- if (error.name === 'ZodError') {
1674
- const validationError = ValidationError.fromZod(error, 'Tier retrieval');
1675
- return {
1676
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1677
- isError: true,
1678
- };
1679
- }
1680
- return {
1681
- content: [{ type: 'text', text: `Error getting tier: ${error.message}` }],
1682
- isError: true,
1683
- };
1684
- }
1685
- }
998
+ withErrorHandling('ghost_get_tier', getTierSchema, async (input) => {
999
+ const { id } = input;
1000
+ const tier = await ghostService.getTier(id);
1001
+ console.error(`Tier retrieved successfully. Tier ID: ${tier.id}`);
1002
+
1003
+ return {
1004
+ content: [{ type: 'text', text: JSON.stringify(tier, null, 2) }],
1005
+ };
1006
+ })
1686
1007
  );
1687
1008
 
1688
1009
  // Create Tier Tool
@@ -1692,38 +1013,14 @@ server.registerTool(
1692
1013
  description: 'Creates a new tier (membership level) in Ghost CMS with pricing and benefits.',
1693
1014
  inputSchema: createTierSchema,
1694
1015
  },
1695
- async (rawInput) => {
1696
- const validation = validateToolInput(createTierSchema, rawInput, 'ghost_create_tier');
1697
- if (!validation.success) {
1698
- return validation.errorResponse;
1699
- }
1700
- const input = validation.data;
1016
+ withErrorHandling('ghost_create_tier', createTierSchema, async (input) => {
1017
+ const tier = await ghostService.createTier(input);
1018
+ console.error(`Tier created successfully. Tier ID: ${tier.id}`);
1701
1019
 
1702
- console.error(`Executing tool: ghost_create_tier`);
1703
- try {
1704
- await loadServices();
1705
-
1706
- const tier = await ghostService.createTier(input);
1707
- console.error(`Tier created successfully. Tier ID: ${tier.id}`);
1708
-
1709
- return {
1710
- content: [{ type: 'text', text: JSON.stringify(tier, null, 2) }],
1711
- };
1712
- } catch (error) {
1713
- console.error(`Error in ghost_create_tier:`, error);
1714
- if (error.name === 'ZodError') {
1715
- const validationError = ValidationError.fromZod(error, 'Tier creation');
1716
- return {
1717
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1718
- isError: true,
1719
- };
1720
- }
1721
- return {
1722
- content: [{ type: 'text', text: `Error creating tier: ${error.message}` }],
1723
- isError: true,
1724
- };
1725
- }
1726
- }
1020
+ return {
1021
+ content: [{ type: 'text', text: JSON.stringify(tier, null, 2) }],
1022
+ };
1023
+ })
1727
1024
  );
1728
1025
 
1729
1026
  // Update Tier Tool
@@ -1734,40 +1031,16 @@ server.registerTool(
1734
1031
  'Updates an existing tier (membership level) in Ghost CMS. Can update pricing, benefits, and other tier properties.',
1735
1032
  inputSchema: updateTierInputSchema,
1736
1033
  },
1737
- async (rawInput) => {
1738
- const validation = validateToolInput(updateTierInputSchema, rawInput, 'ghost_update_tier');
1739
- if (!validation.success) {
1740
- return validation.errorResponse;
1741
- }
1742
- const input = validation.data;
1034
+ withErrorHandling('ghost_update_tier', updateTierInputSchema, async (input) => {
1035
+ const { id, ...updateData } = input;
1743
1036
 
1744
- console.error(`Executing tool: ghost_update_tier for tier ID: ${input.id}`);
1745
- try {
1746
- await loadServices();
1037
+ const updatedTier = await ghostService.updateTier(id, updateData);
1038
+ console.error(`Tier updated successfully. Tier ID: ${updatedTier.id}`);
1747
1039
 
1748
- const { id, ...updateData } = input;
1749
-
1750
- const updatedTier = await ghostService.updateTier(id, updateData);
1751
- console.error(`Tier updated successfully. Tier ID: ${updatedTier.id}`);
1752
-
1753
- return {
1754
- content: [{ type: 'text', text: JSON.stringify(updatedTier, null, 2) }],
1755
- };
1756
- } catch (error) {
1757
- console.error(`Error in ghost_update_tier:`, error);
1758
- if (error.name === 'ZodError') {
1759
- const validationError = ValidationError.fromZod(error, 'Tier update');
1760
- return {
1761
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1762
- isError: true,
1763
- };
1764
- }
1765
- return {
1766
- content: [{ type: 'text', text: `Error updating tier: ${error.message}` }],
1767
- isError: true,
1768
- };
1769
- }
1770
- }
1040
+ return {
1041
+ content: [{ type: 'text', text: JSON.stringify(updatedTier, null, 2) }],
1042
+ };
1043
+ })
1771
1044
  );
1772
1045
 
1773
1046
  // Delete Tier Tool
@@ -1778,38 +1051,15 @@ server.registerTool(
1778
1051
  'Deletes a tier (membership level) from Ghost CMS by ID. This operation is permanent and cannot be undone.',
1779
1052
  inputSchema: deleteTierSchema,
1780
1053
  },
1781
- async (rawInput) => {
1782
- const validation = validateToolInput(deleteTierSchema, rawInput, 'ghost_delete_tier');
1783
- if (!validation.success) {
1784
- return validation.errorResponse;
1785
- }
1786
- const { id } = validation.data;
1787
-
1788
- console.error(`Executing tool: ghost_delete_tier for tier ID: ${id}`);
1789
- try {
1790
- await loadServices();
1791
-
1792
- await ghostService.deleteTier(id);
1793
- console.error(`Tier deleted successfully. Tier ID: ${id}`);
1794
-
1795
- return {
1796
- content: [{ type: 'text', text: `Tier ${id} has been successfully deleted.` }],
1797
- };
1798
- } catch (error) {
1799
- console.error(`Error in ghost_delete_tier:`, error);
1800
- if (error.name === 'ZodError') {
1801
- const validationError = ValidationError.fromZod(error, 'Tier deletion');
1802
- return {
1803
- content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
1804
- isError: true,
1805
- };
1806
- }
1807
- return {
1808
- content: [{ type: 'text', text: `Error deleting tier: ${error.message}` }],
1809
- isError: true,
1810
- };
1811
- }
1812
- }
1054
+ withErrorHandling('ghost_delete_tier', deleteTierSchema, async (input) => {
1055
+ const { id } = input;
1056
+ await ghostService.deleteTier(id);
1057
+ console.error(`Tier deleted successfully. Tier ID: ${id}`);
1058
+
1059
+ return {
1060
+ content: [{ type: 'text', text: `Tier ${id} has been successfully deleted.` }],
1061
+ };
1062
+ })
1813
1063
  );
1814
1064
 
1815
1065
  // --- Main Entry Point ---