@monoes/mcp 1.0.6 → 2.24.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.
Files changed (53) hide show
  1. package/dist/protocol-types.d.ts +182 -0
  2. package/dist/protocol-types.d.ts.map +1 -0
  3. package/dist/protocol-types.js +8 -0
  4. package/dist/protocol-types.js.map +1 -0
  5. package/dist/resource-helpers.d.ts +32 -0
  6. package/dist/resource-helpers.d.ts.map +1 -0
  7. package/dist/resource-helpers.js +77 -0
  8. package/dist/resource-helpers.js.map +1 -0
  9. package/dist/resource-registry.d.ts +2 -24
  10. package/dist/resource-registry.d.ts.map +1 -1
  11. package/dist/resource-registry.js +1 -70
  12. package/dist/resource-registry.js.map +1 -1
  13. package/dist/server-builtins.d.ts +12 -0
  14. package/dist/server-builtins.d.ts.map +1 -0
  15. package/dist/server-builtins.js +165 -0
  16. package/dist/server-builtins.js.map +1 -0
  17. package/dist/server-core.d.ts +118 -0
  18. package/dist/server-core.d.ts.map +1 -0
  19. package/dist/server-core.js +343 -0
  20. package/dist/server-core.js.map +1 -0
  21. package/dist/server-handlers.d.ts +17 -0
  22. package/dist/server-handlers.d.ts.map +1 -0
  23. package/dist/server-handlers.js +173 -0
  24. package/dist/server-handlers.js.map +1 -0
  25. package/dist/server-routing.d.ts +16 -0
  26. package/dist/server-routing.d.ts.map +1 -0
  27. package/dist/server-routing.js +194 -0
  28. package/dist/server-routing.js.map +1 -0
  29. package/dist/server.d.ts +2 -115
  30. package/dist/server.d.ts.map +1 -1
  31. package/dist/server.js +2 -839
  32. package/dist/server.js.map +1 -1
  33. package/dist/transport/http-base.d.ts +84 -0
  34. package/dist/transport/http-base.d.ts.map +1 -0
  35. package/dist/transport/http-base.js +238 -0
  36. package/dist/transport/http-base.js.map +1 -0
  37. package/dist/transport/http.d.ts +5 -69
  38. package/dist/transport/http.d.ts.map +1 -1
  39. package/dist/transport/http.js +3 -228
  40. package/dist/transport/http.js.map +1 -1
  41. package/dist/transport/websocket-base.d.ts +71 -0
  42. package/dist/transport/websocket-base.d.ts.map +1 -0
  43. package/dist/transport/websocket-base.js +199 -0
  44. package/dist/transport/websocket-base.js.map +1 -0
  45. package/dist/transport/websocket.d.ts +4 -63
  46. package/dist/transport/websocket.d.ts.map +1 -1
  47. package/dist/transport/websocket.js +3 -191
  48. package/dist/transport/websocket.js.map +1 -1
  49. package/dist/types.d.ts +1 -174
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js +5 -0
  52. package/dist/types.js.map +1 -1
  53. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -3,154 +3,10 @@
3
3
  *
4
4
  * High-performance MCP server implementation
5
5
  */
6
- import { EventEmitter } from 'node:events';
7
- import { arch, platform } from 'node:os';
8
- import { createConnectionPool } from './connection-pool.js';
9
- import { createPromptRegistry } from './prompt-registry.js';
10
- import { createRateLimiter } from './rate-limiter.js';
11
- import { createResourceRegistry } from './resource-registry.js';
12
- import { createSamplingManager } from './sampling.js';
13
- import { createSessionManager } from './session-manager.js';
14
- import { createTaskManager } from './task-manager.js';
15
- import { createToolRegistry } from './tool-registry.js';
6
+ import { MCPServerRouting } from './server-routing.js';
16
7
  import { createTransport } from './transport/index.js';
17
8
  import { ErrorCodes, MCPServerError } from './types.js';
