@jgardner04/ghost-mcp-server 1.0.0 → 1.1.1

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.
@@ -2,32 +2,29 @@
2
2
  * Enhanced MCP Server with Advanced Resource Management
3
3
  */
4
4
 
5
- import {
6
- MCPServer,
7
- Tool,
8
- } from "@modelcontextprotocol/sdk/server/index.js";
9
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
11
- import { WebSocketServerTransport } from "@modelcontextprotocol/sdk/server/websocket.js";
12
- import dotenv from "dotenv";
13
- import express from "express";
14
- import { WebSocketServer } from "ws";
5
+ import { MCPServer, Tool } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
8
+ import { WebSocketServerTransport } from '@modelcontextprotocol/sdk/server/websocket.js';
9
+ import dotenv from 'dotenv';
10
+ import express from 'express';
11
+ import { WebSocketServer } from 'ws';
15
12
 
16
13
  // Import services
17
- import ghostService from "./services/ghostServiceImproved.js";
18
- import { ResourceManager } from "./resources/ResourceManager.js";
19
- import { ErrorHandler, ValidationError } from "./errors/index.js";
20
- import {
21
- errorLogger,
22
- mcpCors,
14
+ import ghostService from './services/ghostServiceImproved.js';
15
+ import { ResourceManager } from './resources/ResourceManager.js';
16
+ import { ErrorHandler, ValidationError } from './errors/index.js';
17
+ import {
18
+ errorLogger,
19
+ mcpCors,
23
20
  RateLimiter,
24
21
  healthCheck,
25
- GracefulShutdown
26
- } from "./middleware/errorMiddleware.js";
22
+ GracefulShutdown,
23
+ } from './middleware/errorMiddleware.js';
27
24
 
28
25
  dotenv.config();
29
26
 
30
- console.log("Initializing Enhanced MCP Server...");
27
+ console.log('Initializing Enhanced MCP Server...');
31
28
 
32
29
  // Initialize components
33
30
  const resourceManager = new ResourceManager(ghostService);
@@ -37,59 +34,59 @@ const gracefulShutdown = new GracefulShutdown();
37
34
  // Create MCP Server
38
35
  const mcpServer = new MCPServer({
39
36
  metadata: {
40
- name: "Ghost CMS Manager",
41
- description: "Enhanced MCP Server for Ghost CMS with advanced resource management",
42
- version: "2.0.0",
37
+ name: 'Ghost CMS Manager',
38
+ description: 'Enhanced MCP Server for Ghost CMS with advanced resource management',
39
+ version: '2.0.0',
43
40
  capabilities: {
44
41
  resources: true,
45
42
  tools: true,
46
43
  subscriptions: true,
47
- batch: true
48
- }
44
+ batch: true,
45
+ },
49
46
  },
50
47
  });
51
48
 
52
49
  // --- Register Resources with Enhanced Fetching ---
53
50
 
54
- console.log("Registering enhanced resources...");
51
+ console.log('Registering enhanced resources...');
55
52
 
56
53
  // Ghost Post Resource
