@jgardner04/ghost-mcp-server 1.4.0 → 1.6.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.
@@ -509,6 +509,469 @@ server.tool(
509
509
  }
510
510
  );
511
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
+
783
+ // =============================================================================
784
+ // NEWSLETTER TOOLS
785
+ // =============================================================================
786
+
787
+ // Get Newsletters Tool
788
+ server.tool(
789
+ 'ghost_get_newsletters',
790
+ 'Retrieves a list of newsletters from Ghost CMS with optional filtering.',
791
+ {
792
+ limit: z
793
+ .number()
794
+ .min(1)
795
+ .max(100)
796
+ .optional()
797
+ .describe('Number of newsletters to retrieve (1-100). Default is all.'),
798
+ filter: z.string().optional().describe('Ghost NQL filter string for advanced filtering.'),
799
+ },
800
+ async (input) => {
801
+ console.error(`Executing tool: ghost_get_newsletters`);
802
+ try {
803
+ await loadServices();
804
+
805
+ const options = {};
806
+ if (input.limit !== undefined) options.limit = input.limit;
807
+ if (input.filter !== undefined) options.filter = input.filter;
808
+
809
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
810
+ const newsletters = await ghostServiceImproved.getNewsletters(options);
811
+ console.error(`Retrieved ${newsletters.length} newsletters from Ghost.`);
812
+
813
+ return {
814
+ content: [{ type: 'text', text: JSON.stringify(newsletters, null, 2) }],
815
+ };
816
+ } catch (error) {
817
+ console.error(`Error in ghost_get_newsletters:`, error);
818
+ return {
819
+ content: [{ type: 'text', text: `Error retrieving newsletters: ${error.message}` }],
820
+ isError: true,
821
+ };
822
+ }
823
+ }
824
+ );
825
+
826
+ // Get Newsletter Tool
827
+ server.tool(
828
+ 'ghost_get_newsletter',
829
+ 'Retrieves a single newsletter from Ghost CMS by ID.',
830
+ {
831
+ id: z.string().describe('The ID of the newsletter to retrieve.'),
832
+ },
833
+ async ({ id }) => {
834
+ console.error(`Executing tool: ghost_get_newsletter for ID: ${id}`);
835
+ try {
836
+ await loadServices();
837
+
838
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
839
+ const newsletter = await ghostServiceImproved.getNewsletter(id);
840
+ console.error(`Retrieved newsletter: ${newsletter.name} (ID: ${newsletter.id})`);
841
+
842
+ return {
843
+ content: [{ type: 'text', text: JSON.stringify(newsletter, null, 2) }],
844
+ };
845
+ } catch (error) {
846
+ console.error(`Error in ghost_get_newsletter:`, error);
847
+ return {
848
+ content: [{ type: 'text', text: `Error retrieving newsletter: ${error.message}` }],
849
+ isError: true,
850
+ };
851
+ }
852
+ }
853
+ );
854
+
855
+ // Create Newsletter Tool
856
+ server.tool(
857
+ 'ghost_create_newsletter',
858
+ 'Creates a new newsletter in Ghost CMS with customizable sender settings and display options.',
859
+ {
860
+ name: z.string().describe('The name of the newsletter.'),
861
+ description: z.string().optional().describe('A description for the newsletter.'),
862
+ sender_name: z.string().optional().describe('The sender name for newsletter emails.'),
863
+ sender_email: z
864
+ .string()
865
+ .email()
866
+ .optional()
867
+ .describe('The sender email address for newsletter emails.'),
868
+ sender_reply_to: z
869
+ .enum(['newsletter', 'support'])
870
+ .optional()
871
+ .describe('Reply-to address setting. Options: newsletter, support.'),
872
+ subscribe_on_signup: z
873
+ .boolean()
874
+ .optional()
875
+ .describe('Whether new members are automatically subscribed to this newsletter on signup.'),
876
+ show_header_icon: z
877
+ .boolean()
878
+ .optional()
879
+ .describe('Whether to show the site icon in the newsletter header.'),
880
+ show_header_title: z
881
+ .boolean()
882
+ .optional()
883
+ .describe('Whether to show the site title in the newsletter header.'),
884
+ },
885
+ async (input) => {
886
+ console.error(`Executing tool: ghost_create_newsletter with name: ${input.name}`);
887
+ try {
888
+ await loadServices();
889
+
890
+ const newsletterService = await import('./services/newsletterService.js');
891
+ const createdNewsletter = await newsletterService.createNewsletterService(input);
892
+ console.error(`Newsletter created successfully. Newsletter ID: ${createdNewsletter.id}`);
893
+
894
+ return {
895
+ content: [{ type: 'text', text: JSON.stringify(createdNewsletter, null, 2) }],
896
+ };
897
+ } catch (error) {
898
+ console.error(`Error in ghost_create_newsletter:`, error);
899
+ return {
900
+ content: [{ type: 'text', text: `Error creating newsletter: ${error.message}` }],
901
+ isError: true,
902
+ };
903
+ }
904
+ }
905
+ );
906
+
907
+ // Update Newsletter Tool
908
+ server.tool(
909
+ 'ghost_update_newsletter',
910
+ 'Updates an existing newsletter in Ghost CMS. Can update name, description, sender settings, and display options.',
911
+ {
912
+ id: z.string().describe('The ID of the newsletter to update.'),
913
+ name: z.string().optional().describe('New name for the newsletter.'),
914
+ description: z.string().optional().describe('New description for the newsletter.'),
915
+ sender_name: z.string().optional().describe('New sender name for newsletter emails.'),
916
+ sender_email: z.string().email().optional().describe('New sender email address.'),
917
+ subscribe_on_signup: z
918
+ .boolean()
919
+ .optional()
920
+ .describe('Whether new members are automatically subscribed to this newsletter on signup.'),
921
+ },
922
+ async (input) => {
923
+ console.error(`Executing tool: ghost_update_newsletter for newsletter ID: ${input.id}`);
924
+ try {
925
+ await loadServices();
926
+
927
+ const { id, ...updateData } = input;
928
+
929
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
930
+ const updatedNewsletter = await ghostServiceImproved.updateNewsletter(id, updateData);
931
+ console.error(`Newsletter updated successfully. Newsletter ID: ${updatedNewsletter.id}`);
932
+
933
+ return {
934
+ content: [{ type: 'text', text: JSON.stringify(updatedNewsletter, null, 2) }],
935
+ };
936
+ } catch (error) {
937
+ console.error(`Error in ghost_update_newsletter:`, error);
938
+ return {
939
+ content: [{ type: 'text', text: `Error updating newsletter: ${error.message}` }],
940
+ isError: true,
941
+ };
942
+ }
943
+ }
944
+ );
945
+
946
+ // Delete Newsletter Tool
947
+ server.tool(
948
+ 'ghost_delete_newsletter',
949
+ 'Deletes a newsletter from Ghost CMS by ID. This operation is permanent and cannot be undone.',
950
+ {
951
+ id: z.string().describe('The ID of the newsletter to delete.'),
952
+ },
953
+ async ({ id }) => {
954
+ console.error(`Executing tool: ghost_delete_newsletter for newsletter ID: ${id}`);
955
+ try {
956
+ await loadServices();
957
+
958
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
959
+ await ghostServiceImproved.deleteNewsletter(id);
960
+ console.error(`Newsletter deleted successfully. Newsletter ID: ${id}`);
961
+
962
+ return {
963
+ content: [{ type: 'text', text: `Newsletter ${id} has been successfully deleted.` }],
964
+ };
965
+ } catch (error) {
966
+ console.error(`Error in ghost_delete_newsletter:`, error);
967
+ return {
968
+ content: [{ type: 'text', text: `Error deleting newsletter: ${error.message}` }],
969
+ isError: true,
970
+ };
971
+ }
972
+ }
973
+ );
974
+
512
975
  // --- Main Entry Point ---
513
976
 
514
977
  async function main() {
@@ -519,7 +982,10 @@ async function main() {
519
982
 
520
983
  console.error('Ghost MCP Server running on stdio transport');
521
984
  console.error(
522
- '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'
985
+ 'Available tools: ghost_get_tags, ghost_create_tag, ghost_upload_image, ' +
986
+ 'ghost_create_post, ghost_get_posts, ghost_get_post, ghost_search_posts, ghost_update_post, ghost_delete_post, ' +
987
+ 'ghost_get_pages, ghost_get_page, ghost_create_page, ghost_update_page, ghost_delete_page, ghost_search_pages, ' +
988
+ 'ghost_get_newsletters, ghost_get_newsletter, ghost_create_newsletter, ghost_update_newsletter, ghost_delete_newsletter'
523
989
  );
524
990
  }
525
991