@jgardner04/ghost-mcp-server 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -376,6 +376,51 @@ server.tool(
376
376
  }
377
377
  );
378
378
 
379
+ // Search Posts Tool
380
+ server.tool(
381
+ 'ghost_search_posts',
382
+ 'Search for posts in Ghost CMS by query string with optional status filtering.',
383
+ {
384
+ query: z.string().describe('Search query to find in post titles.'),
385
+ status: z
386
+ .enum(['published', 'draft', 'scheduled', 'all'])
387
+ .optional()
388
+ .describe('Filter by post status. Default searches all statuses.'),
389
+ limit: z
390
+ .number()
391
+ .min(1)
392
+ .max(50)
393
+ .optional()
394
+ .describe('Maximum number of results (1-50). Default is 15.'),
395
+ },
396
+ async (input) => {
397
+ console.error(`Executing tool: ghost_search_posts with query: ${input.query}`);
398
+ try {
399
+ await loadServices();
400
+
401
+ // Build options object with provided parameters
402
+ const options = {};
403
+ if (input.status !== undefined) options.status = input.status;
404
+ if (input.limit !== undefined) options.limit = input.limit;
405
+
406
+ // Search posts using ghostServiceImproved
407
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
408
+ const posts = await ghostServiceImproved.searchPosts(input.query, options);
409
+ console.error(`Found ${posts.length} posts matching "${input.query}".`);
410
+
411
+ return {
412
+ content: [{ type: 'text', text: JSON.stringify(posts, null, 2) }],
413
+ };
414
+ } catch (error) {
415
+ console.error(`Error in ghost_search_posts:`, error);
416
+ return {
417
+ content: [{ type: 'text', text: `Error searching posts: ${error.message}` }],
418
+ isError: true,
419
+ };
420
+ }
421
+ }
422
+ );
423
+
379
424
  // Update Post Tool
380
425
  server.tool(
381
426
  'ghost_update_post',
@@ -464,6 +509,277 @@ server.tool(
464
509
  }
465
510
  );
466
511
 