57
54
  const postResource = resourceManager.registerResource(
58
- "ghost/post",
55
+ 'ghost/post',
59
56
  {
60
- type: "object",
57
+ type: 'object',
61
58
  properties: {
62
- id: { type: "string" },
63
- uuid: { type: "string" },
64
- title: { type: "string" },
65
- slug: { type: "string" },
66
- html: { type: ["string", "null"] },
67
- status: {
68
- type: "string",
69
- enum: ["draft", "published", "scheduled"]
59
+ id: { type: 'string' },
60
+ uuid: { type: 'string' },
61
+ title: { type: 'string' },
62
+ slug: { type: 'string' },
63
+ html: { type: ['string', 'null'] },
64
+ status: {
65
+ type: 'string',
66
+ enum: ['draft', 'published', 'scheduled'],
70
67
  },
71
- feature_image: { type: ["string", "null"] },
72
- published_at: {
73
- type: ["string", "null"],
74
- format: "date-time"
68
+ feature_image: { type: ['string', 'null'] },
69
+ published_at: {
70
+ type: ['string', 'null'],
71
+ format: 'date-time',
75
72
  },
76
73
  tags: {
77
- type: "array",
78
- items: { $ref: "ghost/tag#/schema" }
74
+ type: 'array',
75
+ items: { $ref: 'ghost/tag#/schema' },
79
76
  },
80
- meta_title: { type: ["string", "null"] },
81
- meta_description: { type: ["string", "null"] }
77
+ meta_title: { type: ['string', 'null'] },
78
+ meta_description: { type: ['string', 'null'] },
82
79
  },
83
- required: ["id", "uuid", "title", "slug", "status"]
80
+ required: ['id', 'uuid', 'title', 'slug', 'status'],
84
81
  },
85
82
  {
86
- description: "Ghost blog post with support for multiple identifier types",
83
+ description: 'Ghost blog post with support for multiple identifier types',
87
84
  examples: [
88
- "ghost/post/123",
89
- "ghost/post/slug:my-awesome-post",
90
- "ghost/post/uuid:550e8400-e29b-41d4-a716-446655440000",
91
- "ghost/posts?status=published&limit=10&page=1"
92
- ]
85
+ 'ghost/post/123',
86
+ 'ghost/post/slug:my-awesome-post',
87
+ 'ghost/post/uuid:550e8400-e29b-41d4-a716-446655440000',
88
+ 'ghost/posts?status=published&limit=10&page=1',
89
+ ],
93
90
  }
94
91
  );
95
92
 
@@ -97,32 +94,32 @@ mcpServer.addResource(postResource);
97
94
 
98
95
  // Ghost Tag Resource
99
96
  const tagResource = resourceManager.registerResource(
100
- "ghost/tag",
97
+ 'ghost/tag',
101
98
  {
102
- type: "object",
99
+ type: 'object',
103
100
  properties: {
104
- id: { type: "string" },
105
- name: { type: "string" },
106
- slug: { type: "string" },
107
- description: { type: ["string", "null"] },
108
- feature_image: { type: ["string", "null"] },
109
- visibility: {
110
- type: "string",
111
- enum: ["public", "internal"]
101
+ id: { type: 'string' },
102
+ name: { type: 'string' },
103
+ slug: { type: 'string' },
104
+ description: { type: ['string', 'null'] },
105
+ feature_image: { type: ['string', 'null'] },
106
+ visibility: {
107
+ type: 'string',
108
+ enum: ['public', 'internal'],
112
109
  },
113
- meta_title: { type: ["string", "null"] },
114
- meta_description: { type: ["string", "null"] }
110
+ meta_title: { type: ['string', 'null'] },
111
+ meta_description: { type: ['string', 'null'] },
115
112
  },
116
- required: ["id", "name", "slug"]
113
+ required: ['id', 'name', 'slug'],
117
114
  },
118
115
  {
119
- description: "Ghost tag for categorizing posts",
116
+ description: 'Ghost tag for categorizing posts',
120
117
  examples: [
121
- "ghost/tag/technology",
122
- "ghost/tag/slug:web-development",
123
- "ghost/tag/name:JavaScript",
124
- "ghost/tags?limit=20"
125
- ]
118
+ 'ghost/tag/technology',
119
+ 'ghost/tag/slug:web-development',
120
+ 'ghost/tag/name:JavaScript',
121
+ 'ghost/tags?limit=20',
122
+ ],
126
123
  }
127
124
  );
128
125
 
@@ -130,72 +127,69 @@ mcpServer.addResource(tagResource);
130
127
 
131
128
  // --- Enhanced Tools ---
132
129
 
133
- console.log("Registering enhanced tools...");
130
+ console.log('Registering enhanced tools...');
134
131
 
135
132
  // Batch Operations Tool
136
133
  const batchOperationsTool = new Tool({
137
- name: "ghost_batch_operations",
138
- description: "Execute multiple Ghost operations in a single request",
134
+ name: 'ghost_batch_operations',
135
+ description: 'Execute multiple Ghost operations in a single request',
139
136
  inputSchema: {
140
- type: "object",
137
+ type: 'object',
141
138
  properties: {
142
139
  operations: {
143
- type: "array",
140
+ type: 'array',
144
141
  items: {
145
- type: "object",
142
+ type: 'object',
146
143
  properties: {
147
- id: { type: "string", description: "Operation ID for reference" },
148
- type: {
149
- type: "string",
150
- enum: ["create_post", "update_post", "create_tag", "fetch_resource"]
144
+ id: { type: 'string', description: 'Operation ID for reference' },
145
+ type: {
146
+ type: 'string',
147
+ enum: ['create_post', 'update_post', 'create_tag', 'fetch_resource'],
151
148
  },
152
- data: { type: "object", description: "Operation-specific data" }
149
+ data: { type: 'object', description: 'Operation-specific data' },
153
150
  },
154
- required: ["id", "type", "data"]
151
+ required: ['id', 'type', 'data'],
155
152
  },
156
153
  minItems: 1,
157
- maxItems: 10
154
+ maxItems: 10,
158
155
  },
159
156
  stopOnError: {
160
- type: "boolean",
157
+ type: 'boolean',
161
158
  default: false,
162
- description: "Stop processing on first error"
163
- }
159
+ description: 'Stop processing on first error',
160
+ },
164
161
  },
165
- required: ["operations"]
162
+ required: ['operations'],
166
163
  },
167
164
  outputSchema: {
168
- type: "object",
165
+ type: 'object',
169
166
  properties: {
170
167
  results: {
171
- type: "object",
168
+ type: 'object',
172
169
  additionalProperties: {
173
- type: "object",
170
+ type: 'object',
174
171
  properties: {
175
- success: { type: "boolean" },
176
- data: { type: "object" },
177
- error: { type: "object" }
178
- }
179
- }
180
- }
181
- }
172
+ success: { type: 'boolean' },
173
+ data: { type: 'object' },
174
+ error: { type: 'object' },
175
+ },
176
+ },
177
+ },
178
+ },
182
179
  },
