@1mcp/agent 0.7.0 → 0.8.2

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 (86) hide show
  1. package/README.md +55 -0
  2. package/build/capabilities/capabilityManager.d.ts +34 -0
  3. package/build/capabilities/capabilityManager.d.ts.map +1 -0
  4. package/build/capabilities/capabilityManager.js +1 -0
  5. package/build/capabilities/capabilityManager.js.map +1 -0
  6. package/build/client.d.ts +25 -0
  7. package/build/client.d.ts.map +1 -0
  8. package/build/client.js +3 -10
  9. package/build/client.js.map +1 -0
  10. package/build/clients/clientManager.d.ts +40 -0
  11. package/build/clients/clientManager.d.ts.map +1 -0
  12. package/build/clients/clientManager.js +1 -0
  13. package/build/clients/clientManager.js.map +1 -0
  14. package/build/config/configManager.d.ts +54 -0
  15. package/build/config/configManager.d.ts.map +1 -0
  16. package/build/config/configManager.js +1 -0
  17. package/build/config/configManager.js.map +1 -0
  18. package/build/config/transportConfig.d.ts +7 -0
  19. package/build/config/transportConfig.d.ts.map +1 -0
  20. package/build/config/transportConfig.js +1 -0
  21. package/build/config/transportConfig.js.map +1 -0
  22. package/build/constants.d.ts +43 -0
  23. package/build/constants.d.ts.map +1 -0
  24. package/build/constants.js +19 -4
  25. package/build/constants.js.map +1 -0
  26. package/build/handlers/notificationHandlers.d.ts +14 -0
  27. package/build/handlers/notificationHandlers.d.ts.map +1 -0
  28. package/build/handlers/notificationHandlers.js +1 -0
  29. package/build/handlers/notificationHandlers.js.map +1 -0
  30. package/build/handlers/requestHandlers.d.ts +10 -0
  31. package/build/handlers/requestHandlers.d.ts.map +1 -0
  32. package/build/handlers/requestHandlers.js +1 -0
  33. package/build/handlers/requestHandlers.js.map +1 -0
  34. package/build/index.d.ts +3 -0
  35. package/build/index.d.ts.map +1 -0
  36. package/build/index.js +6 -1
  37. package/build/index.js.map +1 -0
  38. package/build/logger/logger.d.ts +13 -0
  39. package/build/logger/logger.d.ts.map +1 -0
  40. package/build/logger/logger.js +5 -35
  41. package/build/logger/logger.js.map +1 -0
  42. package/build/middleware/loggingMiddleware.d.ts +6 -0
  43. package/build/middleware/loggingMiddleware.d.ts.map +1 -0
  44. package/build/middleware/loggingMiddleware.js +1 -0
  45. package/build/middleware/loggingMiddleware.js.map +1 -0
  46. package/build/server/expressServer.d.ts +10 -0
  47. package/build/server/expressServer.d.ts.map +1 -0
  48. package/build/server/expressServer.js +2 -11
  49. package/build/server/expressServer.js.map +1 -0
  50. package/build/server.d.ts +7 -0
  51. package/build/server.d.ts.map +1 -0
  52. package/build/server.js +1 -0
  53. package/build/server.js.map +1 -0
  54. package/build/serverManager.d.ts +25 -0
  55. package/build/serverManager.d.ts.map +1 -0
  56. package/build/serverManager.js +4 -2
  57. package/build/serverManager.js.map +1 -0
  58. package/build/services/configReloadService.d.ts +37 -0
  59. package/build/services/configReloadService.d.ts.map +1 -0
  60. package/build/services/configReloadService.js +1 -0
  61. package/build/services/configReloadService.js.map +1 -0
  62. package/build/types.d.ts +129 -0
  63. package/build/types.d.ts.map +1 -0
  64. package/build/types.js +1 -0
  65. package/build/types.js.map +1 -0
  66. package/build/utils/clientFiltering.d.ts +37 -0
  67. package/build/utils/clientFiltering.d.ts.map +1 -0
  68. package/build/utils/clientFiltering.js +1 -0
  69. package/build/utils/clientFiltering.js.map +1 -0
  70. package/build/utils/errorHandling.d.ts +62 -0
  71. package/build/utils/errorHandling.d.ts.map +1 -0
  72. package/build/utils/errorHandling.js +1 -0
  73. package/build/utils/errorHandling.js.map +1 -0
  74. package/build/utils/errorTypes.d.ts +25 -0
  75. package/build/utils/errorTypes.d.ts.map +1 -0
  76. package/build/utils/errorTypes.js +1 -0
  77. package/build/utils/errorTypes.js.map +1 -0
  78. package/package.json +8 -9
  79. package/build/clients/clientManager.test.js +0 -172
  80. package/build/config/configManager.test.js +0 -92
  81. package/build/logger/mcpTransport.js +0 -150
  82. package/build/serverManager.test.js +0 -158
  83. package/build/test-utils/fixtures/commonFixtures.js +0 -44
  84. package/build/test-utils/helpers/testHelpers.js +0 -23
  85. package/build/test-utils/mocks/commonMocks.js +0 -65
  86. package/build/utils/clientFiltering.test.js +0 -124
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1mcp/agent",
3
- "version": "0.7.0",
3
+ "version": "0.8.2",
4
4
  "description": "One MCP server to aggregate them all - A unified Model Context Protocol server implementation",
