@jgardner04/ghost-mcp-server 1.12.5 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/__tests__/mcp_server.test.js +258 -0
- package/src/__tests__/mcp_server_pages.test.js +21 -6
- package/src/controllers/__tests__/tagController.test.js +118 -3
- package/src/controllers/tagController.js +32 -6
- package/src/mcp_server.js +225 -112
- package/src/resources/ResourceManager.js +6 -2
- package/src/resources/__tests__/ResourceManager.test.js +2 -2
- package/src/schemas/__tests__/tagSchemas.test.js +100 -0
- package/src/schemas/tagSchemas.js +33 -5
- package/src/services/__tests__/ghostService.test.js +30 -23
- package/src/services/__tests__/ghostServiceImproved.tags.test.js +475 -0
- package/src/services/ghostService.js +21 -18
- package/src/services/ghostServiceImproved.js +16 -10
package/src/mcp_server.js
CHANGED
|
@@ -14,7 +14,7 @@ import { trackTempFile, cleanupTempFiles } from './utils/tempFileManager.js';
|
|
|
14
14
|
import {
|
|
15
15
|
createTagSchema,
|
|
16
16
|
updateTagSchema,
|
|
17
|
-
|
|
17
|
+
tagQueryBaseSchema,
|
|
18
18
|
ghostIdSchema,
|
|
19
19
|
emailSchema,
|
|
20
20
|
createPostSchema,
|
|
@@ -72,6 +72,17 @@ const getDefaultAltText = (filePath) => {
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Escapes single quotes in NQL filter values by doubling them.
|
|
77
|
+
* This prevents filter injection attacks when building NQL query strings.
|
|
78
|
+
* Example: "O'Reilly" becomes "O''Reilly" for use in name:'O''Reilly'
|
|
79
|
+
* @param {string} value - The value to escape
|
|
80
|
+
* @returns {string} The escaped value safe for NQL filter strings
|
|
81
|
+
*/
|
|
82
|
+
const escapeNqlValue = (value) => {
|
|
83
|
+
return value.replace(/'/g, "''");
|
|
84
|
+
};
|
|
85
|
+
|
|
75
86
|
// Create server instance with new API
|
|
76
87
|
const server = new McpServer({
|
|
77
88
|
name: 'ghost-mcp-server',
|
|
@@ -81,7 +92,7 @@ const server = new McpServer({
|
|
|
81
92
|
// --- Register Tools ---
|
|
82
93
|
|
|
83
94
|
// --- Schema Definitions for Tools ---
|
|
84
|
-
const getTagsSchema =
|
|
95
|
+
const getTagsSchema = tagQueryBaseSchema.partial();
|
|
85
96
|
const getTagSchema = z
|
|
86
97
|
.object({
|
|
87
98
|
id: ghostIdSchema.optional().describe('The ID of the tag to retrieve.'),
|
|
@@ -98,10 +109,13 @@ const updateTagInputSchema = updateTagSchema.extend({ id: ghostIdSchema });
|
|
|
98
109
|
const deleteTagSchema = z.object({ id: ghostIdSchema });
|
|
99
110
|
|
|
100
111
|
// Get Tags Tool
|
|
101
|
-
server.
|
|
112
|
+
server.registerTool(
|
|
102
113
|
'ghost_get_tags',
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
{
|
|
115
|
+
description:
|
|
116
|
+
'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
|
+
inputSchema: getTagsSchema,
|
|
118
|
+
},
|
|
105
119
|
async (rawInput) => {
|
|
106
120
|
const validation = validateToolInput(getTagsSchema, rawInput, 'ghost_get_tags');
|
|
107
121
|
if (!validation.success) {
|
|
@@ -112,16 +126,30 @@ server.tool(
|
|
|
112
126
|
console.error(`Executing tool: ghost_get_tags`);
|
|
113
127
|
try {
|
|
114
128
|
await loadServices();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (input.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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('+');
|
|
123
146
|
}
|
|
124
147
|
|
|
148
|
+
const tags = await ghostService.getTags(options);
|
|
149
|
+
console.error(`Retrieved ${tags.length} tags from Ghost.`);
|
|
150
|
+
|
|
151
|
+
const result = tags;
|
|
152
|
+
|
|
125
153
|
return {
|
|
126
154
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
127
155
|
};
|
|
@@ -143,10 +171,12 @@ server.tool(
|
|
|
143
171
|
);
|
|
144
172
|
|
|
145
173
|
// Create Tag Tool
|
|
146
|
-
server.
|
|
174
|
+
server.registerTool(
|
|
147
175
|
'ghost_create_tag',
|
|
148
|
-
|
|
149
|
-
|
|
176
|
+
{
|
|
177
|
+
description: 'Creates a new tag in Ghost CMS.',
|
|
178
|
+
inputSchema: createTagSchema,
|
|
179
|
+
},
|
|
150
180
|
async (rawInput) => {
|
|
151
181
|
const validation = validateToolInput(createTagSchema, rawInput, 'ghost_create_tag');
|
|
152
182
|
if (!validation.success) {
|
|
@@ -181,10 +211,12 @@ server.tool(
|
|
|
181
211
|
);
|
|
182
212
|
|
|
183
213
|
// Get Tag Tool
|
|
184
|
-
server.
|
|
214
|
+
server.registerTool(
|
|
185
215
|
'ghost_get_tag',
|
|
186
|
-
|
|
187
|
-
|
|
216
|
+
{
|
|
217
|
+
description: 'Retrieves a single tag from Ghost CMS by ID or slug.',
|
|
218
|
+
inputSchema: getTagSchema,
|
|
219
|
+
},
|
|
188
220
|
async (rawInput) => {
|
|
189
221
|
const validation = validateToolInput(getTagSchema, rawInput, 'ghost_get_tag');
|
|
190
222
|
if (!validation.success) {
|
|
@@ -224,10 +256,12 @@ server.tool(
|
|
|
224
256
|
);
|
|
225
257
|
|
|
226
258
|
// Update Tag Tool
|
|
227
|
-
server.
|
|
259
|
+
server.registerTool(
|
|
228
260
|
'ghost_update_tag',
|
|
229
|
-
|
|
230
|
-
|
|
261
|
+
{
|
|
262
|
+
description: 'Updates an existing tag in Ghost CMS.',
|
|
263
|
+
inputSchema: updateTagInputSchema,
|
|
264
|
+
},
|
|
231
265
|
async (rawInput) => {
|
|
232
266
|
const validation = validateToolInput(updateTagInputSchema, rawInput, 'ghost_update_tag');
|
|
233
267
|
if (!validation.success) {
|
|
@@ -270,10 +304,12 @@ server.tool(
|
|
|
270
304
|
);
|
|
271
305
|
|
|
272
306
|
// Delete Tag Tool
|
|
273
|
-
server.
|
|
307
|
+
server.registerTool(
|
|
274
308
|
'ghost_delete_tag',
|
|
275
|
-
|
|
276
|
-
|
|
309
|
+
{
|
|
310
|
+
description: 'Deletes a tag from Ghost CMS by ID. This operation is permanent.',
|
|
311
|
+
inputSchema: deleteTagSchema,
|
|
312
|
+
},
|
|
277
313
|
async (rawInput) => {
|
|
278
314
|
const validation = validateToolInput(deleteTagSchema, rawInput, 'ghost_delete_tag');
|
|
279
315
|
if (!validation.success) {
|
|
@@ -322,10 +358,13 @@ const uploadImageSchema = z.object({
|
|
|
322
358
|
});
|
|
323
359
|
|
|
324
360
|
// Upload Image Tool
|
|
325
|
-
server.
|
|
361
|
+
server.registerTool(
|
|
326
362
|
'ghost_upload_image',
|
|
327
|
-
|
|
328
|
-
|
|
363
|
+
{
|
|
364
|
+
description:
|
|
365
|
+
'Downloads an image from a URL, processes it, uploads it to Ghost CMS, and returns the final Ghost image URL and alt text.',
|
|
366
|
+
inputSchema: uploadImageSchema,
|
|
367
|
+
},
|
|
329
368
|
async (rawInput) => {
|
|
330
369
|
const validation = validateToolInput(uploadImageSchema, rawInput, 'ghost_upload_image');
|
|
331
370
|
if (!validation.success) {
|
|
@@ -442,10 +481,12 @@ const updatePostInputSchema = updatePostSchema.extend({ id: ghostIdSchema });
|
|
|
442
481
|
const deletePostSchema = z.object({ id: ghostIdSchema });
|
|
443
482
|
|
|
444
483
|
// Create Post Tool
|
|
445
|
-
server.
|
|
484
|
+
server.registerTool(
|
|
446
485
|
'ghost_create_post',
|
|
447
|
-
|
|
448
|
-
|
|
486
|
+
{
|
|
487
|
+
description: 'Creates a new post in Ghost CMS.',
|
|
488
|
+
inputSchema: createPostSchema,
|
|
489
|
+
},
|
|
449
490
|
async (rawInput) => {
|
|
450
491
|
const validation = validateToolInput(createPostSchema, rawInput, 'ghost_create_post');
|
|
451
492
|
if (!validation.success) {
|
|
@@ -480,10 +521,13 @@ server.tool(
|
|
|
480
521
|
);
|
|
481
522
|
|
|
482
523
|
// Get Posts Tool
|
|
483
|
-
server.
|
|
524
|
+
server.registerTool(
|
|
484
525
|
'ghost_get_posts',
|
|
485
|
-
|
|
486
|
-
|
|
526
|
+
{
|
|
527
|
+
description:
|
|
528
|
+
'Retrieves a list of posts from Ghost CMS with pagination, filtering, and sorting options.',
|
|
529
|
+
inputSchema: getPostsSchema,
|
|
530
|
+
},
|
|
487
531
|
async (rawInput) => {
|
|
488
532
|
const validation = validateToolInput(getPostsSchema, rawInput, 'ghost_get_posts');
|
|
489
533
|
if (!validation.success) {
|
|
@@ -503,6 +547,8 @@ server.tool(
|
|
|
503
547
|
if (input.include !== undefined) options.include = input.include;
|
|
504
548
|
if (input.filter !== undefined) options.filter = input.filter;
|
|
505
549
|
if (input.order !== undefined) options.order = input.order;
|
|
550
|
+
if (input.fields !== undefined) options.fields = input.fields;
|
|
551
|
+
if (input.formats !== undefined) options.formats = input.formats;
|
|
506
552
|
|
|
507
553
|
const posts = await ghostService.getPosts(options);
|
|
508
554
|
console.error(`Retrieved ${posts.length} posts from Ghost.`);
|
|
@@ -528,10 +574,12 @@ server.tool(
|
|
|
528
574
|
);
|
|
529
575
|
|
|
530
576
|
// Get Post Tool
|
|
531
|
-
server.
|
|
577
|
+
server.registerTool(
|
|
532
578
|
'ghost_get_post',
|
|
533
|
-
|
|
534
|
-
|
|
579
|
+
{
|
|
580
|
+
description: 'Retrieves a single post from Ghost CMS by ID or slug.',
|
|
581
|
+
inputSchema: getPostSchema,
|
|
582
|
+
},
|
|
535
583
|
async (rawInput) => {
|
|
536
584
|
const validation = validateToolInput(getPostSchema, rawInput, 'ghost_get_post');
|
|
537
585
|
if (!validation.success) {
|
|
@@ -574,10 +622,12 @@ server.tool(
|
|
|
574
622
|
);
|
|
575
623
|
|
|
576
624
|
// Search Posts Tool
|
|
577
|
-
server.
|
|
625
|
+
server.registerTool(
|
|
578
626
|
'ghost_search_posts',
|
|
579
|
-
|
|
580
|
-
|
|
627
|
+
{
|
|
628
|
+
description: 'Search for posts in Ghost CMS by query string with optional status filtering.',
|
|
629
|
+
inputSchema: searchPostsSchema,
|
|
630
|
+
},
|
|
581
631
|
async (rawInput) => {
|
|
582
632
|
const validation = validateToolInput(searchPostsSchema, rawInput, 'ghost_search_posts');
|
|
583
633
|
if (!validation.success) {
|
|
@@ -618,10 +668,13 @@ server.tool(
|
|
|
618
668
|
);
|
|
619
669
|
|
|
620
670
|
// Update Post Tool
|
|
621
|
-
server.
|
|
671
|
+
server.registerTool(
|
|
622
672
|
'ghost_update_post',
|
|
623
|
-
|
|
624
|
-
|
|
673
|
+
{
|
|
674
|
+
description:
|
|
675
|
+
'Updates an existing post in Ghost CMS. Can update title, content, status, tags, images, and SEO fields.',
|
|
676
|
+
inputSchema: updatePostInputSchema,
|
|
677
|
+
},
|
|
625
678
|
async (rawInput) => {
|
|
626
679
|
const validation = validateToolInput(updatePostInputSchema, rawInput, 'ghost_update_post');
|
|
627
680
|
if (!validation.success) {
|
|
@@ -660,10 +713,13 @@ server.tool(
|
|
|
660
713
|
);
|
|
661
714
|
|
|
662
715
|
// Delete Post Tool
|
|
663
|
-
server.
|
|
716
|
+
server.registerTool(
|
|
664
717
|
'ghost_delete_post',
|
|
665
|
-
|
|
666
|
-
|
|
718
|
+
{
|
|
719
|
+
description:
|
|
720
|
+
'Deletes a post from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
721
|
+
inputSchema: deletePostSchema,
|
|
722
|
+
},
|
|
667
723
|
async (rawInput) => {
|
|
668
724
|
const validation = validateToolInput(deletePostSchema, rawInput, 'ghost_delete_post');
|
|
669
725
|
if (!validation.success) {
|
|
@@ -740,10 +796,13 @@ const searchPagesSchema = z.object({
|
|
|
740
796
|
});
|
|
741
797
|
|
|
742
798
|
// Get Pages Tool
|
|
743
|
-
server.
|
|
799
|
+
server.registerTool(
|
|
744
800
|
'ghost_get_pages',
|
|
745
|
-
|
|
746
|
-
|
|
801
|
+
{
|
|
802
|
+
description:
|
|
803
|
+
'Retrieves a list of pages from Ghost CMS with pagination, filtering, and sorting options.',
|
|
804
|
+
inputSchema: pageQuerySchema,
|
|
805
|
+
},
|
|
747
806
|
async (rawInput) => {
|
|
748
807
|
const validation = validateToolInput(pageQuerySchema, rawInput, 'ghost_get_pages');
|
|
749
808
|
if (!validation.success) {
|
|
@@ -788,10 +847,12 @@ server.tool(
|
|
|
788
847
|
);
|
|
789
848
|
|
|
790
849
|
// Get Page Tool
|
|
791
|
-
server.
|
|
850
|
+
server.registerTool(
|
|
792
851
|
'ghost_get_page',
|
|
793
|
-
|
|
794
|
-
|
|
852
|
+
{
|
|
853
|
+
description: 'Retrieves a single page from Ghost CMS by ID or slug.',
|
|
854
|
+
inputSchema: getPageSchema,
|
|
855
|
+
},
|
|
795
856
|
async (rawInput) => {
|
|
796
857
|
const validation = validateToolInput(getPageSchema, rawInput, 'ghost_get_page');
|
|
797
858
|
if (!validation.success) {
|
|
@@ -832,10 +893,13 @@ server.tool(
|
|
|
832
893
|
);
|
|
833
894
|
|
|
834
895
|
// Create Page Tool
|
|
835
|
-
server.
|
|
896
|
+
server.registerTool(
|
|
836
897
|
'ghost_create_page',
|
|
837
|
-
|
|
838
|
-
|
|
898
|
+
{
|
|
899
|
+
description:
|
|
900
|
+
'Creates a new page in Ghost CMS. Note: Pages do NOT typically use tags (unlike posts).',
|
|
901
|
+
inputSchema: createPageSchema,
|
|
902
|
+
},
|
|
839
903
|
async (rawInput) => {
|
|
840
904
|
const validation = validateToolInput(createPageSchema, rawInput, 'ghost_create_page');
|
|
841
905
|
if (!validation.success) {
|
|
@@ -871,10 +935,13 @@ server.tool(
|
|
|
871
935
|
);
|
|
872
936
|
|
|
873
937
|
// Update Page Tool
|
|
874
|
-
server.
|
|
938
|
+
server.registerTool(
|
|
875
939
|
'ghost_update_page',
|
|
876
|
-
|
|
877
|
-
|
|
940
|
+
{
|
|
941
|
+
description:
|
|
942
|
+
'Updates an existing page in Ghost CMS. Can update title, content, status, images, and SEO fields.',
|
|
943
|
+
inputSchema: updatePageInputSchema,
|
|
944
|
+
},
|
|
878
945
|
async (rawInput) => {
|
|
879
946
|
const validation = validateToolInput(updatePageInputSchema, rawInput, 'ghost_update_page');
|
|
880
947
|
if (!validation.success) {
|
|
@@ -912,10 +979,13 @@ server.tool(
|
|
|
912
979
|
);
|
|
913
980
|
|
|
914
981
|
// Delete Page Tool
|
|
915
|
-
server.
|
|
982
|
+
server.registerTool(
|
|
916
983
|
'ghost_delete_page',
|
|
917
|
-
|
|
918
|
-
|
|
984
|
+
{
|
|
985
|
+
description:
|
|
986
|
+
'Deletes a page from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
987
|
+
inputSchema: deletePageSchema,
|
|
988
|
+
},
|
|
919
989
|
async (rawInput) => {
|
|
920
990
|
const validation = validateToolInput(deletePageSchema, rawInput, 'ghost_delete_page');
|
|
921
991
|
if (!validation.success) {
|
|
@@ -951,10 +1021,12 @@ server.tool(
|
|
|
951
1021
|
);
|
|
952
1022
|
|
|
953
1023
|
// Search Pages Tool
|
|
954
|
-
server.
|
|
1024
|
+
server.registerTool(
|
|
955
1025
|
'ghost_search_pages',
|
|
956
|
-
|
|
957
|
-
|
|
1026
|
+
{
|
|
1027
|
+
description: 'Search for pages in Ghost CMS by query string with optional status filtering.',
|
|
1028
|
+
inputSchema: searchPagesSchema,
|
|
1029
|
+
},
|
|
958
1030
|
async (rawInput) => {
|
|
959
1031
|
const validation = validateToolInput(searchPagesSchema, rawInput, 'ghost_search_pages');
|
|
960
1032
|
if (!validation.success) {
|
|
@@ -1022,10 +1094,12 @@ const searchMembersSchema = z.object({
|
|
|
1022
1094
|
});
|
|
1023
1095
|
|
|
1024
1096
|
// Create Member Tool
|
|
1025
|
-
server.
|
|
1097
|
+
server.registerTool(
|
|
1026
1098
|
'ghost_create_member',
|
|
1027
|
-
|
|
1028
|
-
|
|
1099
|
+
{
|
|
1100
|
+
description: 'Creates a new member (subscriber) in Ghost CMS.',
|
|
1101
|
+
inputSchema: createMemberSchema,
|
|
1102
|
+
},
|
|
1029
1103
|
async (rawInput) => {
|
|
1030
1104
|
const validation = validateToolInput(createMemberSchema, rawInput, 'ghost_create_member');
|
|
1031
1105
|
if (!validation.success) {
|
|
@@ -1061,10 +1135,12 @@ server.tool(
|
|
|
1061
1135
|
);
|
|
1062
1136
|
|
|
1063
1137
|
// Update Member Tool
|
|
1064
|
-
server.
|
|
1138
|
+
server.registerTool(
|
|
1065
1139
|
'ghost_update_member',
|
|
1066
|
-
|
|
1067
|
-
|
|
1140
|
+
{
|
|
1141
|
+
description: 'Updates an existing member in Ghost CMS. All fields except id are optional.',
|
|
1142
|
+
inputSchema: updateMemberInputSchema,
|
|
1143
|
+
},
|
|
1068
1144
|
async (rawInput) => {
|
|
1069
1145
|
const validation = validateToolInput(updateMemberInputSchema, rawInput, 'ghost_update_member');
|
|
1070
1146
|
if (!validation.success) {
|
|
@@ -1102,10 +1178,13 @@ server.tool(
|
|
|
1102
1178
|
);
|
|
1103
1179
|
|
|
1104
1180
|
// Delete Member Tool
|
|
1105
|
-
server.
|
|
1181
|
+
server.registerTool(
|
|
1106
1182
|
'ghost_delete_member',
|
|
1107
|
-
|
|
1108
|
-
|
|
1183
|
+
{
|
|
1184
|
+
description:
|
|
1185
|
+
'Deletes a member from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
1186
|
+
inputSchema: deleteMemberSchema,
|
|
1187
|
+
},
|
|
1109
1188
|
async (rawInput) => {
|
|
1110
1189
|
const validation = validateToolInput(deleteMemberSchema, rawInput, 'ghost_delete_member');
|
|
1111
1190
|
if (!validation.success) {
|
|
@@ -1141,10 +1220,13 @@ server.tool(
|
|
|
1141
1220
|
);
|
|
1142
1221
|
|
|
1143
1222
|
// Get Members Tool
|
|
1144
|
-
server.
|
|
1223
|
+
server.registerTool(
|
|
1145
1224
|
'ghost_get_members',
|
|
1146
|
-
|
|
1147
|
-
|
|
1225
|
+
{
|
|
1226
|
+
description:
|
|
1227
|
+
'Retrieves a list of members (subscribers) from Ghost CMS with optional filtering, pagination, and includes.',
|
|
1228
|
+
inputSchema: getMembersSchema,
|
|
1229
|
+
},
|
|
1148
1230
|
async (rawInput) => {
|
|
1149
1231
|
const validation = validateToolInput(getMembersSchema, rawInput, 'ghost_get_members');
|
|
1150
1232
|
if (!validation.success) {
|
|
@@ -1187,10 +1269,13 @@ server.tool(
|
|
|
1187
1269
|
);
|
|
1188
1270
|
|
|
1189
1271
|
// Get Member Tool
|
|
1190
|
-
server.
|
|
1272
|
+
server.registerTool(
|
|
1191
1273
|
'ghost_get_member',
|
|
1192
|
-
|
|
1193
|
-
|
|
1274
|
+
{
|
|
1275
|
+
description:
|
|
1276
|
+
'Retrieves a single member from Ghost CMS by ID or email. Provide either id OR email.',
|
|
1277
|
+
inputSchema: getMemberSchema,
|
|
1278
|
+
},
|
|
1194
1279
|
async (rawInput) => {
|
|
1195
1280
|
const validation = validateToolInput(getMemberSchema, rawInput, 'ghost_get_member');
|
|
1196
1281
|
if (!validation.success) {
|
|
@@ -1226,10 +1311,12 @@ server.tool(
|
|
|
1226
1311
|
);
|
|
1227
1312
|
|
|
1228
1313
|
// Search Members Tool
|
|
1229
|
-
server.
|
|
1314
|
+
server.registerTool(
|
|
1230
1315
|
'ghost_search_members',
|
|
1231
|
-
|
|
1232
|
-
|
|
1316
|
+
{
|
|
1317
|
+
description: 'Searches for members by name or email in Ghost CMS.',
|
|
1318
|
+
inputSchema: searchMembersSchema,
|
|
1319
|
+
},
|
|
1233
1320
|
async (rawInput) => {
|
|
1234
1321
|
const validation = validateToolInput(searchMembersSchema, rawInput, 'ghost_search_members');
|
|
1235
1322
|
if (!validation.success) {
|
|
@@ -1277,10 +1364,12 @@ const updateNewsletterInputSchema = z.object({ id: ghostIdSchema }).merge(update
|
|
|
1277
1364
|
const deleteNewsletterSchema = z.object({ id: ghostIdSchema });
|
|
1278
1365
|
|
|
1279
1366
|
// Get Newsletters Tool
|
|
1280
|
-
server.
|
|
1367
|
+
server.registerTool(
|
|
1281
1368
|
'ghost_get_newsletters',
|
|
1282
|
-
|
|
1283
|
-
|
|
1369
|
+
{
|
|
1370
|
+
description: 'Retrieves a list of newsletters from Ghost CMS with optional filtering.',
|
|
1371
|
+
inputSchema: newsletterQuerySchema,
|
|
1372
|
+
},
|
|
1284
1373
|
async (rawInput) => {
|
|
1285
1374
|
const validation = validateToolInput(newsletterQuerySchema, rawInput, 'ghost_get_newsletters');
|
|
1286
1375
|
if (!validation.success) {
|
|
@@ -1322,10 +1411,12 @@ server.tool(
|
|
|
1322
1411
|
);
|
|
1323
1412
|
|
|
1324
1413
|
// Get Newsletter Tool
|
|
1325
|
-
server.
|
|
1414
|
+
server.registerTool(
|
|
1326
1415
|
'ghost_get_newsletter',
|
|
1327
|
-
|
|
1328
|
-
|
|
1416
|
+
{
|
|
1417
|
+
description: 'Retrieves a single newsletter from Ghost CMS by ID.',
|
|
1418
|
+
inputSchema: getNewsletterSchema,
|
|
1419
|
+
},
|
|
1329
1420
|
async (rawInput) => {
|
|
1330
1421
|
const validation = validateToolInput(getNewsletterSchema, rawInput, 'ghost_get_newsletter');
|
|
1331
1422
|
if (!validation.success) {
|
|
@@ -1361,10 +1452,13 @@ server.tool(
|
|
|
1361
1452
|
);
|
|
1362
1453
|
|
|
1363
1454
|
// Create Newsletter Tool
|
|
1364
|
-
server.
|
|
1455
|
+
server.registerTool(
|
|
1365
1456
|
'ghost_create_newsletter',
|
|
1366
|
-
|
|
1367
|
-
|
|
1457
|
+
{
|
|
1458
|
+
description:
|
|
1459
|
+
'Creates a new newsletter in Ghost CMS with customizable sender settings and display options.',
|
|
1460
|
+
inputSchema: createNewsletterSchema,
|
|
1461
|
+
},
|
|
1368
1462
|
async (rawInput) => {
|
|
1369
1463
|
const validation = validateToolInput(
|
|
1370
1464
|
createNewsletterSchema,
|
|
@@ -1404,10 +1498,13 @@ server.tool(
|
|
|
1404
1498
|
);
|
|
1405
1499
|
|
|
1406
1500
|
// Update Newsletter Tool
|
|
1407
|
-
server.
|
|
1501
|
+
server.registerTool(
|
|
1408
1502
|
'ghost_update_newsletter',
|
|
1409
|
-
|
|
1410
|
-
|
|
1503
|
+
{
|
|
1504
|
+
description:
|
|
1505
|
+
'Updates an existing newsletter in Ghost CMS. Can update name, description, sender settings, and display options.',
|
|
1506
|
+
inputSchema: updateNewsletterInputSchema,
|
|
1507
|
+
},
|
|
1411
1508
|
async (rawInput) => {
|
|
1412
1509
|
const validation = validateToolInput(
|
|
1413
1510
|
updateNewsletterInputSchema,
|
|
@@ -1449,10 +1546,13 @@ server.tool(
|
|
|
1449
1546
|
);
|
|
1450
1547
|
|
|
1451
1548
|
// Delete Newsletter Tool
|
|
1452
|
-
server.
|
|
1549
|
+
server.registerTool(
|
|
1453
1550
|
'ghost_delete_newsletter',
|
|
1454
|
-
|
|
1455
|
-
|
|
1551
|
+
{
|
|
1552
|
+
description:
|
|
1553
|
+
'Deletes a newsletter from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
1554
|
+
inputSchema: deleteNewsletterSchema,
|
|
1555
|
+
},
|
|
1456
1556
|
async (rawInput) => {
|
|
1457
1557
|
const validation = validateToolInput(
|
|
1458
1558
|
deleteNewsletterSchema,
|
|
@@ -1499,10 +1599,13 @@ const updateTierInputSchema = z.object({ id: ghostIdSchema }).merge(updateTierSc
|
|
|
1499
1599
|
const deleteTierSchema = z.object({ id: ghostIdSchema });
|
|
1500
1600
|
|
|
1501
1601
|
// Get Tiers Tool
|
|
1502
|
-
server.
|
|
1602
|
+
server.registerTool(
|
|
1503
1603
|
'ghost_get_tiers',
|
|
1504
|
-
|
|
1505
|
-
|
|
1604
|
+
{
|
|
1605
|
+
description:
|
|
1606
|
+
'Retrieves a list of tiers (membership levels) from Ghost CMS with optional filtering by type (free/paid).',
|
|
1607
|
+
inputSchema: tierQuerySchema,
|
|
1608
|
+
},
|
|
1506
1609
|
async (rawInput) => {
|
|
1507
1610
|
const validation = validateToolInput(tierQuerySchema, rawInput, 'ghost_get_tiers');
|
|
1508
1611
|
if (!validation.success) {
|
|
@@ -1538,10 +1641,12 @@ server.tool(
|
|
|
1538
1641
|
);
|
|
1539
1642
|
|
|
1540
1643
|
// Get Tier Tool
|
|
1541
|
-
server.
|
|
1644
|
+
server.registerTool(
|
|
1542
1645
|
'ghost_get_tier',
|
|
1543
|
-
|
|
1544
|
-
|
|
1646
|
+
{
|
|
1647
|
+
description: 'Retrieves a single tier (membership level) from Ghost CMS by ID.',
|
|
1648
|
+
inputSchema: getTierSchema,
|
|
1649
|
+
},
|
|
1545
1650
|
async (rawInput) => {
|
|
1546
1651
|
const validation = validateToolInput(getTierSchema, rawInput, 'ghost_get_tier');
|
|
1547
1652
|
if (!validation.success) {
|
|
@@ -1577,10 +1682,12 @@ server.tool(
|
|
|
1577
1682
|
);
|
|
1578
1683
|
|
|
1579
1684
|
// Create Tier Tool
|
|
1580
|
-
server.
|
|
1685
|
+
server.registerTool(
|
|
1581
1686
|
'ghost_create_tier',
|
|
1582
|
-
|
|
1583
|
-
|
|
1687
|
+
{
|
|
1688
|
+
description: 'Creates a new tier (membership level) in Ghost CMS with pricing and benefits.',
|
|
1689
|
+
inputSchema: createTierSchema,
|
|
1690
|
+
},
|
|
1584
1691
|
async (rawInput) => {
|
|
1585
1692
|
const validation = validateToolInput(createTierSchema, rawInput, 'ghost_create_tier');
|
|
1586
1693
|
if (!validation.success) {
|
|
@@ -1616,10 +1723,13 @@ server.tool(
|
|
|
1616
1723
|
);
|
|
1617
1724
|
|
|
1618
1725
|
// Update Tier Tool
|
|
1619
|
-
server.
|
|
1726
|
+
server.registerTool(
|
|
1620
1727
|
'ghost_update_tier',
|
|
1621
|
-
|
|
1622
|
-
|
|
1728
|
+
{
|
|
1729
|
+
description:
|
|
1730
|
+
'Updates an existing tier (membership level) in Ghost CMS. Can update pricing, benefits, and other tier properties.',
|
|
1731
|
+
inputSchema: updateTierInputSchema,
|
|
1732
|
+
},
|
|
1623
1733
|
async (rawInput) => {
|
|
1624
1734
|
const validation = validateToolInput(updateTierInputSchema, rawInput, 'ghost_update_tier');
|
|
1625
1735
|
if (!validation.success) {
|
|
@@ -1657,10 +1767,13 @@ server.tool(
|
|
|
1657
1767
|
);
|
|
1658
1768
|
|
|
1659
1769
|
// Delete Tier Tool
|
|
1660
|
-
server.
|
|
1770
|
+
server.registerTool(
|
|
1661
1771
|
'ghost_delete_tier',
|
|
1662
|
-
|
|
1663
|
-
|
|
1772
|
+
{
|
|
1773
|
+
description:
|
|
1774
|
+
'Deletes a tier (membership level) from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
1775
|
+
inputSchema: deleteTierSchema,
|
|
1776
|
+
},
|
|
1664
1777
|
async (rawInput) => {
|
|
1665
1778
|
const validation = validateToolInput(deleteTierSchema, rawInput, 'ghost_delete_tier');
|
|
1666
1779
|
if (!validation.success) {
|
|
@@ -296,7 +296,7 @@ class ResourceFetcher {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
case 'name': {
|
|
299
|
-
const tagsByName = await this.ghostService.getTags(identifier);
|
|
299
|
+
const tagsByName = await this.ghostService.getTags({ filter: `name:'${identifier}'` });
|
|
300
300
|
tag = tagsByName[0];
|
|
301
301
|
break;
|
|
302
302
|
}
|
|
@@ -329,7 +329,11 @@ class ResourceFetcher {
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
// Fetch from Ghost
|
|
332
|
-
const
|
|
332
|
+
const options = {};
|
|
333
|
+
if (query.name) {
|
|
334
|
+
options.filter = `name:'${query.name}'`;
|
|
335
|
+
}
|
|
336
|
+
const tags = await this.ghostService.getTags(options);
|
|
333
337
|
|
|
334
338
|
// Apply client-side filtering if needed
|
|
335
339
|
let filteredTags = tags;
|
|
@@ -459,7 +459,7 @@ describe('ResourceManager', () => {
|
|
|
459
459
|
const result = await resourceManager.fetchResource('ghost/tag/name:Technology');
|
|
460
460
|
|
|
461
461
|
expect(result).toEqual(tags[0]);
|
|
462
|
-
expect(mockGhostService.getTags).toHaveBeenCalledWith('Technology');
|
|
462
|
+
expect(mockGhostService.getTags).toHaveBeenCalledWith({ filter: "name:'Technology'" });
|
|
463
463
|
});
|
|
464
464
|
|
|
465
465
|
it('should use id by default when no identifier type specified', async () => {
|
|
@@ -511,7 +511,7 @@ describe('ResourceManager', () => {
|
|
|
511
511
|
|
|
512
512
|
await resourceManager.fetchResource('ghost/tags?name=Tech');
|
|
513
513
|
|
|
514
|
-
expect(mockGhostService.getTags).toHaveBeenCalledWith('Tech');
|
|
514
|
+
expect(mockGhostService.getTags).toHaveBeenCalledWith({ filter: "name:'Tech'" });
|
|
515
515
|
});
|
|
516
516
|
|
|
517
517
|
it('should apply client-side filtering', async () => {
|