183
180
  implementation: async (input) => {
184
181
  const results = {};
185
-
182
+
186
183
  for (const operation of input.operations) {
187
184
  try {
188
185
  let result;
189
-
186
+
190
187
  switch (operation.type) {
191
188
  case 'create_post':
192
189
  result = await ghostService.createPost(operation.data);
193
190
  break;
194
191
  case 'update_post':
195
- result = await ghostService.updatePost(
196
- operation.data.id,
197
- operation.data
198
- );
192
+ result = await ghostService.updatePost(operation.data.id, operation.data);
199
193
  break;
200
194
  case 'create_tag':
201
195
  result = await ghostService.createTag(operation.data);
@@ -206,237 +200,236 @@ const batchOperationsTool = new Tool({
206
200
  default:
207
201
  throw new ValidationError(`Unknown operation type: ${operation.type}`);
208
202
  }
209
-
203
+
210
204
  results[operation.id] = {
211
205
  success: true,
212
- data: result
206
+ data: result,
213
207
  };
214
-
215
208
  } catch (error) {
216
209
  results[operation.id] = {
217
210
  success: false,
218
- error: ErrorHandler.formatMCPError(error)
211
+ error: ErrorHandler.formatMCPError(error),
219
212
  };
220
-
213
+
221
214
  if (input.stopOnError) {
222
215
  break;
223
216
  }
224
217
  }
225
218
  }
226
-
219
+
227
220
  return { results };
228
- }
221
+ },
229
222
  });
230
223
 