5
5
  "keywords": [
6
6
  "mcp",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@modelcontextprotocol/sdk": "^1.7.0",
52
- "express": "^4.21.2",
52
+ "express": "^5.1.0",
53
53
  "uuid": "^11.1.0",
54
54
  "winston": "^3.17.0",
55
55
  "winston-transport": "^4.9.0",
@@ -61,25 +61,24 @@
61
61
  "@eslint/js": "^9.22.0",
62
62
  "@testing-library/jest-dom": "^6.6.3",
63
63
  "@types/express": "^5.0.0",
64
- "@types/jest": "^29.5.14",
65
64
  "@types/node": "^22.13.10",
66
65
  "@types/uuid": "^10.0.0",
67
66
  "@types/yargs": "^17.0.33",
68
67
  "@typescript-eslint/eslint-plugin": "^8.27.0",
69
68
  "@typescript-eslint/parser": "^8.27.0",
69
+ "@vitest/coverage-v8": "^1.4.0",
70
70
  "eslint": "^9.22.0",
71
71
  "eslint-config-prettier": "^10.1.1",
72
72
  "eslint-plugin-prettier": "^5.2.3",
73
73
  "globals": "^16.0.0",
74
74
  "husky": "^9.1.7",
75
- "jest": "^29.7.0",
76
75
  "jiti": "^2.4.2",
77
76
  "lint-staged": "^15.5.0",
78
77
  "prettier": "^3.5.3",
79
- "ts-jest": "^29.2.6",
80
78
  "ts-node": "^10.9.2",
81
79
  "tsc-watch": "^6.2.1",
82
- "typescript": "^5.3.3"
80
+ "typescript": "^5.3.3",
81
+ "vitest": "^1.4.0"
83
82
  },
84
83
  "scripts": {
85
84
  "dev": "tsc-watch --onSuccess \"node build/index.js --config ./mcp.json\"",
@@ -88,8 +87,8 @@
88
87
  "inspector": "npx -y @modelcontextprotocol/inspector",
89
88
  "lint": "eslint . --ext .ts",
90
89
  "lint:fix": "eslint . --ext .ts --fix",
91
- "test": "jest",
92
- "test:watch": "jest --watch",
93
- "test:coverage": "jest --coverage"
90
+ "test": "vitest run",
91
+ "test:watch": "vitest",
92
+ "test:coverage": "vitest run --coverage"
94
93
  }
95
94
  }