512
+ // =============================================================================
513
+ // PAGE TOOLS
514
+ // Pages are similar to posts but do NOT support tags
515
+ // =============================================================================
516
+
517
+ // Get Pages Tool
518
+ server.tool(
519
+ 'ghost_get_pages',
520
+ 'Retrieves a list of pages from Ghost CMS with pagination, filtering, and sorting options.',
521
+ {
522
+ limit: z
523
+ .number()
524
+ .min(1)
525
+ .max(100)
526
+ .optional()
527
+ .describe('Number of pages to retrieve (1-100). Default is 15.'),
528
+ page: z.number().min(1).optional().describe('Page number for pagination. Default is 1.'),
529
+ status: z
530
+ .enum(['published', 'draft', 'scheduled', 'all'])
531
+ .optional()
532
+ .describe('Filter pages by status.'),
533
+ include: z
534
+ .string()
535
+ .optional()
536
+ .describe('Comma-separated list of relations to include (e.g., "authors").'),
537
+ filter: z.string().optional().describe('Ghost NQL filter string for advanced filtering.'),
538
+ order: z.string().optional().describe('Sort order for results (e.g., "published_at DESC").'),
539
+ },
540
+ async (input) => {
541
+ console.error(`Executing tool: ghost_get_pages`);
542
+ try {
543
+ await loadServices();
544
+
545
+ const options = {};
546
+ if (input.limit !== undefined) options.limit = input.limit;
547
+ if (input.page !== undefined) options.page = input.page;
548
+ if (input.status !== undefined) options.status = input.status;
549
+ if (input.include !== undefined) options.include = input.include;
550
+ if (input.filter !== undefined) options.filter = input.filter;
551
+ if (input.order !== undefined) options.order = input.order;
552
+
553
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
554
+ const pages = await ghostServiceImproved.getPages(options);
555
+ console.error(`Retrieved ${pages.length} pages from Ghost.`);
556
+
557
+ return {
558
+ content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
559
+ };
560
+ } catch (error) {
561
+ console.error(`Error in ghost_get_pages:`, error);
562
+ return {
563
+ content: [{ type: 'text', text: `Error retrieving pages: ${error.message}` }],
564
+ isError: true,
565
+ };
566
+ }
567
+ }
568
+ );
569
+
570
+ // Get Page Tool
571
+ server.tool(
572
+ 'ghost_get_page',
573
+ 'Retrieves a single page from Ghost CMS by ID or slug.',
574
+ {
575
+ id: z.string().optional().describe('The ID of the page to retrieve.'),
576
+ slug: z.string().optional().describe('The slug of the page to retrieve.'),
577
+ include: z
578
+ .string()
579
+ .optional()
580
+ .describe('Comma-separated list of relations to include (e.g., "authors").'),
581
+ },
582
+ async (input) => {
583
+ console.error(`Executing tool: ghost_get_page`);
584
+ try {
585
+ if (!input.id && !input.slug) {
586
+ throw new Error('Either id or slug is required to retrieve a page');
587
+ }
588
+
589
+ await loadServices();
590
+
591
+ const options = {};
592
+ if (input.include !== undefined) options.include = input.include;
593
+
594
+ const identifier = input.id || `slug/${input.slug}`;
595
+
596
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
597
+ const page = await ghostServiceImproved.getPage(identifier, options);
598
+ console.error(`Retrieved page: ${page.title} (ID: ${page.id})`);
599
+
600
+ return {
601
+ content: [{ type: 'text', text: JSON.stringify(page, null, 2) }],
602
+ };
603
+ } catch (error) {
604
+ console.error(`Error in ghost_get_page:`, error);
605
+ return {
606
+ content: [{ type: 'text', text: `Error retrieving page: ${error.message}` }],
607
+ isError: true,
608
+ };
609
+ }
610
+ }
611
+ );
612
+
613
+ // Create Page Tool
614
+ server.tool(
615
+ 'ghost_create_page',
616
+ 'Creates a new page in Ghost CMS. Note: Pages do NOT support tags (unlike posts).',
617
+ {
618
+ title: z.string().describe('The title of the page.'),
619
+ html: z.string().describe('The HTML content of the page.'),
620
+ status: z
621
+ .enum(['draft', 'published', 'scheduled'])
622
+ .optional()
623
+ .describe("The status of the page. Defaults to 'draft'."),
624
+ // NO tags parameter - pages don't support tags
625
+ published_at: z
626
+ .string()
627
+ .optional()
628
+ .describe("ISO 8601 date/time to publish the page. Required if status is 'scheduled'."),
629
+ custom_excerpt: z.string().optional().describe('A custom short summary for the page.'),
630
+ feature_image: z.string().optional().describe('URL of the image to use as the featured image.'),
631
+ feature_image_alt: z.string().optional().describe('Alt text for the featured image.'),
632
+ feature_image_caption: z.string().optional().describe('Caption for the featured image.'),
633
+ meta_title: z
634
+ .string()
635
+ .optional()
636
+ .describe('Custom title for SEO (max 70 chars). Defaults to page title if omitted.'),
637
+ meta_description: z
638
+ .string()
639
+ .optional()
640
+ .describe(
641
+ 'Custom description for SEO (max 160 chars). Defaults to excerpt or generated summary if omitted.'
642
+ ),
643
+ },
644
+ async (input) => {
645
+ console.error(`Executing tool: ghost_create_page with title: ${input.title}`);
646
+ try {
647
+ await loadServices();
648
+
649
+ const pageService = await import('./services/pageService.js');
650
+ const createdPage = await pageService.createPageService(input);
651
+ console.error(`Page created successfully. Page ID: ${createdPage.id}`);
652
+
653
+ return {
654
+ content: [{ type: 'text', text: JSON.stringify(createdPage, null, 2) }],
655
+ };
656
+ } catch (error) {
657
+ console.error(`Error in ghost_create_page:`, error);
658
+ return {
659
+ content: [{ type: 'text', text: `Error creating page: ${error.message}` }],
660
+ isError: true,
661
+ };
662
+ }
663
+ }
664
+ );
665
+
666
+ // Update Page Tool
667
+ server.tool(
668
+ 'ghost_update_page',
669
+ 'Updates an existing page in Ghost CMS. Can update title, content, status, images, and SEO fields. Note: Pages do NOT support tags.',
670
+ {
671
+ id: z.string().describe('The ID of the page to update.'),
672
+ title: z.string().optional().describe('New title for the page.'),
673
+ html: z.string().optional().describe('New HTML content for the page.'),
674
+ status: z
675
+ .enum(['draft', 'published', 'scheduled'])
676
+ .optional()
677
+ .describe('New status for the page.'),
678
+ // NO tags parameter - pages don't support tags
679
+ feature_image: z.string().optional().describe('New featured image URL.'),
680
+ feature_image_alt: z.string().optional().describe('New alt text for the featured image.'),
681
+ feature_image_caption: z.string().optional().describe('New caption for the featured image.'),
682
+ meta_title: z.string().optional().describe('New custom title for SEO.'),
683
+ meta_description: z.string().optional().describe('New custom description for SEO.'),
684
+ published_at: z.string().optional().describe('New publication date/time in ISO 8601 format.'),
685
+ custom_excerpt: z.string().optional().describe('New custom short summary for the page.'),
686
+ },
687
+ async (input) => {
688
+ console.error(`Executing tool: ghost_update_page for page ID: ${input.id}`);
689
+ try {
690
+ await loadServices();
691
+
692
+ const { id, ...updateData } = input;
693
+
694
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
695
+ const updatedPage = await ghostServiceImproved.updatePage(id, updateData);
696
+ console.error(`Page updated successfully. Page ID: ${updatedPage.id}`);
697
+
698
+ return {
699
+ content: [{ type: 'text', text: JSON.stringify(updatedPage, null, 2) }],
700
+ };
701
+ } catch (error) {
702
+ console.error(`Error in ghost_update_page:`, error);
703
+ return {
704
+ content: [{ type: 'text', text: `Error updating page: ${error.message}` }],
705
+ isError: true,
706
+ };
707
+ }
708
+ }
709
+ );
710
+
711
+ // Delete Page Tool
712
+ server.tool(
713
+ 'ghost_delete_page',
714
+ 'Deletes a page from Ghost CMS by ID. This operation is permanent and cannot be undone.',
715
+ {
716
+ id: z.string().describe('The ID of the page to delete.'),
717
+ },
718
+ async ({ id }) => {
719
+ console.error(`Executing tool: ghost_delete_page for page ID: ${id}`);
720
+ try {
721
+ await loadServices();
722
+
723
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
724
+ await ghostServiceImproved.deletePage(id);
725
+ console.error(`Page deleted successfully. Page ID: ${id}`);
726
+
727
+ return {
728
+ content: [{ type: 'text', text: `Page ${id} has been successfully deleted.` }],
729
+ };
730
+ } catch (error) {
731
+ console.error(`Error in ghost_delete_page:`, error);
732
+ return {
733
+ content: [{ type: 'text', text: `Error deleting page: ${error.message}` }],
734
+ isError: true,
735
+ };
736
+ }
737
+ }
738
+ );
739
+
740
+ // Search Pages Tool
741
+ server.tool(
742
+ 'ghost_search_pages',
743
+ 'Search for pages in Ghost CMS by query string with optional status filtering.',
744
+ {
745
+ query: z.string().describe('Search query to find in page titles.'),
746
+ status: z
747
+ .enum(['published', 'draft', 'scheduled', 'all'])
748
+ .optional()
749
+ .describe('Filter by page status. Default searches all statuses.'),
750
+ limit: z
751
+ .number()
752
+ .min(1)
753
+ .max(50)
754
+ .optional()
755
+ .describe('Maximum number of results (1-50). Default is 15.'),
756
+ },
757
+ async (input) => {
758
+ console.error(`Executing tool: ghost_search_pages with query: ${input.query}`);
759
+ try {
760
+ await loadServices();
761
+
762
+ const options = {};
763
+ if (input.status !== undefined) options.status = input.status;
764
+ if (input.limit !== undefined) options.limit = input.limit;
765
+
766
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
767
+ const pages = await ghostServiceImproved.searchPages(input.query, options);
768
+ console.error(`Found ${pages.length} pages matching "${input.query}".`);
769
+
770
+ return {
771
+ content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
772
+ };
773
+ } catch (error) {
774
+ console.error(`Error in ghost_search_pages:`, error);
775
+ return {
776
+ content: [{ type: 'text', text: `Error searching pages: ${error.message}` }],
777
+ isError: true,
778
+ };
779
+ }
780
+ }
781
+ );
782
+
467
783
  // --- Main Entry Point ---
468
784
 
469
785
  async function main() {
@@ -474,7 +790,9 @@ async function main() {
474
790
 
475
791
  console.error('Ghost MCP Server running on stdio transport');
476
792
  console.error(
477
- 'Available tools: ghost_get_tags, ghost_create_tag, ghost_upload_image, ghost_create_post, ghost_get_posts, ghost_get_post, ghost_update_post, ghost_delete_post'
793
+ 'Available tools: ghost_get_tags, ghost_create_tag, ghost_upload_image, ' +
794
+ 'ghost_create_post, ghost_get_posts, ghost_get_post, ghost_search_posts, ghost_update_post, ghost_delete_post, ' +
795
+ 'ghost_get_pages, ghost_get_page, ghost_create_page, ghost_update_page, ghost_delete_page, ghost_search_pages'
478
796
  );
479
797
  }
480
798