231
224
  mcpServer.addTool(batchOperationsTool);
232
225
 
233
226
  // Resource Search Tool
234
227
  const searchResourcesTool = new Tool({
235
- name: "ghost_search_resources",
236
- description: "Search for Ghost resources with advanced filtering",
228
+ name: 'ghost_search_resources',
229
+ description: 'Search for Ghost resources with advanced filtering',
237
230
  inputSchema: {
238
- type: "object",
231
+ type: 'object',
239
232
  properties: {
240
233
  resourceType: {
241
- type: "string",
242
- enum: ["posts", "tags"],
243
- description: "Type of resource to search"
234
+ type: 'string',
235
+ enum: ['posts', 'tags'],
236
+ description: 'Type of resource to search',
244
237
  },
245
238
  query: {
246
- type: "string",
247
- description: "Search query"
239
+ type: 'string',
240
+ description: 'Search query',
248
241
  },
249
242
  filters: {
250
- type: "object",
243
+ type: 'object',
251
244
  properties: {
252
- status: { type: "string" },
253
- visibility: { type: "string" },
254
- tag: { type: "string" },
255
- author: { type: "string" },
256
- published_after: { type: "string", format: "date-time" },
257
- published_before: { type: "string", format: "date-time" }
258
- }
245
+ status: { type: 'string' },
246
+ visibility: { type: 'string' },
247
+ tag: { type: 'string' },
248
+ author: { type: 'string' },
249
+ published_after: { type: 'string', format: 'date-time' },
250
+ published_before: { type: 'string', format: 'date-time' },
251
+ },
259
252
  },
260
253
  sort: {
261
- type: "string",
262
- default: "published_at desc",
263
- description: "Sort order"
254
+ type: 'string',
255
+ default: 'published_at desc',
256
+ description: 'Sort order',
264
257
  },
265
258
  limit: {
266
- type: "integer",
259
+ type: 'integer',
267
260
  minimum: 1,
268
261
  maximum: 100,
269
- default: 15
262
+ default: 15,
270
263
  },
271
264
  page: {
272
- type: "integer",
265
+ type: 'integer',
273
266
  minimum: 1,
274
- default: 1
275
- }
267
+ default: 1,
268
+ },
276
269
  },
277
- required: ["resourceType"]
270
+ required: ['resourceType'],
278
271
  },
279
272
  implementation: async (input) => {
280
273
  const { resourceType, query, filters = {}, sort, limit, page } = input;
281
-
274
+
282
275
  // Build Ghost filter string
283
276
  let filterParts = [];
284
-
277
+
285
278
  if (query) {
286
279
  filterParts.push(`title:~'${query}'`);
287
280
  }
288
-
281
+
289
282
  if (filters.status) {
290
283
  filterParts.push(`status:${filters.status}`);
291
284
  }
292
-
285
+
293
286
  if (filters.visibility) {
294
287
  filterParts.push(`visibility:${filters.visibility}`);
295
288
  }
296
-
289
+
297
290
  if (filters.tag) {
298
291
  filterParts.push(`tag:${filters.tag}`);
299
292
  }
300
-
293
+
301
294
  if (filters.author) {
302
295
  filterParts.push(`author:${filters.author}`);
303
296
  }
304
-
297
+
305
298
  if (filters.published_after) {
306
299
  filterParts.push(`published_at:>='${filters.published_after}'`);
307
300
  }
308
-
301
+
309
302
  if (filters.published_before) {
310
303
  filterParts.push(`published_at:<='${filters.published_before}'`);
311
304
  }
312
-
305
+
313
306
  const ghostFilter = filterParts.join('+');
314
-
307
+
315
308
  // Build resource URI
316
309
  const uri = `ghost/${resourceType}?${new URLSearchParams({
317
310
  filter: ghostFilter,
318
311
  order: sort,
319
312
  limit,
320
- page
313
+ page,
321
314
  }).toString()}`;
322
-
315
+
323
316
  // Fetch using ResourceManager
324
317
  return await resourceManager.fetchResource(uri);
325
- }
318
+ },
326
319
  });
