@jgardner04/ghost-mcp-server 1.12.3 → 1.12.5
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 +3 -2
- package/src/errors/index.js +1 -1
- package/src/mcp_server_enhanced.js +19 -19
- package/src/middleware/__tests__/errorMiddleware.test.js +4 -4
- package/src/middleware/errorMiddleware.js +4 -4
- package/src/resources/ResourceManager.js +8 -8
- package/src/resources/__tests__/ResourceManager.test.js +1 -1
- package/src/services/ghostServiceImproved.js +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgardner04/ghost-mcp-server",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.5",
|
|
4
4
|
"description": "A Model Context Protocol (MCP) server for interacting with Ghost CMS via the Admin API",
|
|
5
5
|
"author": "Jonathan Gardner",
|
|
6
6
|
"type": "module",
|
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
"sharp": "^0.34.1",
|
|
74
74
|
"uuid": "^11.1.0",
|
|
75
75
|
"winston": "^3.17.0",
|
|
76
|
-
"zod": "^3.25.76"
|
|
76
|
+
"zod": "^3.25.76",
|
|
77
|
+
"zod-to-json-schema": "^3.25.0"
|
|
77
78
|
},
|
|
78
79
|
"keywords": [
|
|
79
80
|
"ghost",
|
package/src/errors/index.js
CHANGED
|
@@ -439,7 +439,7 @@ export async function retryWithBackoff(fn, options = {}) {
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
const delay = ErrorHandler.getRetryDelay(attempt, error);
|
|
442
|
-
console.
|
|
442
|
+
console.error(`Retry attempt ${attempt}/${maxAttempts} after ${delay}ms`);
|
|
443
443
|
|
|
444
444
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
445
445
|
onRetry(attempt, error);
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
|
|
25
25
|
dotenv.config();
|
|
26
26
|
|
|
27
|
-
console.
|
|
27
|
+
console.error('Initializing Enhanced MCP Server...');
|
|
28
28
|
|
|
29
29
|
// Initialize components
|
|
30
30
|
const resourceManager = new ResourceManager(ghostService);
|
|
@@ -48,7 +48,7 @@ const mcpServer = new MCPServer({
|
|
|
48
48
|
|
|
49
49
|
// --- Register Resources with Enhanced Fetching ---
|
|
50
50
|
|
|
51
|
-
console.
|
|
51
|
+
console.error('Registering enhanced resources...');
|
|
52
52
|
|
|
53
53
|
// Ghost Post Resource
|
|
54
54
|
const postResource = resourceManager.registerResource(
|
|
@@ -127,7 +127,7 @@ mcpServer.addResource(tagResource);
|
|
|
127
127
|
|
|
128
128
|
// --- Enhanced Tools ---
|
|
129
129
|
|
|
130
|
-
console.
|
|
130
|
+
console.error('Registering enhanced tools...');
|
|
131
131
|
|
|
132
132
|
// Batch Operations Tool
|
|
133
133
|
const batchOperationsTool = new Tool({
|
|
@@ -434,7 +434,7 @@ const subscriptionTool = new Tool({
|
|
|
434
434
|
input.uri,
|
|
435
435
|
(event) => {
|
|
436
436
|
// In a real implementation, this would send events to the client
|
|
437
|
-
console.
|
|
437
|
+
console.error('Resource update:', event);
|
|
438
438
|
},
|
|
439
439
|
input.options || {}
|
|
440
440
|
);
|
|
@@ -468,13 +468,13 @@ mcpServer.addTool(subscriptionTool);
|
|
|
468
468
|
|
|
469
469
|
const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
|
|
470
470
|
try {
|
|
471
|
-
console.
|
|
471
|
+
console.error(`Starting Enhanced MCP Server with ${transport} transport...`);
|
|
472
472
|
|
|
473
473
|
switch (transport) {
|
|
474
474
|
case 'stdio': {
|
|
475
475
|
const stdioTransport = new StdioServerTransport();
|
|
476
476
|
await mcpServer.connect(stdioTransport);
|
|
477
|
-
console.
|
|
477
|
+
console.error('Enhanced MCP Server running on stdio transport');
|
|
478
478
|
break;
|
|
479
479
|
}
|
|
480
480
|
|
|
@@ -553,10 +553,10 @@ const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
|
|
|
553
553
|
await mcpServer.connect(sseTransport);
|
|
554
554
|
|
|
555
555
|
const server = app.listen(port, () => {
|
|
556
|
-
console.
|
|
557
|
-
console.
|
|
558
|
-
console.
|
|
559
|
-
console.
|
|
556
|
+
console.error(`Enhanced MCP Server (SSE) listening on port ${port}`);
|
|
557
|
+
console.error(`Health: http://localhost:${port}/health`);
|
|
558
|
+
console.error(`Resources: http://localhost:${port}/resources`);
|
|
559
|
+
console.error(`SSE: http://localhost:${port}/mcp/sse`);
|
|
560
560
|
});
|
|
561
561
|
|
|
562
562
|
mcpServer._httpServer = server;
|
|
@@ -574,7 +574,7 @@ const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
|
|
|
574
574
|
const wss = new WebSocketServer({ port: wsPort });
|
|
575
575
|
|
|
576
576
|
wss.on('connection', async (ws) => {
|
|
577
|
-
console.
|
|
577
|
+
console.error('New WebSocket connection');
|
|
578
578
|
|
|
579
579
|
const wsTransport = new WebSocketServerTransport(ws);
|
|
580
580
|
await mcpServer.connect(wsTransport);
|
|
@@ -616,7 +616,7 @@ const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
|
|
|
616
616
|
});
|
|
617
617
|
});
|
|
618
618
|
|
|
619
|
-
console.
|
|
619
|
+
console.error(`Enhanced MCP Server (WebSocket) listening on port ${wsPort}`);
|
|
620
620
|
mcpServer._wss = wss;
|
|
621
621
|
break;
|
|
622
622
|
}
|
|
@@ -626,18 +626,18 @@ const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
|
|
|
626
626
|
}
|
|
627
627
|
|
|
628
628
|
// Log capabilities
|
|
629
|
-
console.
|
|
630
|
-
console.
|
|
629
|
+
console.error('Server Capabilities:');
|
|
630
|
+
console.error(
|
|
631
631
|
'- Resources:',
|
|
632
632
|
mcpServer.listResources().map((r) => r.name)
|
|
633
633
|
);
|
|
634
|
-
console.
|
|
634
|
+
console.error(
|
|
635
635
|
'- Tools:',
|
|
636
636
|
mcpServer.listTools().map((t) => t.name)
|
|
637
637
|
);
|
|
638
|
-
console.
|
|
639
|
-
console.
|
|
640
|
-
console.
|
|
638
|
+
console.error('- Cache enabled with LRU eviction');
|
|
639
|
+
console.error('- Subscription support for real-time updates');
|
|
640
|
+
console.error('- Batch operations for efficiency');
|
|
641
641
|
} catch (error) {
|
|
642
642
|
errorLogger.logError(error);
|
|
643
643
|
console.error('Failed to start Enhanced MCP Server:', error);
|
|
@@ -647,7 +647,7 @@ const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
|
|
|
647
647
|
|
|
648
648
|
// Graceful shutdown
|
|
649
649
|
const shutdown = async () => {
|
|
650
|
-
console.
|
|
650
|
+
console.error('\nShutting down Enhanced MCP Server...');
|
|
651
651
|
|
|
652
652
|
// Clear all subscriptions
|
|
653
653
|
resourceManager.subscriptionManager.subscriptions.clear();
|
|
@@ -299,7 +299,7 @@ describe('errorMiddleware', () => {
|
|
|
299
299
|
|
|
300
300
|
describe('logInfo', () => {
|
|
301
301
|
it('should log info message when level allows', async () => {
|
|
302
|
-
const consoleSpy = vi.spyOn(console, '
|
|
302
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
303
303
|
|
|
304
304
|
const logger = new ErrorLogger({ logLevel: 'info', enableFileLogging: false });
|
|
305
305
|
await logger.logInfo('Info message');
|
|
@@ -309,7 +309,7 @@ describe('errorMiddleware', () => {
|
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
it('should not log when level is higher', async () => {
|
|
312
|
-
const consoleSpy = vi.spyOn(console, '
|
|
312
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
313
313
|
|
|
314
314
|
const logger = new ErrorLogger({ logLevel: 'error', enableFileLogging: false });
|
|
315
315
|
await logger.logInfo('Info message');
|
|
@@ -333,7 +333,7 @@ describe('errorMiddleware', () => {
|
|
|
333
333
|
|
|
334
334
|
describe('logDebug', () => {
|
|
335
335
|
it('should log debug message when level is debug', async () => {
|
|
336
|
-
const consoleSpy = vi.spyOn(console, '
|
|
336
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
337
337
|
|
|
338
338
|
const logger = new ErrorLogger({ logLevel: 'debug', enableFileLogging: false });
|
|
339
339
|
await logger.logDebug('Debug message');
|
|
@@ -343,7 +343,7 @@ describe('errorMiddleware', () => {
|
|
|
343
343
|
});
|
|
344
344
|
|
|
345
345
|
it('should not log when level is not debug', async () => {
|
|
346
|
-
const consoleSpy = vi.spyOn(console, '
|
|
346
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
347
347
|
|
|
348
348
|
const logger = new ErrorLogger({ logLevel: 'info', enableFileLogging: false });
|
|
349
349
|
await logger.logDebug('Debug message');
|
|
@@ -111,7 +111,7 @@ export class ErrorLogger {
|
|
|
111
111
|
|
|
112
112
|
async logInfo(message, meta = {}) {
|
|
113
113
|
if (['info', 'debug'].includes(this.logLevel)) {
|
|
114
|
-
console.
|
|
114
|
+
console.error(`[INFO] ${message}`);
|
|
115
115
|
const entry = this.formatLogEntry('info', message, meta);
|
|
116
116
|
await this.writeToFile('app', entry);
|
|
117
117
|
}
|
|
@@ -127,7 +127,7 @@ export class ErrorLogger {
|
|
|
127
127
|
|
|
128
128
|
async logDebug(message, meta = {}) {
|
|
129
129
|
if (this.logLevel === 'debug') {
|
|
130
|
-
console.
|
|
130
|
+
console.error(`[DEBUG] ${message}`);
|
|
131
131
|
const entry = this.formatLogEntry('debug', message, meta);
|
|
132
132
|
await this.writeToFile('debug', entry);
|
|
133
133
|
}
|
|
@@ -446,11 +446,11 @@ export class GracefulShutdown {
|
|
|
446
446
|
if (this.isShuttingDown) return;
|
|
447
447
|
|
|
448
448
|
this.isShuttingDown = true;
|
|
449
|
-
console.
|
|
449
|
+
console.error('Graceful shutdown initiated...');
|
|
450
450
|
|
|
451
451
|
// Stop accepting new connections
|
|
452
452
|
server.close(() => {
|
|
453
|
-
console.
|
|
453
|
+
console.error('Server closed to new connections');
|
|
454
454
|
});
|
|
455
455
|
|
|
456
456
|
// Close existing connections
|
|
@@ -168,7 +168,7 @@ class ResourceFetcher {
|
|
|
168
168
|
// Check cache
|
|
169
169
|
const cached = this.cache.get(cacheKey);
|
|
170
170
|
if (cached) {
|
|
171
|
-
console.
|
|
171
|
+
console.error(`Cache hit for ${cacheKey}`);
|
|
172
172
|
return cached;
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -221,7 +221,7 @@ class ResourceFetcher {
|
|
|
221
221
|
// Check cache
|
|
222
222
|
const cached = this.cache.get(cacheKey);
|
|
223
223
|
if (cached) {
|
|
224
|
-
console.
|
|
224
|
+
console.error(`Cache hit for posts query`);
|
|
225
225
|
return cached;
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -277,7 +277,7 @@ class ResourceFetcher {
|
|
|
277
277
|
// Check cache
|
|
278
278
|
const cached = this.cache.get(cacheKey);
|
|
279
279
|
if (cached) {
|
|
280
|
-
console.
|
|
280
|
+
console.error(`Cache hit for ${cacheKey}`);
|
|
281
281
|
return cached;
|
|
282
282
|
}
|
|
283
283
|
|
|
@@ -324,7 +324,7 @@ class ResourceFetcher {
|
|
|
324
324
|
// Check cache
|
|
325
325
|
const cached = this.cache.get(cacheKey);
|
|
326
326
|
if (cached) {
|
|
327
|
-
console.
|
|
327
|
+
console.error(`Cache hit for tags query`);
|
|
328
328
|
return cached;
|
|
329
329
|
}
|
|
330
330
|
|
|
@@ -400,7 +400,7 @@ class ResourceSubscriptionManager extends EventEmitter {
|
|
|
400
400
|
this.startPolling(subscriptionId, pollingInterval);
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
-
console.
|
|
403
|
+
console.error(`Created subscription ${subscriptionId} for ${uri}`);
|
|
404
404
|
|
|
405
405
|
return subscriptionId;
|
|
406
406
|
}
|
|
@@ -418,7 +418,7 @@ class ResourceSubscriptionManager extends EventEmitter {
|
|
|
418
418
|
// Remove subscription
|
|
419
419
|
this.subscriptions.delete(subscriptionId);
|
|
420
420
|
|
|
421
|
-
console.
|
|
421
|
+
console.error(`Removed subscription ${subscriptionId}`);
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
startPolling(subscriptionId, interval) {
|
|
@@ -539,7 +539,7 @@ export class ResourceManager {
|
|
|
539
539
|
try {
|
|
540
540
|
const parsed = ResourceURIParser.parse(uri);
|
|
541
541
|
|
|
542
|
-
console.
|
|
542
|
+
console.error('Fetching resource:', { uri: uri.substring(0, 100), parsed });
|
|
543
543
|
|
|
544
544
|
// Route to appropriate fetcher
|
|
545
545
|
switch (parsed.resourceType) {
|
|
@@ -605,7 +605,7 @@ export class ResourceManager {
|
|
|
605
605
|
*/
|
|
606
606
|
invalidateCache(pattern = null) {
|
|
607
607
|
this.cache.invalidate(pattern);
|
|
608
|
-
console.
|
|
608
|
+
console.error(`Cache invalidated${pattern ? ` for pattern: ${pattern}` : ''}`);
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
/**
|
|
@@ -836,7 +836,7 @@ describe('ResourceManager', () => {
|
|
|
836
836
|
});
|
|
837
837
|
|
|
838
838
|
it('should log invalidation', async () => {
|
|
839
|
-
const consoleSpy = vi.spyOn(console, '
|
|
839
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
840
840
|
|
|
841
841
|
resourceManager.invalidateCache();
|
|
842
842
|
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Cache invalidated'));
|
|
@@ -54,7 +54,7 @@ const handleApiRequest = async (resource, action, data = {}, options = {}, confi
|
|
|
54
54
|
// Main execution function
|
|
55
55
|
const executeRequest = async () => {
|
|
56
56
|
try {
|
|
57
|
-
console.
|
|
57
|
+
console.error(`Executing Ghost API request: ${operation}`);
|
|
58
58
|
|
|
59
59
|
let result;
|
|
60
60
|
|
|
@@ -78,7 +78,7 @@ const handleApiRequest = async (resource, action, data = {}, options = {}, confi
|
|
|
78
78
|
result = await api[resource][action](data);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
console.
|
|
81
|
+
console.error(`Successfully executed Ghost API request: ${operation}`);
|
|
82
82
|
return result;
|
|
83
83
|
} catch (error) {
|
|
84
84
|
// Transform Ghost API errors into our error types
|
|
@@ -96,12 +96,12 @@ const handleApiRequest = async (resource, action, data = {}, options = {}, confi
|
|
|
96
96
|
return await retryWithBackoff(wrappedExecute, {
|
|
97
97
|
maxAttempts: maxRetries,
|
|
98
98
|
onRetry: (attempt, _error) => {
|
|
99
|
-
console.
|
|
99
|
+
console.error(`Retrying ${operation} (attempt ${attempt}/${maxRetries})`);
|
|
100
100
|
|
|
101
101
|
// Log circuit breaker state if relevant
|
|
102
102
|
if (useCircuitBreaker) {
|
|
103
103
|
const state = ghostCircuitBreaker.getState();
|
|
104
|
-
console.
|
|
104
|
+
console.error(`Circuit breaker state:`, state);
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
107
|
});
|