@jgardner04/ghost-mcp-server 1.5.0 → 1.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgardner04/ghost-mcp-server",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "A Model Context Protocol (MCP) server for interacting with Ghost CMS via the Admin API",
5
5
  "author": "Jonathan Gardner",
6
6
  "type": "module",
@@ -41,7 +41,10 @@ vi.mock('crypto', () => ({
41
41
  const mockGetPosts = vi.fn();
42
42
  const mockGetPost = vi.fn();
43
43
  const mockGetTags = vi.fn();
44
+ const mockGetTag = vi.fn();
44
45
  const mockCreateTag = vi.fn();
46
+ const mockUpdateTag = vi.fn();
47
+ const mockDeleteTag = vi.fn();
45
48
  const mockUploadImage = vi.fn();
46
49
  const mockCreatePostService = vi.fn();
47
50
  const mockProcessImage = vi.fn();
@@ -67,6 +70,9 @@ vi.mock('../services/ghostServiceImproved.js', () => ({
67
70
  updatePost: (...args) => mockUpdatePost(...args),
68
71
  deletePost: (...args) => mockDeletePost(...args),
69
72
  searchPosts: (...args) => mockSearchPosts(...args),
73
+ getTag: (...args) => mockGetTag(...args),
74
+ updateTag: (...args) => mockUpdateTag(...args),
75
+ deleteTag: (...args) => mockDeleteTag(...args),
70
76
  }));
71
77
 
72
78
  vi.mock('../services/imageProcessingService.js', () => ({
@@ -907,3 +913,282 @@ describe('mcp_server_improved - ghost_search_posts tool', () => {
907
913
  expect(result.isError).toBeUndefined();
908
914
  });
909
915
  });
916
+
917
+ describe('ghost_get_tag', () => {
918
+ beforeEach(() => {
919
+ vi.clearAllMocks();
920
+ });
921
+
922
+ it('should be registered as a tool', () => {
923
+ expect(mockTools.has('ghost_get_tag')).toBe(true);
924
+ const tool = mockTools.get('ghost_get_tag');
925
+ expect(tool.name).toBe('ghost_get_tag');
926
+ expect(tool.description).toBeDefined();
927
+ expect(tool.schema).toBeDefined();
928
+ expect(tool.handler).toBeDefined();
929
+ });
930
+
931
+ it('should have correct schema with id and slug as optional', () => {
932
+ const tool = mockTools.get('ghost_get_tag');
933
+ expect(tool.schema.id).toBeDefined();
934
+ expect(tool.schema.slug).toBeDefined();
935
+ expect(tool.schema.include).toBeDefined();
936
+ });
937
+
938
+ it('should retrieve tag by ID', async () => {
939
+ const mockTag = {
940
+ id: '123',
941
+ name: 'Test Tag',
942
+ slug: 'test-tag',
943
+ description: 'A test tag',
944
+ };
945
+ mockGetTag.mockResolvedValue(mockTag);
946
+
947
+ const tool = mockTools.get('ghost_get_tag');
948
+ const result = await tool.handler({ id: '123' });
949
+
950
+ expect(mockGetTag).toHaveBeenCalledWith('123', {});
951
+ expect(result.content).toBeDefined();
952
+ expect(result.content[0].type).toBe('text');
953
+ expect(result.content[0].text).toContain('"id": "123"');
954
+ expect(result.content[0].text).toContain('"name": "Test Tag"');
955
+ });
956
+
957
+ it('should retrieve tag by slug', async () => {
958
+ const mockTag = {
959
+ id: '123',
960
+ name: 'Test Tag',
961
+ slug: 'test-tag',
962
+ description: 'A test tag',
963
+ };
964
+ mockGetTag.mockResolvedValue(mockTag);
965
+
966
+ const tool = mockTools.get('ghost_get_tag');
967
+ const result = await tool.handler({ slug: 'test-tag' });
968
+
969
+ expect(mockGetTag).toHaveBeenCalledWith('slug/test-tag', {});
970
+ expect(result.content[0].text).toContain('"slug": "test-tag"');
971
+ });
972
+
973
+ it('should support include parameter for post count', async () => {
974
+ const mockTag = {
975
+ id: '123',
976
+ name: 'Test Tag',
977
+ slug: 'test-tag',
978
+ count: { posts: 5 },
979
+ };
980
+ mockGetTag.mockResolvedValue(mockTag);
981
+
982
+ const tool = mockTools.get('ghost_get_tag');
983
+ const result = await tool.handler({ id: '123', include: 'count.posts' });
984
+
985
+ expect(mockGetTag).toHaveBeenCalledWith('123', { include: 'count.posts' });
986
+ expect(result.content[0].text).toContain('"count"');
987
+ });
988
+
989
+ it('should return error when neither id nor slug provided', async () => {
990
+ const tool = mockTools.get('ghost_get_tag');
991
+ const result = await tool.handler({});
992
+
993
+ expect(result.content[0].type).toBe('text');
994
+ expect(result.content[0].text).toContain('Either id or slug must be provided');
995
+ expect(result.isError).toBe(true);
996
+ });
997
+
998
+ it('should handle not found error', async () => {
999
+ mockGetTag.mockRejectedValue(new Error('Tag not found'));
1000
+
1001
+ const tool = mockTools.get('ghost_get_tag');
1002
+ const result = await tool.handler({ id: '999' });
1003
+
1004
+ expect(result.isError).toBe(true);
1005
+ expect(result.content[0].text).toContain('Tag not found');
1006
+ });
1007
+ });
1008
+
1009
+ describe('ghost_update_tag', () => {
1010
+ beforeEach(() => {
1011
+ vi.clearAllMocks();
1012
+ });
1013
+
1014
+ it('should be registered as a tool', () => {
1015
+ expect(mockTools.has('ghost_update_tag')).toBe(true);
1016
+ const tool = mockTools.get('ghost_update_tag');
1017
+ expect(tool.name).toBe('ghost_update_tag');
1018
+ expect(tool.description).toBeDefined();
1019
+ expect(tool.schema).toBeDefined();
1020
+ expect(tool.handler).toBeDefined();
1021
+ });
1022
+
1023
+ it('should have correct schema with all update fields', () => {
1024
+ const tool = mockTools.get('ghost_update_tag');
1025
+ expect(tool.schema.id).toBeDefined();
1026
+ expect(tool.schema.name).toBeDefined();
1027
+ expect(tool.schema.slug).toBeDefined();
1028
+ expect(tool.schema.description).toBeDefined();
1029
+ expect(tool.schema.feature_image).toBeDefined();
1030
+ expect(tool.schema.meta_title).toBeDefined();
1031
+ expect(tool.schema.meta_description).toBeDefined();
1032
+ });
1033
+
1034
+ it('should update tag name', async () => {
1035
+ const mockUpdatedTag = {
1036
+ id: '123',
1037
+ name: 'Updated Tag',
1038
+ slug: 'updated-tag',
1039
+ };
1040
+ mockUpdateTag.mockResolvedValue(mockUpdatedTag);
1041
+
1042
+ const tool = mockTools.get('ghost_update_tag');
1043
+ const result = await tool.handler({ id: '123', name: 'Updated Tag' });
1044
+
1045
+ expect(mockUpdateTag).toHaveBeenCalledWith('123', { name: 'Updated Tag' });
1046
+ expect(result.content[0].text).toContain('"name": "Updated Tag"');
1047
+ });
1048
+
1049
+ it('should update tag description', async () => {
1050
+ const mockUpdatedTag = {
1051
+ id: '123',
1052
+ name: 'Test Tag',
1053
+ description: 'New description',
1054
+ };
1055
+ mockUpdateTag.mockResolvedValue(mockUpdatedTag);
1056
+
1057
+ const tool = mockTools.get('ghost_update_tag');
1058
+ const result = await tool.handler({ id: '123', description: 'New description' });
1059
+
1060
+ expect(mockUpdateTag).toHaveBeenCalledWith('123', { description: 'New description' });
1061
+ expect(result.content[0].text).toContain('"description": "New description"');
1062
+ });
1063
+
1064
+ it('should update multiple fields at once', async () => {
1065
+ const mockUpdatedTag = {
1066
+ id: '123',
1067
+ name: 'Updated Tag',
1068
+ slug: 'updated-tag',
1069
+ description: 'Updated description',
1070
+ meta_title: 'Updated Meta',
1071
+ };
1072
+ mockUpdateTag.mockResolvedValue(mockUpdatedTag);
1073
+
1074
+ const tool = mockTools.get('ghost_update_tag');
1075
+ await tool.handler({
1076
+ id: '123',
1077
+ name: 'Updated Tag',
1078
+ description: 'Updated description',
1079
+ meta_title: 'Updated Meta',
1080
+ });
1081
+
1082
+ expect(mockUpdateTag).toHaveBeenCalledWith('123', {
1083
+ name: 'Updated Tag',
1084
+ description: 'Updated description',
1085
+ meta_title: 'Updated Meta',
1086
+ });
1087
+ });
1088
+
1089
+ it('should update tag feature image', async () => {
1090
+ const mockUpdatedTag = {
1091
+ id: '123',
1092
+ name: 'Test Tag',
1093
+ feature_image: 'https://example.com/image.jpg',
1094
+ };
1095
+ mockUpdateTag.mockResolvedValue(mockUpdatedTag);
1096
+
1097
+ const tool = mockTools.get('ghost_update_tag');
1098
+ await tool.handler({
1099
+ id: '123',
1100
+ feature_image: 'https://example.com/image.jpg',
1101
+ });
1102
+
1103
+ expect(mockUpdateTag).toHaveBeenCalledWith('123', {
1104
+ feature_image: 'https://example.com/image.jpg',
1105
+ });
1106
+ });
1107
+
1108
+ it('should return error when id is missing', async () => {
1109
+ const tool = mockTools.get('ghost_update_tag');
1110
+ const result = await tool.handler({ name: 'Test' });
1111
+
1112
+ expect(result.isError).toBe(true);
1113
+ expect(result.content[0].text).toContain('Tag ID is required');
1114
+ });
1115
+
1116
+ it('should handle validation error', async () => {
1117
+ mockUpdateTag.mockRejectedValue(new Error('Validation failed'));
1118
+
1119
+ const tool = mockTools.get('ghost_update_tag');
1120
+ const result = await tool.handler({ id: '123', name: '' });
1121
+
1122
+ expect(result.isError).toBe(true);
1123
+ expect(result.content[0].text).toContain('Validation failed');
1124
+ });
1125
+
1126
+ it('should handle not found error', async () => {
1127
+ mockUpdateTag.mockRejectedValue(new Error('Tag not found'));
1128
+
1129
+ const tool = mockTools.get('ghost_update_tag');
1130
+ const result = await tool.handler({ id: '999', name: 'Test' });
1131
+
1132
+ expect(result.isError).toBe(true);
1133
+ expect(result.content[0].text).toContain('Tag not found');
1134
+ });
1135
+ });
1136
+
1137
+ describe('ghost_delete_tag', () => {
1138
+ beforeEach(() => {
1139
+ vi.clearAllMocks();
1140
+ });
1141
+
1142
+ it('should be registered as a tool', () => {
1143
+ expect(mockTools.has('ghost_delete_tag')).toBe(true);
1144
+ const tool = mockTools.get('ghost_delete_tag');
1145
+ expect(tool.name).toBe('ghost_delete_tag');
1146
+ expect(tool.description).toBeDefined();
1147
+ expect(tool.schema).toBeDefined();
1148
+ expect(tool.handler).toBeDefined();
1149
+ });
1150
+
1151
+ it('should have correct schema with id field', () => {
1152
+ const tool = mockTools.get('ghost_delete_tag');
1153
+ expect(tool.schema.id).toBeDefined();
1154
+ });
1155
+
1156
+ it('should delete tag successfully', async () => {
1157
+ mockDeleteTag.mockResolvedValue({ success: true });
1158
+
1159
+ const tool = mockTools.get('ghost_delete_tag');
1160
+ const result = await tool.handler({ id: '123' });
1161
+
1162
+ expect(mockDeleteTag).toHaveBeenCalledWith('123');
1163
+ expect(result.content[0].text).toContain('successfully deleted');
1164
+ expect(result.isError).toBeUndefined();
1165
+ });
1166
+
1167
+ it('should return error when id is missing', async () => {
1168
+ const tool = mockTools.get('ghost_delete_tag');
1169
+ const result = await tool.handler({});
1170
+
1171
+ expect(result.isError).toBe(true);
1172
+ expect(result.content[0].text).toContain('Tag ID is required');
1173
+ });
1174
+
1175
+ it('should handle not found error', async () => {
1176
+ mockDeleteTag.mockRejectedValue(new Error('Tag not found'));
1177
+
1178
+ const tool = mockTools.get('ghost_delete_tag');
1179
+ const result = await tool.handler({ id: '999' });
1180
+
1181
+ expect(result.isError).toBe(true);
1182
+ expect(result.content[0].text).toContain('Tag not found');
1183
+ });
1184
+
1185
+ it('should handle deletion error', async () => {
1186
+ mockDeleteTag.mockRejectedValue(new Error('Failed to delete tag'));
1187
+
1188
+ const tool = mockTools.get('ghost_delete_tag');
1189
+ const result = await tool.handler({ id: '123' });
1190
+
1191
+ expect(result.isError).toBe(true);
1192
+ expect(result.content[0].text).toContain('Failed to delete tag');
1193
+ });
1194
+ });
@@ -122,6 +122,129 @@ server.tool(
122
122
  }
123
123
  );
124
124
 
125
+ // Get Tag Tool
126
+ server.tool(
127
+ 'ghost_get_tag',
128
+ 'Retrieves a single tag from Ghost CMS by ID or slug.',
129
+ {
130
+ id: z.string().optional().describe('The ID of the tag to retrieve.'),
131
+ slug: z.string().optional().describe('The slug of the tag to retrieve.'),
132
+ include: z
133
+ .string()
134
+ .optional()
135
+ .describe('Additional resources to include (e.g., "count.posts").'),
136
+ },
137
+ async ({ id, slug, include }) => {
138
+ console.error(`Executing tool: ghost_get_tag`);
139
+ try {
140
+ if (!id && !slug) {
141
+ throw new Error('Either id or slug must be provided');
142
+ }
143
+
144
+ await loadServices();
145
+
146
+ // If slug is provided, use the slug/slug-name format
147
+ const identifier = slug ? `slug/${slug}` : id;
148
+ const options = include ? { include } : {};
149
+
150
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
151
+ const tag = await ghostServiceImproved.getTag(identifier, options);
152
+ console.error(`Tag retrieved successfully. Tag ID: ${tag.id}`);
153
+
154
+ return {
155
+ content: [{ type: 'text', text: JSON.stringify(tag, null, 2) }],
156
+ };
157
+ } catch (error) {
158
+ console.error(`Error in ghost_get_tag:`, error);
159
+ return {
160
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
161
+ isError: true,
162
+ };
163
+ }
164
+ }
165
+ );
166
+
167
+ // Update Tag Tool
168
+ server.tool(
169
+ 'ghost_update_tag',
170
+ 'Updates an existing tag in Ghost CMS.',
171
+ {
172
+ id: z.string().describe('The ID of the tag to update.'),
173
+ name: z.string().optional().describe('The new name for the tag.'),
174
+ slug: z.string().optional().describe('The new slug for the tag.'),
175
+ description: z.string().optional().describe('The new description for the tag.'),
176
+ feature_image: z.string().url().optional().describe('URL of the feature image for the tag.'),
177
+ meta_title: z.string().optional().describe('SEO meta title for the tag.'),
178
+ meta_description: z.string().optional().describe('SEO meta description for the tag.'),
179
+ },
180
+ async ({ id, name, slug, description, feature_image, meta_title, meta_description }) => {
181
+ console.error(`Executing tool: ghost_update_tag for ID: ${id}`);
182
+ try {
183
+ if (!id) {
184
+ throw new Error('Tag ID is required');
185
+ }
186
+
187
+ await loadServices();
188
+
189
+ // Build update data object with only provided fields
190
+ const updateData = {};
191
+ if (name !== undefined) updateData.name = name;
192
+ if (slug !== undefined) updateData.slug = slug;
193
+ if (description !== undefined) updateData.description = description;
194
+ if (feature_image !== undefined) updateData.feature_image = feature_image;
195
+ if (meta_title !== undefined) updateData.meta_title = meta_title;
196
+ if (meta_description !== undefined) updateData.meta_description = meta_description;
197
+
198
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
199
+ const updatedTag = await ghostServiceImproved.updateTag(id, updateData);
200
+ console.error(`Tag updated successfully. Tag ID: ${updatedTag.id}`);
201
+
202
+ return {
203
+ content: [{ type: 'text', text: JSON.stringify(updatedTag, null, 2) }],
204
+ };
205
+ } catch (error) {
206
+ console.error(`Error in ghost_update_tag:`, error);
207
+ return {
208
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
209
+ isError: true,
210
+ };
211
+ }
212
+ }
213
+ );
214
+
215
+ // Delete Tag Tool
216
+ server.tool(
217
+ 'ghost_delete_tag',
218
+ 'Deletes a tag from Ghost CMS by ID. This operation is permanent.',
219
+ {
220
+ id: z.string().describe('The ID of the tag to delete.'),
221
+ },
222
+ async ({ id }) => {
223
+ console.error(`Executing tool: ghost_delete_tag for ID: ${id}`);
224
+ try {
225
+ if (!id) {
226
+ throw new Error('Tag ID is required');
227
+ }
228
+
229
+ await loadServices();
230
+
231
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
232
+ await ghostServiceImproved.deleteTag(id);
233
+ console.error(`Tag deleted successfully. Tag ID: ${id}`);
234
+
235
+ return {
236
+ content: [{ type: 'text', text: `Tag with ID ${id} has been successfully deleted.` }],
237
+ };
238
+ } catch (error) {
239
+ console.error(`Error in ghost_delete_tag:`, error);
240
+ return {
241
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
242
+ isError: true,
243
+ };
244
+ }
245
+ }
246
+ );
247
+
125
248
  // Upload Image Tool
126
249
  server.tool(
127
250
  'ghost_upload_image',
@@ -780,6 +903,198 @@ server.tool(
780
903
  }
781
904
  );
782
905
 
906
+ // =============================================================================
907
+ // NEWSLETTER TOOLS
908
+ // =============================================================================
909
+
910
+ // Get Newsletters Tool
911
+ server.tool(
912
+ 'ghost_get_newsletters',
913
+ 'Retrieves a list of newsletters from Ghost CMS with optional filtering.',
914
+ {
915
+ limit: z
916
+ .number()
917
+ .min(1)
918
+ .max(100)
919
+ .optional()
920
+ .describe('Number of newsletters to retrieve (1-100). Default is all.'),
921
+ filter: z.string().optional().describe('Ghost NQL filter string for advanced filtering.'),
922
+ },
923
+ async (input) => {
924
+ console.error(`Executing tool: ghost_get_newsletters`);
925
+ try {
926
+ await loadServices();
927
+
928
+ const options = {};
929
+ if (input.limit !== undefined) options.limit = input.limit;
930
+ if (input.filter !== undefined) options.filter = input.filter;
931
+
932
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
933
+ const newsletters = await ghostServiceImproved.getNewsletters(options);
934
+ console.error(`Retrieved ${newsletters.length} newsletters from Ghost.`);
935
+
936
+ return {
937
+ content: [{ type: 'text', text: JSON.stringify(newsletters, null, 2) }],
938
+ };
939
+ } catch (error) {
940
+ console.error(`Error in ghost_get_newsletters:`, error);
941
+ return {
942
+ content: [{ type: 'text', text: `Error retrieving newsletters: ${error.message}` }],
943
+ isError: true,
944
+ };
945
+ }
946
+ }
947
+ );
948
+
949
+ // Get Newsletter Tool
950
+ server.tool(
951
+ 'ghost_get_newsletter',
952
+ 'Retrieves a single newsletter from Ghost CMS by ID.',
953
+ {
954
+ id: z.string().describe('The ID of the newsletter to retrieve.'),
955
+ },
956
+ async ({ id }) => {
957
+ console.error(`Executing tool: ghost_get_newsletter for ID: ${id}`);
958
+ try {
959
+ await loadServices();
960
+
961
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
962
+ const newsletter = await ghostServiceImproved.getNewsletter(id);
963
+ console.error(`Retrieved newsletter: ${newsletter.name} (ID: ${newsletter.id})`);
964
+
965
+ return {
966
+ content: [{ type: 'text', text: JSON.stringify(newsletter, null, 2) }],
967
+ };
968
+ } catch (error) {
969
+ console.error(`Error in ghost_get_newsletter:`, error);
970
+ return {
971
+ content: [{ type: 'text', text: `Error retrieving newsletter: ${error.message}` }],
972
+ isError: true,
973
+ };
974
+ }
975
+ }
976
+ );
977
+
978
+ // Create Newsletter Tool
979
+ server.tool(
980
+ 'ghost_create_newsletter',
981
+ 'Creates a new newsletter in Ghost CMS with customizable sender settings and display options.',
982
+ {
983
+ name: z.string().describe('The name of the newsletter.'),
984
+ description: z.string().optional().describe('A description for the newsletter.'),
985
+ sender_name: z.string().optional().describe('The sender name for newsletter emails.'),
986
+ sender_email: z
987
+ .string()
988
+ .email()
989
+ .optional()
990
+ .describe('The sender email address for newsletter emails.'),
991
+ sender_reply_to: z
992
+ .enum(['newsletter', 'support'])
993
+ .optional()
994
+ .describe('Reply-to address setting. Options: newsletter, support.'),
995
+ subscribe_on_signup: z
996
+ .boolean()
997
+ .optional()
998
+ .describe('Whether new members are automatically subscribed to this newsletter on signup.'),
999
+ show_header_icon: z
1000
+ .boolean()
1001
+ .optional()
1002
+ .describe('Whether to show the site icon in the newsletter header.'),
1003
+ show_header_title: z
1004
+ .boolean()
1005
+ .optional()
1006
+ .describe('Whether to show the site title in the newsletter header.'),
1007
+ },
1008
+ async (input) => {
1009
+ console.error(`Executing tool: ghost_create_newsletter with name: ${input.name}`);
1010
+ try {
1011
+ await loadServices();
1012
+
1013
+ const newsletterService = await import('./services/newsletterService.js');
1014
+ const createdNewsletter = await newsletterService.createNewsletterService(input);
1015
+ console.error(`Newsletter created successfully. Newsletter ID: ${createdNewsletter.id}`);
1016
+
1017
+ return {
1018
+ content: [{ type: 'text', text: JSON.stringify(createdNewsletter, null, 2) }],
1019
+ };
1020
+ } catch (error) {
1021
+ console.error(`Error in ghost_create_newsletter:`, error);
1022
+ return {
1023
+ content: [{ type: 'text', text: `Error creating newsletter: ${error.message}` }],
1024
+ isError: true,
1025
+ };
1026
+ }
1027
+ }
1028
+ );
1029
+
1030
+ // Update Newsletter Tool
1031
+ server.tool(
1032
+ 'ghost_update_newsletter',
1033
+ 'Updates an existing newsletter in Ghost CMS. Can update name, description, sender settings, and display options.',
1034
+ {
1035
+ id: z.string().describe('The ID of the newsletter to update.'),
1036
+ name: z.string().optional().describe('New name for the newsletter.'),
1037
+ description: z.string().optional().describe('New description for the newsletter.'),
1038
+ sender_name: z.string().optional().describe('New sender name for newsletter emails.'),
1039
+ sender_email: z.string().email().optional().describe('New sender email address.'),
1040
+ subscribe_on_signup: z
1041
+ .boolean()
1042
+ .optional()
1043
+ .describe('Whether new members are automatically subscribed to this newsletter on signup.'),
1044
+ },
1045
+ async (input) => {
1046
+ console.error(`Executing tool: ghost_update_newsletter for newsletter ID: ${input.id}`);
1047
+ try {
1048
+ await loadServices();
1049
+
1050
+ const { id, ...updateData } = input;
1051
+
1052
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
1053
+ const updatedNewsletter = await ghostServiceImproved.updateNewsletter(id, updateData);
1054
+ console.error(`Newsletter updated successfully. Newsletter ID: ${updatedNewsletter.id}`);
1055
+
1056
+ return {
1057
+ content: [{ type: 'text', text: JSON.stringify(updatedNewsletter, null, 2) }],
1058
+ };
1059
+ } catch (error) {
1060
+ console.error(`Error in ghost_update_newsletter:`, error);
1061
+ return {
1062
+ content: [{ type: 'text', text: `Error updating newsletter: ${error.message}` }],
1063
+ isError: true,
1064
+ };
1065
+ }
1066
+ }
1067
+ );
1068
+
1069
+ // Delete Newsletter Tool
1070
+ server.tool(
1071
+ 'ghost_delete_newsletter',
1072
+ 'Deletes a newsletter from Ghost CMS by ID. This operation is permanent and cannot be undone.',
1073
+ {
1074
+ id: z.string().describe('The ID of the newsletter to delete.'),
1075
+ },
1076
+ async ({ id }) => {
1077
+ console.error(`Executing tool: ghost_delete_newsletter for newsletter ID: ${id}`);
1078
+ try {
1079
+ await loadServices();
1080
+
1081
+ const ghostServiceImproved = await import('./services/ghostServiceImproved.js');
1082
+ await ghostServiceImproved.deleteNewsletter(id);
1083
+ console.error(`Newsletter deleted successfully. Newsletter ID: ${id}`);
1084
+
1085
+ return {
1086
+ content: [{ type: 'text', text: `Newsletter ${id} has been successfully deleted.` }],
1087
+ };
1088
+ } catch (error) {
1089
+ console.error(`Error in ghost_delete_newsletter:`, error);
1090
+ return {
1091
+ content: [{ type: 'text', text: `Error deleting newsletter: ${error.message}` }],
1092
+ isError: true,
1093
+ };
1094
+ }
1095
+ }
1096
+ );
1097
+
783
1098
  // --- Main Entry Point ---
784
1099
 
785
1100
  async function main() {
@@ -790,9 +1105,10 @@ async function main() {
790
1105
 
791
1106
  console.error('Ghost MCP Server running on stdio transport');
792
1107
  console.error(
793
- 'Available tools: ghost_get_tags, ghost_create_tag, ghost_upload_image, ' +
1108
+ 'Available tools: ghost_get_tags, ghost_create_tag, ghost_get_tag, ghost_update_tag, ghost_delete_tag, ghost_upload_image, ' +
794
1109
  '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'
1110
+ 'ghost_get_pages, ghost_get_page, ghost_create_page, ghost_update_page, ghost_delete_page, ghost_search_pages, ' +
1111
+ 'ghost_get_newsletters, ghost_get_newsletter, ghost_create_newsletter, ghost_update_newsletter, ghost_delete_newsletter'
796
1112
  );
797
1113
  }
798
1114