327
320
 
328
321
  mcpServer.addTool(searchResourcesTool);
329
322
 
330
323
  // Cache Management Tool
331
324
  const cacheManagementTool = new Tool({
332
- name: "ghost_cache_management",
333
- description: "Manage resource cache",
325
+ name: 'ghost_cache_management',
326
+ description: 'Manage resource cache',
334
327
  inputSchema: {
335
- type: "object",
328
+ type: 'object',
336
329
  properties: {
337
330
  action: {
338
- type: "string",
339
- enum: ["invalidate", "stats", "prefetch"],
340
- description: "Cache action to perform"
331
+ type: 'string',
332
+ enum: ['invalidate', 'stats', 'prefetch'],
333
+ description: 'Cache action to perform',
341
334
  },
342
335
  pattern: {
343
- type: "string",
344
- description: "Pattern for invalidation (optional)"
336
+ type: 'string',
337
+ description: 'Pattern for invalidation (optional)',
345
338
  },
346
339
  uris: {
347
- type: "array",
348
- items: { type: "string" },
349
- description: "URIs to prefetch (for prefetch action)"
350
- }
340
+ type: 'array',
341
+ items: { type: 'string' },
342
+ description: 'URIs to prefetch (for prefetch action)',
343
+ },
351
344
  },
352
- required: ["action"]
345
+ required: ['action'],
353
346
  },
354
347
  implementation: async (input) => {
355
348
  switch (input.action) {
356
349
  case 'invalidate':
357
350
  resourceManager.invalidateCache(input.pattern);
358
- return {
359
- success: true,
360
- message: `Cache invalidated${input.pattern ? ` for pattern: ${input.pattern}` : ''}`
351
+ return {
352
+ success: true,
353
+ message: `Cache invalidated${input.pattern ? ` for pattern: ${input.pattern}` : ''}`,
361
354
  };
362
-
355
+
363
356
  case 'stats':
364
357
  return resourceManager.getCacheStats();
365
-
358
+
366
359
  case 'prefetch':
367
360
  if (!input.uris || input.uris.length === 0) {
368
361
  throw new ValidationError('URIs required for prefetch action');
369
362
  }
370
363
  return await resourceManager.prefetch(input.uris);
371
-
364
+
372
365
  default:
373
366
  throw new ValidationError(`Unknown action: ${input.action}`);
374
367
  }
375
- }
368
+ },
376
369
  });
377
370
 
378
371
  mcpServer.addTool(cacheManagementTool);
379
372
 
380
373
  // Resource Subscription Tool
381
374
  const subscriptionTool = new Tool({
382
- name: "ghost_subscribe",
383
- description: "Subscribe to resource changes",
375
+ name: 'ghost_subscribe',
376
+ description: 'Subscribe to resource changes',
384
377
  inputSchema: {
385
- type: "object",
378
+ type: 'object',
386
379
  properties: {
387
380
  action: {
388
- type: "string",
389
- enum: ["subscribe", "unsubscribe"],
390
- description: "Subscription action"
381
+ type: 'string',
382
+ enum: ['subscribe', 'unsubscribe'],
383
+ description: 'Subscription action',
391
384
  },
392
385
  uri: {
393
- type: "string",
394
- description: "Resource URI to subscribe to (required for subscribe action)"
386
+ type: 'string',
387
+ description: 'Resource URI to subscribe to (required for subscribe action)',
395
388
  },
396
389
  subscriptionId: {
397
- type: "string",
398
- description: "Subscription ID (required for unsubscribe action)"
390
+ type: 'string',
391
+ description: 'Subscription ID (required for unsubscribe action)',
399
392
  },
400
393
  options: {
401
- type: "object",
394
+ type: 'object',
402
395
  properties: {
403
396
  pollingInterval: {
404
- type: "integer",
397
+ type: 'integer',
405
398
  minimum: 5000,
406
399
  default: 30000,
407
- description: "Polling interval in milliseconds"
400
+ description: 'Polling interval in milliseconds',
408
401
  },
409
402
  enablePolling: {
410
- type: "boolean",
403
+ type: 'boolean',
411
404
  default: false,
412
- description: "Enable automatic polling"
413
- }
414
- }
415
- }
405
+ description: 'Enable automatic polling',
406
+ },
407
+ },
408
+ },
416
409
  },
