@jgardner04/ghost-mcp-server 1.13.2 → 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 +5 -13
- 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 +35 -31
- 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 -11
- package/src/schemas/postSchemas.js +22 -15
- package/src/schemas/tagSchemas.js +12 -7
- package/src/schemas/tierSchemas.js +17 -8
- package/src/services/__tests__/ghostServiceImproved.members.test.js +7 -2
- package/src/services/__tests__/ghostServiceImproved.newsletters.test.js +10 -6
- package/src/services/__tests__/ghostServiceImproved.pages.test.js +4 -4
- package/src/services/__tests__/ghostServiceImproved.posts.test.js +4 -4
- package/src/services/__tests__/ghostServiceImproved.tags.test.js +2 -2
- package/src/services/__tests__/ghostServiceImproved.tiers.test.js +9 -14
- package/src/services/__tests__/memberService.test.js +0 -28
- package/src/services/__tests__/tierService.test.js +0 -28
- package/src/services/ghostServiceImproved.js +69 -217
- 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/nqlSanitizer.js +11 -0
|
@@ -232,7 +232,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
232
232
|
const tool = mockTools.get('ghost_get_posts');
|
|
233
233
|
const result = await tool.handler({});
|
|
234
234
|
|
|
235
|
-
expect(mockGetPosts).toHaveBeenCalledWith({});
|
|
235
|
+
expect(mockGetPosts).toHaveBeenCalledWith(expect.objectContaining({}));
|
|
236
236
|
expect(result.content[0].text).toContain('Post 1');
|
|
237
237
|
expect(result.content[0].text).toContain('Post 2');
|
|
238
238
|
});
|
|
@@ -244,7 +244,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
244
244
|
const tool = mockTools.get('ghost_get_posts');
|
|
245
245
|
await tool.handler({ limit: 10, page: 2 });
|
|
246
246
|
|
|
247
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ limit: 10, page: 2 });
|
|
247
|
+
expect(mockGetPosts).toHaveBeenCalledWith(expect.objectContaining({ limit: 10, page: 2 }));
|
|
248
248
|
});
|
|
249
249
|
|
|
250
250
|
it('should validate limit is between 1 and 100', () => {
|
|
@@ -276,7 +276,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
276
276
|
const tool = mockTools.get('ghost_get_posts');
|
|
277
277
|
await tool.handler({ status: 'published' });
|
|
278
278
|
|
|
279
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ status: 'published' });
|
|
279
|
+
expect(mockGetPosts).toHaveBeenCalledWith(expect.objectContaining({ status: 'published' }));
|
|
280
280
|
});
|
|
281
281
|
|
|
282
282
|
it('should validate status enum values', () => {
|
|
@@ -306,7 +306,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
306
306
|
const tool = mockTools.get('ghost_get_posts');
|
|
307
307
|
await tool.handler({ include: 'tags,authors' });
|
|
308
308
|
|
|
309
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ include: 'tags,authors' });
|
|
309
|
+
expect(mockGetPosts).toHaveBeenCalledWith(expect.objectContaining({ include: 'tags,authors' }));
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it('should pass filter parameter (NQL)', async () => {
|
|
@@ -316,7 +316,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
316
316
|
const tool = mockTools.get('ghost_get_posts');
|
|
317
317
|
await tool.handler({ filter: 'featured:true' });
|
|
318
318
|
|
|
319
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ filter: 'featured:true' });
|
|
319
|
+
expect(mockGetPosts).toHaveBeenCalledWith(expect.objectContaining({ filter: 'featured:true' }));
|
|
320
320
|
});
|
|
321
321
|
|
|
322
322
|
it('should pass order parameter', async () => {
|
|
@@ -329,7 +329,9 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
329
329
|
const tool = mockTools.get('ghost_get_posts');
|
|
330
330
|
await tool.handler({ order: 'published_at DESC' });
|
|
331
331
|
|
|
332
|
-
expect(mockGetPosts).toHaveBeenCalledWith(
|
|
332
|
+
expect(mockGetPosts).toHaveBeenCalledWith(
|
|
333
|
+
expect.objectContaining({ order: 'published_at DESC' })
|
|
334
|
+
);
|
|
333
335
|
});
|
|
334
336
|
|
|
335
337
|
it('should pass fields parameter', async () => {
|
|
@@ -339,7 +341,7 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
339
341
|
const tool = mockTools.get('ghost_get_posts');
|
|
340
342
|
await tool.handler({ fields: 'id,title,slug' });
|
|
341
343
|
|
|
342
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ fields: 'id,title,slug' });
|
|
344
|
+
expect(mockGetPosts).toHaveBeenCalledWith(expect.objectContaining({ fields: 'id,title,slug' }));
|
|
343
345
|
});
|
|
344
346
|
|
|
345
347
|
it('should pass formats parameter', async () => {
|
|
@@ -349,7 +351,9 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
349
351
|
const tool = mockTools.get('ghost_get_posts');
|
|
350
352
|
await tool.handler({ formats: 'html,plaintext' });
|
|
351
353
|
|
|
352
|
-
expect(mockGetPosts).toHaveBeenCalledWith(
|
|
354
|
+
expect(mockGetPosts).toHaveBeenCalledWith(
|
|
355
|
+
expect.objectContaining({ formats: 'html,plaintext' })
|
|
356
|
+
);
|
|
353
357
|
});
|
|
354
358
|
|
|
355
359
|
it('should pass both fields and formats parameters', async () => {
|
|
@@ -359,7 +363,9 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
359
363
|
const tool = mockTools.get('ghost_get_posts');
|
|
360
364
|
await tool.handler({ fields: 'id,title', formats: 'html' });
|
|
361
365
|
|
|
362
|
-
expect(mockGetPosts).toHaveBeenCalledWith(
|
|
366
|
+
expect(mockGetPosts).toHaveBeenCalledWith(
|
|
367
|
+
expect.objectContaining({ fields: 'id,title', formats: 'html' })
|
|
368
|
+
);
|
|
363
369
|
});
|
|
364
370
|
|
|
365
371
|
it('should pass all parameters combined', async () => {
|
|
@@ -378,16 +384,18 @@ describe('mcp_server - ghost_get_posts tool', () => {
|
|
|
378
384
|
formats: 'html,plaintext',
|
|
379
385
|
});
|
|
380
386
|
|
|
381
|
-
expect(mockGetPosts).toHaveBeenCalledWith(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
387
|
+
expect(mockGetPosts).toHaveBeenCalledWith(
|
|
388
|
+
expect.objectContaining({
|
|
389
|
+
limit: 20,
|
|
390
|
+
page: 1,
|
|
391
|
+
status: 'published',
|
|
392
|
+
include: 'tags,authors',
|
|
393
|
+
filter: 'featured:true',
|
|
394
|
+
order: 'published_at DESC',
|
|
395
|
+
fields: 'id,title,slug',
|
|
396
|
+
formats: 'html,plaintext',
|
|
397
|
+
})
|
|
398
|
+
);
|
|
391
399
|
});
|
|
392
400
|
|
|
393
401
|
it('should handle errors from ghostService', async () => {
|
|
@@ -461,7 +469,7 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
461
469
|
const tool = mockTools.get('ghost_get_tags');
|
|
462
470
|
const result = await tool.handler({});
|
|
463
471
|
|
|
464
|
-
expect(mockGetTags).toHaveBeenCalledWith({});
|
|
472
|
+
expect(mockGetTags).toHaveBeenCalledWith(expect.objectContaining({}));
|
|
465
473
|
expect(result.content[0].text).toContain('Tag 1');
|
|
466
474
|
expect(result.content[0].text).toContain('Tag 2');
|
|
467
475
|
});
|
|
@@ -473,10 +481,12 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
473
481
|
const tool = mockTools.get('ghost_get_tags');
|
|
474
482
|
await tool.handler({ limit: 10, page: 2 });
|
|
475
483
|
|
|
476
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
484
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
485
|
+
expect.objectContaining({
|
|
486
|
+
limit: 10,
|
|
487
|
+
page: 2,
|
|
488
|
+
})
|
|
489
|
+
);
|
|
480
490
|
});
|
|
481
491
|
|
|
482
492
|
it('should pass order parameter', async () => {
|
|
@@ -486,9 +496,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
486
496
|
const tool = mockTools.get('ghost_get_tags');
|
|
487
497
|
await tool.handler({ order: 'name ASC' });
|
|
488
498
|
|
|
489
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
490
|
-
|
|
491
|
-
|
|
499
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
500
|
+
expect.objectContaining({
|
|
501
|
+
order: 'name ASC',
|
|
502
|
+
})
|
|
503
|
+
);
|
|
492
504
|
});
|
|
493
505
|
|
|
494
506
|
it('should pass include parameter', async () => {
|
|
@@ -498,9 +510,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
498
510
|
const tool = mockTools.get('ghost_get_tags');
|
|
499
511
|
await tool.handler({ include: 'count.posts' });
|
|
500
512
|
|
|
501
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
502
|
-
|
|
503
|
-
|
|
513
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
514
|
+
expect.objectContaining({
|
|
515
|
+
include: 'count.posts',
|
|
516
|
+
})
|
|
517
|
+
);
|
|
504
518
|
});
|
|
505
519
|
|
|
506
520
|
it('should filter by name parameter', async () => {
|
|
@@ -510,9 +524,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
510
524
|
const tool = mockTools.get('ghost_get_tags');
|
|
511
525
|
await tool.handler({ name: 'Test Tag' });
|
|
512
526
|
|
|
513
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
514
|
-
|
|
515
|
-
|
|
527
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
528
|
+
expect.objectContaining({
|
|
529
|
+
filter: "name:'Test Tag'",
|
|
530
|
+
})
|
|
531
|
+
);
|
|
516
532
|
});
|
|
517
533
|
|
|
518
534
|
it('should filter by slug parameter', async () => {
|
|
@@ -522,9 +538,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
522
538
|
const tool = mockTools.get('ghost_get_tags');
|
|
523
539
|
await tool.handler({ slug: 'test-tag' });
|
|
524
540
|
|
|
525
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
526
|
-
|
|
527
|
-
|
|
541
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
542
|
+
expect.objectContaining({
|
|
543
|
+
filter: "slug:'test-tag'",
|
|
544
|
+
})
|
|
545
|
+
);
|
|
528
546
|
});
|
|
529
547
|
|
|
530
548
|
it('should filter by visibility parameter', async () => {
|
|
@@ -534,9 +552,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
534
552
|
const tool = mockTools.get('ghost_get_tags');
|
|
535
553
|
await tool.handler({ visibility: 'public' });
|
|
536
554
|
|
|
537
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
538
|
-
|
|
539
|
-
|
|
555
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
556
|
+
expect.objectContaining({
|
|
557
|
+
filter: "visibility:'public'",
|
|
558
|
+
})
|
|
559
|
+
);
|
|
540
560
|
});
|
|
541
561
|
|
|
542
562
|
it('should escape single quotes in name parameter', async () => {
|
|
@@ -546,9 +566,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
546
566
|
const tool = mockTools.get('ghost_get_tags');
|
|
547
567
|
await tool.handler({ name: "O'Reilly" });
|
|
548
568
|
|
|
549
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
550
|
-
|
|
551
|
-
|
|
569
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
570
|
+
expect.objectContaining({
|
|
571
|
+
filter: "name:'O''Reilly'",
|
|
572
|
+
})
|
|
573
|
+
);
|
|
552
574
|
});
|
|
553
575
|
|
|
554
576
|
it('should escape single quotes in slug parameter', async () => {
|
|
@@ -558,9 +580,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
558
580
|
const tool = mockTools.get('ghost_get_tags');
|
|
559
581
|
await tool.handler({ slug: "test'slug" });
|
|
560
582
|
|
|
561
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
562
|
-
|
|
563
|
-
|
|
583
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
584
|
+
expect.objectContaining({
|
|
585
|
+
filter: "slug:'test''slug'",
|
|
586
|
+
})
|
|
587
|
+
);
|
|
564
588
|
});
|
|
565
589
|
|
|
566
590
|
it('should combine multiple filter parameters', async () => {
|
|
@@ -570,9 +594,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
570
594
|
const tool = mockTools.get('ghost_get_tags');
|
|
571
595
|
await tool.handler({ name: 'News', visibility: 'public' });
|
|
572
596
|
|
|
573
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
574
|
-
|
|
575
|
-
|
|
597
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
598
|
+
expect.objectContaining({
|
|
599
|
+
filter: "name:'News'+visibility:'public'",
|
|
600
|
+
})
|
|
601
|
+
);
|
|
576
602
|
});
|
|
577
603
|
|
|
578
604
|
it('should combine individual filters with custom filter parameter', async () => {
|
|
@@ -582,9 +608,11 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
582
608
|
const tool = mockTools.get('ghost_get_tags');
|
|
583
609
|
await tool.handler({ name: 'News', filter: 'featured:true' });
|
|
584
610
|
|
|
585
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
586
|
-
|
|
587
|
-
|
|
611
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
612
|
+
expect.objectContaining({
|
|
613
|
+
filter: "name:'News'+featured:true",
|
|
614
|
+
})
|
|
615
|
+
);
|
|
588
616
|
});
|
|
589
617
|
|
|
590
618
|
it('should pass all parameters combined', async () => {
|
|
@@ -601,13 +629,15 @@ describe('mcp_server - ghost_get_tags tool', () => {
|
|
|
601
629
|
visibility: 'public',
|
|
602
630
|
});
|
|
603
631
|
|
|
604
|
-
expect(mockGetTags).toHaveBeenCalledWith(
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
632
|
+
expect(mockGetTags).toHaveBeenCalledWith(
|
|
633
|
+
expect.objectContaining({
|
|
634
|
+
limit: 20,
|
|
635
|
+
page: 1,
|
|
636
|
+
order: 'name ASC',
|
|
637
|
+
include: 'count.posts',
|
|
638
|
+
filter: "name:'News'+visibility:'public'",
|
|
639
|
+
})
|
|
640
|
+
);
|
|
611
641
|
});
|
|
612
642
|
|
|
613
643
|
it('should handle service errors', async () => {
|
|
@@ -652,8 +682,8 @@ describe('mcp_server - ghost_get_post tool', () => {
|
|
|
652
682
|
expect(tool).toBeDefined();
|
|
653
683
|
expect(tool.description).toContain('post');
|
|
654
684
|
expect(tool.schema).toBeDefined();
|
|
655
|
-
//
|
|
656
|
-
const shape = tool.schema.
|
|
685
|
+
// In Zod v4, refined schemas expose .shape directly
|
|
686
|
+
const shape = tool.schema.shape;
|
|
657
687
|
expect(shape.id).toBeDefined();
|
|
658
688
|
expect(shape.slug).toBeDefined();
|
|
659
689
|
expect(shape.include).toBeDefined();
|
|
@@ -672,7 +702,10 @@ describe('mcp_server - ghost_get_post tool', () => {
|
|
|
672
702
|
const tool = mockTools.get('ghost_get_post');
|
|
673
703
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
674
704
|
|
|
675
|
-
expect(mockGetPost).toHaveBeenCalledWith(
|
|
705
|
+
expect(mockGetPost).toHaveBeenCalledWith(
|
|
706
|
+
'507f1f77bcf86cd799439011',
|
|
707
|
+
expect.objectContaining({})
|
|
708
|
+
);
|
|
676
709
|
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
677
710
|
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
678
711
|
});
|
|
@@ -690,7 +723,7 @@ describe('mcp_server - ghost_get_post tool', () => {
|
|
|
690
723
|
const tool = mockTools.get('ghost_get_post');
|
|
691
724
|
const result = await tool.handler({ slug: 'test-post' });
|
|
692
725
|
|
|
693
|
-
expect(mockGetPost).toHaveBeenCalledWith('slug/test-post', {});
|
|
726
|
+
expect(mockGetPost).toHaveBeenCalledWith('slug/test-post', expect.objectContaining({}));
|
|
694
727
|
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
695
728
|
});
|
|
696
729
|
|
|
@@ -706,9 +739,12 @@ describe('mcp_server - ghost_get_post tool', () => {
|
|
|
706
739
|
const tool = mockTools.get('ghost_get_post');
|
|
707
740
|
await tool.handler({ id: '507f1f77bcf86cd799439011', include: 'tags,authors' });
|
|
708
741
|
|
|
709
|
-
expect(mockGetPost).toHaveBeenCalledWith(
|
|
710
|
-
|
|
711
|
-
|
|
742
|
+
expect(mockGetPost).toHaveBeenCalledWith(
|
|
743
|
+
'507f1f77bcf86cd799439011',
|
|
744
|
+
expect.objectContaining({
|
|
745
|
+
include: 'tags,authors',
|
|
746
|
+
})
|
|
747
|
+
);
|
|
712
748
|
});
|
|
713
749
|
|
|
714
750
|
it('should pass include parameter with slug', async () => {
|
|
@@ -723,7 +759,10 @@ describe('mcp_server - ghost_get_post tool', () => {
|
|
|
723
759
|
const tool = mockTools.get('ghost_get_post');
|
|
724
760
|
await tool.handler({ slug: 'test-post', include: 'tags' });
|
|
725
761
|
|
|
726
|
-
expect(mockGetPost).toHaveBeenCalledWith(
|
|
762
|
+
expect(mockGetPost).toHaveBeenCalledWith(
|
|
763
|
+
'slug/test-post',
|
|
764
|
+
expect.objectContaining({ include: 'tags' })
|
|
765
|
+
);
|
|
727
766
|
});
|
|
728
767
|
|
|
729
768
|
it('should prefer ID over slug when both provided', async () => {
|
|
@@ -733,7 +772,10 @@ describe('mcp_server - ghost_get_post tool', () => {
|
|
|
733
772
|
const tool = mockTools.get('ghost_get_post');
|
|
734
773
|
await tool.handler({ id: '507f1f77bcf86cd799439011', slug: 'wrong-slug' });
|
|
735
774
|
|
|
736
|
-
expect(mockGetPost).toHaveBeenCalledWith(
|
|
775
|
+
expect(mockGetPost).toHaveBeenCalledWith(
|
|
776
|
+
'507f1f77bcf86cd799439011',
|
|
777
|
+
expect.objectContaining({})
|
|
778
|
+
);
|
|
737
779
|
});
|
|
738
780
|
|
|
739
781
|
it('should handle not found errors', async () => {
|
|
@@ -835,9 +877,12 @@ describe('mcp_server - ghost_update_post tool', () => {
|
|
|
835
877
|
const tool = mockTools.get('ghost_update_post');
|
|
836
878
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated Title' });
|
|
837
879
|
|
|
838
|
-
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
839
|
-
|
|
840
|
-
|
|
880
|
+
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
881
|
+
'507f1f77bcf86cd799439011',
|
|
882
|
+
expect.objectContaining({
|
|
883
|
+
title: 'Updated Title',
|
|
884
|
+
})
|
|
885
|
+
);
|
|
841
886
|
expect(result.content[0].text).toContain('"title": "Updated Title"');
|
|
842
887
|
});
|
|
843
888
|
|
|
@@ -857,9 +902,12 @@ describe('mcp_server - ghost_update_post tool', () => {
|
|
|
857
902
|
html: '<p>Updated content</p>',
|
|
858
903
|
});
|
|
859
904
|
|
|
860
|
-
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
861
|
-
|
|
862
|
-
|
|
905
|
+
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
906
|
+
'507f1f77bcf86cd799439011',
|
|
907
|
+
expect.objectContaining({
|
|
908
|
+
html: '<p>Updated content</p>',
|
|
909
|
+
})
|
|
910
|
+
);
|
|
863
911
|
expect(result.content[0].text).toContain('Updated content');
|
|
864
912
|
});
|
|
865
913
|
|
|
@@ -876,9 +924,12 @@ describe('mcp_server - ghost_update_post tool', () => {
|
|
|
876
924
|
const tool = mockTools.get('ghost_update_post');
|
|
877
925
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', status: 'published' });
|
|
878
926
|
|
|
879
|
-
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
880
|
-
|
|
881
|
-
|
|
927
|
+
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
928
|
+
'507f1f77bcf86cd799439011',
|
|
929
|
+
expect.objectContaining({
|
|
930
|
+
status: 'published',
|
|
931
|
+
})
|
|
932
|
+
);
|
|
882
933
|
expect(result.content[0].text).toContain('"status": "published"');
|
|
883
934
|
});
|
|
884
935
|
|
|
@@ -898,9 +949,12 @@ describe('mcp_server - ghost_update_post tool', () => {
|
|
|
898
949
|
tags: ['tech', 'javascript'],
|
|
899
950
|
});
|
|
900
951
|
|
|
901
|
-
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
902
|
-
|
|
903
|
-
|
|
952
|
+
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
953
|
+
'507f1f77bcf86cd799439011',
|
|
954
|
+
expect.objectContaining({
|
|
955
|
+
tags: ['tech', 'javascript'],
|
|
956
|
+
})
|
|
957
|
+
);
|
|
904
958
|
expect(result.content[0].text).toContain('tech');
|
|
905
959
|
expect(result.content[0].text).toContain('javascript');
|
|
906
960
|
});
|
|
@@ -922,10 +976,13 @@ describe('mcp_server - ghost_update_post tool', () => {
|
|
|
922
976
|
feature_image_alt: 'New image',
|
|
923
977
|
});
|
|
924
978
|
|
|
925
|
-
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
979
|
+
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
980
|
+
'507f1f77bcf86cd799439011',
|
|
981
|
+
expect.objectContaining({
|
|
982
|
+
feature_image: 'https://example.com/new-image.jpg',
|
|
983
|
+
feature_image_alt: 'New image',
|
|
984
|
+
})
|
|
985
|
+
);
|
|
929
986
|
expect(result.content[0].text).toContain('new-image.jpg');
|
|
930
987
|
});
|
|
931
988
|
|
|
@@ -946,10 +1003,13 @@ describe('mcp_server - ghost_update_post tool', () => {
|
|
|
946
1003
|
meta_description: 'SEO Description',
|
|
947
1004
|
});
|
|
948
1005
|
|
|
949
|
-
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1006
|
+
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
1007
|
+
'507f1f77bcf86cd799439011',
|
|
1008
|
+
expect.objectContaining({
|
|
1009
|
+
meta_title: 'SEO Title',
|
|
1010
|
+
meta_description: 'SEO Description',
|
|
1011
|
+
})
|
|
1012
|
+
);
|
|
953
1013
|
expect(result.content[0].text).toContain('SEO Title');
|
|
954
1014
|
expect(result.content[0].text).toContain('SEO Description');
|
|
955
1015
|
});
|
|
@@ -974,12 +1034,15 @@ describe('mcp_server - ghost_update_post tool', () => {
|
|
|
974
1034
|
tags: ['tech'],
|
|
975
1035
|
});
|
|
976
1036
|
|
|
977
|
-
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1037
|
+
expect(mockUpdatePost).toHaveBeenCalledWith(
|
|
1038
|
+
'507f1f77bcf86cd799439011',
|
|
1039
|
+
expect.objectContaining({
|
|
1040
|
+
title: 'Updated Title',
|
|
1041
|
+
html: '<p>Updated content</p>',
|
|
1042
|
+
status: 'published',
|
|
1043
|
+
tags: ['tech'],
|
|
1044
|
+
})
|
|
1045
|
+
);
|
|
983
1046
|
expect(result.content[0].text).toContain('Updated Title');
|
|
984
1047
|
});
|
|
985
1048
|
|
|
@@ -1151,7 +1214,7 @@ describe('mcp_server - ghost_search_posts tool', () => {
|
|
|
1151
1214
|
const tool = mockTools.get('ghost_search_posts');
|
|
1152
1215
|
const result = await tool.handler({ query: 'JavaScript' });
|
|
1153
1216
|
|
|
1154
|
-
expect(mockSearchPosts).toHaveBeenCalledWith('JavaScript', {});
|
|
1217
|
+
expect(mockSearchPosts).toHaveBeenCalledWith('JavaScript', expect.objectContaining({}));
|
|
1155
1218
|
expect(result.content[0].text).toContain('JavaScript Tips');
|
|
1156
1219
|
expect(result.content[0].text).toContain('JavaScript Tricks');
|
|
1157
1220
|
});
|
|
@@ -1165,7 +1228,10 @@ describe('mcp_server - ghost_search_posts tool', () => {
|
|
|
1165
1228
|
const tool = mockTools.get('ghost_search_posts');
|
|
1166
1229
|
await tool.handler({ query: 'test', status: 'published' });
|
|
1167
1230
|
|
|
1168
|
-
expect(mockSearchPosts).toHaveBeenCalledWith(
|
|
1231
|
+
expect(mockSearchPosts).toHaveBeenCalledWith(
|
|
1232
|
+
'test',
|
|
1233
|
+
expect.objectContaining({ status: 'published' })
|
|
1234
|
+
);
|
|
1169
1235
|
});
|
|
1170
1236
|
|
|
1171
1237
|
it('should search posts with query and limit', async () => {
|
|
@@ -1175,7 +1241,7 @@ describe('mcp_server - ghost_search_posts tool', () => {
|
|
|
1175
1241
|
const tool = mockTools.get('ghost_search_posts');
|
|
1176
1242
|
await tool.handler({ query: 'test', limit: 10 });
|
|
1177
1243
|
|
|
1178
|
-
expect(mockSearchPosts).toHaveBeenCalledWith('test', { limit: 10 });
|
|
1244
|
+
expect(mockSearchPosts).toHaveBeenCalledWith('test', expect.objectContaining({ limit: 10 }));
|
|
1179
1245
|
});
|
|
1180
1246
|
|
|
1181
1247
|
it('should validate limit is between 1 and 50', () => {
|
|
@@ -1213,10 +1279,13 @@ describe('mcp_server - ghost_search_posts tool', () => {
|
|
|
1213
1279
|
limit: 20,
|
|
1214
1280
|
});
|
|
1215
1281
|
|
|
1216
|
-
expect(mockSearchPosts).toHaveBeenCalledWith(
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1282
|
+
expect(mockSearchPosts).toHaveBeenCalledWith(
|
|
1283
|
+
'JavaScript',
|
|
1284
|
+
expect.objectContaining({
|
|
1285
|
+
status: 'published',
|
|
1286
|
+
limit: 20,
|
|
1287
|
+
})
|
|
1288
|
+
);
|
|
1220
1289
|
});
|
|
1221
1290
|
|
|
1222
1291
|
it('should handle errors from searchPosts', async () => {
|
|
@@ -1286,8 +1355,8 @@ describe('ghost_get_tag', () => {
|
|
|
1286
1355
|
|
|
1287
1356
|
it('should have correct schema with id and slug as optional', () => {
|
|
1288
1357
|
const tool = mockTools.get('ghost_get_tag');
|
|
1289
|
-
//
|
|
1290
|
-
const shape = tool.schema.
|
|
1358
|
+
// In Zod v4, refined schemas expose .shape directly
|
|
1359
|
+
const shape = tool.schema.shape;
|
|
1291
1360
|
expect(shape.id).toBeDefined();
|
|
1292
1361
|
expect(shape.slug).toBeDefined();
|
|
1293
1362
|
expect(shape.include).toBeDefined();
|
|
@@ -1305,7 +1374,10 @@ describe('ghost_get_tag', () => {
|
|
|
1305
1374
|
const tool = mockTools.get('ghost_get_tag');
|
|
1306
1375
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
1307
1376
|
|
|
1308
|
-
expect(mockGetTag).toHaveBeenCalledWith(
|
|
1377
|
+
expect(mockGetTag).toHaveBeenCalledWith(
|
|
1378
|
+
'507f1f77bcf86cd799439011',
|
|
1379
|
+
expect.objectContaining({})
|
|
1380
|
+
);
|
|
1309
1381
|
expect(result.content).toBeDefined();
|
|
1310
1382
|
expect(result.content[0].type).toBe('text');
|
|
1311
1383
|
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
@@ -1324,7 +1396,7 @@ describe('ghost_get_tag', () => {
|
|
|
1324
1396
|
const tool = mockTools.get('ghost_get_tag');
|
|
1325
1397
|
const result = await tool.handler({ slug: 'test-tag' });
|
|
1326
1398
|
|
|
1327
|
-
expect(mockGetTag).toHaveBeenCalledWith('slug/test-tag', {});
|
|
1399
|
+
expect(mockGetTag).toHaveBeenCalledWith('slug/test-tag', expect.objectContaining({}));
|
|
1328
1400
|
expect(result.content[0].text).toContain('"slug": "test-tag"');
|
|
1329
1401
|
});
|
|
1330
1402
|
|
|
@@ -1340,7 +1412,10 @@ describe('ghost_get_tag', () => {
|
|
|
1340
1412
|
const tool = mockTools.get('ghost_get_tag');
|
|
1341
1413
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', include: 'count.posts' });
|
|
1342
1414
|
|
|
1343
|
-
expect(mockGetTag).toHaveBeenCalledWith(
|
|
1415
|
+
expect(mockGetTag).toHaveBeenCalledWith(
|
|
1416
|
+
'507f1f77bcf86cd799439011',
|
|
1417
|
+
expect.objectContaining({ include: 'count.posts' })
|
|
1418
|
+
);
|
|
1344
1419
|
expect(result.content[0].text).toContain('"count"');
|
|
1345
1420
|
});
|
|
1346
1421
|
|
|
@@ -1401,7 +1476,10 @@ describe('ghost_update_tag', () => {
|
|
|
1401
1476
|
const tool = mockTools.get('ghost_update_tag');
|
|
1402
1477
|
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', name: 'Updated Tag' });
|
|
1403
1478
|
|
|
1404
|
-
expect(mockUpdateTag).toHaveBeenCalledWith(
|
|
1479
|
+
expect(mockUpdateTag).toHaveBeenCalledWith(
|
|
1480
|
+
'507f1f77bcf86cd799439011',
|
|
1481
|
+
expect.objectContaining({ name: 'Updated Tag' })
|
|
1482
|
+
);
|
|
1405
1483
|
expect(result.content[0].text).toContain('"name": "Updated Tag"');
|
|
1406
1484
|
});
|
|
1407
1485
|
|
|
@@ -1419,9 +1497,12 @@ describe('ghost_update_tag', () => {
|
|
|
1419
1497
|
description: 'New description',
|
|
1420
1498
|
});
|
|
1421
1499
|
|
|
1422
|
-
expect(mockUpdateTag).toHaveBeenCalledWith(
|
|
1423
|
-
|
|
1424
|
-
|
|
1500
|
+
expect(mockUpdateTag).toHaveBeenCalledWith(
|
|
1501
|
+
'507f1f77bcf86cd799439011',
|
|
1502
|
+
expect.objectContaining({
|
|
1503
|
+
description: 'New description',
|
|
1504
|
+
})
|
|
1505
|
+
);
|
|
1425
1506
|
expect(result.content[0].text).toContain('"description": "New description"');
|
|
1426
1507
|
});
|
|
1427
1508
|
|
|
@@ -1443,11 +1524,14 @@ describe('ghost_update_tag', () => {
|
|
|
1443
1524
|
meta_title: 'Updated Meta',
|
|
1444
1525
|
});
|
|
1445
1526
|
|
|
1446
|
-
expect(mockUpdateTag).toHaveBeenCalledWith(
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1527
|
+
expect(mockUpdateTag).toHaveBeenCalledWith(
|
|
1528
|
+
'507f1f77bcf86cd799439011',
|
|
1529
|
+
expect.objectContaining({
|
|
1530
|
+
name: 'Updated Tag',
|
|
1531
|
+
description: 'Updated description',
|
|
1532
|
+
meta_title: 'Updated Meta',
|
|
1533
|
+
})
|
|
1534
|
+
);
|
|
1451
1535
|
});
|
|
1452
1536
|
|
|
1453
1537
|
it('should update tag feature image', async () => {
|
|
@@ -1464,9 +1548,12 @@ describe('ghost_update_tag', () => {
|
|
|
1464
1548
|
feature_image: 'https://example.com/image.jpg',
|
|
1465
1549
|
});
|
|
1466
1550
|
|
|
1467
|
-
expect(mockUpdateTag).toHaveBeenCalledWith(
|
|
1468
|
-
|
|
1469
|
-
|
|
1551
|
+
expect(mockUpdateTag).toHaveBeenCalledWith(
|
|
1552
|
+
'507f1f77bcf86cd799439011',
|
|
1553
|
+
expect.objectContaining({
|
|
1554
|
+
feature_image: 'https://example.com/image.jpg',
|
|
1555
|
+
})
|
|
1556
|
+
);
|
|
1470
1557
|
});
|
|
1471
1558
|
|
|
1472
1559
|
it('should return error when id is missing', async () => {
|