@@ -1,172 +0,0 @@
1
- import { createClients, getClient, executeOperation, executeClientOperation, executeServerOperation, } from './clientManager.js';
2
- import createClientFn from '../client.js';
3
- import logger from '../logger/logger.js';
4
- import { ClientStatus } from '../types.js';
5
- import { ClientConnectionError, ClientNotFoundError, MCPError } from '../utils/errorTypes.js';
6
- import { MCP_SERVER_NAME, CONNECTION_RETRY } from '../constants.js';
7
- // Mock dependencies
8
- jest.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
9
- Client: jest.fn(),
10
- }));
11
- jest.mock('../client.js', () => ({
12
- __esModule: true,
13
- default: jest.fn(),
14
- }));
15
- jest.mock('../logger/logger.js', () => ({
16
- __esModule: true,
17
- default: {
18
- info: jest.fn(),
19
- error: jest.fn(),
20
- },
21
- }));
22
- describe('clientManager', () => {
23
- let mockTransport;
24
- let mockClient;
25
- let mockTransports;
26
- beforeEach(() => {
27
- jest.clearAllMocks();
28
- jest.useFakeTimers();
29
- mockTransport = {
30
- name: 'test-transport',
31
- start: jest.fn(),
32
- send: jest.fn(),
33
- close: jest.fn(),
34
- };
35
- mockClient = {
36
- connect: jest.fn(),
37
- getServerVersion: jest.fn(),
38
- };
39
- mockTransports = {
40
- 'test-client': mockTransport,
41
- };
42
- createClientFn.mockResolvedValue(mockClient);
43
- });
44
- afterEach(() => {
45
- jest.useRealTimers();
46
- });
47
- describe('createClients', () => {
48
- it('should create clients successfully', async () => {
49
- mockClient.connect.mockResolvedValue(undefined);
50
- mockClient.getServerVersion.mockResolvedValue({ name: 'test-server', version: '1.0.0' });
51
- const clientsPromise = createClients(mockTransports);
52
- await jest.runAllTimersAsync();
53
- const clients = await clientsPromise;
54
- expect(clients['test-client']).toBeDefined();
55
- expect(clients['test-client'].status).toBe(ClientStatus.Connected);
56
- expect(clients['test-client'].transport).toBe(mockTransport);
57
- expect(logger.info).toHaveBeenCalledWith('Client created for test-client');
58
- });
59
- it('should handle client connection failure after retries', async () => {
60
- const error = new Error('Connection failed');
61
- mockClient.connect.mockRejectedValue(error);
62
- const clientsPromise = createClients(mockTransports);
63
- // Run through all retry attempts
64
- for (let i = 0; i < CONNECTION_RETRY.MAX_ATTEMPTS; i++) {
65
- await jest.advanceTimersByTimeAsync(CONNECTION_RETRY.INITIAL_DELAY_MS * Math.pow(2, i));
66
- }
67
- const clients = await clientsPromise;
68
- expect(clients['test-client'].status).toBe(ClientStatus.Error);
69
- expect(clients['test-client'].lastError).toBeInstanceOf(ClientConnectionError);
70
- expect(clients['test-client'].lastError?.message).toContain('Connection failed');
71
- expect(mockClient.connect).toHaveBeenCalledTimes(CONNECTION_RETRY.MAX_ATTEMPTS);
72
- });
73
- it('should prevent circular dependency with MCP server', async () => {
74
- mockClient.connect.mockResolvedValue(undefined);
75
- mockClient.getServerVersion.mockResolvedValue({ name: MCP_SERVER_NAME, version: '1.0.0' });
76
- const clientsPromise = createClients(mockTransports);
77
- await jest.runAllTimersAsync();
78
- const clients = await clientsPromise;
79
- expect(clients['test-client'].status).toBe(ClientStatus.Error);
80
- expect(clients['test-client'].lastError).toBeInstanceOf(ClientConnectionError);
81
- expect(clients['test-client'].lastError?.message).toContain('circular dependency');
82
- });
83
- });
84
- describe('getClient', () => {
85
- let clients;
86
- beforeEach(async () => {
87
- clients = await createClients(mockTransports);
88
- });
89
- it('should return client info for existing client', () => {
90
- const clientInfo = getClient(clients, 'test-client');
91
- expect(clientInfo).toBeDefined();
92
- expect(clientInfo.name).toBe('test-client');
93
- });
94
- it('should throw ClientNotFoundError for non-existent client', () => {
95
- expect(() => getClient(clients, 'non-existent')).toThrow(ClientNotFoundError);
96
- });
97
- });
98
- describe('executeOperation', () => {
99
- it('should execute operation successfully', async () => {
100
- const operation = jest.fn().mockResolvedValue('result');
101
- const result = await executeOperation(operation, 'test-context');
102
- expect(result).toBe('result');
103
- expect(operation).toHaveBeenCalled();
104
- });
105
- it('should retry failed operations', async () => {
106
- const error = new Error('Operation failed');
107
- const operation = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce('result');
108
- const operationPromise = executeOperation(operation, 'test-context', { retryCount: 1, retryDelay: 1000 });
109
- // Advance timer by retry delay
110
- await jest.advanceTimersByTimeAsync(1000);
111
- const result = await operationPromise;
112
- expect(result).toBe('result');
113
- expect(operation).toHaveBeenCalledTimes(2);
114
- expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('Retrying operation'));
115
- });
116
- it('should throw error after max retries', async () => {
117
- const error = new Error('Operation failed');
118
- const operation = jest.fn().mockRejectedValue(error);
119
- const operationPromise = executeOperation(operation, 'test-context', { retryCount: 2, retryDelay: 1000 });
120
- // Create rejection assertion before advancing timers
121
- const rejection = expect(operationPromise).rejects.toMatchObject({
122
- message: 'Error executing operation on test-context',
123
- data: { originalError: error },
124
- });
125
- // Advance timer for each retry
126
- for (let i = 0; i < 2; i++) {
127
- await jest.advanceTimersByTimeAsync(1000);
128
- }
129
- // Wait for the rejection assertion
130
- await rejection;
131
- expect(operation).toHaveBeenCalledTimes(3); // Initial try + 2 retries
132
- expect(logger.error).toHaveBeenCalled();
133
- });
134
- });
135
- describe('executeClientOperation', () => {
136
- let clients;
137
- beforeEach(async () => {
138
- clients = await createClients(mockTransports);
139
- });
140
- it('should execute client operation successfully', async () => {
141
- const operation = jest.fn().mockResolvedValue('result');
142
- const result = await executeClientOperation(clients, 'test-client', operation);
143
- expect(result).toBe('result');
144
- expect(operation).toHaveBeenCalledWith(clients['test-client']);
145
- });
146
- it('should throw error for non-existent client', async () => {
147
- const operation = jest.fn();
148
- await expect(executeClientOperation(clients, 'non-existent', operation)).rejects.toThrow(ClientNotFoundError);
149
- });
150
- });
151
- describe('executeServerOperation', () => {
152
- let mockServer;
153
- beforeEach(() => {
154
- mockServer = {
155
- server: {
156
- request: jest.fn().mockResolvedValue('result'),
157
- },
158
- };
159
- });
160
- it('should execute server operation successfully', async () => {
161
- const operation = jest.fn().mockResolvedValue('result');
162
- const result = await executeServerOperation(mockServer, operation);
163
- expect(result).toBe('result');
164
- expect(operation).toHaveBeenCalledWith(mockServer);
165
- });
166
- it('should handle server operation failure', async () => {
167
- const error = new Error('Server operation failed');
168
- const operation = jest.fn().mockRejectedValue(error);
169
- await expect(executeServerOperation(mockServer, operation)).rejects.toThrow(MCPError);
170
- });
171
- });
172
- });
@@ -1,92 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { ConfigManager, ConfigChangeEvent } from './configManager.js';
4
- import { DEFAULT_CONFIG } from '../constants.js';
5
- import { setupCommonJestMocks } from '../test-utils/mocks/commonMocks.js';
6
- import { testConfig } from '../test-utils/fixtures/commonFixtures.js';
7
- import { clearAllMocks, createEventEmitterSpy } from '../test-utils/helpers/testHelpers.js';
8
- // Mock modules
9
- jest.mock('fs', () => ({
10
- existsSync: jest.fn(),
11
- mkdirSync: jest.fn(),
12
- writeFileSync: jest.fn(),
13
- readFileSync: jest.fn(),
14
- watch: jest.fn(() => ({ close: jest.fn() })),
15
- }));
16
- // Setup common mocks
17
- setupCommonJestMocks();
18
- // Mock constants
19
- jest.mock('../constants.js', () => ({
20
- __esModule: true,
21
- DEFAULT_CONFIG: { mcpServers: {} },
22
- getGlobalConfigPath: jest.fn(),
23
- getGlobalConfigDir: jest.fn().mockReturnValue('/test'),
24
- }));
25
- describe('ConfigManager', () => {
26
- const testConfigPath = '/test/config.json';
27
- beforeEach(() => {
28
- // Reset all mocks and timers
29
- clearAllMocks();
30
- // Reset singleton instance
31
- ConfigManager.instance = undefined;
32
- // Default mock implementations
33
- fs.existsSync.mockReturnValue(true);
34
- fs.readFileSync.mockReturnValue(JSON.stringify({ mcpServers: {} }));
35
- });
36
- describe('getInstance', () => {
37
- it('should create singleton instance', () => {
38
- const instance1 = ConfigManager.getInstance(testConfigPath);
39
- const instance2 = ConfigManager.getInstance(testConfigPath);
40
- expect(instance1).toBe(instance2);
41
- });
42
- it('should use provided config path', () => {
43
- const instance = ConfigManager.getInstance(testConfigPath);
44
- expect(instance.configFilePath).toBe(testConfigPath);
45
- });
46
- });
47
- describe('ensureConfigExists', () => {
48
- it('should create config directory and file if they do not exist', () => {
49
- fs.existsSync
50
- .mockReturnValueOnce(false) // Directory doesn't exist
51
- .mockReturnValueOnce(false); // File doesn't exist
52
- ConfigManager.getInstance(testConfigPath);
53
- expect(fs.mkdirSync).toHaveBeenCalledWith(path.dirname(testConfigPath), { recursive: true });
54
- expect(fs.writeFileSync).toHaveBeenCalledWith(testConfigPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
55
- });
56
- });
57
- describe('config watching', () => {
58
- it('should start and stop watching config file', () => {
59
- const instance = ConfigManager.getInstance(testConfigPath);
60
- instance.startWatching();
61
- expect(fs.watch).toHaveBeenCalledWith(testConfigPath, expect.any(Function));
62
- instance.stopWatching();
63
- expect(instance['configWatcher']).toBeNull();
64
- });
65
- it('should reload config on file change', () => {
66
- const instance = ConfigManager.getInstance(testConfigPath);
67
- const mockWatcher = { close: jest.fn() };
68
- let watchCallback = () => { };
69
- fs.watch.mockImplementation((path, callback) => {
70
- watchCallback = callback;
71
- return mockWatcher;
72
- });
73
- instance.startWatching();
74
- // Setup spy for event emission
75
- const emitSpy = createEventEmitterSpy(instance);
76
- // Mock new config data
77
- fs.readFileSync.mockReturnValueOnce(JSON.stringify(testConfig));
78
- // Simulate file change
79
- watchCallback('change', path.basename(testConfigPath));
80
- expect(emitSpy).toHaveBeenCalledWith(ConfigChangeEvent.TRANSPORT_CONFIG_CHANGED, testConfig.mcpServers);
81
- });
82
- });
83
- describe('getTransportConfig', () => {
84
- it('should return copy of transport config', () => {
85
- fs.readFileSync.mockReturnValueOnce(JSON.stringify(testConfig));
86
- const instance = ConfigManager.getInstance(testConfigPath);
87
- const config = instance.getTransportConfig();
88
- expect(config).toEqual(testConfig.mcpServers);
89
- expect(config).not.toBe(instance['transportConfig']); // Should be a copy
90
- });
91
- });
92
- });
@@ -1,150 +0,0 @@
1
- import Transport from 'winston-transport';
2
- // Map Winston log levels to MCP log levels
3
- const LOG_LEVEL_MAP = {
4
- error: 'error',
5
- warn: 'warning',
6
- info: 'info',
7
- verbose: 'debug',
8
- debug: 'debug',
9
- silly: 'debug',
10
- };
11
- /**
12
- * Winston transport for sending logs to MCP clients
13
- */
14
- export class MCPTransport extends Transport {
15
- server;
16
- loggerName;
17
- connected = false;
18
- constructor(options) {
19
- super(options);
20
- this.server = options.server;
21
- this.loggerName = options.loggerName || '1mcp';
22
- // Set up connection status handlers
23
- this.setupConnectionHandlers();
24
- }
25
- /**
26
- * Set up handlers for connection status changes
27
- */
28
- setupConnectionHandlers() {
29
- // Handle connection close
30
- this.server.onclose = () => {
31
- this.setConnected(false);
32
- };
33
- }
34
- /**
35
- * Set the connection status of the transport
36
- * @param connected Whether the server is connected
37
- */
38
- setConnected(connected) {
39
- const previousState = this.connected;
40
- this.connected = connected;
41
- // Log connection state changes
42
- if (previousState !== connected) {
43
- console.debug(`MCP Transport connection state changed to: ${connected ? 'connected' : 'disconnected'}`);
44
- }
45
- }
46
- /**
47
- * Send a log notification to the MCP client
48
- * @param info The log info object
49
- * @returns true if the notification was sent, false otherwise
50
- */
51
- sendLogNotification(info) {
52
- if (!this.server || !this.connected) {
53
- return false;
54
- }
55
- // Map Winston log level to MCP log level
56
- const level = LOG_LEVEL_MAP[info.level] || 'info';
57
- const sanitizedData = this.sanitizeData(info);
58
- // Extract message and metadata
59
- const { message, level: _, ...meta } = sanitizedData;
60
- // Create MCP logging notification
61
- const notification = {
62
- method: 'notifications/message',
63
- params: {
64
- level,
65
- message: typeof message === 'string' ? message : JSON.stringify(message),
66
- logger: this.loggerName,
67
- data: Object.keys(meta).length > 0 ? meta : undefined,
68
- },
69
- };
70
- // Send notification to MCP clients
71
- try {
72
- this.server.notification(notification);
73
- return true;
74
- }
75
- catch (error) {
76
- console.error('Failed to send log notification to MCP client:', error);
77
- return false;
78
- }
79
- }
80
- /**
81
- * Sanitize log data to remove sensitive information
82
- * @param data The data to sanitize
83
- * @returns Sanitized data
84
- */
85
- sanitizeData(data) {
86
- if (typeof data !== 'object' || data === null) {
87
- return data;
88
- }
89
- // List of potentially sensitive field names
90
- const sensitiveFields = [
91
- 'password',
92
- 'token',
93
- 'secret',
94
- 'key',
95
- 'credential',
96
- 'auth',
97
- 'apiKey',
98
- 'api_key',
99
- 'accessToken',
100
- 'access_token',
101
- 'jwt',
102
- 'ssn',
103
- 'creditCard',
104
- 'credit_card',
105
- 'cvv',
106
- 'pin',
107
- ];
108
- if (Array.isArray(data)) {
109
- return data.map((item) => this.sanitizeData(item));
110
- }
111
- const sanitized = {};
112
- for (const [key, value] of Object.entries(data)) {
113
- // Check if this is a sensitive field
114
- const isSensitive = sensitiveFields.some((field) => key.toLowerCase().includes(field.toLowerCase()));
115
- if (isSensitive) {
116
- sanitized[key] = '[REDACTED]';
117
- }
118
- else if (typeof value === 'object' && value !== null) {
119
- sanitized[key] = this.sanitizeData(value);
120
- }
121
- else {
122
- sanitized[key] = value;
123
- }
124
- }
125
- return sanitized;
126
- }
127
- log(info, callback) {
128
- setImmediate(() => {
129
- this.emit('logged', info);
130
- });
131
- // Only try to send if connected
132
- if (this.connected) {
133
- try {
134
- this.sendLogNotification(info);
135
- }
136
- catch (_) {
137
- // Ignore any errors during log sending
138
- }
139
- }
140
- // Always call the callback to prevent blocking
141
- callback();
142
- }
143
- /**
144
- * Clean up resources when the transport is closed
145
- */
146
- close() {
147
- this.setConnected(false);
148
- this.server.onclose = undefined;
149
- }
150
- }
@@ -1,158 +0,0 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { ServerManager } from './serverManager.js';
3
- import logger from './logger/logger.js';
4
- import configReloadService from './services/configReloadService.js';
5
- import { setupCapabilities } from './capabilities/capabilityManager.js';
6
- import { enhanceServerWithLogging } from './middleware/loggingMiddleware.js';
7
- // Mock dependencies
8
- jest.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
9
- Server: jest.fn(),
10
- }));
11
- jest.mock('@modelcontextprotocol/sdk/shared/transport.js', () => ({
12
- Transport: jest.fn(),
13
- }));
14
- jest.mock('./logger/logger.js', () => {
15
- const mockLogger = {
16
- info: jest.fn(),
17
- error: jest.fn(),
18
- };
19
- return {
20
- __esModule: true,
21
- default: mockLogger,
22
- };
23
- });
24
- jest.mock('./services/configReloadService.js', () => ({
25
- __esModule: true,
26
- default: {
27
- initialize: jest.fn(),
28
- },
29
- }));
30
- jest.mock('./capabilities/capabilityManager.js', () => ({
31
- setupCapabilities: jest.fn(),
32
- }));
33
- jest.mock('./middleware/loggingMiddleware.js', () => ({
34
- enhanceServerWithLogging: jest.fn(),
35
- }));
36
- describe('ServerManager', () => {
37
- let mockConfig;
38
- let mockCapabilities;
39
- let mockClients;
40
- let mockTransports;
41
- let mockTransport;
42
- let mockServer;
43
- beforeEach(() => {
44
- // Reset all mocks
45
- jest.clearAllMocks();
46
- // Setup test data
47
- mockConfig = { name: 'test-server', version: '1.0.0' };
48
- mockCapabilities = { capabilities: { test: true } };
49
- mockClients = {};
50
- mockTransports = {};
51
- mockTransport = {
52
- // Add any required Transport properties here
53
- };
54
- mockServer = {
55
- connect: jest.fn().mockResolvedValue(undefined),
56
- transport: mockTransport,
57
- };
58
- // Setup mocks
59
- Server.mockImplementation(() => mockServer);
60
- setupCapabilities.mockResolvedValue(undefined);
61
- enhanceServerWithLogging.mockReturnValue(undefined);
62
- configReloadService.initialize.mockImplementation(() => undefined);
63
- });
64
- describe('getInstance', () => {
65
- it('should create a singleton instance', () => {
66
- const instance1 = ServerManager.getInstance(mockConfig, mockCapabilities, mockClients, mockTransports);
67
- const instance2 = ServerManager.getInstance(mockConfig, mockCapabilities, mockClients, mockTransports);
68
- expect(instance1).toBe(instance2);
69
- });
70
- });
71
- describe('connectTransport', () => {
72
- let serverManager;
73
- const sessionId = 'test-session';
74
- const tags = ['tag1', 'tag2'];
75
- beforeEach(() => {
76
- serverManager = ServerManager.getInstance(mockConfig, mockCapabilities, mockClients, mockTransports);
77
- });
78
- it('should successfully connect a transport', async () => {
79
- await serverManager.connectTransport(mockTransport, sessionId, tags);
80
- expect(Server).toHaveBeenCalledWith(mockConfig, mockCapabilities);
81
- expect(enhanceServerWithLogging).toHaveBeenCalledWith(mockServer);
82
- expect(setupCapabilities).toHaveBeenCalled();
83
- expect(configReloadService.initialize).toHaveBeenCalled();
84
- expect(mockServer.connect).toHaveBeenCalledWith(mockTransport);
85
- expect(logger.info).toHaveBeenCalledWith(`Connected transport for session ${sessionId}`);
86
- });
87
- it('should handle connection errors', async () => {
88
- const error = new Error('Connection failed');
89
- mockServer.connect.mockRejectedValueOnce(error);
90
- await expect(serverManager.connectTransport(mockTransport, sessionId, tags)).rejects.toThrow('Connection failed');
91
- expect(logger.error).toHaveBeenCalled();
92
- });
93
- });
94
- describe('disconnectTransport', () => {
95
- let serverManager;
96
- const sessionId = 'test-session';
97
- beforeEach(() => {
98
- serverManager = ServerManager.getInstance(mockConfig, mockCapabilities, mockClients, mockTransports);
99
- });
100
- it('should successfully disconnect a transport', async () => {
101
- await serverManager.connectTransport(mockTransport, sessionId);
102
- jest.clearAllMocks(); // Clear the logs from connectTransport
103
- serverManager.disconnectTransport(sessionId);
104
- expect(logger.info).toHaveBeenCalledWith(`Disconnected transport for session ${sessionId}`);
105
- });
106
- it('should handle non-existent session gracefully', () => {
107
- serverManager.disconnectTransport('non-existent');
108
- expect(logger.info).not.toHaveBeenCalled();
109
- });
110
- });
111
- describe('transport management methods', () => {
112
- let serverManager;
113
- const sessionId = 'test-session';
114
- beforeEach(async () => {
115
- serverManager = ServerManager.getInstance(mockConfig, mockCapabilities, mockClients, mockTransports);
116
- await serverManager.connectTransport(mockTransport, sessionId);
117
- });
118
- it('should get transport by session id', () => {
119
- const transport = serverManager.getTransport(sessionId);
120
- expect(transport).toBe(mockTransport);
121
- });
122
- it('should return undefined for non-existent session', () => {
123
- const transport = serverManager.getTransport('non-existent');
124
- expect(transport).toBeUndefined();
125
- });
126
- it('should get all transports', () => {
127
- const transports = serverManager.getTransports();
128
- expect(transports.size).toBe(1);
129
- expect(transports.get(sessionId)).toBe(mockTransport);
130
- });
131
- it('should get client transports', () => {
132
- const clientTransports = serverManager.getClientTransports();
133
- expect(clientTransports).toEqual(mockTransports);
134
- });
135
- it('should get active transports count', () => {
136
- expect(serverManager.getActiveTransportsCount()).toBe(1);
137
- });
138
- });
139
- describe('getServer', () => {
140
- let serverManager;
141
- const sessionId = 'test-session';
142
- const tags = ['tag1', 'tag2'];
143
- beforeEach(async () => {
144
- serverManager = ServerManager.getInstance(mockConfig, mockCapabilities, mockClients, mockTransports);
145
- await serverManager.connectTransport(mockTransport, sessionId, tags);
146
- });
147
- it('should return server info for existing session', () => {
148
- const serverInfo = serverManager.getServer(sessionId);
149
- expect(serverInfo).toBeDefined();
150
- expect(serverInfo?.server).toBe(mockServer);
151
- expect(serverInfo?.tags).toEqual(tags);
152
- });
153
- it('should return undefined for non-existent session', () => {
154
- const serverInfo = serverManager.getServer('non-existent');
155
- expect(serverInfo).toBeUndefined();
156
- });
157
- });
158
- });
@@ -1,44 +0,0 @@
1
- import { createMockTransport } from '../mocks/commonMocks.js';
2
- // Common test clients fixture
3
- export const createTestClients = () => ({
4
- client1: {
5
- capabilities: {
6
- resources: {},
7
- tools: {},
8
- },
9
- transport: createMockTransport(),
10
- },
11
- client2: {
12
- capabilities: {
13
- resources: {},
14
- },
15
- transport: createMockTransport(),
16
- },
17
- client3: {
18
- capabilities: {
19
- tools: {},
20
- },
21
- transport: createMockTransport(),
22
- },
23
- clientNoCapabilities: {
24
- transport: createMockTransport(),
25
- },
26
- clientNoTags: {
27
- capabilities: {
28
- resources: {},
29
- },
30
- transport: createMockTransport(),
31
- },
32
- });
33
- // Common test config fixture
34
- export const testConfig = {
35
- mcpServers: {
36
- test: { url: 'test-url' },
37
- },
38
- };
39
- // Common test capabilities fixture
40
- export const testCapabilities = {
41
- resources: {},
42
- tools: {},
43
- test: true,
44
- };