417
- required: ["action"],
410
+ required: ['action'],
418
411
  // Add conditional validation using JSON Schema if/then/else
419
412
  if: {
420
- properties: { action: { const: "subscribe" } }
413
+ properties: { action: { const: 'subscribe' } },
421
414
  },
422
415
  then: {
423
- required: ["uri"]
416
+ required: ['uri'],
424
417
  },
425
418
  else: {
426
419
  if: {
427
- properties: { action: { const: "unsubscribe" } }
420
+ properties: { action: { const: 'unsubscribe' } },
428
421
  },
429
422
  then: {
430
- required: ["subscriptionId"]
431
- }
432
- }
423
+ required: ['subscriptionId'],
424
+ },
425
+ },
433
426
  },
434
427
  implementation: async (input) => {
435
428
  if (input.action === 'subscribe') {
436
429
  if (!input.uri) {
437
430
  throw new ValidationError('URI required for subscribe action');
438
431
  }
439
-
432
+
440
433
  const subscriptionId = resourceManager.subscribe(
441
434
  input.uri,
442
435
  (event) => {
@@ -445,26 +438,25 @@ const subscriptionTool = new Tool({
445
438
  },
446
439
  input.options || {}
447
440
  );
448
-
449
- return {
441
+
442
+ return {
450
443
  success: true,
451
444
  subscriptionId,
452
- message: `Subscribed to ${input.uri}`
445
+ message: `Subscribed to ${input.uri}`,
453
446
  };
454
-
455
447
  } else if (input.action === 'unsubscribe') {
456
448
  if (!input.subscriptionId) {
457
449
  throw new ValidationError('Subscription ID required for unsubscribe action');
458
450
  }
459
-
451
+
460
452
  resourceManager.unsubscribe(input.subscriptionId);
461
-
453
+
462
454
  return {
463
455
  success: true,
464
- message: `Unsubscribed from ${input.subscriptionId}`
456
+ message: `Unsubscribed from ${input.subscriptionId}`,
465
457
  };
466
458
  }
467
- }
459
+ },
468
460
  });
469
461
 
470
462
  mcpServer.addTool(subscriptionTool);
@@ -477,27 +469,28 @@ mcpServer.addTool(subscriptionTool);
477
469
  const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
