@jgardner04/ghost-mcp-server 1.11.0 → 1.12.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.
@@ -91,15 +91,21 @@ vi.mock('axios', () => ({
91
91
  }));
92
92
 
93
93
  // Mock fs
94
- const mockUnlink = vi.fn((path, cb) => cb(null));
95
94
  const mockCreateWriteStream = vi.fn();
96
95
  vi.mock('fs', () => ({
97
96
  default: {
98
- unlink: (...args) => mockUnlink(...args),
99
97
  createWriteStream: (...args) => mockCreateWriteStream(...args),
100
98
  },
101
99
  }));
102
100
 
101
+ // Mock tempFileManager
102
+ const mockTrackTempFile = vi.fn();
103
+ const mockCleanupTempFiles = vi.fn().mockResolvedValue(undefined);
104
+ vi.mock('../utils/tempFileManager.js', () => ({
105
+ trackTempFile: (...args) => mockTrackTempFile(...args),
106
+ cleanupTempFiles: (...args) => mockCleanupTempFiles(...args),
107
+ }));
108
+
103
109
  // Mock os
104
110
  vi.mock('os', () => ({
105
111
  default: { tmpdir: vi.fn().mockReturnValue('/tmp') },
@@ -133,12 +139,13 @@ describe('mcp_server_improved - ghost_get_posts tool', () => {
133
139
  expect(tool).toBeDefined();
134
140
  expect(tool.description).toContain('posts');
135
141
  expect(tool.schema).toBeDefined();
136
- expect(tool.schema.limit).toBeDefined();
137
- expect(tool.schema.page).toBeDefined();
138
- expect(tool.schema.status).toBeDefined();
139
- expect(tool.schema.include).toBeDefined();
140
- expect(tool.schema.filter).toBeDefined();
141
- expect(tool.schema.order).toBeDefined();
142
+ // Zod schemas store field definitions in schema.shape
143
+ expect(tool.schema.shape.limit).toBeDefined();
144
+ expect(tool.schema.shape.page).toBeDefined();
145
+ expect(tool.schema.shape.status).toBeDefined();
146
+ expect(tool.schema.shape.include).toBeDefined();
147
+ expect(tool.schema.shape.filter).toBeDefined();
148
+ expect(tool.schema.shape.order).toBeDefined();
142
149
  });
143
150
 
144
151
  it('should retrieve posts with default options', async () => {
@@ -168,22 +175,24 @@ describe('mcp_server_improved - ghost_get_posts tool', () => {
168
175
 
169
176
  it('should validate limit is between 1 and 100', () => {
170
177
  const tool = mockTools.get('ghost_get_posts');
171
- const schema = tool.schema;
178
+ // Zod schemas store field definitions in schema.shape
179
+ const shape = tool.schema.shape;
172
180
 
173
181
  // Test that limit schema exists and has proper validation
174
- expect(schema.limit).toBeDefined();
175
- expect(() => schema.limit.parse(0)).toThrow();
176
- expect(() => schema.limit.parse(101)).toThrow();
177
- expect(schema.limit.parse(50)).toBe(50);
182
+ expect(shape.limit).toBeDefined();
183
+ expect(() => shape.limit.parse(0)).toThrow();
184
+ expect(() => shape.limit.parse(101)).toThrow();
185
+ expect(shape.limit.parse(50)).toBe(50);
178
186
  });
179
187
 
180
188
  it('should validate page is at least 1', () => {
181
189
  const tool = mockTools.get('ghost_get_posts');
182
- const schema = tool.schema;
190
+ // Zod schemas store field definitions in schema.shape
191
+ const shape = tool.schema.shape;
183
192
 
184
- expect(schema.page).toBeDefined();
185
- expect(() => schema.page.parse(0)).toThrow();
186
- expect(schema.page.parse(1)).toBe(1);
193
+ expect(shape.page).toBeDefined();
194
+ expect(() => shape.page.parse(0)).toThrow();
195
+ expect(shape.page.parse(1)).toBe(1);
187
196
  });
188
197
 
189
198
  it('should pass status filter', async () => {
@@ -198,14 +207,15 @@ describe('mcp_server_improved - ghost_get_posts tool', () => {
198
207
 
199
208
  it('should validate status enum values', () => {
200
209
  const tool = mockTools.get('ghost_get_posts');
201
- const schema = tool.schema;
210
+ // Zod schemas store field definitions in schema.shape
211
+ const shape = tool.schema.shape;
202
212
 
203
- expect(schema.status).toBeDefined();
204
- expect(() => schema.status.parse('invalid')).toThrow();
205
- expect(schema.status.parse('published')).toBe('published');
206
- expect(schema.status.parse('draft')).toBe('draft');
207
- expect(schema.status.parse('scheduled')).toBe('scheduled');
208
- expect(schema.status.parse('all')).toBe('all');
213
+ expect(shape.status).toBeDefined();
214
+ expect(() => shape.status.parse('invalid')).toThrow();
215
+ expect(shape.status.parse('published')).toBe('published');
216
+ expect(shape.status.parse('draft')).toBe('draft');
217
+ expect(shape.status.parse('scheduled')).toBe('scheduled');
218
+ expect(shape.status.parse('all')).toBe('all');
209
219
  });
210
220
 
211
221
  it('should pass include parameter', async () => {
@@ -322,14 +332,15 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
322
332
  expect(tool).toBeDefined();
323
333
  expect(tool.description).toContain('post');
324
334
  expect(tool.schema).toBeDefined();
325
- expect(tool.schema.id).toBeDefined();
326
- expect(tool.schema.slug).toBeDefined();
327
- expect(tool.schema.include).toBeDefined();
335
+ // Zod schemas store field definitions in schema.shape
336
+ expect(tool.schema.shape.id).toBeDefined();
337
+ expect(tool.schema.shape.slug).toBeDefined();
338
+ expect(tool.schema.shape.include).toBeDefined();
328
339
  });
329
340
 
330
341
  it('should retrieve post by ID', async () => {
331
342
  const mockPost = {
332
- id: '123',
343
+ id: '507f1f77bcf86cd799439011',
333
344
  title: 'Test Post',
334
345
  slug: 'test-post',
335
346
  html: '<p>Content</p>',
@@ -338,16 +349,16 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
338
349
  mockGetPost.mockResolvedValue(mockPost);
339
350
 
340
351
  const tool = mockTools.get('ghost_get_post');
341
- const result = await tool.handler({ id: '123' });
352
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
342
353
 
343
- expect(mockGetPost).toHaveBeenCalledWith('123', {});
344
- expect(result.content[0].text).toContain('"id": "123"');
354
+ expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
355
+ expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
345
356
  expect(result.content[0].text).toContain('"title": "Test Post"');
346
357
  });
347
358
 
348
359
  it('should retrieve post by slug', async () => {
349
360
  const mockPost = {
350
- id: '123',
361
+ id: '507f1f77bcf86cd799439011',
351
362
  title: 'Test Post',
352
363
  slug: 'test-post',
353
364
  html: '<p>Content</p>',
@@ -364,7 +375,7 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
364
375
 
365
376
  it('should pass include parameter with ID', async () => {
366
377
  const mockPost = {
367
- id: '123',
378
+ id: '507f1f77bcf86cd799439011',
368
379
  title: 'Post with relations',
369
380
  tags: [{ name: 'tech' }],
370
381
  authors: [{ name: 'John' }],
@@ -372,14 +383,16 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
372
383
  mockGetPost.mockResolvedValue(mockPost);
373
384
 
374
385
  const tool = mockTools.get('ghost_get_post');
375
- await tool.handler({ id: '123', include: 'tags,authors' });
386
+ await tool.handler({ id: '507f1f77bcf86cd799439011', include: 'tags,authors' });
376
387
 
377
- expect(mockGetPost).toHaveBeenCalledWith('123', { include: 'tags,authors' });
388
+ expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
389
+ include: 'tags,authors',
390
+ });
378
391
  });
379
392
 
380
393
  it('should pass include parameter with slug', async () => {
381
394
  const mockPost = {
382
- id: '123',
395
+ id: '507f1f77bcf86cd799439011',
383
396
  title: 'Post with relations',
384
397
  slug: 'test-post',
385
398
  tags: [{ name: 'tech' }],
@@ -393,20 +406,20 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
393
406
  });
394
407
 
395
408
  it('should prefer ID over slug when both provided', async () => {
396
- const mockPost = { id: '123', title: 'Test Post', slug: 'test-post' };
409
+ const mockPost = { id: '507f1f77bcf86cd799439011', title: 'Test Post', slug: 'test-post' };
397
410
  mockGetPost.mockResolvedValue(mockPost);
398
411
 
399
412
  const tool = mockTools.get('ghost_get_post');
400
- await tool.handler({ id: '123', slug: 'wrong-slug' });
413
+ await tool.handler({ id: '507f1f77bcf86cd799439011', slug: 'wrong-slug' });
401
414
 
402
- expect(mockGetPost).toHaveBeenCalledWith('123', {});
415
+ expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
403
416
  });
404
417
 
405
418
  it('should handle not found errors', async () => {
406
419
  mockGetPost.mockRejectedValue(new Error('Post not found'));
407
420
 
408
421
  const tool = mockTools.get('ghost_get_post');
409
- const result = await tool.handler({ id: 'nonexistent' });
422
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
410
423
 
411
424
  expect(result.isError).toBe(true);
412
425
  expect(result.content[0].text).toContain('Post not found');
@@ -424,7 +437,7 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
424
437
 
425
438
  it('should return formatted JSON response', async () => {
426
439
  const mockPost = {
427
- id: '1',
440
+ id: '507f1f77bcf86cd799439011',
428
441
  uuid: 'uuid-123',
429
442
  title: 'Test Post',
430
443
  slug: 'test-post',
@@ -436,11 +449,11 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
436
449
  mockGetPost.mockResolvedValue(mockPost);
437
450
 
438
451
  const tool = mockTools.get('ghost_get_post');
439
- const result = await tool.handler({ id: '1' });
452
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
440
453
 
441
454
  expect(result.content).toBeDefined();
442
455
  expect(result.content[0].type).toBe('text');
443
- expect(result.content[0].text).toContain('"id": "1"');
456
+ expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
444
457
  expect(result.content[0].text).toContain('"title": "Test Post"');
445
458
  expect(result.content[0].text).toContain('"status": "published"');
446
459
  });
@@ -472,23 +485,24 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
472
485
  expect(tool).toBeDefined();
473
486
  expect(tool.description).toContain('Updates an existing post');
474
487
  expect(tool.schema).toBeDefined();
475
- expect(tool.schema.id).toBeDefined();
476
- expect(tool.schema.title).toBeDefined();
477
- expect(tool.schema.html).toBeDefined();
478
- expect(tool.schema.status).toBeDefined();
479
- expect(tool.schema.tags).toBeDefined();
480
- expect(tool.schema.feature_image).toBeDefined();
481
- expect(tool.schema.feature_image_alt).toBeDefined();
482
- expect(tool.schema.feature_image_caption).toBeDefined();
483
- expect(tool.schema.meta_title).toBeDefined();
484
- expect(tool.schema.meta_description).toBeDefined();
485
- expect(tool.schema.published_at).toBeDefined();
486
- expect(tool.schema.custom_excerpt).toBeDefined();
488
+ // Zod schemas store field definitions in schema.shape
489
+ expect(tool.schema.shape.id).toBeDefined();
490
+ expect(tool.schema.shape.title).toBeDefined();
491
+ expect(tool.schema.shape.html).toBeDefined();
492
+ expect(tool.schema.shape.status).toBeDefined();
493
+ expect(tool.schema.shape.tags).toBeDefined();
494
+ expect(tool.schema.shape.feature_image).toBeDefined();
495
+ expect(tool.schema.shape.feature_image_alt).toBeDefined();
496
+ expect(tool.schema.shape.feature_image_caption).toBeDefined();
497
+ expect(tool.schema.shape.meta_title).toBeDefined();
498
+ expect(tool.schema.shape.meta_description).toBeDefined();
499
+ expect(tool.schema.shape.published_at).toBeDefined();
500
+ expect(tool.schema.shape.custom_excerpt).toBeDefined();
487
501
  });
488
502
 
489
503
  it('should update post title', async () => {
490
504
  const mockUpdatedPost = {
491
- id: '123',
505
+ id: '507f1f77bcf86cd799439011',
492
506
  title: 'Updated Title',
493
507
  slug: 'test-post',
494
508
  html: '<p>Content</p>',
@@ -498,15 +512,17 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
498
512
  mockUpdatePost.mockResolvedValue(mockUpdatedPost);
499
513
 
500
514
  const tool = mockTools.get('ghost_update_post');
501
- const result = await tool.handler({ id: '123', title: 'Updated Title' });
515
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated Title' });
502
516
 
503
- expect(mockUpdatePost).toHaveBeenCalledWith('123', { title: 'Updated Title' });
517
+ expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
518
+ title: 'Updated Title',
519
+ });
504
520
  expect(result.content[0].text).toContain('"title": "Updated Title"');
505
521
  });
506
522
 
507
523
  it('should update post content', async () => {
508
524
  const mockUpdatedPost = {
509
- id: '123',
525
+ id: '507f1f77bcf86cd799439011',
510
526
  title: 'Test Post',
511
527
  html: '<p>Updated content</p>',
512
528
  status: 'published',
@@ -515,15 +531,20 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
515
531
  mockUpdatePost.mockResolvedValue(mockUpdatedPost);
516
532
 
517
533
  const tool = mockTools.get('ghost_update_post');
518
- const result = await tool.handler({ id: '123', html: '<p>Updated content</p>' });
534
+ const result = await tool.handler({
535
+ id: '507f1f77bcf86cd799439011',
536
+ html: '<p>Updated content</p>',
537
+ });
519
538
 
520
- expect(mockUpdatePost).toHaveBeenCalledWith('123', { html: '<p>Updated content</p>' });
539
+ expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
540
+ html: '<p>Updated content</p>',
541
+ });
521
542
  expect(result.content[0].text).toContain('Updated content');
522
543
  });
523
544
 
524
545
  it('should update post status', async () => {
525
546
  const mockUpdatedPost = {
526
- id: '123',
547
+ id: '507f1f77bcf86cd799439011',
527
548
  title: 'Test Post',
528
549
  html: '<p>Content</p>',
529
550
  status: 'published',
@@ -532,15 +553,17 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
532
553
  mockUpdatePost.mockResolvedValue(mockUpdatedPost);
533
554
 
534
555
  const tool = mockTools.get('ghost_update_post');
535
- const result = await tool.handler({ id: '123', status: 'published' });
556
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011', status: 'published' });
536
557
 
537
- expect(mockUpdatePost).toHaveBeenCalledWith('123', { status: 'published' });
558
+ expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
559
+ status: 'published',
560
+ });
538
561
  expect(result.content[0].text).toContain('"status": "published"');
539
562
  });
540
563
 
541
564
  it('should update post tags', async () => {
542
565
  const mockUpdatedPost = {
543
- id: '123',
566
+ id: '507f1f77bcf86cd799439011',
544
567
  title: 'Test Post',
545
568
  html: '<p>Content</p>',
546
569
  tags: [{ name: 'tech' }, { name: 'javascript' }],
@@ -549,16 +572,21 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
549
572
  mockUpdatePost.mockResolvedValue(mockUpdatedPost);
550
573
 
551
574
  const tool = mockTools.get('ghost_update_post');
552
- const result = await tool.handler({ id: '123', tags: ['tech', 'javascript'] });
575
+ const result = await tool.handler({
576
+ id: '507f1f77bcf86cd799439011',
577
+ tags: ['tech', 'javascript'],
578
+ });
553
579
 
554
- expect(mockUpdatePost).toHaveBeenCalledWith('123', { tags: ['tech', 'javascript'] });
580
+ expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
581
+ tags: ['tech', 'javascript'],
582
+ });
555
583
  expect(result.content[0].text).toContain('tech');
556
584
  expect(result.content[0].text).toContain('javascript');
557
585
  });
558
586
 
559
587
  it('should update post featured image', async () => {
560
588
  const mockUpdatedPost = {
561
- id: '123',
589
+ id: '507f1f77bcf86cd799439011',
562
590
  title: 'Test Post',
563
591
  feature_image: 'https://example.com/new-image.jpg',
564
592
  feature_image_alt: 'New image',
@@ -568,12 +596,12 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
568
596
 
569
597
  const tool = mockTools.get('ghost_update_post');
570
598
  const result = await tool.handler({
571
- id: '123',
599
+ id: '507f1f77bcf86cd799439011',
572
600
  feature_image: 'https://example.com/new-image.jpg',
573
601
  feature_image_alt: 'New image',
574
602
  });
575
603
 
576
- expect(mockUpdatePost).toHaveBeenCalledWith('123', {
604
+ expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
577
605
  feature_image: 'https://example.com/new-image.jpg',
578
606
  feature_image_alt: 'New image',
579
607
  });
@@ -582,7 +610,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
582
610
 
583
611
  it('should update SEO meta fields', async () => {
584
612
  const mockUpdatedPost = {
585
- id: '123',
613
+ id: '507f1f77bcf86cd799439011',
586
614
  title: 'Test Post',
587
615
  meta_title: 'SEO Title',
588
616
  meta_description: 'SEO Description',
@@ -592,12 +620,12 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
592
620
 
593
621
  const tool = mockTools.get('ghost_update_post');
594
622
  const result = await tool.handler({
595
- id: '123',
623
+ id: '507f1f77bcf86cd799439011',
596
624
  meta_title: 'SEO Title',
597
625
  meta_description: 'SEO Description',
598
626
  });
599
627
 
600
- expect(mockUpdatePost).toHaveBeenCalledWith('123', {
628
+ expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
601
629
  meta_title: 'SEO Title',
602
630
  meta_description: 'SEO Description',
603
631
  });
@@ -607,7 +635,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
607
635
 
608
636
  it('should update multiple fields at once', async () => {
609
637
  const mockUpdatedPost = {
610
- id: '123',
638
+ id: '507f1f77bcf86cd799439011',
611
639
  title: 'Updated Title',
612
640
  html: '<p>Updated content</p>',
613
641
  status: 'published',
@@ -618,14 +646,14 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
618
646
 
619
647
  const tool = mockTools.get('ghost_update_post');
620
648
  const result = await tool.handler({
621
- id: '123',
649
+ id: '507f1f77bcf86cd799439011',
622
650
  title: 'Updated Title',
623
651
  html: '<p>Updated content</p>',
624
652
  status: 'published',
625
653
  tags: ['tech'],
626
654
  });
627
655
 
628
- expect(mockUpdatePost).toHaveBeenCalledWith('123', {
656
+ expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
629
657
  title: 'Updated Title',
630
658
  html: '<p>Updated content</p>',
631
659
  status: 'published',
@@ -638,7 +666,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
638
666
  mockUpdatePost.mockRejectedValue(new Error('Post not found'));
639
667
 
640
668
  const tool = mockTools.get('ghost_update_post');
641
- const result = await tool.handler({ id: 'nonexistent', title: 'New Title' });
669
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439099', title: 'New Title' });
642
670
 
643
671
  expect(result.isError).toBe(true);
644
672
  expect(result.content[0].text).toContain('Post not found');
@@ -648,7 +676,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
648
676
  mockUpdatePost.mockRejectedValue(new Error('Validation failed: Title is required'));
649
677
 
650
678
  const tool = mockTools.get('ghost_update_post');
651
- const result = await tool.handler({ id: '123', title: '' });
679
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: '' });
652
680
 
653
681
  expect(result.isError).toBe(true);
654
682
  expect(result.content[0].text).toContain('Validation failed');
@@ -658,7 +686,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
658
686
  mockUpdatePost.mockRejectedValue(new Error('Ghost API error: Server timeout'));
659
687
 
660
688
  const tool = mockTools.get('ghost_update_post');
661
- const result = await tool.handler({ id: '123', title: 'Updated' });
689
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated' });
662
690
 
663
691
  expect(result.isError).toBe(true);
664
692
  expect(result.content[0].text).toContain('Ghost API error');
@@ -666,7 +694,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
666
694
 
667
695
  it('should return formatted JSON response', async () => {
668
696
  const mockUpdatedPost = {
669
- id: '123',
697
+ id: '507f1f77bcf86cd799439011',
670
698
  uuid: 'uuid-123',
671
699
  title: 'Updated Post',
672
700
  slug: 'updated-post',
@@ -678,11 +706,11 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
678
706
  mockUpdatePost.mockResolvedValue(mockUpdatedPost);
679
707
 
680
708
  const tool = mockTools.get('ghost_update_post');
681
- const result = await tool.handler({ id: '123', title: 'Updated Post' });
709
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated Post' });
682
710
 
683
711
  expect(result.content).toBeDefined();
684
712
  expect(result.content[0].type).toBe('text');
685
- expect(result.content[0].text).toContain('"id": "123"');
713
+ expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
686
714
  expect(result.content[0].text).toContain('"title": "Updated Post"');
687
715
  expect(result.content[0].text).toContain('"status": "published"');
688
716
  });
@@ -707,17 +735,20 @@ describe('mcp_server_improved - ghost_delete_post tool', () => {
707
735
  expect(tool.description).toContain('Deletes a post');
708
736
  expect(tool.description).toContain('permanent');
709
737
  expect(tool.schema).toBeDefined();
710
- expect(tool.schema.id).toBeDefined();
738
+ // Zod schemas store field definitions in schema.shape
739
+ expect(tool.schema.shape.id).toBeDefined();
711
740
  });
712
741
 
713
742
  it('should delete post by ID', async () => {
714
743
  mockDeletePost.mockResolvedValue({ deleted: true });
715
744
 
716
745
  const tool = mockTools.get('ghost_delete_post');
717
- const result = await tool.handler({ id: '123' });
746
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
718
747
 
719
- expect(mockDeletePost).toHaveBeenCalledWith('123');
720
- expect(result.content[0].text).toContain('Post 123 has been successfully deleted');
748
+ expect(mockDeletePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
749
+ expect(result.content[0].text).toContain(
750
+ 'Post 507f1f77bcf86cd799439011 has been successfully deleted'
751
+ );
721
752
  expect(result.isError).toBeUndefined();
722
753
  });
723
754
 
@@ -725,7 +756,7 @@ describe('mcp_server_improved - ghost_delete_post tool', () => {
725
756
  mockDeletePost.mockRejectedValue(new Error('Post not found'));
726
757
 
727
758
  const tool = mockTools.get('ghost_delete_post');
728
- const result = await tool.handler({ id: 'nonexistent' });
759
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
729
760
 
730
761
  expect(result.isError).toBe(true);
731
762
  expect(result.content[0].text).toContain('Post not found');
@@ -735,7 +766,7 @@ describe('mcp_server_improved - ghost_delete_post tool', () => {
735
766
  mockDeletePost.mockRejectedValue(new Error('Ghost API error: Permission denied'));
736
767
 
737
768
  const tool = mockTools.get('ghost_delete_post');
738
- const result = await tool.handler({ id: '123' });
769
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
739
770
 
740
771
  expect(result.isError).toBe(true);
741
772
  expect(result.content[0].text).toContain('Ghost API error');
@@ -745,18 +776,20 @@ describe('mcp_server_improved - ghost_delete_post tool', () => {
745
776
  mockDeletePost.mockResolvedValue({ deleted: true });
746
777
 
747
778
  const tool = mockTools.get('ghost_delete_post');
748
- const result = await tool.handler({ id: 'test-post-id' });
779
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
749
780
 
750
781
  expect(result.content).toBeDefined();
751
782
  expect(result.content[0].type).toBe('text');
752
- expect(result.content[0].text).toBe('Post test-post-id has been successfully deleted.');
783
+ expect(result.content[0].text).toBe(
784
+ 'Post 507f1f77bcf86cd799439011 has been successfully deleted.'
785
+ );
753
786
  });
754
787
 
755
788
  it('should handle network errors', async () => {
756
789
  mockDeletePost.mockRejectedValue(new Error('Network error: Connection refused'));
757
790
 
758
791
  const tool = mockTools.get('ghost_delete_post');
759
- const result = await tool.handler({ id: '123' });
792
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439012' });
760
793
 
761
794
  expect(result.isError).toBe(true);
762
795
  expect(result.content[0].text).toContain('Network error');
@@ -781,9 +814,10 @@ describe('mcp_server_improved - ghost_search_posts tool', () => {
781
814
  expect(tool).toBeDefined();
782
815
  expect(tool.description).toContain('Search');
783
816
  expect(tool.schema).toBeDefined();
784
- expect(tool.schema.query).toBeDefined();
785
- expect(tool.schema.status).toBeDefined();
786
- expect(tool.schema.limit).toBeDefined();
817
+ // Zod schemas store field definitions in schema.shape
818
+ expect(tool.schema.shape.query).toBeDefined();
819
+ expect(tool.schema.shape.status).toBeDefined();
820
+ expect(tool.schema.shape.limit).toBeDefined();
787
821
  });
788
822
 
789
823
  it('should search posts with query only', async () => {
@@ -825,24 +859,26 @@ describe('mcp_server_improved - ghost_search_posts tool', () => {
825
859
 
826
860
  it('should validate limit is between 1 and 50', () => {
827
861
  const tool = mockTools.get('ghost_search_posts');
828
- const schema = tool.schema;
862
+ // Zod schemas store field definitions in schema.shape
863
+ const shape = tool.schema.shape;
829
864
 
830
- expect(schema.limit).toBeDefined();
831
- expect(() => schema.limit.parse(0)).toThrow();
832
- expect(() => schema.limit.parse(51)).toThrow();
833
- expect(schema.limit.parse(25)).toBe(25);
865
+ expect(shape.limit).toBeDefined();
866
+ expect(() => shape.limit.parse(0)).toThrow();
867
+ expect(() => shape.limit.parse(51)).toThrow();
868
+ expect(shape.limit.parse(25)).toBe(25);
834
869
  });
835
870
 
836
871
  it('should validate status enum values', () => {
837
872
  const tool = mockTools.get('ghost_search_posts');
838
- const schema = tool.schema;
873
+ // Zod schemas store field definitions in schema.shape
874
+ const shape = tool.schema.shape;
839
875
 
840
- expect(schema.status).toBeDefined();
841
- expect(() => schema.status.parse('invalid')).toThrow();
842
- expect(schema.status.parse('published')).toBe('published');
843
- expect(schema.status.parse('draft')).toBe('draft');
844
- expect(schema.status.parse('scheduled')).toBe('scheduled');
845
- expect(schema.status.parse('all')).toBe('all');
876
+ expect(shape.status).toBeDefined();
877
+ expect(() => shape.status.parse('invalid')).toThrow();
878
+ expect(shape.status.parse('published')).toBe('published');
879
+ expect(shape.status.parse('draft')).toBe('draft');
880
+ expect(shape.status.parse('scheduled')).toBe('scheduled');
881
+ expect(shape.status.parse('all')).toBe('all');
846
882
  });
847
883
 
848
884
  it('should pass all parameters combined', async () => {
@@ -863,13 +899,12 @@ describe('mcp_server_improved - ghost_search_posts tool', () => {
863
899
  });
864
900
 
865
901
  it('should handle errors from searchPosts', async () => {
866
- mockSearchPosts.mockRejectedValue(new Error('Search query is required'));
867
-
902
+ // Empty query is now caught by Zod validation
868
903
  const tool = mockTools.get('ghost_search_posts');
869
904
  const result = await tool.handler({ query: '' });
870
905
 
871
906
  expect(result.isError).toBe(true);
872
- expect(result.content[0].text).toContain('Search query is required');
907
+ expect(result.content[0].text).toContain('VALIDATION_ERROR');
873
908
  });
874
909
 
875
910
  it('should handle Ghost API errors', async () => {
@@ -930,14 +965,15 @@ describe('ghost_get_tag', () => {
930
965
 
931
966
  it('should have correct schema with id and slug as optional', () => {
932
967
  const tool = mockTools.get('ghost_get_tag');
933
- expect(tool.schema.id).toBeDefined();
934
- expect(tool.schema.slug).toBeDefined();
935
- expect(tool.schema.include).toBeDefined();
968
+ // Zod schemas store field definitions in schema.shape
969
+ expect(tool.schema.shape.id).toBeDefined();
970
+ expect(tool.schema.shape.slug).toBeDefined();
971
+ expect(tool.schema.shape.include).toBeDefined();
936
972
  });
937
973
 
938
974
  it('should retrieve tag by ID', async () => {
939
975
  const mockTag = {
940
- id: '123',
976
+ id: '507f1f77bcf86cd799439011',
941
977
  name: 'Test Tag',
942
978
  slug: 'test-tag',
943
979
  description: 'A test tag',
@@ -945,18 +981,18 @@ describe('ghost_get_tag', () => {
945
981
  mockGetTag.mockResolvedValue(mockTag);
946
982
 
947
983
  const tool = mockTools.get('ghost_get_tag');
948
- const result = await tool.handler({ id: '123' });
984
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
949
985
 
950
- expect(mockGetTag).toHaveBeenCalledWith('123', {});
986
+ expect(mockGetTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
951
987
  expect(result.content).toBeDefined();
952
988
  expect(result.content[0].type).toBe('text');
953
- expect(result.content[0].text).toContain('"id": "123"');
989
+ expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
954
990
  expect(result.content[0].text).toContain('"name": "Test Tag"');
955
991
  });
956
992
 
957
993
  it('should retrieve tag by slug', async () => {
958
994
  const mockTag = {
959
- id: '123',
995
+ id: '507f1f77bcf86cd799439011',
960
996
  name: 'Test Tag',
961
997
  slug: 'test-tag',
962
998
  description: 'A test tag',
@@ -972,7 +1008,7 @@ describe('ghost_get_tag', () => {
972
1008
 
973
1009
  it('should support include parameter for post count', async () => {
974
1010
  const mockTag = {
975
- id: '123',
1011
+ id: '507f1f77bcf86cd799439011',
976
1012
  name: 'Test Tag',
977
1013
  slug: 'test-tag',
978
1014
  count: { posts: 5 },
@@ -980,9 +1016,9 @@ describe('ghost_get_tag', () => {
980
1016
  mockGetTag.mockResolvedValue(mockTag);
981
1017
 
982
1018
  const tool = mockTools.get('ghost_get_tag');
983
- const result = await tool.handler({ id: '123', include: 'count.posts' });
1019
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011', include: 'count.posts' });
984
1020
 
985
- expect(mockGetTag).toHaveBeenCalledWith('123', { include: 'count.posts' });
1021
+ expect(mockGetTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', { include: 'count.posts' });
986
1022
  expect(result.content[0].text).toContain('"count"');
987
1023
  });
988
1024
 
@@ -999,7 +1035,7 @@ describe('ghost_get_tag', () => {
999
1035
  mockGetTag.mockRejectedValue(new Error('Tag not found'));
1000
1036
 
1001
1037
  const tool = mockTools.get('ghost_get_tag');
1002
- const result = await tool.handler({ id: '999' });
1038
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
1003
1039
 
1004
1040
  expect(result.isError).toBe(true);
1005
1041
  expect(result.content[0].text).toContain('Tag not found');
@@ -1022,48 +1058,54 @@ describe('ghost_update_tag', () => {
1022
1058
 
1023
1059
  it('should have correct schema with all update fields', () => {
1024
1060
  const tool = mockTools.get('ghost_update_tag');
1025
- expect(tool.schema.id).toBeDefined();
1026
- expect(tool.schema.name).toBeDefined();
1027
- expect(tool.schema.slug).toBeDefined();
1028
- expect(tool.schema.description).toBeDefined();
1029
- expect(tool.schema.feature_image).toBeDefined();
1030
- expect(tool.schema.meta_title).toBeDefined();
1031
- expect(tool.schema.meta_description).toBeDefined();
1061
+ // Zod schemas store field definitions in schema.shape
1062
+ expect(tool.schema.shape.id).toBeDefined();
1063
+ expect(tool.schema.shape.name).toBeDefined();
1064
+ expect(tool.schema.shape.slug).toBeDefined();
1065
+ expect(tool.schema.shape.description).toBeDefined();
1066
+ expect(tool.schema.shape.feature_image).toBeDefined();
1067
+ expect(tool.schema.shape.meta_title).toBeDefined();
1068
+ expect(tool.schema.shape.meta_description).toBeDefined();
1032
1069
  });
1033
1070
 
1034
1071
  it('should update tag name', async () => {
1035
1072
  const mockUpdatedTag = {
1036
- id: '123',
1073
+ id: '507f1f77bcf86cd799439011',
1037
1074
  name: 'Updated Tag',
1038
1075
  slug: 'updated-tag',
1039
1076
  };
1040
1077
  mockUpdateTag.mockResolvedValue(mockUpdatedTag);
1041
1078
 
1042
1079
  const tool = mockTools.get('ghost_update_tag');
1043
- const result = await tool.handler({ id: '123', name: 'Updated Tag' });
1080
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011', name: 'Updated Tag' });
1044
1081
 
1045
- expect(mockUpdateTag).toHaveBeenCalledWith('123', { name: 'Updated Tag' });
1082
+ expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', { name: 'Updated Tag' });
1046
1083
  expect(result.content[0].text).toContain('"name": "Updated Tag"');
1047
1084
  });
1048
1085
 
1049
1086
  it('should update tag description', async () => {
1050
1087
  const mockUpdatedTag = {
1051
- id: '123',
1088
+ id: '507f1f77bcf86cd799439011',
1052
1089
  name: 'Test Tag',
1053
1090
  description: 'New description',
1054
1091
  };
1055
1092
  mockUpdateTag.mockResolvedValue(mockUpdatedTag);
1056
1093
 
1057
1094
  const tool = mockTools.get('ghost_update_tag');
1058
- const result = await tool.handler({ id: '123', description: 'New description' });
1095
+ const result = await tool.handler({
1096
+ id: '507f1f77bcf86cd799439011',
1097
+ description: 'New description',
1098
+ });
1059
1099
 
1060
- expect(mockUpdateTag).toHaveBeenCalledWith('123', { description: 'New description' });
1100
+ expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
1101
+ description: 'New description',
1102
+ });
1061
1103
  expect(result.content[0].text).toContain('"description": "New description"');
1062
1104
  });
1063
1105
 
1064
1106
  it('should update multiple fields at once', async () => {
1065
1107
  const mockUpdatedTag = {
1066
- id: '123',
1108
+ id: '507f1f77bcf86cd799439011',
1067
1109
  name: 'Updated Tag',
1068
1110
  slug: 'updated-tag',
1069
1111
  description: 'Updated description',
@@ -1073,13 +1115,13 @@ describe('ghost_update_tag', () => {
1073
1115
 
1074
1116
  const tool = mockTools.get('ghost_update_tag');
1075
1117
  await tool.handler({
1076
- id: '123',
1118
+ id: '507f1f77bcf86cd799439011',
1077
1119
  name: 'Updated Tag',
1078
1120
  description: 'Updated description',
1079
1121
  meta_title: 'Updated Meta',
1080
1122
  });
1081
1123
 
1082
- expect(mockUpdateTag).toHaveBeenCalledWith('123', {
1124
+ expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
1083
1125
  name: 'Updated Tag',
1084
1126
  description: 'Updated description',
1085
1127
  meta_title: 'Updated Meta',
@@ -1088,7 +1130,7 @@ describe('ghost_update_tag', () => {
1088
1130
 
1089
1131
  it('should update tag feature image', async () => {
1090
1132
  const mockUpdatedTag = {
1091
- id: '123',
1133
+ id: '507f1f77bcf86cd799439011',
1092
1134
  name: 'Test Tag',
1093
1135
  feature_image: 'https://example.com/image.jpg',
1094
1136
  };
@@ -1096,11 +1138,11 @@ describe('ghost_update_tag', () => {
1096
1138
 
1097
1139
  const tool = mockTools.get('ghost_update_tag');
1098
1140
  await tool.handler({
1099
- id: '123',
1141
+ id: '507f1f77bcf86cd799439011',
1100
1142
  feature_image: 'https://example.com/image.jpg',
1101
1143
  });
1102
1144
 
1103
- expect(mockUpdateTag).toHaveBeenCalledWith('123', {
1145
+ expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
1104
1146
  feature_image: 'https://example.com/image.jpg',
1105
1147
  });
1106
1148
  });
@@ -1110,14 +1152,14 @@ describe('ghost_update_tag', () => {
1110
1152
  const result = await tool.handler({ name: 'Test' });
1111
1153
 
1112
1154
  expect(result.isError).toBe(true);
1113
- expect(result.content[0].text).toContain('Tag ID is required');
1155
+ expect(result.content[0].text).toContain('VALIDATION_ERROR');
1114
1156
  });
1115
1157
 
1116
1158
  it('should handle validation error', async () => {
1117
1159
  mockUpdateTag.mockRejectedValue(new Error('Validation failed'));
1118
1160
 
1119
1161
  const tool = mockTools.get('ghost_update_tag');
1120
- const result = await tool.handler({ id: '123', name: '' });
1162
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011', name: '' });
1121
1163
 
1122
1164
  expect(result.isError).toBe(true);
1123
1165
  expect(result.content[0].text).toContain('Validation failed');
@@ -1127,7 +1169,7 @@ describe('ghost_update_tag', () => {
1127
1169
  mockUpdateTag.mockRejectedValue(new Error('Tag not found'));
1128
1170
 
1129
1171
  const tool = mockTools.get('ghost_update_tag');
1130
- const result = await tool.handler({ id: '999', name: 'Test' });
1172
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439099', name: 'Test' });
1131
1173
 
1132
1174
  expect(result.isError).toBe(true);
1133
1175
  expect(result.content[0].text).toContain('Tag not found');
@@ -1150,16 +1192,17 @@ describe('ghost_delete_tag', () => {
1150
1192
 
1151
1193
  it('should have correct schema with id field', () => {
1152
1194
  const tool = mockTools.get('ghost_delete_tag');
1153
- expect(tool.schema.id).toBeDefined();
1195
+ // Zod schemas store field definitions in schema.shape
1196
+ expect(tool.schema.shape.id).toBeDefined();
1154
1197
  });
1155
1198
 
1156
1199
  it('should delete tag successfully', async () => {
1157
1200
  mockDeleteTag.mockResolvedValue({ success: true });
1158
1201
 
1159
1202
  const tool = mockTools.get('ghost_delete_tag');
1160
- const result = await tool.handler({ id: '123' });
1203
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
1161
1204
 
1162
- expect(mockDeleteTag).toHaveBeenCalledWith('123');
1205
+ expect(mockDeleteTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
1163
1206
  expect(result.content[0].text).toContain('successfully deleted');
1164
1207
  expect(result.isError).toBeUndefined();
1165
1208
  });
@@ -1169,14 +1212,14 @@ describe('ghost_delete_tag', () => {
1169
1212
  const result = await tool.handler({});
1170
1213
 
1171
1214
  expect(result.isError).toBe(true);
1172
- expect(result.content[0].text).toContain('Tag ID is required');
1215
+ expect(result.content[0].text).toContain('VALIDATION_ERROR');
1173
1216
  });
1174
1217
 
1175
1218
  it('should handle not found error', async () => {
1176
1219
  mockDeleteTag.mockRejectedValue(new Error('Tag not found'));
1177
1220
 
1178
1221
  const tool = mockTools.get('ghost_delete_tag');
1179
- const result = await tool.handler({ id: '999' });
1222
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
1180
1223
 
1181
1224
  expect(result.isError).toBe(true);
1182
1225
  expect(result.content[0].text).toContain('Tag not found');
@@ -1186,7 +1229,7 @@ describe('ghost_delete_tag', () => {
1186
1229
  mockDeleteTag.mockRejectedValue(new Error('Failed to delete tag'));
1187
1230
 
1188
1231
  const tool = mockTools.get('ghost_delete_tag');
1189
- const result = await tool.handler({ id: '123' });
1232
+ const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
1190
1233
 
1191
1234
  expect(result.isError).toBe(true);
1192
1235
  expect(result.content[0].text).toContain('Failed to delete tag');