@jgardner04/ghost-mcp-server 1.13.1 → 1.13.3
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 +6 -18
- package/src/__tests__/mcp_server.test.js +204 -117
- package/src/__tests__/mcp_server_pages.test.js +32 -18
- package/src/config/mcp-config.js +1 -1
- package/src/controllers/__tests__/tagController.test.js +12 -8
- package/src/controllers/tagController.js +2 -2
- package/src/errors/__tests__/index.test.js +3 -3
- package/src/errors/index.js +1 -1
- package/src/index.js +1 -1
- package/src/mcp_server.js +37 -33
- package/src/schemas/__tests__/postSchemas.test.js +19 -0
- package/src/schemas/__tests__/tagSchemas.test.js +1 -1
- package/src/schemas/common.js +2 -2
- package/src/schemas/memberSchemas.js +20 -8
- package/src/schemas/newsletterSchemas.js +10 -10
- package/src/schemas/pageSchemas.js +16 -13
- package/src/schemas/postSchemas.js +22 -13
- package/src/schemas/tagSchemas.js +12 -7
- package/src/schemas/tierSchemas.js +17 -8
- package/src/services/__tests__/ghostServiceImproved.members.test.js +15 -6
- package/src/services/__tests__/ghostServiceImproved.newsletters.test.js +21 -12
- package/src/services/__tests__/ghostServiceImproved.pages.test.js +59 -16
- package/src/services/__tests__/ghostServiceImproved.posts.test.js +233 -0
- package/src/services/__tests__/ghostServiceImproved.tags.test.js +13 -2
- package/src/services/__tests__/ghostServiceImproved.tiers.test.js +18 -19
- package/src/services/__tests__/memberService.test.js +0 -28
- package/src/services/__tests__/tierService.test.js +0 -28
- package/src/services/ghostServiceImproved.js +108 -379
- package/src/services/imageProcessingService.js +1 -1
- package/src/services/memberService.js +0 -13
- package/src/services/tierService.js +0 -13
- package/src/utils/__tests__/nqlSanitizer.test.js +38 -0
- package/src/utils/__tests__/urlValidator.test.js +137 -1
- package/src/utils/nqlSanitizer.js +11 -0
- package/src/utils/urlValidator.js +25 -2
|
@@ -403,7 +403,7 @@ describe('ghostServiceImproved - Tags', () => {
|
|
|
403
403
|
});
|
|
404
404
|
|
|
405
405
|
describe('updateTag', () => {
|
|
406
|
-
it('should update
|
|
406
|
+
it('should send only update fields, not the full existing tag', async () => {
|
|
407
407
|
const tagId = 'tag-1';
|
|
408
408
|
const updateData = {
|
|
409
409
|
name: 'Updated JavaScript',
|
|
@@ -414,6 +414,8 @@ describe('ghostServiceImproved - Tags', () => {
|
|
|
414
414
|
id: tagId,
|
|
415
415
|
name: 'JavaScript',
|
|
416
416
|
slug: 'javascript',
|
|
417
|
+
url: 'https://example.com/tag/javascript',
|
|
418
|
+
visibility: 'public',
|
|
417
419
|
};
|
|
418
420
|
|
|
419
421
|
const mockUpdatedTag = {
|
|
@@ -427,7 +429,16 @@ describe('ghostServiceImproved - Tags', () => {
|
|
|
427
429
|
const result = await updateTag(tagId, updateData);
|
|
428
430
|
|
|
429
431
|
expect(api.tags.read).toHaveBeenCalled();
|
|
430
|
-
|
|
432
|
+
// Should send ONLY updateData, NOT the full existing tag
|
|
433
|
+
expect(api.tags.edit).toHaveBeenCalledWith(
|
|
434
|
+
{ id: tagId, name: 'Updated JavaScript', description: 'Updated description' },
|
|
435
|
+
{}
|
|
436
|
+
);
|
|
437
|
+
// Verify read-only fields are NOT sent
|
|
438
|
+
const editCallData = api.tags.edit.mock.calls[0][0];
|
|
439
|
+
expect(editCallData).not.toHaveProperty('url');
|
|
440
|
+
expect(editCallData).not.toHaveProperty('visibility');
|
|
441
|
+
expect(editCallData).not.toHaveProperty('slug');
|
|
431
442
|
expect(result).toEqual(mockUpdatedTag);
|
|
432
443
|
});
|
|
433
444
|
|
|
@@ -270,14 +270,7 @@ describe('ghostServiceImproved - Tiers', () => {
|
|
|
270
270
|
|
|
271
271
|
const result = await getTier('tier-1');
|
|
272
272
|
|
|
273
|
-
expect(api.tiers.read).toHaveBeenCalledWith(
|
|
274
|
-
expect.objectContaining({
|
|
275
|
-
id: 'tier-1',
|
|
276
|
-
}),
|
|
277
|
-
expect.objectContaining({
|
|
278
|
-
id: 'tier-1',
|
|
279
|
-
})
|
|
280
|
-
);
|
|
273
|
+
expect(api.tiers.read).toHaveBeenCalledWith({}, { id: 'tier-1' });
|
|
281
274
|
expect(result).toEqual(mockTier);
|
|
282
275
|
});
|
|
283
276
|
|
|
@@ -300,12 +293,15 @@ describe('ghostServiceImproved - Tiers', () => {
|
|
|
300
293
|
});
|
|
301
294
|
|
|
302
295
|
describe('updateTier', () => {
|
|
303
|
-
it('should update
|
|
296
|
+
it('should send only update fields and updated_at, not the full existing tier', async () => {
|
|
304
297
|
const existingTier = {
|
|
305
298
|
id: 'tier-1',
|
|
299
|
+
slug: 'premium',
|
|
306
300
|
name: 'Premium',
|
|
307
301
|
currency: 'USD',
|
|
308
302
|
monthly_price: 999,
|
|
303
|
+
type: 'paid',
|
|
304
|
+
active: true,
|
|
309
305
|
updated_at: '2024-01-01T00:00:00.000Z',
|
|
310
306
|
};
|
|
311
307
|
|
|
@@ -324,19 +320,22 @@ describe('ghostServiceImproved - Tiers', () => {
|
|
|
324
320
|
|
|
325
321
|
const result = await updateTier('tier-1', updateData);
|
|
326
322
|
|
|
327
|
-
expect(api.tiers.read).toHaveBeenCalledWith(
|
|
328
|
-
|
|
329
|
-
expect.objectContaining({ id: 'tier-1' })
|
|
330
|
-
);
|
|
323
|
+
expect(api.tiers.read).toHaveBeenCalledWith({}, { id: 'tier-1' });
|
|
324
|
+
// Should send ONLY updateData + updated_at, NOT the full existing tier
|
|
331
325
|
expect(api.tiers.edit).toHaveBeenCalledWith(
|
|
332
|
-
|
|
333
|
-
...existingTier,
|
|
334
|
-
...updateData,
|
|
335
|
-
}),
|
|
336
|
-
expect.objectContaining({
|
|
326
|
+
{
|
|
337
327
|
id: 'tier-1',
|
|
338
|
-
|
|
328
|
+
name: 'Premium Plus',
|
|
329
|
+
monthly_price: 1299,
|
|
330
|
+
updated_at: '2024-01-01T00:00:00.000Z',
|
|
331
|
+
},
|
|
332
|
+
{}
|
|
339
333
|
);
|
|
334
|
+
// Verify read-only fields are NOT sent
|
|
335
|
+
const editCallData = api.tiers.edit.mock.calls[0][0];
|
|
336
|
+
expect(editCallData).not.toHaveProperty('slug');
|
|
337
|
+
expect(editCallData).not.toHaveProperty('type');
|
|
338
|
+
expect(editCallData).not.toHaveProperty('active');
|
|
340
339
|
expect(result).toEqual(mockUpdatedTier);
|
|
341
340
|
});
|
|
342
341
|
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
validateMemberLookup,
|
|
7
7
|
validateSearchQuery,
|
|
8
8
|
validateSearchOptions,
|
|
9
|
-
sanitizeNqlValue,
|
|
10
9
|
} from '../memberService.js';
|
|
11
10
|
|
|
12
11
|
describe('memberService - Validation', () => {
|
|
@@ -447,31 +446,4 @@ describe('memberService - Validation', () => {
|
|
|
447
446
|
);
|
|
448
447
|
});
|
|
449
448
|
});
|
|
450
|
-
|
|
451
|
-
describe('sanitizeNqlValue', () => {
|
|
452
|
-
it('should escape backslashes', () => {
|
|
453
|
-
expect(sanitizeNqlValue('test\\value')).toBe('test\\\\value');
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
it('should escape single quotes', () => {
|
|
457
|
-
expect(sanitizeNqlValue("test'value")).toBe("test\\'value");
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
it('should escape double quotes', () => {
|
|
461
|
-
expect(sanitizeNqlValue('test"value')).toBe('test\\"value');
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it('should handle multiple special characters', () => {
|
|
465
|
-
expect(sanitizeNqlValue('test\'value"with\\chars')).toBe('test\\\'value\\"with\\\\chars');
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it('should not modify strings without special characters', () => {
|
|
469
|
-
expect(sanitizeNqlValue('normalvalue')).toBe('normalvalue');
|
|
470
|
-
expect(sanitizeNqlValue('test@example.com')).toBe('test@example.com');
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
it('should handle empty string', () => {
|
|
474
|
-
expect(sanitizeNqlValue('')).toBe('');
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
449
|
});
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
validateTierData,
|
|
4
4
|
validateTierUpdateData,
|
|
5
5
|
validateTierQueryOptions,
|
|
6
|
-
sanitizeNqlValue,
|
|
7
6
|
} from '../tierService.js';
|
|
8
7
|
import { ValidationError } from '../../errors/index.js';
|
|
9
8
|
|
|
@@ -342,31 +341,4 @@ describe('tierService - Validation', () => {
|
|
|
342
341
|
).not.toThrow();
|
|
343
342
|
});
|
|
344
343
|
});
|
|
345
|
-
|
|
346
|
-
describe('sanitizeNqlValue', () => {
|
|
347
|
-
it('should return value if undefined or null', () => {
|
|
348
|
-
expect(sanitizeNqlValue(null)).toBe(null);
|
|
349
|
-
expect(sanitizeNqlValue(undefined)).toBe(undefined);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('should escape backslashes', () => {
|
|
353
|
-
expect(sanitizeNqlValue('test\\value')).toBe('test\\\\value');
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it('should escape single quotes', () => {
|
|
357
|
-
expect(sanitizeNqlValue("test'value")).toBe("test\\'value");
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it('should escape double quotes', () => {
|
|
361
|
-
expect(sanitizeNqlValue('test"value')).toBe('test\\"value');
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('should escape multiple special characters', () => {
|
|
365
|
-
expect(sanitizeNqlValue('test\\value"with\'quotes')).toBe('test\\\\value\\"with\\\'quotes');
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it('should handle strings without special characters', () => {
|
|
369
|
-
expect(sanitizeNqlValue('simple-value')).toBe('simple-value');
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
344
|
});
|