478
470
  try {
479
471
  console.log(`Starting Enhanced MCP Server with ${transport} transport...`);
480
-
472
+
481
473
  switch (transport) {
482
- case 'stdio':
474
+ case 'stdio': {
483
475
  const stdioTransport = new StdioServerTransport();
484
476
  await mcpServer.connect(stdioTransport);
485
- console.log("Enhanced MCP Server running on stdio transport");
477
+ console.log('Enhanced MCP Server running on stdio transport');
486
478
  break;
487
-
479
+ }
480
+
488
481
  case 'http':
489
- case 'sse':
482
+ case 'sse': {
490
483
  const port = options.port || 3001;
491
484
  const app = express();
492
-
485
+
493
486
  // Apply middleware
494
487
  app.use(gracefulShutdown.middleware());
495
488
  app.use(rateLimiter.middleware());
496
489
  app.use(mcpCors(options.allowedOrigins));
497
-
490
+
498
491
  // Health check with Ghost status
499
492
  app.get('/health', healthCheck(ghostService));
500
-
493
+
501
494
  // Resource endpoints
502
495
  app.get('/resources', async (req, res) => {
503
496
  try {
@@ -508,27 +501,27 @@ const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
508
501
  res.status(formatted.statusCode).json(formatted.body);
509
502
  }
510
503
  });
511
-
504
+
512
505
  app.get('/resources/*', async (req, res) => {
513
506
  try {
514
507
  const uri = req.params[0];
515
-
508
+
516
509
  // Validate and sanitize the URI to prevent path traversal
517
510
  if (!uri || typeof uri !== 'string') {
518
511
  throw new ValidationError('Invalid resource URI');
519
512
  }
520
-
513
+
521
514
  // Ensure the URI doesn't contain path traversal attempts
522
515
  if (uri.includes('..') || uri.includes('//') || uri.includes('\\')) {
523
516
  throw new ValidationError('Invalid resource URI: path traversal detected');
524
517
  }
525
-
518
+
526
519
  // Only allow specific URI patterns for Ghost resources
527
520
  const validPatterns = /^ghost\/(post|posts|tag|tags|author|authors|page|pages)/;
528
521
  if (!validPatterns.test(uri)) {
529
522
  throw new ValidationError('Invalid resource type');
530
523
  }
531
-
524
+
532
525
  const result = await resourceManager.fetchResource(uri);
533
526
  res.json(result);
534
527
  } catch (error) {
@@ -536,7 +529,7 @@ const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
536
529
  res.status(formatted.statusCode).json(formatted.body);
537
530
  }
538
531
  });
539
-
532
+
540
533
  // Batch endpoint
541
534
  app.post('/batch', async (req, res) => {
542
535
  try {
@@ -547,114 +540,127 @@ const startEnhancedMCPServer = async (transport = 'http', options = {}) => {
547
540
  res.status(formatted.statusCode).json(formatted.body);
548
541
  }
549
542
  });
550
-
543
+
551
544
  // Cache stats endpoint
552
545
  app.get('/cache/stats', (req, res) => {
553
546
  res.json(resourceManager.getCacheStats());
554
547
  });
555
-
548
+
556
549
  // SSE endpoint for MCP
557
550
  const sseTransport = new SSEServerTransport();
558
551
  app.get('/mcp/sse', sseTransport.handler());
559
-
552
+
560
553
  await mcpServer.connect(sseTransport);
561
-
554
+
562
555
  const server = app.listen(port, () => {
563
556
  console.log(`Enhanced MCP Server (SSE) listening on port ${port}`);
564
557
  console.log(`Health: http://localhost:${port}/health`);
565
558
  console.log(`Resources: http://localhost:${port}/resources`);
566
559
  console.log(`SSE: http://localhost:${port}/mcp/sse`);
567
560
  });
568
-
561
+
569
562
  mcpServer._httpServer = server;
570
-
563
+
571
564
  // Track connections for graceful shutdown
572
565
  server.on('connection', (connection) => {
573
566
  gracefulShutdown.trackConnection(connection);
574
567
  });
575
-
568
+
576
569
  break;
577
-
578
- case 'websocket':
570
+ }
571
+
572
+ case 'websocket': {
579
573
  const wsPort = options.port || 3001;
580
574
  const wss = new WebSocketServer({ port: wsPort });
581
-
575
+
582
576
  wss.on('connection', async (ws) => {
583
577
  console.log('New WebSocket connection');
584
-
578
+
585
579
  const wsTransport = new WebSocketServerTransport(ws);
586
580
  await mcpServer.connect(wsTransport);
587
-
581
+
588
582
  // Handle subscriptions over WebSocket
589
583
  ws.on('message', async (data) => {
590
584
  try {
591
585
  const message = JSON.parse(data);
592
-
586
+
593
587
  if (message.type === 'subscribe') {
594
588
  const subscriptionId = resourceManager.subscribe(
595
589
  message.uri,
596
590
  (event) => {
597
- ws.send(JSON.stringify({
598
- type: 'subscription_update',
599
- ...event
600
- }));
591
+ ws.send(
592
+ JSON.stringify({
593
+ type: 'subscription_update',
594
+ ...event,
595
+ })
596
+ );
601
597
  },
602
598
  message.options || {}
603
599
  );
604
-
605
- ws.send(JSON.stringify({
606
- type: 'subscription_created',
607
- subscriptionId
608
- }));
600
+
601
+ ws.send(
602
+ JSON.stringify({
603
+ type: 'subscription_created',
604
+ subscriptionId,
605
+ })
606
+ );
609
607
  }
610
608
  } catch (error) {
611
- ws.send(JSON.stringify({
612
- type: 'error',
613
- error: error.message
614
- }));
609
+ ws.send(
610
+ JSON.stringify({
611
+ type: 'error',
612
+ error: error.message,
613
+ })
614
+ );
615
615
  }
616
616
  });
617
617
  });
618
-
618
+
619
619
  console.log(`Enhanced MCP Server (WebSocket) listening on port ${wsPort}`);
620
620
  mcpServer._wss = wss;
621
621
  break;
622
-
622
+ }
623
+
623
624
  default:
624
625
  throw new Error(`Unknown transport type: ${transport}`);
625
626
  }
626
-
627
+
627
628
  // Log capabilities
628
- console.log("Server Capabilities:");
629
- console.log("- Resources:", mcpServer.listResources().map(r => r.name));
630
- console.log("- Tools:", mcpServer.listTools().map(t => t.name));
631
- console.log("- Cache enabled with LRU eviction");
632
- console.log("- Subscription support for real-time updates");
633
- console.log("- Batch operations for efficiency");
634
-
629
+ console.log('Server Capabilities:');
630
+ console.log(
631
+ '- Resources:',
632
+ mcpServer.listResources().map((r) => r.name)
633
+ );
634
+ console.log(
635
+ '- Tools:',
636
+ mcpServer.listTools().map((t) => t.name)
637
+ );
638
+ console.log('- Cache enabled with LRU eviction');
639
+ console.log('- Subscription support for real-time updates');
640
+ console.log('- Batch operations for efficiency');
635
641
  } catch (error) {
636
642
  errorLogger.logError(error);
637
- console.error("Failed to start Enhanced MCP Server:", error);
643
+ console.error('Failed to start Enhanced MCP Server:', error);
638
644
  process.exit(1);
639
645
  }
640
646
  };
