@1mcp/agent 0.1.0 → 0.4.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.
- package/README.md +176 -53
- package/build/capabilities/capabilityManager.js +8 -10
- package/build/client.js +4 -3
- package/build/clients/clientManager.js +47 -12
- package/build/config/configManager.js +31 -9
- package/build/config/configManager.test.js +106 -0
- package/build/config/transportConfig.js +38 -23
- package/build/constants.js +32 -1
- package/build/handlers/notificationHandlers.js +11 -11
- package/build/handlers/requestHandlers.js +81 -52
- package/build/index.js +64 -98
- package/build/{logger.js → logger/logger.js} +7 -20
- package/build/{mcpTransport.js → logger/mcpTransport.js} +33 -5
- package/build/middleware/loggingMiddleware.js +114 -0
- package/build/server/expressServer.js +109 -0
- package/build/server.js +9 -42
- package/build/serverManager.js +78 -0
- package/build/serverManager.test.js +158 -0
- package/build/services/configReloadService.js +21 -26
- package/build/types.js +31 -0
- package/build/utils/clientFiltering.js +29 -0
- package/build/utils/errorHandling.js +67 -44
- package/build/utils/errorTypes.js +47 -0
- package/package.json +26 -4
|
@@ -18,14 +18,30 @@ export class MCPTransport extends Transport {
|
|
|
18
18
|
constructor(options) {
|
|
19
19
|
super(options);
|
|
20
20
|
this.server = options.server;
|
|
21
|
-
this.loggerName = options.loggerName || '1mcp
|
|
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
|
+
};
|
|
22
33
|
}
|
|
23
34
|
/**
|
|
24
35
|
* Set the connection status of the transport
|
|
25
36
|
* @param connected Whether the server is connected
|
|
26
37
|
*/
|
|
27
38
|
setConnected(connected) {
|
|
39
|
+
const previousState = this.connected;
|
|
28
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
|
+
}
|
|
29
45
|
}
|
|
30
46
|
/**
|
|
31
47
|
* Send a log notification to the MCP client
|
|
@@ -112,11 +128,23 @@ export class MCPTransport extends Transport {
|
|
|
112
128
|
setImmediate(() => {
|
|
113
129
|
this.emit('logged', info);
|
|
114
130
|
});
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
}
|
|
118
139
|
}
|
|
119
|
-
|
|
140
|
+
// Always call the callback to prevent blocking
|
|
120
141
|
callback();
|
|
121
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Clean up resources when the transport is closed
|
|
145
|
+
*/
|
|
146
|
+
close() {
|
|
147
|
+
this.setConnected(false);
|
|
148
|
+
this.server.onclose = undefined;
|
|
149
|
+
}
|
|
122
150
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import logger from '../logger/logger.js';
|
|
3
|
+
const activeRequests = new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Logs MCP request details
|
|
6
|
+
*/
|
|
7
|
+
function logRequest(requestId, method, params) {
|
|
8
|
+
logger.info('MCP Request', {
|
|
9
|
+
requestId,
|
|
10
|
+
method,
|
|
11
|
+
params: JSON.stringify(params),
|
|
12
|
+
timestamp: new Date().toISOString(),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Logs MCP response details
|
|
17
|
+
*/
|
|
18
|
+
function logResponse(requestId, result, duration) {
|
|
19
|
+
logger.info('MCP Response', {
|
|
20
|
+
requestId,
|
|
21
|
+
duration,
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Logs MCP error details
|
|
27
|
+
*/
|
|
28
|
+
function logError(requestId, error, duration) {
|
|
29
|
+
logger.error('MCP Error', {
|
|
30
|
+
requestId,
|
|
31
|
+
error: error instanceof Error ? error.message : JSON.stringify(error),
|
|
32
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
33
|
+
duration,
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Logs MCP notification details
|
|
39
|
+
*/
|
|
40
|
+
function logNotification(method, params) {
|
|
41
|
+
logger.info('MCP Notification', {
|
|
42
|
+
method,
|
|
43
|
+
params: JSON.stringify(params),
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Wraps the original request handler with logging
|
|
49
|
+
*/
|
|
50
|
+
function wrapRequestHandler(originalHandler, method) {
|
|
51
|
+
return async (request, extra) => {
|
|
52
|
+
const requestId = uuidv4();
|
|
53
|
+
const startTime = Date.now();
|
|
54
|
+
// Store request context
|
|
55
|
+
activeRequests.set(requestId, {
|
|
56
|
+
requestId,
|
|
57
|
+
method,
|
|
58
|
+
startTime,
|
|
59
|
+
});
|
|
60
|
+
// Log request
|
|
61
|
+
logRequest(requestId, method, request.params);
|
|
62
|
+
try {
|
|
63
|
+
// Execute original handler
|
|
64
|
+
const result = await originalHandler(request, extra);
|
|
65
|
+
// Log response
|
|
66
|
+
const duration = Date.now() - startTime;
|
|
67
|
+
logResponse(requestId, result, duration);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
// Log error
|
|
72
|
+
const duration = Date.now() - startTime;
|
|
73
|
+
logError(requestId, error, duration);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
// Clean up request context
|
|
78
|
+
activeRequests.delete(requestId);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Wraps the original notification handler with logging
|
|
84
|
+
*/
|
|
85
|
+
function wrapNotificationHandler(originalHandler, method) {
|
|
86
|
+
return async (notification) => {
|
|
87
|
+
// Log notification
|
|
88
|
+
logNotification(method, notification.params);
|
|
89
|
+
// Execute original handler
|
|
90
|
+
await originalHandler(notification);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Enhances an MCP server with request/response logging
|
|
95
|
+
*/
|
|
96
|
+
export function enhanceServerWithLogging(server) {
|
|
97
|
+
// Store original methods
|
|
98
|
+
const originalSetRequestHandler = server.setRequestHandler.bind(server);
|
|
99
|
+
const originalSetNotificationHandler = server.setNotificationHandler.bind(server);
|
|
100
|
+
const originalNotification = server.notification.bind(server);
|
|
101
|
+
// Override request handler registration
|
|
102
|
+
server.setRequestHandler = (schema, handler) => {
|
|
103
|
+
return originalSetRequestHandler(schema, wrapRequestHandler(handler, schema._def.shape().method._def.value));
|
|
104
|
+
};
|
|
105
|
+
// Override notification handler registration
|
|
106
|
+
server.setNotificationHandler = (schema, handler) => {
|
|
107
|
+
return originalSetNotificationHandler(schema, wrapNotificationHandler(handler, schema._def.shape().method._def.value));
|
|
108
|
+
};
|
|
109
|
+
// Override notification sending
|
|
110
|
+
server.notification = (notification) => {
|
|
111
|
+
logNotification(notification.method, notification.params);
|
|
112
|
+
return originalNotification(notification);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
3
|
+
import logger, { setMCPTransportConnected } from '../logger/logger.js';
|
|
4
|
+
import { SSE_ENDPOINT, MESSAGES_ENDPOINT, ERROR_CODES } from '../constants.js';
|
|
5
|
+
export class ExpressServer {
|
|
6
|
+
app;
|
|
7
|
+
serverManager;
|
|
8
|
+
constructor(serverManager) {
|
|
9
|
+
this.app = express();
|
|
10
|
+
this.serverManager = serverManager;
|
|
11
|
+
this.setupMiddleware();
|
|
12
|
+
this.setupRoutes();
|
|
13
|
+
}
|
|
14
|
+
setupMiddleware() {
|
|
15
|
+
// Add error handling middleware
|
|
16
|
+
this.app.use((err, req, res, _next) => {
|
|
17
|
+
logger.error('Express error:', err);
|
|
18
|
+
res.status(500).json({
|
|
19
|
+
error: {
|
|
20
|
+
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
|
|
21
|
+
message: 'Internal server error',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
setupRoutes() {
|
|
27
|
+
this.app.get(SSE_ENDPOINT, async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
logger.info('sse', { query: req.query, headers: req.headers });
|
|
30
|
+
const transport = new SSEServerTransport(MESSAGES_ENDPOINT, res);
|
|
31
|
+
// Extract and validate tags from query parameters
|
|
32
|
+
let tags;
|
|
33
|
+
if (req.query.tags) {
|
|
34
|
+
const tagsStr = req.query.tags;
|
|
35
|
+
if (typeof tagsStr !== 'string') {
|
|
36
|
+
res.status(400).json({
|
|
37
|
+
error: {
|
|
38
|
+
code: ERROR_CODES.INVALID_PARAMS,
|
|
39
|
+
message: 'Invalid params: tags must be a string',
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
tags = tagsStr.split(',').filter((tag) => tag.trim().length > 0);
|
|
45
|
+
}
|
|
46
|
+
// Connect the transport using the server manager
|
|
47
|
+
await this.serverManager.connectTransport(transport, transport.sessionId, tags);
|
|
48
|
+
// Update MCP transport connection status when a client connects
|
|
49
|
+
if (this.serverManager.getActiveTransportsCount() === 1) {
|
|
50
|
+
setMCPTransportConnected(true);
|
|
51
|
+
logger.info('First client connected, enabling MCP logging transport');
|
|
52
|
+
}
|
|
53
|
+
transport.onclose = () => {
|
|
54
|
+
this.serverManager.disconnectTransport(transport.sessionId);
|
|
55
|
+
logger.info('transport closed', transport.sessionId);
|
|
56
|
+
// Update MCP transport connection status when all clients disconnect
|
|
57
|
+
if (this.serverManager.getActiveTransportsCount() === 0) {
|
|
58
|
+
setMCPTransportConnected(false);
|
|
59
|
+
logger.info('All clients disconnected, disabling MCP logging transport');
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.error('SSE connection error:', error);
|
|
65
|
+
res.status(500).end();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
this.app.post(MESSAGES_ENDPOINT, async (req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
const sessionId = req.query.sessionId;
|
|
71
|
+
if (!sessionId) {
|
|
72
|
+
res.status(400).json({
|
|
73
|
+
error: {
|
|
74
|
+
code: ERROR_CODES.INVALID_PARAMS,
|
|
75
|
+
message: 'Invalid params: sessionId is required',
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
logger.info('message', { body: req.body, sessionId });
|
|
81
|
+
const transport = this.serverManager.getTransport(sessionId);
|
|
82
|
+
if (transport instanceof SSEServerTransport) {
|
|
83
|
+
await transport.handlePostMessage(req, res);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
res.status(404).json({
|
|
87
|
+
error: {
|
|
88
|
+
code: ERROR_CODES.TRANSPORT_NOT_FOUND,
|
|
89
|
+
message: 'Transport not found',
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
logger.error('Message handling error:', error);
|
|
95
|
+
res.status(500).json({
|
|
96
|
+
error: {
|
|
97
|
+
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
|
|
98
|
+
message: 'Internal server error',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
start(port, host) {
|
|
105
|
+
this.app.listen(port, host, () => {
|
|
106
|
+
logger.info(`Server is running on port ${port} with HTTP/SSE transport`);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
package/build/server.js
CHANGED
|
@@ -1,61 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import logger, { addMCPTransport } from './logger.js';
|
|
1
|
+
import { MCP_SERVER_CAPABILITIES, MCP_SERVER_NAME, MCP_SERVER_VERSION } from './constants.js';
|
|
2
|
+
import logger from './logger/logger.js';
|
|
4
3
|
import { createTransports } from './config/transportConfig.js';
|
|
5
4
|
import { createClients } from './clients/clientManager.js';
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
/**
|
|
9
|
-
* Initialize and configure the MCP server
|
|
10
|
-
* @returns The configured MCP server instance
|
|
11
|
-
*/
|
|
12
|
-
async function initializeServer() {
|
|
13
|
-
try {
|
|
14
|
-
// Create the server instance
|
|
15
|
-
const server = new Server({
|
|
16
|
-
name: MCP_SERVER_NAME,
|
|
17
|
-
version: MCP_SERVER_VERSION,
|
|
18
|
-
}, {
|
|
19
|
-
capabilities: {
|
|
20
|
-
logging: {},
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
// Initialize the MCP transport for logging
|
|
24
|
-
addMCPTransport(server, MCP_SERVER_NAME);
|
|
25
|
-
logger.info('Server created and MCP transport initialized for logging');
|
|
26
|
-
return server;
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
logger.error(`Failed to initialize server: ${error}`);
|
|
30
|
-
throw error;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
5
|
+
import { ServerManager } from './serverManager.js';
|
|
6
|
+
import { ConfigManager } from './config/configManager.js';
|
|
33
7
|
/**
|
|
34
8
|
* Main function to set up the MCP server
|
|
35
9
|
*/
|
|
36
10
|
async function setupServer() {
|
|
37
11
|
try {
|
|
38
|
-
|
|
39
|
-
const server = await initializeServer();
|
|
12
|
+
const mcpConfig = ConfigManager.getInstance().getTransportConfig();
|
|
40
13
|
// Create transports from configuration
|
|
41
|
-
const transports = createTransports();
|
|
14
|
+
const transports = createTransports(mcpConfig);
|
|
42
15
|
logger.info(`Created ${Object.keys(transports).length} transports`);
|
|
43
16
|
// Create clients for each transport
|
|
44
17
|
const clients = await createClients(transports);
|
|
45
18
|
logger.info(`Created ${Object.keys(clients).length} clients`);
|
|
46
|
-
|
|
47
|
-
await collectAndRegisterCapabilities(clients, server);
|
|
48
|
-
// Initialize the configuration reload service
|
|
49
|
-
configReloadService.initialize(server, transports);
|
|
50
|
-
logger.info('Configuration reload service initialized');
|
|
19
|
+
const serverManager = ServerManager.getInstance({ name: MCP_SERVER_NAME, version: MCP_SERVER_VERSION }, { capabilities: MCP_SERVER_CAPABILITIES }, clients, transports);
|
|
51
20
|
logger.info('Server setup completed successfully');
|
|
52
|
-
return
|
|
21
|
+
return serverManager;
|
|
53
22
|
}
|
|
54
23
|
catch (error) {
|
|
55
24
|
logger.error(`Failed to set up server: ${error}`);
|
|
56
25
|
throw error;
|
|
57
26
|
}
|
|
58
27
|
}
|
|
59
|
-
|
|
60
|
-
const server = await setupServer();
|
|
61
|
-
export { server };
|
|
28
|
+
export { setupServer };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import logger from './logger/logger.js';
|
|
3
|
+
import configReloadService from './services/configReloadService.js';
|
|
4
|
+
import { setupCapabilities } from './capabilities/capabilityManager.js';
|
|
5
|
+
import { enhanceServerWithLogging } from './middleware/loggingMiddleware.js';
|
|
6
|
+
export class ServerManager {
|
|
7
|
+
static instance;
|
|
8
|
+
servers = new Map();
|
|
9
|
+
serverConfig;
|
|
10
|
+
serverCapabilities;
|
|
11
|
+
clients = {};
|
|
12
|
+
transports = {};
|
|
13
|
+
constructor(config, capabilities, clients, transports) {
|
|
14
|
+
this.serverConfig = config;
|
|
15
|
+
this.serverCapabilities = capabilities;
|
|
16
|
+
this.clients = clients;
|
|
17
|
+
this.transports = transports;
|
|
18
|
+
}
|
|
19
|
+
static getInstance(config, capabilities, clients, transports) {
|
|
20
|
+
if (!ServerManager.instance) {
|
|
21
|
+
ServerManager.instance = new ServerManager(config, capabilities, clients, transports);
|
|
22
|
+
}
|
|
23
|
+
return ServerManager.instance;
|
|
24
|
+
}
|
|
25
|
+
async connectTransport(transport, sessionId, tags) {
|
|
26
|
+
try {
|
|
27
|
+
// Create a new server instance for this transport
|
|
28
|
+
const server = new Server(this.serverConfig, this.serverCapabilities);
|
|
29
|
+
// Enhance server with logging middleware
|
|
30
|
+
enhanceServerWithLogging(server);
|
|
31
|
+
const serverInfo = {
|
|
32
|
+
server,
|
|
33
|
+
tags,
|
|
34
|
+
};
|
|
35
|
+
// Set up capabilities for this server instance
|
|
36
|
+
await setupCapabilities(this.clients, serverInfo);
|
|
37
|
+
// Initialize the configuration reload service
|
|
38
|
+
configReloadService.initialize(serverInfo, this.transports);
|
|
39
|
+
// Store the server instance
|
|
40
|
+
this.servers.set(sessionId, serverInfo);
|
|
41
|
+
// Connect the transport to the new server instance
|
|
42
|
+
await server.connect(transport);
|
|
43
|
+
logger.info(`Connected transport for session ${sessionId}`);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
logger.error(`Failed to connect transport for session ${sessionId}:`, error);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
disconnectTransport(sessionId) {
|
|
51
|
+
const server = this.servers.get(sessionId);
|
|
52
|
+
if (server) {
|
|
53
|
+
this.servers.delete(sessionId);
|
|
54
|
+
logger.info(`Disconnected transport for session ${sessionId}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
getTransport(sessionId) {
|
|
58
|
+
return this.servers.get(sessionId)?.server.transport;
|
|
59
|
+
}
|
|
60
|
+
getTransports() {
|
|
61
|
+
const transports = new Map();
|
|
62
|
+
for (const [id, server] of this.servers.entries()) {
|
|
63
|
+
if (server.server.transport) {
|
|
64
|
+
transports.set(id, server.server.transport);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return transports;
|
|
68
|
+
}
|
|
69
|
+
getClientTransports() {
|
|
70
|
+
return this.transports;
|
|
71
|
+
}
|
|
72
|
+
getActiveTransportsCount() {
|
|
73
|
+
return this.servers.size;
|
|
74
|
+
}
|
|
75
|
+
getServer(sessionId) {
|
|
76
|
+
return this.servers.get(sessionId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
});
|