18
- const DEFAULT_CONFIG = {
19
- name: 'Monomind MCP Server V1',
20
- version: '3.0.0',
21
- transport: 'stdio',
22
- host: 'localhost',
23
- port: 3000,
24
- enableMetrics: true,
25
- enableCaching: true,
26
- cacheTTL: 10000,
27
- logLevel: 'info',
28
- requestTimeout: 30000,
29
- maxRequestSize: 10 * 1024 * 1024,
30
- };
31
- export class MCPServer extends EventEmitter {
32
- logger;
33
- orchestrator;
34
- swarmCoordinator;
35
- config;
36
- toolRegistry;
37
- sessionManager;
38
- resourceRegistry;
39
- promptRegistry;
40
- taskManager;
41
- connectionPool;
42
- rateLimiter;
43
- samplingManager;
44
- transport;
45
- running = false;
46
- startTime;
47
- startupDuration;
48
- // Fallback singleton session, used only for transports that are inherently
49
- // single-client (stdio, in-process) where the transport never supplies a
50
- // connectionId to handleRequest/handleNotification.
51
- currentSession;
52
- // connectionId -> session, for multi-client transports (http, websocket).
53
- // Without this, every connection shared `currentSession` above and a
54
- // second client's `initialize` would silently reassign the session out
55
- // from under the first client (issue #93).
56
- connectionSessions = new Map();
57
- // sessionId -> (uri -> subscriptionId). The registry-issued subscriptionId
58
- // must be retained so resources/unsubscribe (and session teardown) can call
59
- // resourceRegistry.unsubscribe(id) — without it callbacks leak and phantom
60
- // notifications fire after unsubscribe (issue #92).
61
- resourceSubscriptions = new Map();
62
- serverInfo = {
63
- name: 'Monomind MCP Server V1',
64
- version: '3.0.0',
65
- };
66
- // MCP 2025-11-25 protocol version
67
- protocolVersion = {
68
- major: 2025,
69
- minor: 11,
70
- patch: 25,
71
- };
72
- // Full MCP 2025-11-25 capabilities
73
- capabilities = {
74
- logging: { level: 'info' },
75
- tools: { listChanged: true },
76
- resources: { listChanged: true, subscribe: true },
77
- prompts: { listChanged: true },
78
- sampling: {},
79
- };
80
- requestStats = {
81
- total: 0,
82
- successful: 0,
83
- failed: 0,
84
- totalResponseTime: 0,
85
- };
86
- constructor(config, logger, orchestrator, swarmCoordinator) {
87
- super();
88
- this.logger = logger;
89
- this.orchestrator = orchestrator;
90
- this.swarmCoordinator = swarmCoordinator;
91
- this.config = { ...DEFAULT_CONFIG, ...config };
92
- this.toolRegistry = createToolRegistry(logger);
93
- this.sessionManager = createSessionManager(logger, {
94
- maxSessions: 100,
95
- sessionTimeout: 30 * 60 * 1000,
96
- });
97
- this.resourceRegistry = createResourceRegistry(logger, {
98
- enableSubscriptions: true,
99
- cacheEnabled: true,
100
- cacheTTL: 60000,
101
- });
102
- this.promptRegistry = createPromptRegistry(logger);
103
- this.taskManager = createTaskManager(logger, {
104
- maxConcurrentTasks: 10,
105
- taskTimeout: 300000,
106
- });
107
- this.rateLimiter = createRateLimiter(logger, {
108
- requestsPerSecond: 100,
109
- burstSize: 200,
110
- perSessionLimit: 50,
111
- });
112
- this.samplingManager = createSamplingManager(logger);
113
- if (this.config.connectionPool) {
114
- this.connectionPool = createConnectionPool(this.config.connectionPool, logger, this.config.transport);
115
- }
116
- this.setupEventHandlers();
117
- }
118
- /**
119
- * Get resource registry for external registration
120
- */
121
- getResourceRegistry() {
122
- return this.resourceRegistry;
123
- }
124
- /**
125
- * Get prompt registry for external registration
126
- */
127
- getPromptRegistry() {
128
- return this.promptRegistry;
129
- }
130
- /**
131
- * Get task manager for async operations
132
- */
133
- getTaskManager() {
134
- return this.taskManager;
135
- }
136
- /**
137
- * Get rate limiter for configuration
138
- */
139
- getRateLimiter() {
140
- return this.rateLimiter;
141
- }
142
- /**
143
- * Get sampling manager for LLM provider registration
144
- */
145
- getSamplingManager() {
146
- return this.samplingManager;
147
- }
148
- /**
149
- * Register an LLM provider for sampling
150
- */
151
- registerLLMProvider(provider, isDefault = false) {
152
- this.samplingManager.registerProvider(provider, isDefault);
153
- }
9
+ export class MCPServer extends MCPServerRouting {
154
10
  async start() {
155
11
  if (this.running) {
156
12
  throw new MCPServerError('Server already running');
@@ -242,73 +98,6 @@ export class MCPServer extends EventEmitter {
242
98
  throw error;
243
99
  }
244
100
  }
245
- registerTool(tool) {
246
- return this.toolRegistry.register(tool);
247
- }
248
- registerTools(tools) {
249
- return this.toolRegistry.registerBatch(tools);
250
- }
251
- unregisterTool(name) {
252
- return this.toolRegistry.unregister(name);
253
- }
254
- async getHealthStatus() {
255
- try {
256
- const transportHealth = this.transport
257
- ? await this.transport.getHealthStatus()
258
- : { healthy: false, error: 'Transport not initialized' };
259
- const sessionMetrics = this.sessionManager.getSessionMetrics();
260
- const poolStats = this.connectionPool?.getStats();
261
- const metrics = {
262
- registeredTools: this.toolRegistry.getToolCount(),
263
- totalRequests: this.requestStats.total,
264
- successfulRequests: this.requestStats.successful,
265
- failedRequests: this.requestStats.failed,
266
- totalSessions: sessionMetrics.total,
267
- activeSessions: sessionMetrics.active,
268
- ...(transportHealth.metrics || {}),
269
- };
270
- if (poolStats) {
271
- metrics.poolConnections = poolStats.totalConnections;
272
- metrics.poolIdleConnections = poolStats.idleConnections;
273
- metrics.poolBusyConnections = poolStats.busyConnections;
274
- }
275
- return {
276
- healthy: this.running && transportHealth.healthy,
277
- error: transportHealth.error,
278
- metrics,
279
- };
280
- }
281
- catch (error) {
282
- return {
283
- healthy: false,
284
- error: error instanceof Error ? error.message : 'Unknown error',
285
- };
286
- }
287
- }
288
- getMetrics() {
289
- const sessionMetrics = this.sessionManager.getSessionMetrics();
290
- const registryStats = this.toolRegistry.getStats();
291
- return {
292
- totalRequests: this.requestStats.total,
293
- successfulRequests: this.requestStats.successful,
294
- failedRequests: this.requestStats.failed,
295
- averageResponseTime: this.requestStats.total > 0
296
- ? this.requestStats.totalResponseTime / this.requestStats.total
297
- : 0,
298
- activeSessions: sessionMetrics.active,
299
- toolInvocations: Object.fromEntries(registryStats.topTools.map((t) => [t.name, t.calls])),
300
- errors: {},
301
- lastReset: this.startTime || new Date(),
302
- startupTime: this.startupDuration,
303
- uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0,
304
- };
305
- }
306
- getSessions() {
307
- return this.sessionManager.getActiveSessions();
308
- }
309
- getSession(sessionId) {
310
- return this.sessionManager.getSession(sessionId);
311
- }
312
101
  terminateSession(sessionId) {
313
102
  const result = this.sessionManager.closeSession(sessionId, 'Terminated by server');
314
103
  if (this.currentSession?.id === sessionId) {
@@ -321,59 +110,6 @@ export class MCPServer extends EventEmitter {
321
110
  }
322
111
  return result;
323
112
  }
324
- /**
325
- * Resolve the session bound to a connection without creating one.
326
- * Multi-client transports (http, websocket) pass a `connectionId`, which is
327
- * looked up in `connectionSessions`. Single-client transports (stdio,
328
- * in-process) omit it, falling back to the `currentSession` singleton.
329
- */
330
- getSessionForConnection(connectionId) {
331
- if (connectionId) {
332
- return this.connectionSessions.get(connectionId);
333
- }
334
- return this.currentSession;
335
- }
336
- /**
337
- * Bind a newly-created session to a connection: per-connectionId when the
338
- * transport supports multiple clients, otherwise the singleton fallback.
339
- */
340
- bindSessionToConnection(session, connectionId) {
341
- if (connectionId) {
342
- this.connectionSessions.set(connectionId, session);
343
- }
344
- else {
345
- this.currentSession = session;
346
- }
347
- }
348
- /**
349
- * Tear down the session owned by a connection when the transport reports
350
- * that connection has closed — prevents closed clients' sessions (and any
351
- * resource subscriptions) from lingering until the idle-timeout sweep.
352
- */
353
- closeConnectionSession(connectionId) {
354
- const session = this.connectionSessions.get(connectionId);
355
- this.connectionSessions.delete(connectionId);
356
- if (session) {
357
- // closeSession emits 'session:closed', which purges the session's
358
- // resource subscriptions (see setupEventHandlers).
359
- this.sessionManager.closeSession(session.id, 'Connection closed');
360
- }
361
- }
362
- /**
363
- * Detach every registry subscription owned by a session. Called whenever a
364
- * session ends (terminated, connection closed, expired, server stopped) so
365
- * no update callback outlives its session (issue #92).
366
- */
367
- purgeSessionSubscriptions(sessionId) {
368
- const sessionSubs = this.resourceSubscriptions.get(sessionId);
369
- if (!sessionSubs) {
370
- return;
371
- }
372
- for (const subscriptionId of sessionSubs.values()) {
373
- this.resourceRegistry.unsubscribe(subscriptionId);
374
- }
375
- this.resourceSubscriptions.delete(sessionId);
376
- }
377
113
  async handleRequest(request, connectionId) {
378
114
  const startTime = performance.now();
379
115
  this.requestStats.total++;
@@ -468,579 +204,6 @@ export class MCPServer extends EventEmitter {
468
204
  result,
469
205
  };
470
206
  }
471
- async routeRequest(request, connectionId) {
472
- switch (request.method) {
473
- // Tool methods
474
- case 'tools/list':
475
- return this.handleToolsList(request);
476
- case 'tools/call':
477
- return this.handleToolsCall(request, connectionId);
478
- // Resource methods (MCP 2025-11-25)
479
- case 'resources/list':
480
- return this.handleResourcesList(request);
481
- case 'resources/read':
482
- return this.handleResourcesRead(request);
483
- case 'resources/subscribe':
484
- return this.handleResourcesSubscribe(request, connectionId);
485
- case 'resources/unsubscribe':
486
- return this.handleResourcesUnsubscribe(request, connectionId);
487
- // Prompt methods (MCP 2025-11-25)
488
- case 'prompts/list':
489
- return this.handlePromptsList(request);
490
- case 'prompts/get':
491
- return this.handlePromptsGet(request);
492
- // Task methods (MCP 2025-11-25)
493
- case 'tasks/status':
494
- return this.handleTasksStatus(request);
495
- case 'tasks/cancel':
496
- return this.handleTasksCancel(request);
497
- // Completion (MCP 2025-11-25)
498
- case 'completion/complete':
499
- return this.handleCompletion(request);
500
- // Logging (MCP 2025-11-25)
501
- case 'logging/setLevel':
502
- return this.handleLoggingSetLevel(request);
503
- // Sampling (MCP 2025-11-25)
504
- case 'sampling/createMessage':
505
- return this.handleSamplingCreateMessage(request, connectionId);
506
- // Utility
507
- case 'ping':
508
- return {
509
- jsonrpc: '2.0',
510
- id: request.id,
511
- result: { pong: true, timestamp: Date.now() },
512
- };
513
- default:
514
- // Check if it's a direct tool call
515
- if (this.toolRegistry.hasTool(request.method)) {
516
- return this.handleToolExecution(request, connectionId);
517
- }
518
- return this.createErrorResponse(request.id, ErrorCodes.METHOD_NOT_FOUND, `Method not found: ${request.method}`);
519
- }
520
- }
521
- handleToolsList(request) {
522
- const tools = this.toolRegistry.listTools().map((t) => ({
523
- name: t.name,
524
- description: t.description,
525
- inputSchema: this.toolRegistry.getTool(t.name)?.inputSchema,
526
- }));
527
- return {
528
- jsonrpc: '2.0',
529
- id: request.id,
530
- result: { tools },
531
- };
532
- }
533
- async handleToolsCall(request, connectionId) {
534
- const params = request.params;
535
- if (!params?.name) {
536
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'Tool name is required');
537
- }
538
- const context = {
539
- sessionId: this.getSessionForConnection(connectionId)?.id || 'unknown',
540
- requestId: request.id,
541
- orchestrator: this.orchestrator,
542
- swarmCoordinator: this.swarmCoordinator,
543
- };
544
- const result = await this.toolRegistry.execute(params.name, params.arguments || {}, context);
545
- return {
546
- jsonrpc: '2.0',
547
- id: request.id,
548
- result,
549
- };
550
- }
551
- async handleToolExecution(request, connectionId) {
552
- const context = {
553
- sessionId: this.getSessionForConnection(connectionId)?.id || 'unknown',
554
- requestId: request.id,
555
- orchestrator: this.orchestrator,
556
- swarmCoordinator: this.swarmCoordinator,
557
- };
558
- const result = await this.toolRegistry.execute(request.method, request.params || {}, context);
559
- return {
560
- jsonrpc: '2.0',
561
- id: request.id,
562
- result,
563
- };
564
- }
565
- // ============================================================================
566
- // Resource Handlers (MCP 2025-11-25)
567
- // ============================================================================
568
- handleResourcesList(request) {
569
- const params = request.params;
570
- const result = this.resourceRegistry.list(params?.cursor);
571
- return {
572
- jsonrpc: '2.0',
573
- id: request.id,
574
- result,
575
- };
576
- }
577
- async handleResourcesRead(request) {
578
- const params = request.params;
579
- if (!params?.uri) {
580
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'Resource URI is required');
581
- }
582
- try {
583
- const result = await this.resourceRegistry.read(params.uri);
584
- return {
585
- jsonrpc: '2.0',
586
- id: request.id,
587
- result,
588
- };
589
- }
590
- catch (error) {
591
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, error instanceof Error ? error.message : 'Resource read failed');
592
- }
593
- }
594
- handleResourcesSubscribe(request, connectionId) {
595
- const params = request.params;
596
- const sessionId = this.getSessionForConnection(connectionId)?.id;
597
- if (!params?.uri) {
598
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'Resource URI is required');
599
- }
600
- if (!sessionId) {
601
- return this.createErrorResponse(request.id, ErrorCodes.SERVER_NOT_INITIALIZED, 'No active session');
602
- }
603
- try {
604
- // Track subscription for this session
605
- let sessionSubs = this.resourceSubscriptions.get(sessionId);
606
- if (!sessionSubs) {
607
- sessionSubs = new Map();
608
- this.resourceSubscriptions.set(sessionId, sessionSubs);
609
- }
610
- // Re-subscribing the same URI replaces the previous subscription —
611
- // otherwise the old callback would keep firing with no way to reach it.
612
- const existingId = sessionSubs.get(params.uri);
613
- if (existingId) {
614
- this.resourceRegistry.unsubscribe(existingId);
615
- }
616
- const subscriptionId = this.resourceRegistry.subscribe(params.uri, (uri, _content) => {
617
- // Send notification when resource updates
618
- this.sendNotification('notifications/resources/updated', { uri });
619
- });
620
- sessionSubs.set(params.uri, subscriptionId);
621
- return {
622
- jsonrpc: '2.0',
623
- id: request.id,
624
- result: { subscriptionId },
625
- };
626
- }
627
- catch (error) {
628
- return this.createErrorResponse(request.id, ErrorCodes.INTERNAL_ERROR, error instanceof Error ? error.message : 'Subscription failed');
629
- }
630
- }
631
- handleResourcesUnsubscribe(request, connectionId) {
632
- const params = request.params;
633
- const sessionId = this.getSessionForConnection(connectionId)?.id;
634
- if (!params?.uri) {
635
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'Resource URI is required');
636
- }
637
- if (sessionId) {
638
- const sessionSubs = this.resourceSubscriptions.get(sessionId);
639
- const subscriptionId = sessionSubs?.get(params.uri);
640
- if (sessionSubs && subscriptionId) {
641
- // Actually detach the registry callback — just forgetting the URI
642
- // locally left the callback live, producing phantom notifications
643
- // (issue #92).
644
- this.resourceRegistry.unsubscribe(subscriptionId);
645
- sessionSubs.delete(params.uri);
646
- if (sessionSubs.size === 0) {
647
- this.resourceSubscriptions.delete(sessionId);
648
- }
649
- }
650
- }
651
- return {
652
- jsonrpc: '2.0',
653
- id: request.id,
654
- result: { success: true },
655
- };
656
- }
657
- // ============================================================================
658
- // Prompt Handlers (MCP 2025-11-25)
659
- // ============================================================================
660
- handlePromptsList(request) {
661
- const params = request.params;
662
- const result = this.promptRegistry.list(params?.cursor);
663
- return {
664
- jsonrpc: '2.0',
665
- id: request.id,
666
- result,
667
- };
668
- }
669
- async handlePromptsGet(request) {
670
- const params = request.params;
671
- if (!params?.name) {
672
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'Prompt name is required');
673
- }
674
- try {
675
- const result = await this.promptRegistry.get(params.name, params.arguments);
676
- return {
677
- jsonrpc: '2.0',
678
- id: request.id,
679
- result,
680
- };
681
- }
682
- catch (error) {
683
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, error instanceof Error ? error.message : 'Prompt get failed');
684
- }
685
- }
686
- // ============================================================================
687
- // Task Handlers (MCP 2025-11-25)
688
- // ============================================================================
689
- handleTasksStatus(request) {
690
- const params = request.params;
691
- if (params?.taskId) {
692
- const task = this.taskManager.getTask(params.taskId);
693
- if (!task) {
694
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, `Task not found: ${params.taskId}`);
695
- }
696
- return {
697
- jsonrpc: '2.0',
698
- id: request.id,
699
- result: task,
700
- };
701
- }
702
- // Return all tasks
703
- return {
704
- jsonrpc: '2.0',
705
- id: request.id,
706
- result: { tasks: this.taskManager.getAllTasks() },
707
- };
708
- }
709
- handleTasksCancel(request) {
710
- const params = request.params;
711
- if (!params?.taskId) {
712
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'Task ID is required');
713
- }
714
- const success = this.taskManager.cancelTask(params.taskId, params.reason);
715
- return {
716
- jsonrpc: '2.0',
717
- id: request.id,
718
- result: { success },
719
- };
720
- }
721
- // ============================================================================
722
- // Completion Handler (MCP 2025-11-25)
723
- // ============================================================================
724
- handleCompletion(request) {
725
- const params = request.params;
726
- if (!params?.ref || !params?.argument) {
727
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'Completion reference and argument are required');
728
- }
729
- // Basic completion implementation - can be extended
730
- const completions = [];
731
- if (params.ref.type === 'ref/prompt') {
732
- // Get prompt argument completions
733
- const prompt = this.promptRegistry.getPrompt(params.ref.name || '');
734
- if (prompt?.arguments) {
735
- for (const arg of prompt.arguments) {
736
- if (arg.name === params.argument.name) {
737
- // Could add domain-specific completions here
738
- }
739
- }
740
- }
741
- }
742
- else if (params.ref.type === 'ref/resource') {
743
- // Get resource URI completions
744
- const { resources } = this.resourceRegistry.list();
745
- for (const resource of resources) {
746
- if (resource.uri.startsWith(params.argument.value)) {
747
- completions.push(resource.uri);
748
- }
749
- }
750
- }
751
- return {
752
- jsonrpc: '2.0',
753
- id: request.id,
754
- result: {
755
- completion: {
756
- values: completions.slice(0, 10),
757
- total: completions.length,
758
- hasMore: completions.length > 10,
759
- },
760
- },
761
- };
762
- }
763
- // ============================================================================
764
- // Logging Handler (MCP 2025-11-25)
765
- // ============================================================================
766
- handleLoggingSetLevel(request) {
767
- const params = request.params;
768
- if (!params?.level) {
769
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'Log level is required');
770
- }
771
- // Update capabilities
772
- this.capabilities.logging = { level: params.level };
773
- this.logger.info('Log level updated', { level: params.level });
774
- return {
775
- jsonrpc: '2.0',
776
- id: request.id,
777
- result: { success: true },
778
- };
779
- }
780
- // ============================================================================
781
- // Sampling Handler (MCP 2025-11-25)
782
- // ============================================================================
783
- async handleSamplingCreateMessage(request, connectionId) {
784
- const params = request.params;
785
- if (!params?.messages || !params?.maxTokens) {
786
- return this.createErrorResponse(request.id, ErrorCodes.INVALID_PARAMS, 'messages and maxTokens are required');
787
- }
788
- // Check if sampling is available
789
- const available = await this.samplingManager.isAvailable();
790
- if (!available) {
791
- return this.createErrorResponse(request.id, ErrorCodes.INTERNAL_ERROR, 'No LLM provider available for sampling');
792
- }
793
- try {
794
- const result = await this.samplingManager.createMessage({
795
- messages: params.messages.map((m) => ({
796
- role: m.role,
797
- content: m.content,
798
- })),
799
- maxTokens: params.maxTokens,
800
- systemPrompt: params.systemPrompt,
801
- modelPreferences: params.modelPreferences,
802
- includeContext: params.includeContext,
803
- temperature: params.temperature,
804
- stopSequences: params.stopSequences,
805
- metadata: params.metadata,
806
- }, {
807
- sessionId: this.getSessionForConnection(connectionId)?.id || 'unknown',
808
- serverId: this.serverInfo.name,
809
- });
810
- return {
811
- jsonrpc: '2.0',
812
- id: request.id,
813
- result,
814
- };
815
- }
816
- catch (error) {
817
- return this.createErrorResponse(request.id, ErrorCodes.INTERNAL_ERROR, error instanceof Error ? error.message : 'Sampling failed');
818
- }
819
- }
820
- // ============================================================================
821
- // Notification Sender
822
- // ============================================================================
823
- async sendNotification(method, params) {
824
- if (this.transport?.sendNotification) {
825
- await this.transport.sendNotification({
826
- jsonrpc: '2.0',
827
- method,
828
- params,
829
- });
830
- }
831
- }
832
- getOrCreateSession(connectionId) {
833
- const existing = this.getSessionForConnection(connectionId);
834
- if (existing) {
835
- return existing;
836
- }
837
- // A client is calling a non-initialize method on a connection that never
838
- // sent `initialize` (or whose session already expired). Create a fresh
839
- // session bound to this specific connection rather than reusing/
840
- // overwriting another client's session.
841
- const session = this.sessionManager.createSession(this.config.transport);
842
- this.bindSessionToConnection(session, connectionId);
843
- return session;
844
- }
845
- createErrorResponse(id, code, message) {
846
- return {
847
- jsonrpc: '2.0',
848
- id,
849
- error: { code, message },
850
- };
851
- }
852
- async registerBuiltInTools() {
853
- this.registerTool({
854
- name: 'system/info',
855
- description: 'Get system information',
856
- inputSchema: { type: 'object', properties: {} },
857
- handler: async () => ({
858
- name: this.serverInfo.name,
859
- version: this.serverInfo.version,
860
- platform: platform(),
861
- arch: arch(),
862
- runtime: 'Node.js',
863
- uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0,
864
- }),
865
- category: 'system',
866
- });
867
- this.registerTool({
868
- name: 'system/health',
869
- description: 'Get system health status',
870
- inputSchema: { type: 'object', properties: {} },
871
- handler: async () => await this.getHealthStatus(),
872
- category: 'system',
873
- cacheable: true,
874
- cacheTTL: 2000,
875
- });
876
- this.registerTool({
877
- name: 'system/metrics',
878
- description: 'Get server metrics',
879
- inputSchema: { type: 'object', properties: {} },
880
- handler: async () => this.getMetrics(),
881
- category: 'system',
882
- cacheable: true,
883
- cacheTTL: 1000,
884
- });
885
- this.registerTool({
886
- name: 'tools/list-detailed',
887
- description: 'List all registered tools with details',
888
- inputSchema: {
889
- type: 'object',
890
- properties: {
891
- category: { type: 'string', description: 'Filter by category' },
892
- },
893
- },
894
- handler: async (input) => {
895
- const params = input;
896
- if (params.category) {
897
- return this.toolRegistry.getByCategory(params.category);
898
- }
899
- return this.toolRegistry.listTools();
900
- },
901
- category: 'system',
902
- });
903
- this.logger.info('Built-in tools registered', {
904
- count: 4,
905
- });
906
- }
907
- // P2-1: Populate the Resources registry with system-level resources.
908
- // The registry scaffolding always shipped empty — these adapter registrations
909
- // surface monomind's state to MCP clients as app-driven context.
910
- registerBuiltInResources() {
911
- this.resourceRegistry.registerResource({
912
- uri: 'monomind://system/status',
913
- name: 'System Status',
914
- description: 'Current monomind MCP server health, uptime, and tool count',
915
- mimeType: 'application/json',
916
- }, async (uri) => [
917
- {
918
- uri,
919
- mimeType: 'application/json',
920
- text: JSON.stringify({
921
- status: 'running',
922
- uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0,
923
- tools: this.toolRegistry.listTools().length,
924
- version: this.serverInfo.version,
925
- }, null, 2),
926
- },
927
- ]);
928
- this.resourceRegistry.registerResource({
929
- uri: 'monomind://system/metrics',
930
- name: 'Server Metrics',
931
- description: 'Request count, cache hit rate, error rate',
932
- mimeType: 'application/json',
933
- }, async (uri) => [
934
- {
935
- uri,
936
- mimeType: 'application/json',
937
- text: JSON.stringify(this.getMetrics(), null, 2),
938
- },
939
- ]);
940
- this.resourceRegistry.registerResource({
941
- uri: 'monomind://system/tools',
942
- name: 'Tool Inventory',
943
- description: 'Complete list of registered MCP tools with categories',
944
- mimeType: 'application/json',
945
- }, async (uri) => [
946
- {
947
- uri,
948
- mimeType: 'application/json',
949
- text: JSON.stringify(this.toolRegistry.listTools(), null, 2),
950
- },
951
- ]);
952
- this.logger.info('Built-in resources registered', { count: 3 });
953
- }
954
- // P2-2: Populate the Prompts registry with curated workflow templates.
955
- // These give MCP clients pre-built prompt structures for common dev tasks.
956
- registerBuiltInPrompts() {
957
- this.promptRegistry.register({
958
- name: 'monomind:research-plan',
959
- description: 'Generate a structured research plan for a topic',
960
- arguments: [{ name: 'topic', description: 'The research topic', required: true }],
961
- handler: async (args) => [
962
- {
963
- role: 'user',
964
- content: {
965
- type: 'text',
966
- text: `Research plan for: ${args.topic ?? '(unspecified)'}\n\n1. Define scope and key questions\n2. Identify sources and prior work\n3. Outline methodology\n4. Execute research (parallel agents if appropriate)\n5. Synthesize findings\n6. Review and refine`,
967
- },
968
- },
969
- ],
970
- });
971
- this.promptRegistry.register({
972
- name: 'monomind:pr-review',
973
- description: 'Structured pull-request review checklist',
974
- arguments: [{ name: 'changes', description: 'Description of the changes', required: true }],
975
- handler: async (args) => [
976
- {
977
- role: 'user',
978
- content: {
979
- type: 'text',
980
- text: `PR Review checklist for: ${args.changes ?? '(unspecified)'}\n\n- [ ] Correctness: does the code do what it claims?\n- [ ] Tests: are there tests covering the changes?\n- [ ] Security: any input validation gaps?\n- [ ] Performance: any O(n²) or unnecessary allocations?\n- [ ] Docs: are relevant docs updated?\n- [ ] Breaking changes: any removed APIs or changed behavior?\n- [ ] Honesty: any fabricated metrics or misleading output?`,
981
- },
982
- },
983
- ],
984
- });
985
- this.promptRegistry.register({
986
- name: 'monomind:debug-protocol',
987
- description: 'Systematic root-cause debugging protocol (4-phase)',
988
- arguments: [
989
- {
990
- name: 'symptom',
991
- description: 'Description of the bug or unexpected behavior',
992
- required: true,
993
- },
994
- ],
995
- handler: async (args) => [
996
- {
997
- role: 'user',
998
- content: {
999
- type: 'text',
1000
- text: `Debug protocol for: ${args.symptom ?? '(unspecified)'}\n\nPhase 1 — REPRODUCE: Write a test that triggers the bug. Verify it fails.\nPhase 2 — ISOLATE: Binary-search the cause. What's the smallest change that triggers it?\nPhase 3 — FIX: Make the test pass with the minimal code change.\nPhase 4 — VERIFY: Run the full test suite. Confirm no regressions.`,
1001
- },
1002
- },
1003
- ],
1004
- });
1005
- this.logger.info('Built-in prompts registered', { count: 3 });
1006
- }
1007
- setupEventHandlers() {
1008
- this.toolRegistry.on('tool:registered', (name) => {
1009
- this.emit('tool:registered', name);
1010
- });
1011
- this.toolRegistry.on('tool:called', (data) => {
1012
- this.emit('tool:called', data);
1013
- });
1014
- this.toolRegistry.on('tool:completed', (data) => {
1015
- this.emit('tool:completed', data);
1016
- });
1017
- this.toolRegistry.on('tool:error', (data) => {
1018
- this.emit('tool:error', data);
1019
- });
1020
- this.sessionManager.on('session:created', (session) => {
1021
- this.emit('session:created', session);
1022
- });
1023
- this.sessionManager.on('session:closed', (data) => {
1024
- this.purgeSessionSubscriptions(data.session.id);
1025
- this.emit('session:closed', data);
1026
- });
1027
- this.sessionManager.on('session:expired', (session) => {
1028
- this.purgeSessionSubscriptions(session.id);
1029
- // Drop references to the expired session — otherwise `currentSession` /
1030
- // `connectionSessions` keep pointing at a session the manager has
1031
- // already reaped, and subsequent updateActivity calls fail silently
1032
- // against the ghost (issue #93).
1033
- if (this.currentSession?.id === session.id) {
1034
- this.currentSession = undefined;
1035
- }
1036
- for (const [connectionId, s] of this.connectionSessions) {
1037
- if (s.id === session.id) {
1038
- this.connectionSessions.delete(connectionId);
1039
- }
1040
- }
1041
- this.emit('session:expired', session);
1042
- });
1043
- }
1044
207
  }
1045
208
  export function createMCPServer(config, logger, orchestrator, swarmCoordinator) {
1046
209
  return new MCPServer(config, logger, orchestrator, swarmCoordinator);