641
647
 
642
648
  // Graceful shutdown
643
649
  const shutdown = async () => {
644
- console.log("\nShutting down Enhanced MCP Server...");
645
-
650
+ console.log('\nShutting down Enhanced MCP Server...');
651
+
646
652
  // Clear all subscriptions
647
653
  resourceManager.subscriptionManager.subscriptions.clear();
648
-
654
+
649
655
  // Close servers
650
656
  if (mcpServer._httpServer) {
651
657
  await gracefulShutdown.shutdown(mcpServer._httpServer);
652
658
  }
653
-
659
+
654
660
  if (mcpServer._wss) {
655
661
  mcpServer._wss.close();
656
662
  }
657
-
663
+
658
664
  await mcpServer.close();
659
665
  process.exit(0);
660
666
  };
@@ -670,6 +676,6 @@ if (import.meta.url === `file://${process.argv[1]}`) {
670
676
  const transport = process.env.MCP_TRANSPORT || 'http';
671
677
  const port = parseInt(process.env.MCP_PORT || '3001');
672
678
  const allowedOrigins = process.env.MCP_ALLOWED_ORIGINS?.split(',') || ['*'];
673
-
679
+
674
680
  startEnhancedMCPServer(transport, { port, allowedOrigins });
675
- }
681
+ }