@jgardner04/ghost-mcp-server 1.13.4 → 1.14.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/README.md +68 -0
- package/package.json +7 -3
- package/src/__tests__/helpers/testUtils.js +15 -1
- package/src/__tests__/mcp_server.test.js +152 -1
- package/src/__tests__/mcp_server_pages.test.js +23 -6
- package/src/controllers/__tests__/imageController.test.js +2 -2
- package/src/controllers/imageController.js +11 -10
- package/src/mcp_server.js +647 -1203
- package/src/routes/__tests__/imageRoutes.test.js +2 -2
- package/src/schemas/__tests__/common.test.js +3 -3
- package/src/schemas/__tests__/pageSchemas.test.js +11 -2
- package/src/schemas/common.js +3 -2
- package/src/schemas/pageSchemas.js +1 -1
- package/src/schemas/postSchemas.js +1 -1
- package/src/services/__tests__/createResourceService.test.js +468 -0
- package/src/services/__tests__/ghostService.test.js +0 -19
- package/src/services/__tests__/ghostServiceImproved.members.test.js +0 -3
- package/src/services/__tests__/ghostServiceImproved.newsletters.test.js +2 -2
- package/src/services/__tests__/ghostServiceImproved.pages.test.js +0 -3
- package/src/services/__tests__/ghostServiceImproved.posts.test.js +0 -1
- package/src/services/__tests__/ghostServiceImproved.tags.test.js +0 -3
- package/src/services/__tests__/ghostServiceImproved.tiers.test.js +3 -5
- package/src/services/__tests__/imageProcessingService.test.js +148 -177
- package/src/services/__tests__/images.test.js +78 -0
- package/src/services/createResourceService.js +138 -0
- package/src/services/ghostApiClient.js +240 -0
- package/src/services/ghostService.js +1 -19
- package/src/services/ghostServiceImproved.js +76 -915
- package/src/services/imageProcessingService.js +100 -56
- package/src/services/images.js +54 -0
- package/src/services/members.js +127 -0
- package/src/services/newsletters.js +63 -0
- package/src/services/pageService.js +2 -2
- package/src/services/pages.js +116 -0
- package/src/services/posts.js +116 -0
- package/src/services/tags.js +118 -0
- package/src/services/tiers.js +72 -0
- package/src/services/validators.js +218 -0
- package/src/utils/__tests__/imageInputResolver.test.js +134 -0
- package/src/utils/imageInputResolver.js +127 -0
package/src/mcp_server.js
CHANGED
|
@@ -11,6 +11,7 @@ import crypto from 'crypto';
|
|
|
11
11
|
import { ValidationError } from './errors/index.js';
|
|
12
12
|
import { validateToolInput } from './utils/validation.js';
|
|
13
13
|
import { trackTempFile, cleanupTempFiles } from './utils/tempFileManager.js';
|
|
14
|
+
import { resolveLocalImagePath, decodeBase64ToTempFile } from './utils/imageInputResolver.js';
|
|
14
15
|
import {
|
|
15
16
|
createTagSchema,
|
|
16
17
|
updateTagSchema,
|
|
@@ -83,6 +84,49 @@ const escapeNqlValue = (value) => {
|
|
|
83
84
|
return value.replace(/'/g, "''");
|
|
84
85
|
};
|
|
85
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Higher-order function that wraps a tool handler with standardized
|
|
89
|
+
* input validation, service loading, and error handling.
|
|
90
|
+
*
|
|
91
|
+
* Note: Each registerTool call passes the schema twice — once as `inputSchema`
|
|
92
|
+
* (MCP protocol metadata exposed to clients) and once here for runtime input
|
|
93
|
+
* validation via validateToolInput. These serve different purposes and both
|
|
94
|
+
* are required.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} toolName - The tool identifier (e.g., 'ghost_get_tags')
|
|
97
|
+
* @param {object} schema - Zod schema for input validation
|
|
98
|
+
* @param {Function} handler - Async function receiving validated input, returns MCP response
|
|
99
|
+
* @returns {Function} Wrapped async handler for server.registerTool
|
|
100
|
+
*/
|
|
101
|
+
const withErrorHandling = (toolName, schema, handler) => {
|
|
102
|
+
const zodContext = toolName.replace('ghost_', '').replace(/_/g, ' ');
|
|
103
|
+
return async (rawInput) => {
|
|
104
|
+
console.error(`Executing tool: ${toolName}`);
|
|
105
|
+
const validation = validateToolInput(schema, rawInput, toolName);
|
|
106
|
+
if (!validation.success) {
|
|
107
|
+
return validation.errorResponse;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await loadServices();
|
|
112
|
+
return await handler(validation.data);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error(`Error in ${toolName}:`, error);
|
|
115
|
+
if (error.name === 'ZodError') {
|
|
116
|
+
const validationError = ValidationError.fromZod(error, zodContext);
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: 'text', text: JSON.stringify(validationError.toJSON(), null, 2) }],
|
|
119
|
+
isError: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: 'text', text: `Error in ${toolName}: ${error.message}` }],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
86
130
|
// Create server instance with new API
|
|
87
131
|
const server = new McpServer({
|
|
88
132
|
name: 'ghost-mcp-server',
|
|
@@ -116,58 +160,32 @@ server.registerTool(
|
|
|
116
160
|
'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
161
|
inputSchema: getTagsSchema,
|
|
118
162
|
},
|
|
119
|
-
async (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
}
|
|
163
|
+
withErrorHandling('ghost_get_tags', getTagsSchema, async (input) => {
|
|
164
|
+
// Build options object with provided parameters
|
|
165
|
+
const options = {};
|
|
166
|
+
if (input.limit !== undefined) options.limit = input.limit;
|
|
167
|
+
if (input.page !== undefined) options.page = input.page;
|
|
168
|
+
if (input.order !== undefined) options.order = input.order;
|
|
169
|
+
if (input.include !== undefined) options.include = input.include;
|
|
170
|
+
|
|
171
|
+
// Build filter string from individual filter parameters
|
|
172
|
+
const filters = [];
|
|
173
|
+
if (input.name) filters.push(`name:'${escapeNqlValue(input.name)}'`);
|
|
174
|
+
if (input.slug) filters.push(`slug:'${escapeNqlValue(input.slug)}'`);
|
|
175
|
+
if (input.visibility) filters.push(`visibility:'${input.visibility}'`); // visibility is enum-validated, no escaping needed
|
|
176
|
+
if (input.filter) filters.push(input.filter);
|
|
177
|
+
|
|
178
|
+
if (filters.length > 0) {
|
|
179
|
+
options.filter = filters.join('+');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const tags = await ghostService.getTags(options);
|
|
183
|
+
console.error(`Retrieved ${tags.length} tags from Ghost.`);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
content: [{ type: 'text', text: JSON.stringify(tags, null, 2) }],
|
|
187
|
+
};
|
|
188
|
+
})
|
|
171
189
|
);
|
|
172
190
|
|
|
173
191
|
// Create Tag Tool
|
|
@@ -177,37 +195,14 @@ server.registerTool(
|
|
|
177
195
|
description: 'Creates a new tag in Ghost CMS.',
|
|
178
196
|
inputSchema: createTagSchema,
|
|
179
197
|
},
|
|
180
|
-
async (
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
return validation.errorResponse;
|
|
184
|
-
}
|
|
185
|
-
const input = validation.data;
|
|
198
|
+
withErrorHandling('ghost_create_tag', createTagSchema, async (input) => {
|
|
199
|
+
const createdTag = await ghostService.createTag(input);
|
|
200
|
+
console.error(`Tag created successfully. Tag ID: ${createdTag.id}`);
|
|
186
201
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.error(`Tag created successfully. Tag ID: ${createdTag.id}`);
|
|
192
|
-
|
|
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
|
-
}
|
|
202
|
+
return {
|
|
203
|
+
content: [{ type: 'text', text: JSON.stringify(createdTag, null, 2) }],
|
|
204
|
+
};
|
|
205
|
+
})
|
|
211
206
|
);
|
|
212
207
|
|
|
213
208
|
// Get Tag Tool
|
|
@@ -217,42 +212,22 @@ server.registerTool(
|
|
|
217
212
|
description: 'Retrieves a single tag from Ghost CMS by ID or slug.',
|
|
218
213
|
inputSchema: getTagSchema,
|
|
219
214
|
},
|
|
220
|
-
async (
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
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();
|
|
215
|
+
withErrorHandling('ghost_get_tag', getTagSchema, async (input) => {
|
|
216
|
+
const options = {};
|
|
217
|
+
if (input.include !== undefined) options.include = input.include;
|
|
230
218
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
219
|
+
if (!input.id && !input.slug) {
|
|
220
|
+
throw new Error('Either id or slug is required');
|
|
221
|
+
}
|
|
222
|
+
const identifier = input.id || `slug/${input.slug}`;
|
|
234
223
|
|
|
235
|
-
|
|
236
|
-
|
|
224
|
+
const tag = await ghostService.getTag(identifier, options);
|
|
225
|
+
console.error(`Retrieved tag: ${tag.name} (ID: ${tag.id})`);
|
|
237
226
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
}
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: 'text', text: JSON.stringify(tag, null, 2) }],
|
|
229
|
+
};
|
|
230
|
+
})
|
|
256
231
|
);
|
|
257
232
|
|
|
258
233
|
// Update Tag Tool
|
|
@@ -262,184 +237,321 @@ server.registerTool(
|
|
|
262
237
|
description: 'Updates an existing tag in Ghost CMS.',
|
|
263
238
|
inputSchema: updateTagInputSchema,
|
|
264
239
|
},
|
|
265
|
-
async (
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
240
|
+
withErrorHandling('ghost_update_tag', updateTagInputSchema, async (input) => {
|
|
241
|
+
const { id, ...updateData } = input;
|
|
242
|
+
const updatedTag = await ghostService.updateTag(id, updateData);
|
|
243
|
+
console.error(`Tag updated successfully. Tag ID: ${updatedTag.id}`);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
content: [{ type: 'text', text: JSON.stringify(updatedTag, null, 2) }],
|
|
247
|
+
};
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Delete Tag Tool
|
|
252
|
+
server.registerTool(
|
|
253
|
+
'ghost_delete_tag',
|
|
254
|
+
{
|
|
255
|
+
description:
|
|
256
|
+
'Deletes a tag from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
257
|
+
inputSchema: deleteTagSchema,
|
|
258
|
+
},
|
|
259
|
+
withErrorHandling('ghost_delete_tag', deleteTagSchema, async (input) => {
|
|
260
|
+
const { id } = input;
|
|
261
|
+
await ghostService.deleteTag(id);
|
|
262
|
+
console.error(`Tag deleted successfully. Tag ID: ${id}`);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
content: [{ type: 'text', text: `Tag ${id} has been successfully deleted.` }],
|
|
266
|
+
};
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// --- Image Schema ---
|
|
271
|
+
// Base object (plain ZodObject — keeps `.shape` available for MCP schema
|
|
272
|
+
// introspection). Runtime XOR validation is applied at the tool level
|
|
273
|
+
// via a manual check so we never wrap with ZodEffects.
|
|
274
|
+
const imageInputFields = {
|
|
275
|
+
imageUrl: z.string().optional().meta({
|
|
276
|
+
description: 'The publicly accessible URL of the image to download and upload.',
|
|
277
|
+
}),
|
|
278
|
+
imagePath: z.string().optional().meta({
|
|
279
|
+
description:
|
|
280
|
+
'Absolute path to a local image file. Only accepted when the GHOST_MCP_IMAGE_ROOT env var is set; paths must resolve inside that root.',
|
|
281
|
+
}),
|
|
282
|
+
imageBase64: z.string().optional().meta({
|
|
283
|
+
description:
|
|
284
|
+
'Base64-encoded image bytes (with or without data: URI prefix). Decoded size capped at 5MB to respect MCP transport limits. Requires mimeType.',
|
|
285
|
+
}),
|
|
286
|
+
mimeType: z.string().optional().meta({
|
|
287
|
+
description:
|
|
288
|
+
'MIME type for imageBase64 input (e.g. image/png, image/jpeg, image/svg+xml). Required when imageBase64 is used.',
|
|
289
|
+
}),
|
|
290
|
+
alt: z.string().optional().meta({
|
|
291
|
+
description:
|
|
292
|
+
'Alt text for the image. If omitted, a default will be generated from the filename.',
|
|
293
|
+
}),
|
|
294
|
+
purpose: z.enum(['image', 'profile_image', 'icon']).optional().meta({
|
|
295
|
+
description:
|
|
296
|
+
'Intended use. Ghost validates format/size per purpose (icon/profile_image must be square; icon also accepts ICO).',
|
|
297
|
+
}),
|
|
298
|
+
ref: z.string().max(200).optional().meta({
|
|
299
|
+
description:
|
|
300
|
+
'Caller-supplied identifier (e.g. original filename). Ghost echoes it back in the response.',
|
|
301
|
+
}),
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const uploadImageSchema = z.object(imageInputFields);
|
|
305
|
+
|
|
306
|
+
function validateImageInputXor(data) {
|
|
307
|
+
const count = Number(!!data.imageUrl) + Number(!!data.imagePath) + Number(!!data.imageBase64);
|
|
308
|
+
if (count !== 1) return 'Provide exactly one of imageUrl, imagePath, or imageBase64.';
|
|
309
|
+
if (data.imageBase64 && !data.mimeType) {
|
|
310
|
+
return 'mimeType is required when imageBase64 is provided.';
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Axios does not enforce `maxContentLength` for responseType: 'stream',
|
|
316
|
+
// so we cap downloads here by watching bytes on the response stream and
|
|
317
|
+
// destroying it on overflow. Mirrors the config value in urlValidator.js.
|
|
318
|
+
const DOWNLOAD_CAP_BYTES = 50 * 1024 * 1024;
|
|
319
|
+
|
|
320
|
+
async function acquireImageForUpload({ imageUrl, imagePath: localPath, imageBase64, mimeType }) {
|
|
321
|
+
const tempDir = os.tmpdir();
|
|
322
|
+
|
|
323
|
+
if (imageUrl) {
|
|
324
|
+
const urlValidation = urlValidator.validateImageUrl(imageUrl);
|
|
325
|
+
if (!urlValidation.isValid) {
|
|
326
|
+
throw new Error(`Invalid image URL: ${urlValidation.error}`);
|
|
269
327
|
}
|
|
270
|
-
const
|
|
328
|
+
const axiosConfig = urlValidator.createSecureAxiosConfig(urlValidation.sanitizedUrl);
|
|
329
|
+
const response = await axios(axiosConfig);
|
|
271
330
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
331
|
+
const declared = Number(response.headers['content-length']);
|
|
332
|
+
if (Number.isFinite(declared) && declared > DOWNLOAD_CAP_BYTES) {
|
|
333
|
+
response.data.destroy();
|
|
334
|
+
throw new Error(
|
|
335
|
+
`Image exceeds ${DOWNLOAD_CAP_BYTES} byte limit (server declared ${declared})`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const extension = path.extname(imageUrl.split('?')[0]) || '.tmp';
|
|
340
|
+
const filenameHint =
|
|
341
|
+
path.basename(imageUrl.split('?')[0]) || `image-${generateUuid()}${extension}`;
|
|
342
|
+
const downloadedPath = path.join(tempDir, `mcp-download-${generateUuid()}${extension}`);
|
|
343
|
+
|
|
344
|
+
let bytes = 0;
|
|
345
|
+
response.data.on('data', (chunk) => {
|
|
346
|
+
bytes += chunk.length;
|
|
347
|
+
if (bytes > DOWNLOAD_CAP_BYTES) {
|
|
348
|
+
response.data.destroy(new Error(`Image exceeds ${DOWNLOAD_CAP_BYTES} byte limit`));
|
|
276
349
|
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const writer = fs.createWriteStream(downloadedPath);
|
|
353
|
+
response.data.pipe(writer);
|
|
354
|
+
await new Promise((resolve, reject) => {
|
|
355
|
+
writer.on('finish', resolve);
|
|
356
|
+
writer.on('error', reject);
|
|
357
|
+
response.data.on('error', reject);
|
|
358
|
+
});
|
|
359
|
+
return { acquiredPath: downloadedPath, filenameHint, source: 'url' };
|
|
360
|
+
}
|
|
277
361
|
|
|
278
|
-
|
|
362
|
+
if (localPath) {
|
|
363
|
+
const resolved = await resolveLocalImagePath(localPath);
|
|
364
|
+
return { acquiredPath: resolved, filenameHint: path.basename(resolved), source: 'path' };
|
|
365
|
+
}
|
|
279
366
|
|
|
280
|
-
|
|
281
|
-
|
|
367
|
+
if (imageBase64) {
|
|
368
|
+
const decodedPath = await decodeBase64ToTempFile(imageBase64, mimeType);
|
|
369
|
+
return {
|
|
370
|
+
acquiredPath: decodedPath,
|
|
371
|
+
filenameHint: path.basename(decodedPath),
|
|
372
|
+
source: 'base64',
|
|
373
|
+
};
|
|
374
|
+
}
|
|
282
375
|
|
|
283
|
-
|
|
284
|
-
|
|
376
|
+
throw new Error('No image input provided'); // unreachable — schema enforces one
|
|
377
|
+
}
|
|
285
378
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Acquire → process → upload an image from a validated input. Owns its
|
|
381
|
+
* own temp-file lifecycle. Returns { uploadResult, filenameHint, finalAltText }.
|
|
382
|
+
* Does NOT delete the caller's imagePath file.
|
|
383
|
+
*/
|
|
384
|
+
async function performImageUpload(data) {
|
|
385
|
+
await loadServices();
|
|
386
|
+
|
|
387
|
+
let acquiredPath = null;
|
|
388
|
+
let processedPath = null;
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const acquired = await acquireImageForUpload(data);
|
|
392
|
+
acquiredPath = acquired.acquiredPath;
|
|
393
|
+
if (acquired.source !== 'path') trackTempFile(acquiredPath);
|
|
394
|
+
|
|
395
|
+
processedPath = await imageProcessingService.processImage(
|
|
396
|
+
acquiredPath,
|
|
397
|
+
os.tmpdir(),
|
|
398
|
+
data.purpose ? { purpose: data.purpose } : {}
|
|
399
|
+
);
|
|
400
|
+
if (processedPath !== acquiredPath) trackTempFile(processedPath);
|
|
401
|
+
|
|
402
|
+
const uploadOpts = {};
|
|
403
|
+
if (data.purpose) uploadOpts.purpose = data.purpose;
|
|
404
|
+
if (data.ref) uploadOpts.ref = data.ref;
|
|
405
|
+
else if (acquired.filenameHint) uploadOpts.ref = acquired.filenameHint.slice(0, 200);
|
|
406
|
+
|
|
407
|
+
const uploadResult = await ghostService.uploadImage(processedPath, uploadOpts);
|
|
408
|
+
const finalAltText = data.alt || getDefaultAltText(acquired.filenameHint);
|
|
409
|
+
|
|
410
|
+
return { uploadResult, filenameHint: acquired.filenameHint, finalAltText };
|
|
411
|
+
} finally {
|
|
412
|
+
const toClean = [];
|
|
413
|
+
if (acquiredPath && !data.imagePath) toClean.push(acquiredPath);
|
|
414
|
+
if (processedPath && processedPath !== acquiredPath) toClean.push(processedPath);
|
|
415
|
+
if (toClean.length > 0) await cleanupTempFiles(toClean, console);
|
|
303
416
|
}
|
|
304
|
-
|
|
417
|
+
}
|
|
305
418
|
|
|
306
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Runs the standard tool-input validation then the XOR image-input check.
|
|
421
|
+
* Returns { success: true, data } or { success: false, errorResponse }
|
|
422
|
+
* so both image tools can collapse their validation prelude to one call.
|
|
423
|
+
*/
|
|
424
|
+
function validateAndXorImageInput(schema, rawInput, toolName) {
|
|
425
|
+
const validation = validateToolInput(schema, rawInput, toolName);
|
|
426
|
+
if (!validation.success) return validation;
|
|
427
|
+
const xorError = validateImageInputXor(validation.data);
|
|
428
|
+
if (xorError) {
|
|
429
|
+
return {
|
|
430
|
+
success: false,
|
|
431
|
+
errorResponse: { content: [{ type: 'text', text: xorError }], isError: true },
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return validation;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Upload Image Tool
|
|
307
438
|
server.registerTool(
|
|
308
|
-
'
|
|
439
|
+
'ghost_upload_image',
|
|
309
440
|
{
|
|
310
|
-
description:
|
|
311
|
-
|
|
441
|
+
description:
|
|
442
|
+
'Uploads an image to Ghost CMS. Accepts a remote URL, a local file path (when GHOST_MCP_IMAGE_ROOT is configured), or a base64 payload. Returns the Ghost image URL, alt text, and ref (when Ghost echoes it).',
|
|
443
|
+
inputSchema: uploadImageSchema,
|
|
312
444
|
},
|
|
313
445
|
async (rawInput) => {
|
|
314
|
-
const validation =
|
|
315
|
-
if (!validation.success)
|
|
316
|
-
return validation.errorResponse;
|
|
317
|
-
}
|
|
318
|
-
const { id } = validation.data;
|
|
446
|
+
const validation = validateAndXorImageInput(uploadImageSchema, rawInput, 'ghost_upload_image');
|
|
447
|
+
if (!validation.success) return validation.errorResponse;
|
|
319
448
|
|
|
320
|
-
console.error(`Executing tool: ghost_delete_tag for ID: ${id}`);
|
|
321
449
|
try {
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
};
|
|
450
|
+
const { uploadResult, finalAltText } = await performImageUpload(validation.data);
|
|
451
|
+
const result = { url: uploadResult.url, alt: finalAltText };
|
|
452
|
+
if (uploadResult.ref) result.ref = uploadResult.ref;
|
|
453
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
334
454
|
} catch (error) {
|
|
335
|
-
console.error(`Error in
|
|
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
|
-
}
|
|
455
|
+
console.error(`Error in ghost_upload_image:`, error);
|
|
343
456
|
return {
|
|
344
|
-
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
457
|
+
content: [{ type: 'text', text: `Error uploading image: ${error.message}` }],
|
|
345
458
|
isError: true,
|
|
346
459
|
};
|
|
347
460
|
}
|
|
348
461
|
}
|
|
349
462
|
);
|
|
350
463
|
|
|
351
|
-
// --- Image
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
464
|
+
// --- Set Feature Image Tool ---
|
|
465
|
+
// Combined upload-and-assign flow. Ghost has no delete-image endpoint,
|
|
466
|
+
// so when the post/page update fails after a successful upload, the
|
|
467
|
+
// image is orphaned in Ghost's storage — we surface its URL in the
|
|
468
|
+
// error response so the caller can reuse or record it.
|
|
469
|
+
const setFeatureImageSchema = uploadImageSchema.extend({
|
|
470
|
+
type: z.enum(['post', 'page']).meta({
|
|
471
|
+
description: 'Which resource to attach the feature image to.',
|
|
472
|
+
}),
|
|
473
|
+
id: ghostIdSchema.meta({ description: 'ID of the post or page.' }),
|
|
474
|
+
caption: z.string().max(5000).optional().meta({
|
|
475
|
+
description: 'Optional HTML caption for the feature image (max 5000 chars).',
|
|
357
476
|
}),
|
|
358
477
|
});
|
|
359
478
|
|
|
360
|
-
// Upload Image Tool
|
|
361
479
|
server.registerTool(
|
|
362
|
-
'
|
|
480
|
+
'ghost_set_feature_image',
|
|
363
481
|
{
|
|
364
482
|
description:
|
|
365
|
-
'
|
|
366
|
-
inputSchema:
|
|
483
|
+
'Uploads an image and assigns it as the feature image of a post or page (with optional alt text and caption) in one call. Accepts the same imageUrl/imagePath/imageBase64 input modes as ghost_upload_image. Returns the updated resource. If the update fails after the upload, the error response includes the orphaned image URL.',
|
|
484
|
+
inputSchema: setFeatureImageSchema,
|
|
367
485
|
},
|
|
368
486
|
async (rawInput) => {
|
|
369
|
-
const validation =
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
487
|
+
const validation = validateAndXorImageInput(
|
|
488
|
+
setFeatureImageSchema,
|
|
489
|
+
rawInput,
|
|
490
|
+
'ghost_set_feature_image'
|
|
491
|
+
);
|
|
492
|
+
if (!validation.success) return validation.errorResponse;
|
|
374
493
|
|
|
375
|
-
|
|
376
|
-
let downloadedPath = null;
|
|
377
|
-
let processedPath = null;
|
|
494
|
+
const { type, id, caption } = validation.data;
|
|
378
495
|
|
|
496
|
+
let uploadedUrl;
|
|
497
|
+
let uploadedRef;
|
|
498
|
+
let altText;
|
|
379
499
|
try {
|
|
380
|
-
await
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const axiosConfig = urlValidator.createSecureAxiosConfig(urlValidation.sanitizedUrl);
|
|
390
|
-
const response = await axios(axiosConfig);
|
|
391
|
-
const tempDir = os.tmpdir();
|
|
392
|
-
const extension = path.extname(imageUrl.split('?')[0]) || '.tmp';
|
|
393
|
-
const originalFilenameHint =
|
|
394
|
-
path.basename(imageUrl.split('?')[0]) || `image-${generateUuid()}${extension}`;
|
|
395
|
-
downloadedPath = path.join(tempDir, `mcp-download-${generateUuid()}${extension}`);
|
|
396
|
-
|
|
397
|
-
const writer = fs.createWriteStream(downloadedPath);
|
|
398
|
-
response.data.pipe(writer);
|
|
399
|
-
|
|
400
|
-
await new Promise((resolve, reject) => {
|
|
401
|
-
writer.on('finish', resolve);
|
|
402
|
-
writer.on('error', reject);
|
|
403
|
-
});
|
|
404
|
-
// Track temp file for cleanup on process exit
|
|
405
|
-
trackTempFile(downloadedPath);
|
|
406
|
-
console.error(`Downloaded image to temporary path: ${downloadedPath}`);
|
|
407
|
-
|
|
408
|
-
// 3. Process the image
|
|
409
|
-
processedPath = await imageProcessingService.processImage(downloadedPath, tempDir);
|
|
410
|
-
// Track processed file for cleanup on process exit
|
|
411
|
-
if (processedPath !== downloadedPath) {
|
|
412
|
-
trackTempFile(processedPath);
|
|
413
|
-
}
|
|
414
|
-
console.error(`Processed image path: ${processedPath}`);
|
|
415
|
-
|
|
416
|
-
// 4. Determine Alt Text
|
|
417
|
-
const defaultAlt = getDefaultAltText(originalFilenameHint);
|
|
418
|
-
const finalAltText = alt || defaultAlt;
|
|
419
|
-
console.error(`Using alt text: "${finalAltText}"`);
|
|
420
|
-
|
|
421
|
-
// 5. Upload processed image to Ghost
|
|
422
|
-
const uploadResult = await ghostService.uploadImage(processedPath);
|
|
423
|
-
console.error(`Uploaded processed image to Ghost: ${uploadResult.url}`);
|
|
424
|
-
|
|
425
|
-
// 6. Return result
|
|
426
|
-
const result = {
|
|
427
|
-
url: uploadResult.url,
|
|
428
|
-
alt: finalAltText,
|
|
500
|
+
const { uploadResult, finalAltText } = await performImageUpload(validation.data);
|
|
501
|
+
uploadedUrl = uploadResult.url;
|
|
502
|
+
uploadedRef = uploadResult.ref;
|
|
503
|
+
altText = finalAltText;
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.error(`ghost_set_feature_image: upload failed`, error);
|
|
506
|
+
return {
|
|
507
|
+
content: [{ type: 'text', text: `Upload failed: ${error.message}` }],
|
|
508
|
+
isError: true,
|
|
429
509
|
};
|
|
510
|
+
}
|
|
430
511
|
|
|
431
|
-
|
|
432
|
-
|
|
512
|
+
const updatePayload = {
|
|
513
|
+
feature_image: uploadedUrl,
|
|
514
|
+
feature_image_alt: altText,
|
|
515
|
+
};
|
|
516
|
+
if (caption !== undefined) updatePayload.feature_image_caption = caption;
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const updated =
|
|
520
|
+
type === 'post'
|
|
521
|
+
? await ghostService.updatePost(id, updatePayload)
|
|
522
|
+
: await ghostService.updatePage(id, updatePayload);
|
|
523
|
+
console.error(`ghost_set_feature_image: ${type} ${id} updated with ${uploadedUrl}`);
|
|
524
|
+
return {
|
|
525
|
+
content: [
|
|
526
|
+
{
|
|
527
|
+
type: 'text',
|
|
528
|
+
text: JSON.stringify(
|
|
529
|
+
{ uploaded: { url: uploadedUrl, ref: uploadedRef, alt: altText }, [type]: updated },
|
|
530
|
+
null,
|
|
531
|
+
2
|
|
532
|
+
),
|
|
533
|
+
},
|
|
534
|
+
],
|
|
433
535
|
};
|
|
434
536
|
} catch (error) {
|
|
435
|
-
console.error(`
|
|
436
|
-
return {
|
|
437
|
-
content: [
|
|
537
|
+
console.error(`ghost_set_feature_image: update failed (orphaned ${uploadedUrl})`, error);
|
|
538
|
+
return {
|
|
539
|
+
content: [
|
|
540
|
+
{
|
|
541
|
+
type: 'text',
|
|
542
|
+
text: JSON.stringify(
|
|
543
|
+
{
|
|
544
|
+
error: `Upload succeeded but ${type} update failed: ${error.message}`,
|
|
545
|
+
orphanedImage: { url: uploadedUrl, ref: uploadedRef, alt: altText },
|
|
546
|
+
hint: 'Ghost does not expose a delete-image endpoint; reuse this URL or leave it orphaned.',
|
|
547
|
+
},
|
|
548
|
+
null,
|
|
549
|
+
2
|
|
550
|
+
),
|
|
551
|
+
},
|
|
552
|
+
],
|
|
438
553
|
isError: true,
|
|
439
554
|
};
|
|
440
|
-
} finally {
|
|
441
|
-
// Cleanup temporary files with proper async/await
|
|
442
|
-
await cleanupTempFiles([downloadedPath, processedPath], console);
|
|
443
555
|
}
|
|
444
556
|
}
|
|
445
557
|
);
|
|
@@ -486,37 +598,14 @@ server.registerTool(
|
|
|
486
598
|
description: 'Creates a new post in Ghost CMS.',
|
|
487
599
|
inputSchema: createPostSchema,
|
|
488
600
|
},
|
|
489
|
-
async (
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
return validation.errorResponse;
|
|
493
|
-
}
|
|
494
|
-
const input = validation.data;
|
|
601
|
+
withErrorHandling('ghost_create_post', createPostSchema, async (input) => {
|
|
602
|
+
const createdPost = await postService.createPostService(input);
|
|
603
|
+
console.error(`Post created successfully. Post ID: ${createdPost.id}`);
|
|
495
604
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
}
|
|
605
|
+
return {
|
|
606
|
+
content: [{ type: 'text', text: JSON.stringify(createdPost, null, 2) }],
|
|
607
|
+
};
|
|
608
|
+
})
|
|
520
609
|
);
|
|
521
610
|
|
|
522
611
|
// Get Posts Tool
|
|
@@ -527,50 +616,26 @@ server.registerTool(
|
|
|
527
616
|
'Retrieves a list of posts from Ghost CMS with pagination, filtering, and sorting options.',
|
|
528
617
|
inputSchema: getPostsSchema,
|
|
529
618
|
},
|
|
530
|
-
async (
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
}
|
|
573
|
-
);
|
|
619
|
+
withErrorHandling('ghost_get_posts', getPostsSchema, async (input) => {
|
|
620
|
+
// Build options object with provided parameters
|
|
621
|
+
const options = {};
|
|
622
|
+
if (input.limit !== undefined) options.limit = input.limit;
|
|
623
|
+
if (input.page !== undefined) options.page = input.page;
|
|
624
|
+
if (input.status !== undefined) options.status = input.status;
|
|
625
|
+
if (input.include !== undefined) options.include = input.include;
|
|
626
|
+
if (input.filter !== undefined) options.filter = input.filter;
|
|
627
|
+
if (input.order !== undefined) options.order = input.order;
|
|
628
|
+
if (input.fields !== undefined) options.fields = input.fields;
|
|
629
|
+
if (input.formats !== undefined) options.formats = input.formats;
|
|
630
|
+
|
|
631
|
+
const posts = await ghostService.getPosts(options);
|
|
632
|
+
console.error(`Retrieved ${posts.length} posts from Ghost.`);
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
content: [{ type: 'text', text: JSON.stringify(posts, null, 2) }],
|
|
636
|
+
};
|
|
637
|
+
})
|
|
638
|
+
);
|
|
574
639
|
|
|
575
640
|
// Get Post Tool
|
|
576
641
|
server.registerTool(
|
|
@@ -579,45 +644,24 @@ server.registerTool(
|
|
|
579
644
|
description: 'Retrieves a single post from Ghost CMS by ID or slug.',
|
|
580
645
|
inputSchema: getPostSchema,
|
|
581
646
|
},
|
|
582
|
-
async (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
const input = validation.data;
|
|
588
|
-
|
|
589
|
-
console.error(`Executing tool: ghost_get_post`);
|
|
590
|
-
try {
|
|
591
|
-
await loadServices();
|
|
647
|
+
withErrorHandling('ghost_get_post', getPostSchema, async (input) => {
|
|
648
|
+
// Build options object
|
|
649
|
+
const options = {};
|
|
650
|
+
if (input.include !== undefined) options.include = input.include;
|
|
592
651
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const identifier = input.id || `slug/${input.slug}`;
|
|
652
|
+
// Determine identifier (prefer ID over slug)
|
|
653
|
+
if (!input.id && !input.slug) {
|
|
654
|
+
throw new Error('Either id or slug is required');
|
|
655
|
+
}
|
|
656
|
+
const identifier = input.id || `slug/${input.slug}`;
|
|
599
657
|
|
|
600
|
-
|
|
601
|
-
|
|
658
|
+
const post = await ghostService.getPost(identifier, options);
|
|
659
|
+
console.error(`Retrieved post: ${post.title} (ID: ${post.id})`);
|
|
602
660
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
}
|
|
661
|
+
return {
|
|
662
|
+
content: [{ type: 'text', text: JSON.stringify(post, null, 2) }],
|
|
663
|
+
};
|
|
664
|
+
})
|
|
621
665
|
);
|
|
622
666
|
|
|
623
667
|
// Search Posts Tool
|
|
@@ -627,43 +671,19 @@ server.registerTool(
|
|
|
627
671
|
description: 'Search for posts in Ghost CMS by query string with optional status filtering.',
|
|
628
672
|
inputSchema: searchPostsSchema,
|
|
629
673
|
},
|
|
630
|
-
async (
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
console.error(`
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
}
|
|
674
|
+
withErrorHandling('ghost_search_posts', searchPostsSchema, async (input) => {
|
|
675
|
+
// Build options object with provided parameters
|
|
676
|
+
const options = {};
|
|
677
|
+
if (input.status !== undefined) options.status = input.status;
|
|
678
|
+
if (input.limit !== undefined) options.limit = input.limit;
|
|
679
|
+
|
|
680
|
+
const posts = await ghostService.searchPosts(input.query, options);
|
|
681
|
+
console.error(`Found ${posts.length} posts matching "${input.query}".`);
|
|
682
|
+
|
|
683
|
+
return {
|
|
684
|
+
content: [{ type: 'text', text: JSON.stringify(posts, null, 2) }],
|
|
685
|
+
};
|
|
686
|
+
})
|
|
667
687
|
);
|
|
668
688
|
|
|
669
689
|
// Update Post Tool
|
|
@@ -674,41 +694,17 @@ server.registerTool(
|
|
|
674
694
|
'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
695
|
inputSchema: updatePostInputSchema,
|
|
676
696
|
},
|
|
677
|
-
async (
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
return validation.errorResponse;
|
|
681
|
-
}
|
|
682
|
-
const input = validation.data;
|
|
683
|
-
|
|
684
|
-
console.error(`Executing tool: ghost_update_post for post ID: ${input.id}`);
|
|
685
|
-
try {
|
|
686
|
-
await loadServices();
|
|
687
|
-
|
|
688
|
-
// Extract ID from input and build update data
|
|
689
|
-
const { id, ...updateData } = input;
|
|
697
|
+
withErrorHandling('ghost_update_post', updatePostInputSchema, async (input) => {
|
|
698
|
+
// Extract ID from input and build update data
|
|
699
|
+
const { id, ...updateData } = input;
|
|
690
700
|
|
|
691
|
-
|
|
692
|
-
|
|
701
|
+
const updatedPost = await ghostService.updatePost(id, updateData);
|
|
702
|
+
console.error(`Post updated successfully. Post ID: ${updatedPost.id}`);
|
|
693
703
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
}
|
|
704
|
+
return {
|
|
705
|
+
content: [{ type: 'text', text: JSON.stringify(updatedPost, null, 2) }],
|
|
706
|
+
};
|
|
707
|
+
})
|
|
712
708
|
);
|
|
713
709
|
|
|
714
710
|
// Delete Post Tool
|
|
@@ -719,38 +715,15 @@ server.registerTool(
|
|
|
719
715
|
'Deletes a post from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
720
716
|
inputSchema: deletePostSchema,
|
|
721
717
|
},
|
|
722
|
-
async (
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
-
}
|
|
718
|
+
withErrorHandling('ghost_delete_post', deletePostSchema, async (input) => {
|
|
719
|
+
const { id } = input;
|
|
720
|
+
await ghostService.deletePost(id);
|
|
721
|
+
console.error(`Post deleted successfully. Post ID: ${id}`);
|
|
722
|
+
|
|
723
|
+
return {
|
|
724
|
+
content: [{ type: 'text', text: `Post ${id} has been successfully deleted.` }],
|
|
725
|
+
};
|
|
726
|
+
})
|
|
754
727
|
);
|
|
755
728
|
|
|
756
729
|
// =============================================================================
|
|
@@ -804,47 +777,23 @@ server.registerTool(
|
|
|
804
777
|
'Retrieves a list of pages from Ghost CMS with pagination, filtering, and sorting options.',
|
|
805
778
|
inputSchema: pageQuerySchema,
|
|
806
779
|
},
|
|
807
|
-
async (
|
|
808
|
-
const
|
|
809
|
-
if (
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
}
|
|
780
|
+
withErrorHandling('ghost_get_pages', pageQuerySchema, async (input) => {
|
|
781
|
+
const options = {};
|
|
782
|
+
if (input.limit !== undefined) options.limit = input.limit;
|
|
783
|
+
if (input.page !== undefined) options.page = input.page;
|
|
784
|
+
if (input.filter !== undefined) options.filter = input.filter;
|
|
785
|
+
if (input.include !== undefined) options.include = input.include;
|
|
786
|
+
if (input.fields !== undefined) options.fields = input.fields;
|
|
787
|
+
if (input.formats !== undefined) options.formats = input.formats;
|
|
788
|
+
if (input.order !== undefined) options.order = input.order;
|
|
789
|
+
|
|
790
|
+
const pages = await ghostService.getPages(options);
|
|
791
|
+
console.error(`Retrieved ${pages.length} pages from Ghost.`);
|
|
792
|
+
|
|
793
|
+
return {
|
|
794
|
+
content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
|
|
795
|
+
};
|
|
796
|
+
})
|
|
848
797
|
);
|
|
849
798
|
|
|
850
799
|
// Get Page Tool
|
|
@@ -854,43 +803,22 @@ server.registerTool(
|
|
|
854
803
|
description: 'Retrieves a single page from Ghost CMS by ID or slug.',
|
|
855
804
|
inputSchema: getPageSchema,
|
|
856
805
|
},
|
|
857
|
-
async (
|
|
858
|
-
const
|
|
859
|
-
if (
|
|
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;
|
|
806
|
+
withErrorHandling('ghost_get_page', getPageSchema, async (input) => {
|
|
807
|
+
const options = {};
|
|
808
|
+
if (input.include !== undefined) options.include = input.include;
|
|
870
809
|
|
|
871
|
-
|
|
810
|
+
if (!input.id && !input.slug) {
|
|
811
|
+
throw new Error('Either id or slug is required');
|
|
812
|
+
}
|
|
813
|
+
const identifier = input.id || `slug/${input.slug}`;
|
|
872
814
|
|
|
873
|
-
|
|
874
|
-
|
|
815
|
+
const page = await ghostService.getPage(identifier, options);
|
|
816
|
+
console.error(`Retrieved page: ${page.title} (ID: ${page.id})`);
|
|
875
817
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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
|
-
}
|
|
818
|
+
return {
|
|
819
|
+
content: [{ type: 'text', text: JSON.stringify(page, null, 2) }],
|
|
820
|
+
};
|
|
821
|
+
})
|
|
894
822
|
);
|
|
895
823
|
|
|
896
824
|
// Create Page Tool
|
|
@@ -901,38 +829,14 @@ server.registerTool(
|
|
|
901
829
|
'Creates a new page in Ghost CMS. Note: Pages do NOT typically use tags (unlike posts).',
|
|
902
830
|
inputSchema: createPageSchema,
|
|
903
831
|
},
|
|
904
|
-
async (
|
|
905
|
-
const
|
|
906
|
-
|
|
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}`);
|
|
832
|
+
withErrorHandling('ghost_create_page', createPageSchema, async (input) => {
|
|
833
|
+
const createdPage = await pageService.createPageService(input);
|
|
834
|
+
console.error(`Page created successfully. Page ID: ${createdPage.id}`);
|
|
917
835
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
-
}
|
|
836
|
+
return {
|
|
837
|
+
content: [{ type: 'text', text: JSON.stringify(createdPage, null, 2) }],
|
|
838
|
+
};
|
|
839
|
+
})
|
|
936
840
|
);
|
|
937
841
|
|
|
938
842
|
// Update Page Tool
|
|
@@ -943,40 +847,16 @@ server.registerTool(
|
|
|
943
847
|
'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
848
|
inputSchema: updatePageInputSchema,
|
|
945
849
|
},
|
|
946
|
-
async (
|
|
947
|
-
const
|
|
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();
|
|
850
|
+
withErrorHandling('ghost_update_page', updatePageInputSchema, async (input) => {
|
|
851
|
+
const { id, ...updateData } = input;
|
|
956
852
|
|
|
957
|
-
|
|
853
|
+
const updatedPage = await ghostService.updatePage(id, updateData);
|
|
854
|
+
console.error(`Page updated successfully. Page ID: ${updatedPage.id}`);
|
|
958
855
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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
|
-
}
|
|
856
|
+
return {
|
|
857
|
+
content: [{ type: 'text', text: JSON.stringify(updatedPage, null, 2) }],
|
|
858
|
+
};
|
|
859
|
+
})
|
|
980
860
|
);
|
|
981
861
|
|
|
982
862
|
// Delete Page Tool
|
|
@@ -987,38 +867,15 @@ server.registerTool(
|
|
|
987
867
|
'Deletes a page from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
988
868
|
inputSchema: deletePageSchema,
|
|
989
869
|
},
|
|
990
|
-
async (
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
-
}
|
|
870
|
+
withErrorHandling('ghost_delete_page', deletePageSchema, async (input) => {
|
|
871
|
+
const { id } = input;
|
|
872
|
+
await ghostService.deletePage(id);
|
|
873
|
+
console.error(`Page deleted successfully. Page ID: ${id}`);
|
|
874
|
+
|
|
875
|
+
return {
|
|
876
|
+
content: [{ type: 'text', text: `Page ${id} has been successfully deleted.` }],
|
|
877
|
+
};
|
|
878
|
+
})
|
|
1022
879
|
);
|
|
1023
880
|
|
|
1024
881
|
// Search Pages Tool
|
|
@@ -1028,42 +885,18 @@ server.registerTool(
|
|
|
1028
885
|
description: 'Search for pages in Ghost CMS by query string with optional status filtering.',
|
|
1029
886
|
inputSchema: searchPagesSchema,
|
|
1030
887
|
},
|
|
1031
|
-
async (
|
|
1032
|
-
const
|
|
1033
|
-
if (
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1036
|
-
const input = validation.data;
|
|
1037
|
-
|
|
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;
|
|
888
|
+
withErrorHandling('ghost_search_pages', searchPagesSchema, async (input) => {
|
|
889
|
+
const options = {};
|
|
890
|
+
if (input.status !== undefined) options.status = input.status;
|
|
891
|
+
if (input.limit !== undefined) options.limit = input.limit;
|
|
1045
892
|
|
|
1046
|
-
|
|
1047
|
-
|
|
893
|
+
const pages = await ghostService.searchPages(input.query, options);
|
|
894
|
+
console.error(`Found ${pages.length} pages matching "${input.query}".`);
|
|
1048
895
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
-
}
|
|
896
|
+
return {
|
|
897
|
+
content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
|
|
898
|
+
};
|
|
899
|
+
})
|
|
1067
900
|
);
|
|
1068
901
|
|
|
1069
902
|
// =============================================================================
|
|
@@ -1104,38 +937,14 @@ server.registerTool(
|
|
|
1104
937
|
description: 'Creates a new member (subscriber) in Ghost CMS.',
|
|
1105
938
|
inputSchema: createMemberSchema,
|
|
1106
939
|
},
|
|
1107
|
-
async (
|
|
1108
|
-
const
|
|
1109
|
-
|
|
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();
|
|
940
|
+
withErrorHandling('ghost_create_member', createMemberSchema, async (input) => {
|
|
941
|
+
const createdMember = await ghostService.createMember(input);
|
|
942
|
+
console.error(`Member created successfully. Member ID: ${createdMember.id}`);
|
|
1117
943
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
-
}
|
|
944
|
+
return {
|
|
945
|
+
content: [{ type: 'text', text: JSON.stringify(createdMember, null, 2) }],
|
|
946
|
+
};
|
|
947
|
+
})
|
|
1139
948
|
);
|
|
1140
949
|
|
|
1141
950
|
// Update Member Tool
|
|
@@ -1145,40 +954,16 @@ server.registerTool(
|
|
|
1145
954
|
description: 'Updates an existing member in Ghost CMS. All fields except id are optional.',
|
|
1146
955
|
inputSchema: updateMemberInputSchema,
|
|
1147
956
|
},
|
|
1148
|
-
async (
|
|
1149
|
-
const
|
|
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;
|
|
957
|
+
withErrorHandling('ghost_update_member', updateMemberInputSchema, async (input) => {
|
|
958
|
+
const { id, ...updateData } = input;
|
|
1160
959
|
|
|
1161
|
-
|
|
1162
|
-
|
|
960
|
+
const updatedMember = await ghostService.updateMember(id, updateData);
|
|
961
|
+
console.error(`Member updated successfully. Member ID: ${updatedMember.id}`);
|
|
1163
962
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
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
|
-
}
|
|
963
|
+
return {
|
|
964
|
+
content: [{ type: 'text', text: JSON.stringify(updatedMember, null, 2) }],
|
|
965
|
+
};
|
|
966
|
+
})
|
|
1182
967
|
);
|
|
1183
968
|
|
|
1184
969
|
// Delete Member Tool
|
|
@@ -1189,38 +974,15 @@ server.registerTool(
|
|
|
1189
974
|
'Deletes a member from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
1190
975
|
inputSchema: deleteMemberSchema,
|
|
1191
976
|
},
|
|
1192
|
-
async (
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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
|
-
}
|
|
977
|
+
withErrorHandling('ghost_delete_member', deleteMemberSchema, async (input) => {
|
|
978
|
+
const { id } = input;
|
|
979
|
+
await ghostService.deleteMember(id);
|
|
980
|
+
console.error(`Member deleted successfully. Member ID: ${id}`);
|
|
981
|
+
|
|
982
|
+
return {
|
|
983
|
+
content: [{ type: 'text', text: `Member ${id} has been successfully deleted.` }],
|
|
984
|
+
};
|
|
985
|
+
})
|
|
1224
986
|
);
|
|
1225
987
|
|
|
1226
988
|
// Get Members Tool
|
|
@@ -1231,45 +993,21 @@ server.registerTool(
|
|
|
1231
993
|
'Retrieves a list of members (subscribers) from Ghost CMS with optional filtering, pagination, and includes.',
|
|
1232
994
|
inputSchema: getMembersSchema,
|
|
1233
995
|
},
|
|
1234
|
-
async (
|
|
1235
|
-
const
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
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
|
-
}
|
|
996
|
+
withErrorHandling('ghost_get_members', getMembersSchema, async (input) => {
|
|
997
|
+
const options = {};
|
|
998
|
+
if (input.limit !== undefined) options.limit = input.limit;
|
|
999
|
+
if (input.page !== undefined) options.page = input.page;
|
|
1000
|
+
if (input.filter !== undefined) options.filter = input.filter;
|
|
1001
|
+
if (input.order !== undefined) options.order = input.order;
|
|
1002
|
+
if (input.include !== undefined) options.include = input.include;
|
|
1003
|
+
|
|
1004
|
+
const members = await ghostService.getMembers(options);
|
|
1005
|
+
console.error(`Retrieved ${members.length} members from Ghost.`);
|
|
1006
|
+
|
|
1007
|
+
return {
|
|
1008
|
+
content: [{ type: 'text', text: JSON.stringify(members, null, 2) }],
|
|
1009
|
+
};
|
|
1010
|
+
})
|
|
1273
1011
|
);
|
|
1274
1012
|
|
|
1275
1013
|
// Get Member Tool
|
|
@@ -1280,38 +1018,15 @@ server.registerTool(
|
|
|
1280
1018
|
'Retrieves a single member from Ghost CMS by ID or email. Provide either id OR email.',
|
|
1281
1019
|
inputSchema: getMemberSchema,
|
|
1282
1020
|
},
|
|
1283
|
-
async (
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
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
|
-
}
|
|
1021
|
+
withErrorHandling('ghost_get_member', getMemberSchema, async (input) => {
|
|
1022
|
+
const { id, email } = input;
|
|
1023
|
+
const member = await ghostService.getMember({ id, email });
|
|
1024
|
+
console.error(`Retrieved member: ${member.email} (ID: ${member.id})`);
|
|
1025
|
+
|
|
1026
|
+
return {
|
|
1027
|
+
content: [{ type: 'text', text: JSON.stringify(member, null, 2) }],
|
|
1028
|
+
};
|
|
1029
|
+
})
|
|
1315
1030
|
);
|
|
1316
1031
|
|
|
1317
1032
|
// Search Members Tool
|
|
@@ -1321,41 +1036,18 @@ server.registerTool(
|
|
|
1321
1036
|
description: 'Searches for members by name or email in Ghost CMS.',
|
|
1322
1037
|
inputSchema: searchMembersSchema,
|
|
1323
1038
|
},
|
|
1324
|
-
async (
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
}
|
|
1329
|
-
const { query, limit } = validation.data;
|
|
1330
|
-
|
|
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;
|
|
1039
|
+
withErrorHandling('ghost_search_members', searchMembersSchema, async (input) => {
|
|
1040
|
+
const { query, limit } = input;
|
|
1041
|
+
const options = {};
|
|
1042
|
+
if (limit !== undefined) options.limit = limit;
|
|
1337
1043
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1044
|
+
const members = await ghostService.searchMembers(query, options);
|
|
1045
|
+
console.error(`Found ${members.length} members matching "${query}".`);
|
|
1340
1046
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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
|
-
}
|
|
1047
|
+
return {
|
|
1048
|
+
content: [{ type: 'text', text: JSON.stringify(members, null, 2) }],
|
|
1049
|
+
};
|
|
1050
|
+
})
|
|
1359
1051
|
);
|
|
1360
1052
|
|
|
1361
1053
|
// =============================================================================
|
|
@@ -1374,44 +1066,20 @@ server.registerTool(
|
|
|
1374
1066
|
description: 'Retrieves a list of newsletters from Ghost CMS with optional filtering.',
|
|
1375
1067
|
inputSchema: newsletterQuerySchema,
|
|
1376
1068
|
},
|
|
1377
|
-
async (
|
|
1378
|
-
const
|
|
1379
|
-
if (
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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
|
-
}
|
|
1069
|
+
withErrorHandling('ghost_get_newsletters', newsletterQuerySchema, async (input) => {
|
|
1070
|
+
const options = {};
|
|
1071
|
+
if (input.limit !== undefined) options.limit = input.limit;
|
|
1072
|
+
if (input.page !== undefined) options.page = input.page;
|
|
1073
|
+
if (input.filter !== undefined) options.filter = input.filter;
|
|
1074
|
+
if (input.order !== undefined) options.order = input.order;
|
|
1075
|
+
|
|
1076
|
+
const newsletters = await ghostService.getNewsletters(options);
|
|
1077
|
+
console.error(`Retrieved ${newsletters.length} newsletters from Ghost.`);
|
|
1078
|
+
|
|
1079
|
+
return {
|
|
1080
|
+
content: [{ type: 'text', text: JSON.stringify(newsletters, null, 2) }],
|
|
1081
|
+
};
|
|
1082
|
+
})
|
|
1415
1083
|
);
|
|
1416
1084
|
|
|
1417
1085
|
// Get Newsletter Tool
|
|
@@ -1421,38 +1089,15 @@ server.registerTool(
|
|
|
1421
1089
|
description: 'Retrieves a single newsletter from Ghost CMS by ID.',
|
|
1422
1090
|
inputSchema: getNewsletterSchema,
|
|
1423
1091
|
},
|
|
1424
|
-
async (
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
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
|
-
}
|
|
1092
|
+
withErrorHandling('ghost_get_newsletter', getNewsletterSchema, async (input) => {
|
|
1093
|
+
const { id } = input;
|
|
1094
|
+
const newsletter = await ghostService.getNewsletter(id);
|
|
1095
|
+
console.error(`Retrieved newsletter: ${newsletter.name} (ID: ${newsletter.id})`);
|
|
1096
|
+
|
|
1097
|
+
return {
|
|
1098
|
+
content: [{ type: 'text', text: JSON.stringify(newsletter, null, 2) }],
|
|
1099
|
+
};
|
|
1100
|
+
})
|
|
1456
1101
|
);
|
|
1457
1102
|
|
|
1458
1103
|
// Create Newsletter Tool
|
|
@@ -1463,42 +1108,14 @@ server.registerTool(
|
|
|
1463
1108
|
'Creates a new newsletter in Ghost CMS with customizable sender settings and display options.',
|
|
1464
1109
|
inputSchema: createNewsletterSchema,
|
|
1465
1110
|
},
|
|
1466
|
-
async (
|
|
1467
|
-
const
|
|
1468
|
-
|
|
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}`);
|
|
1111
|
+
withErrorHandling('ghost_create_newsletter', createNewsletterSchema, async (input) => {
|
|
1112
|
+
const createdNewsletter = await newsletterService.createNewsletterService(input);
|
|
1113
|
+
console.error(`Newsletter created successfully. Newsletter ID: ${createdNewsletter.id}`);
|
|
1483
1114
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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
|
-
}
|
|
1115
|
+
return {
|
|
1116
|
+
content: [{ type: 'text', text: JSON.stringify(createdNewsletter, null, 2) }],
|
|
1117
|
+
};
|
|
1118
|
+
})
|
|
1502
1119
|
);
|
|
1503
1120
|
|
|
1504
1121
|
// Update Newsletter Tool
|
|
@@ -1509,44 +1126,16 @@ server.registerTool(
|
|
|
1509
1126
|
'Updates an existing newsletter in Ghost CMS. Can update name, description, sender settings, and display options.',
|
|
1510
1127
|
inputSchema: updateNewsletterInputSchema,
|
|
1511
1128
|
},
|
|
1512
|
-
async (
|
|
1513
|
-
const
|
|
1514
|
-
updateNewsletterInputSchema,
|
|
1515
|
-
rawInput,
|
|
1516
|
-
'ghost_update_newsletter'
|
|
1517
|
-
);
|
|
1518
|
-
if (!validation.success) {
|
|
1519
|
-
return validation.errorResponse;
|
|
1520
|
-
}
|
|
1521
|
-
const input = validation.data;
|
|
1522
|
-
|
|
1523
|
-
console.error(`Executing tool: ghost_update_newsletter for newsletter ID: ${input.id}`);
|
|
1524
|
-
try {
|
|
1525
|
-
await loadServices();
|
|
1526
|
-
|
|
1527
|
-
const { id, ...updateData } = input;
|
|
1129
|
+
withErrorHandling('ghost_update_newsletter', updateNewsletterInputSchema, async (input) => {
|
|
1130
|
+
const { id, ...updateData } = input;
|
|
1528
1131
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1132
|
+
const updatedNewsletter = await ghostService.updateNewsletter(id, updateData);
|
|
1133
|
+
console.error(`Newsletter updated successfully. Newsletter ID: ${updatedNewsletter.id}`);
|
|
1531
1134
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
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
|
-
}
|
|
1135
|
+
return {
|
|
1136
|
+
content: [{ type: 'text', text: JSON.stringify(updatedNewsletter, null, 2) }],
|
|
1137
|
+
};
|
|
1138
|
+
})
|
|
1550
1139
|
);
|
|
1551
1140
|
|
|
1552
1141
|
// Delete Newsletter Tool
|
|
@@ -1557,42 +1146,15 @@ server.registerTool(
|
|
|
1557
1146
|
'Deletes a newsletter from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
1558
1147
|
inputSchema: deleteNewsletterSchema,
|
|
1559
1148
|
},
|
|
1560
|
-
async (
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
-
}
|
|
1149
|
+
withErrorHandling('ghost_delete_newsletter', deleteNewsletterSchema, async (input) => {
|
|
1150
|
+
const { id } = input;
|
|
1151
|
+
await ghostService.deleteNewsletter(id);
|
|
1152
|
+
console.error(`Newsletter deleted successfully. Newsletter ID: ${id}`);
|
|
1153
|
+
|
|
1154
|
+
return {
|
|
1155
|
+
content: [{ type: 'text', text: `Newsletter ${id} has been successfully deleted.` }],
|
|
1156
|
+
};
|
|
1157
|
+
})
|
|
1596
1158
|
);
|
|
1597
1159
|
|
|
1598
1160
|
// --- Tier Tools ---
|
|
@@ -1610,38 +1172,14 @@ server.registerTool(
|
|
|
1610
1172
|
'Retrieves a list of tiers (membership levels) from Ghost CMS with optional filtering by type (free/paid).',
|
|
1611
1173
|
inputSchema: tierQuerySchema,
|
|
1612
1174
|
},
|
|
1613
|
-
async (
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
return validation.errorResponse;
|
|
1617
|
-
}
|
|
1618
|
-
const input = validation.data;
|
|
1175
|
+
withErrorHandling('ghost_get_tiers', tierQuerySchema, async (input) => {
|
|
1176
|
+
const tiers = await ghostService.getTiers(input);
|
|
1177
|
+
console.error(`Retrieved ${tiers.length} tiers`);
|
|
1619
1178
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
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
|
-
}
|
|
1179
|
+
return {
|
|
1180
|
+
content: [{ type: 'text', text: JSON.stringify(tiers, null, 2) }],
|
|
1181
|
+
};
|
|
1182
|
+
})
|
|
1645
1183
|
);
|
|
1646
1184
|
|
|
1647
1185
|
// Get Tier Tool
|
|
@@ -1651,38 +1189,15 @@ server.registerTool(
|
|
|
1651
1189
|
description: 'Retrieves a single tier (membership level) from Ghost CMS by ID.',
|
|
1652
1190
|
inputSchema: getTierSchema,
|
|
1653
1191
|
},
|
|
1654
|
-
async (
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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
|
-
}
|
|
1192
|
+
withErrorHandling('ghost_get_tier', getTierSchema, async (input) => {
|
|
1193
|
+
const { id } = input;
|
|
1194
|
+
const tier = await ghostService.getTier(id);
|
|
1195
|
+
console.error(`Tier retrieved successfully. Tier ID: ${tier.id}`);
|
|
1196
|
+
|
|
1197
|
+
return {
|
|
1198
|
+
content: [{ type: 'text', text: JSON.stringify(tier, null, 2) }],
|
|
1199
|
+
};
|
|
1200
|
+
})
|
|
1686
1201
|
);
|
|
1687
1202
|
|
|
1688
1203
|
// Create Tier Tool
|
|
@@ -1692,38 +1207,14 @@ server.registerTool(
|
|
|
1692
1207
|
description: 'Creates a new tier (membership level) in Ghost CMS with pricing and benefits.',
|
|
1693
1208
|
inputSchema: createTierSchema,
|
|
1694
1209
|
},
|
|
1695
|
-
async (
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1698
|
-
return validation.errorResponse;
|
|
1699
|
-
}
|
|
1700
|
-
const input = validation.data;
|
|
1701
|
-
|
|
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}`);
|
|
1210
|
+
withErrorHandling('ghost_create_tier', createTierSchema, async (input) => {
|
|
1211
|
+
const tier = await ghostService.createTier(input);
|
|
1212
|
+
console.error(`Tier created successfully. Tier ID: ${tier.id}`);
|
|
1708
1213
|
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
-
}
|
|
1214
|
+
return {
|
|
1215
|
+
content: [{ type: 'text', text: JSON.stringify(tier, null, 2) }],
|
|
1216
|
+
};
|
|
1217
|
+
})
|
|
1727
1218
|
);
|
|
1728
1219
|
|
|
1729
1220
|
// Update Tier Tool
|
|
@@ -1734,40 +1225,16 @@ server.registerTool(
|
|
|
1734
1225
|
'Updates an existing tier (membership level) in Ghost CMS. Can update pricing, benefits, and other tier properties.',
|
|
1735
1226
|
inputSchema: updateTierInputSchema,
|
|
1736
1227
|
},
|
|
1737
|
-
async (
|
|
1738
|
-
const
|
|
1739
|
-
if (!validation.success) {
|
|
1740
|
-
return validation.errorResponse;
|
|
1741
|
-
}
|
|
1742
|
-
const input = validation.data;
|
|
1743
|
-
|
|
1744
|
-
console.error(`Executing tool: ghost_update_tier for tier ID: ${input.id}`);
|
|
1745
|
-
try {
|
|
1746
|
-
await loadServices();
|
|
1747
|
-
|
|
1748
|
-
const { id, ...updateData } = input;
|
|
1228
|
+
withErrorHandling('ghost_update_tier', updateTierInputSchema, async (input) => {
|
|
1229
|
+
const { id, ...updateData } = input;
|
|
1749
1230
|
|
|
1750
|
-
|
|
1751
|
-
|
|
1231
|
+
const updatedTier = await ghostService.updateTier(id, updateData);
|
|
1232
|
+
console.error(`Tier updated successfully. Tier ID: ${updatedTier.id}`);
|
|
1752
1233
|
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
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
|
-
}
|
|
1234
|
+
return {
|
|
1235
|
+
content: [{ type: 'text', text: JSON.stringify(updatedTier, null, 2) }],
|
|
1236
|
+
};
|
|
1237
|
+
})
|
|
1771
1238
|
);
|
|
1772
1239
|
|
|
1773
1240
|
// Delete Tier Tool
|
|
@@ -1778,38 +1245,15 @@ server.registerTool(
|
|
|
1778
1245
|
'Deletes a tier (membership level) from Ghost CMS by ID. This operation is permanent and cannot be undone.',
|
|
1779
1246
|
inputSchema: deleteTierSchema,
|
|
1780
1247
|
},
|
|
1781
|
-
async (
|
|
1782
|
-
const
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
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
|
-
}
|
|
1248
|
+
withErrorHandling('ghost_delete_tier', deleteTierSchema, async (input) => {
|
|
1249
|
+
const { id } = input;
|
|
1250
|
+
await ghostService.deleteTier(id);
|
|
1251
|
+
console.error(`Tier deleted successfully. Tier ID: ${id}`);
|
|
1252
|
+
|
|
1253
|
+
return {
|
|
1254
|
+
content: [{ type: 'text', text: `Tier ${id} has been successfully deleted.` }],
|
|
1255
|
+
};
|
|
1256
|
+
})
|
|
1813
1257
|
);
|
|
1814
1258
|
|
|
1815
1259
|
// --- Main Entry Point ---
|