@loop_ouroboros/mcp-hub-lite 1.2.1 → 1.2.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 (82) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -0
  3. package/dist/client/assets/{HomeView-BBwvy1oj.js → HomeView-V1fKvWQ8.js} +1 -1
  4. package/dist/client/assets/{ResourceDetailView-CZ2aB73w.js → ResourceDetailView-DHGHssrh.js} +1 -1
  5. package/dist/client/assets/{ResourcesView-CN1NlhWs.js → ResourcesView-B1bvkmQD.js} +1 -1
  6. package/dist/client/assets/{ServerDashboard-k652Vw4Z.js → ServerDashboard-CZCByd7y.js} +1 -1
  7. package/dist/client/assets/{ServerDetail-BLQ-a4cO.js → ServerDetail-CI5UD8gj.js} +1 -1
  8. package/dist/client/assets/{ServerListView-BHrsFD5i.js → ServerListView-D8qv-xYg.js} +1 -1
  9. package/dist/client/assets/SettingsView-C-ae0-zz.js +1 -0
  10. package/dist/client/assets/{ToolCallDialog-BfPjLxfV.js → ToolCallDialog-BudOyGvS.js} +1 -1
  11. package/dist/client/assets/{ToolsView-CyuhYAE2.js → ToolsView-CbQkgTAu.js} +1 -1
  12. package/dist/client/assets/{_baseClone-DO5qfalW.js → _baseClone-e9R6V78L.js} +1 -1
  13. package/dist/client/assets/{el-form-item-CcGsD2K_.js → el-form-item-Dyx5MiWB.js} +1 -1
  14. package/dist/client/assets/{el-input-tYgeiaCT.js → el-input-CL9HPfdW.js} +1 -1
  15. package/dist/client/assets/{el-loading-Dwl9E_Vr.js → el-loading-2TW6JdfY.js} +1 -1
  16. package/dist/client/assets/{el-overlay-kqX_BABo.js → el-overlay-B5ZGCmXY.js} +1 -1
  17. package/dist/client/assets/{el-radio-group-D8aWBVOT.js → el-radio-group-Cr2ScjjJ.js} +1 -1
  18. package/dist/client/assets/{el-skeleton-item-BRwIFspE.js → el-skeleton-item-CdAfEgVR.js} +1 -1
  19. package/dist/client/assets/{el-switch-BF8c-xeU.js → el-switch-DnN1s0Wb.js} +1 -1
  20. package/dist/client/assets/{el-tab-pane-C4Ep94cd.js → el-tab-pane-BebZh0XF.js} +1 -1
  21. package/dist/client/assets/{el-table-column-Cog6uCh-.js → el-table-column-CV2zp3yI.js} +1 -1
  22. package/dist/client/assets/{index-ByNBhPAR.js → index-Ci5n5dA9.js} +1 -1
  23. package/dist/client/assets/index-DTZ9o3XO.js +2 -0
  24. package/dist/client/assets/{omit-CUnDT6sS.js → omit-DlmW8Yd6.js} +1 -1
  25. package/dist/client/assets/{raf-CmzeRPMd.js → raf-CeCd08aN.js} +1 -1
  26. package/dist/client/index.html +1 -1
  27. package/dist/server/shared/models/server.model.d.ts +22 -0
  28. package/dist/server/shared/models/server.model.d.ts.map +1 -1
  29. package/dist/server/shared/models/server.model.js +41 -5
  30. package/dist/server/src/api/mcp/gateway.d.ts.map +1 -1
  31. package/dist/server/src/api/mcp/gateway.js +13 -5
  32. package/dist/server/src/api/web/resources.d.ts.map +1 -1
  33. package/dist/server/src/api/web/resources.js +26 -2
  34. package/dist/server/src/config/config-loader.js +1 -1
  35. package/dist/server/src/config/config-migrator.d.ts.map +1 -1
  36. package/dist/server/src/config/config-migrator.js +1 -0
  37. package/dist/server/src/index.js +2 -7
  38. package/dist/server/src/server/dev-server.js +9 -48
  39. package/dist/server/src/server/runner.d.ts +12 -24
  40. package/dist/server/src/server/runner.d.ts.map +1 -1
  41. package/dist/server/src/server/runner.js +29 -109
  42. package/dist/server/src/server/startup.d.ts +43 -0
  43. package/dist/server/src/server/startup.d.ts.map +1 -0
  44. package/dist/server/src/server/startup.js +89 -0
  45. package/dist/server/src/services/connection/connection-manager.d.ts +52 -0
  46. package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
  47. package/dist/server/src/services/connection/connection-manager.js +283 -193
  48. package/dist/server/src/services/gateway/gateway.service.d.ts.map +1 -1
  49. package/dist/server/src/services/gateway/gateway.service.js +31 -10
  50. package/dist/server/src/services/gateway/global-transport.d.ts.map +1 -1
  51. package/dist/server/src/services/gateway/global-transport.js +11 -5
  52. package/dist/server/src/services/hub-manager.service.d.ts +2 -0
  53. package/dist/server/src/services/hub-manager.service.d.ts.map +1 -1
  54. package/dist/server/src/services/hub-manager.service.js +3 -16
  55. package/dist/server/src/utils/json-utils.d.ts +7 -0
  56. package/dist/server/src/utils/json-utils.d.ts.map +1 -1
  57. package/dist/server/src/utils/json-utils.js +17 -0
  58. package/dist/server/src/utils/logger/log-modules.d.ts +3 -0
  59. package/dist/server/src/utils/logger/log-modules.d.ts.map +1 -1
  60. package/dist/server/src/utils/logger/log-modules.js +2 -0
  61. package/dist/server/src/utils/port-checker.d.ts +18 -0
  62. package/dist/server/src/utils/port-checker.d.ts.map +1 -1
  63. package/dist/server/src/utils/port-checker.js +38 -0
  64. package/dist/server/src/utils/transports/stdio-transport.d.ts +12 -0
  65. package/dist/server/src/utils/transports/stdio-transport.d.ts.map +1 -1
  66. package/dist/server/src/utils/transports/stdio-transport.js +51 -2
  67. package/dist/server/src/utils/transports/transport-factory.d.ts +5 -1
  68. package/dist/server/src/utils/transports/transport-factory.d.ts.map +1 -1
  69. package/dist/server/src/utils/transports/transport-factory.js +5 -2
  70. package/dist/server/tests/integration/gateway/fault-tolerance.test.js +13 -2
  71. package/dist/server/tests/integration/gateway/mcp-connection.test.js +13 -2
  72. package/dist/server/tests/unit/config/config-migrator.test.js +4 -2
  73. package/dist/server/tests/unit/config/config.schema.test.js +2 -1
  74. package/dist/server/tests/unit/server/runner.test.js +77 -92
  75. package/dist/server/tests/unit/services/hub-manager-service.test.js +3 -2
  76. package/dist/server/tests/unit/utils/config.test.js +14 -7
  77. package/dist/server/tests/unit/utils/json-utils.test.js +28 -14
  78. package/dist/server/vite.config.d.ts.map +1 -1
  79. package/dist/server/vite.config.js +1 -0
  80. package/package.json +1 -1
  81. package/dist/client/assets/SettingsView-CUOFNXrz.js +0 -1
  82. package/dist/client/assets/index-CTB6oe-9.js +0 -2
@@ -7,6 +7,7 @@ import { logStorage } from '../log-storage.service.js';
7
7
  import { eventBus, EventTypes } from '../event-bus.service.js';
8
8
  import { hubManager } from '../hub-manager.service.js';
9
9
  import { MCP_HUB_LITE_SERVER } from '../../models/system-tools.constants.js';
10
+ import { configManager } from '../../config/config-manager.js';
10
11
  import { ToolCache } from './tool-cache.js';
11
12
  import { getCompositeKey } from '../../utils/composite-key.js';
12
13
  /**
@@ -94,212 +95,301 @@ export class McpConnectionManager {
94
95
  * ```
95
96
  */
96
97
  async connect(serverName, serverIndex, server) {
97
- let serverInfo;
98
- // Extract serverId at the very beginning for consistent usage in both try and catch blocks
99
98
  const serverId = server.id || 'unknown';
100
99
  const compositeKey = getCompositeKey(serverName, serverIndex);
101
- try {
102
- // Validate server configuration
103
- if (!server.id) {
104
- throw new Error('Server ID is required');
105
- }
106
- logger.info(`Connecting to server [${compositeKey}] (${serverId})...`, LOG_MODULES.CONNECTION_MANAGER);
107
- // First set starting state (connected: false, no error)
108
- this.serverStatus.set(compositeKey, {
109
- connected: false,
110
- lastCheck: Date.now(),
111
- toolsCount: 0,
112
- resourcesCount: 0
113
- });
114
- // Get server name from server instance ID (via hubManager.getServerById)
115
- serverInfo = hubManager.getServerById(serverId);
116
- if (!serverInfo) {
117
- throw new Error(`Server not found for instance: ${serverId}`);
100
+ const { maxRetries, baseRetryDelay } = this.getRetryConfig();
101
+ // Retry loop for connection attempts
102
+ let lastError;
103
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
104
+ try {
105
+ // 1. Validate server configuration
106
+ this.validateServerConfig(server);
107
+ // 2. Log connection attempt
108
+ this.logConnectionAttempt(compositeKey, attempt, maxRetries);
109
+ // 3. Initialize starting status
110
+ this.initializeServerStatus(compositeKey);
111
+ // 4. Get server info
112
+ const serverInfo = this.getServerInfo(serverId);
113
+ // 5. Create transport and set up callbacks
114
+ const { transport, pid } = this.initializeTransport(server, serverInfo, compositeKey, serverName, serverIndex);
115
+ // 6. Establish client connection
116
+ const client = await this.establishClientConnection(transport);
117
+ // 7. Register connection
118
+ this.registerConnection(compositeKey, serverName, client, transport);
119
+ // 8. Update connected status
120
+ this.updateConnectedStatus(compositeKey, client, pid);
121
+ // 9. Publish connection events
122
+ this.publishConnectionEvents(serverName, serverIndex);
123
+ // 10. Refresh resources
124
+ await this.refreshServerResources(serverName, serverIndex, server.type);
125
+ return true;
118
126
  }
119
- if (server.type === 'stdio' && (!server.command || server.command.trim() === '')) {
120
- throw new Error('STDIO server requires a valid command');
127
+ catch (error) {
128
+ lastError = await this.handleConnectionError(error, compositeKey, attempt, maxRetries, baseRetryDelay);
121
129
  }
122
- if ((server.type === 'sse' || server.type === 'streamable-http' || server.type === 'http') &&
123
- (!server.url || server.url.trim() === '')) {
124
- const displayType = server.type === 'http' ? 'streamable-http' : server.type;
125
- throw new Error(`${displayType.toUpperCase()} server requires a valid URL`);
130
+ }
131
+ // All retries failed
132
+ this.handleFinalFailure(compositeKey, serverName, serverIndex, lastError);
133
+ return false;
134
+ }
135
+ /**
136
+ * Gets retry configuration from system settings.
137
+ */
138
+ getRetryConfig() {
139
+ const maxRetries = configManager.getConfig().system.startup?.maxConnectRetries ?? 3;
140
+ const baseRetryDelay = configManager.getConfig().system.startup?.connectRetryDelay ?? 5000;
141
+ return { maxRetries, baseRetryDelay };
142
+ }
143
+ /**
144
+ * Validates server configuration before connection.
145
+ */
146
+ validateServerConfig(server) {
147
+ if (!server.id) {
148
+ throw new Error('Server ID is required');
149
+ }
150
+ if (server.type === 'stdio' && (!server.command || server.command.trim() === '')) {
151
+ throw new Error('STDIO server requires a valid command');
152
+ }
153
+ if ((server.type === 'sse' || server.type === 'streamable-http' || server.type === 'http') &&
154
+ (!server.url || server.url.trim() === '')) {
155
+ const displayType = server.type === 'http' ? 'streamable-http' : server.type;
156
+ throw new Error(`${displayType.toUpperCase()} server requires a valid URL`);
157
+ }
158
+ }
159
+ /**
160
+ * Logs connection attempt information.
161
+ */
162
+ logConnectionAttempt(compositeKey, attempt, maxRetries) {
163
+ logger.info(`Connecting to server [${compositeKey}] (attempt ${attempt}/${maxRetries})...`, LOG_MODULES.CONNECTION_MANAGER);
164
+ }
165
+ /**
166
+ * Initializes server status to starting state.
167
+ */
168
+ initializeServerStatus(compositeKey) {
169
+ this.serverStatus.set(compositeKey, {
170
+ connected: false,
171
+ lastCheck: Date.now(),
172
+ toolsCount: 0,
173
+ resourcesCount: 0
174
+ });
175
+ }
176
+ /**
177
+ * Gets server info by ID from hub manager.
178
+ */
179
+ getServerInfo(serverId) {
180
+ const serverInfo = hubManager.getServerById(serverId);
181
+ if (!serverInfo) {
182
+ throw new Error(`Server not found for instance: ${serverId}`);
183
+ }
184
+ return serverInfo;
185
+ }
186
+ /**
187
+ * Creates transport and sets up all callbacks.
188
+ */
189
+ initializeTransport(server, serverInfo, compositeKey, serverName, serverIndex) {
190
+ const readyPatterns = server.type === 'stdio' ? (serverInfo.config.template.readyPatterns ?? []) : undefined;
191
+ const readyTimeout = configManager.getConfig().system.startup?.readyTimeout ?? 120000;
192
+ const transport = TransportFactory.createTransport({
193
+ ...server,
194
+ name: serverName
195
+ }, compositeKey, {
196
+ readyPatterns,
197
+ readyTimeout
198
+ });
199
+ // Set up message handler for notifications/message
200
+ transport.onmessage = (message) => {
201
+ if (getMcpCommDebugSetting()) {
202
+ const logMessage = formatMcpMessageForLogging(message);
203
+ logger.debug(`MCP message received: ${logMessage}`, LOG_MODULES.CONNECTION_MANAGER);
126
204
  }
127
- // Create transport based on server type
128
- const transport = TransportFactory.createTransport({
129
- ...server,
130
- name: serverName
131
- }, compositeKey);
132
- // Always set up message handler for notifications/message
133
- transport.onmessage = (message) => {
134
- // Communication debug logs: controlled by MCP_COMM_DEBUG environment variable
135
- if (getMcpCommDebugSetting()) {
205
+ logNotificationMessage(message, serverName, compositeKey);
206
+ };
207
+ // Wrap send method for debug logging (if enabled)
208
+ if (getMcpCommDebugSetting()) {
209
+ const originalSend = transport.send;
210
+ transport.send = async (message, options) => {
211
+ try {
136
212
  const logMessage = formatMcpMessageForLogging(message);
137
- logger.debug(`MCP message received: ${logMessage}`, LOG_MODULES.CONNECTION_MANAGER);
213
+ logger.debug(`MCP message sent: ${logMessage}`, LOG_MODULES.CONNECTION_MANAGER);
138
214
  }
139
- // Log notifications/message to application logs (always enabled)
140
- logNotificationMessage(message, serverName, compositeKey);
215
+ catch {
216
+ logger.debug(`MCP message sent: [Error formatting response]`, LOG_MODULES.CONNECTION_MANAGER);
217
+ }
218
+ return await originalSend.call(transport, message, options);
141
219
  };
142
- // Wrap send method for debug logging (if enabled)
143
- if (getMcpCommDebugSetting()) {
144
- const originalSend = transport.send;
145
- transport.send = async (message, options) => {
146
- try {
147
- const logMessage = formatMcpMessageForLogging(message);
148
- logger.debug(`MCP message sent: ${logMessage}`, LOG_MODULES.CONNECTION_MANAGER);
149
- }
150
- catch {
151
- logger.debug(`MCP message sent: [Error formatting response]`, LOG_MODULES.CONNECTION_MANAGER);
152
- }
153
- // Call original send method
154
- return await originalSend.call(transport, message, options);
155
- };
156
- }
157
- // Handle transport close events
158
- if ('onclose' in transport) {
159
- transport.onclose = () => {
160
- logger.info(`Transport closed for server [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
161
- const currentStatus = this.serverStatus.get(compositeKey);
162
- // Only update status if it was previously connected or starting
163
- if (currentStatus && (currentStatus.connected || !currentStatus.error)) {
164
- this.serverStatus.set(compositeKey, {
165
- connected: false,
166
- lastCheck: Date.now(),
167
- toolsCount: 0,
168
- resourcesCount: 0,
169
- error: 'Connection closed unexpectedly'
170
- });
171
- // Publish server status change event
172
- eventBus.publish(EventTypes.SERVER_STATUS_CHANGE, {
173
- serverName,
174
- serverIndex,
175
- status: 'error',
176
- error: 'Connection closed unexpectedly',
177
- timestamp: Date.now()
178
- });
179
- }
180
- };
181
- }
182
- // Add log listeners
183
- if ('onstdout' in transport) {
184
- transport.onstdout = (data) => {
185
- // Skip JSON-RPC communication to avoid log noise
186
- const trimmedData = data.trim();
187
- if (trimmedData) {
188
- // Check if it's a valid JSON-RPC message
189
- let isJsonRpc = false;
190
- if (trimmedData.startsWith('{')) {
191
- try {
192
- const parsed = JSON.parse(trimmedData);
193
- // Only consider it JSON-RPC if it has valid jsonrpc field
194
- isJsonRpc =
195
- typeof parsed.jsonrpc === 'string' &&
196
- (parsed.jsonrpc === '2.0' || parsed.jsonrpc === '1.0');
197
- }
198
- catch {
199
- // Not valid JSON, treat as log output
200
- isJsonRpc = false;
201
- }
220
+ }
221
+ // Handle transport close events
222
+ if ('onclose' in transport) {
223
+ transport.onclose = () => {
224
+ logger.info(`Transport closed for server [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
225
+ const currentStatus = this.serverStatus.get(compositeKey);
226
+ if (currentStatus && (currentStatus.connected || !currentStatus.error)) {
227
+ this.serverStatus.set(compositeKey, {
228
+ connected: false,
229
+ lastCheck: Date.now(),
230
+ toolsCount: 0,
231
+ resourcesCount: 0,
232
+ error: 'Connection closed unexpectedly'
233
+ });
234
+ eventBus.publish(EventTypes.SERVER_STATUS_CHANGE, {
235
+ serverName,
236
+ serverIndex,
237
+ status: 'error',
238
+ error: 'Connection closed unexpectedly',
239
+ timestamp: Date.now()
240
+ });
241
+ }
242
+ };
243
+ }
244
+ // Add log listeners
245
+ if ('onstdout' in transport) {
246
+ transport.onstdout = (data) => {
247
+ const trimmedData = data.trim();
248
+ if (trimmedData) {
249
+ let isJsonRpc = false;
250
+ if (trimmedData.startsWith('{')) {
251
+ try {
252
+ const parsed = JSON.parse(trimmedData);
253
+ isJsonRpc =
254
+ typeof parsed.jsonrpc === 'string' &&
255
+ (parsed.jsonrpc === '2.0' || parsed.jsonrpc === '1.0');
202
256
  }
203
- if (!isJsonRpc) {
204
- // Use composite key for log storage
205
- logStorage.append(compositeKey, 'info', `[${serverName}] [STDOUT] ${data}`);
257
+ catch {
258
+ isJsonRpc = false;
206
259
  }
207
260
  }
208
- };
209
- }
210
- if ('onstderr' in transport) {
211
- transport.onstderr = (data) => {
212
- // Use composite key for log storage
213
- logStorage.append(compositeKey, 'error', `[${serverName}] [STDERR] ${data}`);
214
- };
215
- }
216
- const client = new Client({
217
- name: MCP_HUB_LITE_SERVER,
218
- version: getAppVersion()
219
- }, {
220
- capabilities: {}
221
- });
222
- await client.connect(transport);
223
- this.clients.set(compositeKey, client);
224
- this.transports.set(compositeKey, transport);
225
- this._toolCache.setNameMapping(serverName, compositeKey);
226
- // Register composite key for this server name
227
- if (!this.serverNameToCompositeKeys.has(serverName)) {
228
- this.serverNameToCompositeKeys.set(serverName, new Set());
229
- }
230
- this.serverNameToCompositeKeys.get(serverName).add(compositeKey);
231
- // Get PID if available (only for stdio transport)
232
- let pid;
233
- if ('pid' in transport && typeof transport.pid === 'number') {
234
- pid = transport.pid;
235
- }
236
- // Get server version
237
- const clientServerInfo = client.getServerVersion();
238
- const serverVersion = clientServerInfo?.version || clientServerInfo?.name;
239
- this.serverStatus.set(compositeKey, {
240
- connected: true,
241
- lastCheck: Date.now(),
242
- toolsCount: 0,
243
- resourcesCount: 0,
244
- pid: pid,
245
- startTime: Date.now(),
246
- version: serverVersion
247
- });
248
- logger.info(`Connected to server [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
249
- // Publish server connected event
250
- eventBus.publish(EventTypes.SERVER_CONNECTED, {
251
- serverName,
252
- serverIndex,
253
- status: 'online',
254
- timestamp: Date.now()
255
- });
256
- // Publish server status change event
257
- eventBus.publish(EventTypes.SERVER_STATUS_CHANGE, {
258
- serverName,
259
- serverIndex,
260
- status: 'online',
261
- timestamp: Date.now()
262
- });
263
- // Fetch tools and resources immediately (only for bidirectional transports)
264
- if (server.type !== 'sse') {
265
- const tools = await this.refreshTools(serverName, serverIndex);
266
- const resources = await this.refreshResources(serverName, serverIndex);
267
- // Publish tools and resources updated event
268
- eventBus.publish(EventTypes.TOOLS_UPDATED, {
269
- serverName,
270
- serverIndex,
271
- tools
272
- });
273
- eventBus.publish(EventTypes.RESOURCES_UPDATED, {
274
- serverName,
275
- serverIndex,
276
- resources
277
- });
278
- }
279
- else {
280
- logger.info('SSE transport is unidirectional, skipping tool/resource refresh', LOG_MODULES.CONNECTION_MANAGER);
281
- }
282
- return true;
261
+ if (!isJsonRpc) {
262
+ logStorage.append(compositeKey, 'info', `[${serverName}] [STDOUT] ${data}`);
263
+ }
264
+ }
265
+ };
283
266
  }
284
- catch (error) {
285
- logger.error(`Failed to connect to server ${compositeKey}:`, error, LOG_MODULES.CONNECTION_MANAGER);
286
- this.serverStatus.set(compositeKey, {
287
- connected: false,
288
- error: error instanceof Error ? error.message : String(error),
289
- lastCheck: Date.now(),
290
- toolsCount: 0,
291
- resourcesCount: 0
292
- });
293
- // Publish server status change event (error state)
294
- eventBus.publish(EventTypes.SERVER_STATUS_CHANGE, {
295
- serverName,
296
- serverIndex,
297
- status: 'error',
298
- error: error instanceof Error ? error.message : String(error),
299
- timestamp: Date.now()
300
- });
301
- return false;
267
+ if ('onstderr' in transport) {
268
+ transport.onstderr = (data) => {
269
+ logStorage.append(compositeKey, 'error', `[${serverName}] [STDERR] ${data}`);
270
+ };
271
+ }
272
+ // Get PID if available (only for stdio transport)
273
+ let pid;
274
+ if ('pid' in transport && typeof transport.pid === 'number') {
275
+ pid = transport.pid;
276
+ }
277
+ return { transport, pid };
278
+ }
279
+ /**
280
+ * Creates Client and connects to transport.
281
+ */
282
+ async establishClientConnection(transport) {
283
+ const client = new Client({
284
+ name: MCP_HUB_LITE_SERVER,
285
+ version: getAppVersion()
286
+ }, {
287
+ capabilities: {}
288
+ });
289
+ await client.connect(transport);
290
+ return client;
291
+ }
292
+ /**
293
+ * Registers client, transport, and updates name mappings.
294
+ */
295
+ registerConnection(compositeKey, serverName, client, transport) {
296
+ this.clients.set(compositeKey, client);
297
+ this.transports.set(compositeKey, transport);
298
+ this._toolCache.setNameMapping(serverName, compositeKey);
299
+ if (!this.serverNameToCompositeKeys.has(serverName)) {
300
+ this.serverNameToCompositeKeys.set(serverName, new Set());
301
+ }
302
+ this.serverNameToCompositeKeys.get(serverName).add(compositeKey);
303
+ }
304
+ /**
305
+ * Updates server status to connected state.
306
+ */
307
+ updateConnectedStatus(compositeKey, client, pid) {
308
+ const clientServerInfo = client.getServerVersion();
309
+ const serverVersion = clientServerInfo?.version || clientServerInfo?.name;
310
+ this.serverStatus.set(compositeKey, {
311
+ connected: true,
312
+ lastCheck: Date.now(),
313
+ toolsCount: 0,
314
+ resourcesCount: 0,
315
+ pid: pid,
316
+ startTime: Date.now(),
317
+ version: serverVersion
318
+ });
319
+ logger.info(`Connected to server [${compositeKey}]`, LOG_MODULES.CONNECTION_MANAGER);
320
+ return serverVersion;
321
+ }
322
+ /**
323
+ * Publishes connection events.
324
+ */
325
+ publishConnectionEvents(serverName, serverIndex) {
326
+ eventBus.publish(EventTypes.SERVER_CONNECTED, {
327
+ serverName,
328
+ serverIndex,
329
+ status: 'online',
330
+ timestamp: Date.now()
331
+ });
332
+ eventBus.publish(EventTypes.SERVER_STATUS_CHANGE, {
333
+ serverName,
334
+ serverIndex,
335
+ status: 'online',
336
+ timestamp: Date.now()
337
+ });
338
+ }
339
+ /**
340
+ * Refreshes server tools and resources (only for bidirectional transports).
341
+ */
342
+ async refreshServerResources(serverName, serverIndex, serverType) {
343
+ if (serverType === 'sse') {
344
+ logger.info('SSE transport is unidirectional, skipping tool/resource refresh', LOG_MODULES.CONNECTION_MANAGER);
345
+ return;
302
346
  }
347
+ const tools = await this.refreshTools(serverName, serverIndex);
348
+ const resources = await this.refreshResources(serverName, serverIndex);
349
+ eventBus.publish(EventTypes.TOOLS_UPDATED, {
350
+ serverName,
351
+ serverIndex,
352
+ tools
353
+ });
354
+ eventBus.publish(EventTypes.RESOURCES_UPDATED, {
355
+ serverName,
356
+ serverIndex,
357
+ resources
358
+ });
359
+ }
360
+ /**
361
+ * Handles connection error with logging and retry delay.
362
+ */
363
+ async handleConnectionError(error, compositeKey, attempt, maxRetries, baseRetryDelay) {
364
+ const lastError = error instanceof Error ? error : new Error(String(error));
365
+ logger.warn(`Connection attempt ${attempt}/${maxRetries} failed for ${compositeKey}: ${lastError.message}`, LOG_MODULES.CONNECTION_MANAGER);
366
+ if (attempt < maxRetries) {
367
+ const retryDelay = baseRetryDelay * Math.pow(2, attempt - 1);
368
+ logger.info(`Retrying connection to ${compositeKey} in ${retryDelay}ms...`, LOG_MODULES.CONNECTION_MANAGER);
369
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
370
+ }
371
+ return lastError;
372
+ }
373
+ /**
374
+ * Handles final failure after all retries exhausted.
375
+ */
376
+ handleFinalFailure(compositeKey, serverName, serverIndex, lastError) {
377
+ const errorMessage = lastError?.message || 'Connection failed after all retries';
378
+ logger.error(`Failed to connect to server ${compositeKey} after retries:`, lastError, LOG_MODULES.CONNECTION_MANAGER);
379
+ this.serverStatus.set(compositeKey, {
380
+ connected: false,
381
+ error: errorMessage,
382
+ lastCheck: Date.now(),
383
+ toolsCount: 0,
384
+ resourcesCount: 0
385
+ });
386
+ eventBus.publish(EventTypes.SERVER_STATUS_CHANGE, {
387
+ serverName,
388
+ serverIndex,
389
+ status: 'error',
390
+ error: errorMessage,
391
+ timestamp: Date.now()
392
+ });
303
393
  }
304
394
  /**
305
395
  * Disconnects from an MCP server and cleans up all associated resources.
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/gateway/gateway.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAcpE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5D,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;;IAOpC,OAAO,CAAC,wBAAwB;IAqBhC,OAAO,CAAC,gBAAgB;IAyCxB;;;;;OAKG;IACI,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC;IAIvF;;;;;OAKG;IACI,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM;IAI5C;;;;;OAKG;IACI,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM;IAIpD;;;;OAIG;IACI,sBAAsB,IAAI,SAAS;IAI1C;;;;OAIG;IACU,KAAK;CAKnB;AAED,eAAO,MAAM,OAAO,gBAAuB,CAAC"}
1
+ {"version":3,"file":"gateway.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/gateway/gateway.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAepE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5D,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;;IAOpC,OAAO,CAAC,wBAAwB;IA2BhC,OAAO,CAAC,gBAAgB;IAuDxB;;;;;OAKG;IACI,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC;IAIvF;;;;;OAKG;IACI,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM;IAI5C;;;;;OAKG;IACI,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM;IAIpD;;;;OAIG;IACI,sBAAsB,IAAI,SAAS;IAI1C;;;;OAIG;IACU,KAAK;CAKnB;AAED,eAAO,MAAM,OAAO,gBAAuB,CAAC"}
@@ -20,6 +20,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
20
20
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
21
21
  import { logger } from '../../utils/index.js';
22
22
  import { LOG_MODULES } from '../../utils/logger/log-modules.js';
23
+ import { getGatewayDebugSetting } from '../../utils/json-utils.js';
23
24
  import { MCP_HUB_LITE_SERVER } from '../../models/system-tools.constants.js';
24
25
  import { registerInitializeHandlers, registerResourcesHandlers, registerSystemToolsHandlers, registerCallToolHandler } from './request-handlers/index.js';
25
26
  import { getAppVersion } from '../../utils/version.js';
@@ -34,7 +35,9 @@ export class GatewayService {
34
35
  this.server = this.createServerWithHandlers();
35
36
  }
36
37
  createServerWithHandlers() {
37
- logger.debug('Creating new MCP server with handlers', LOG_MODULES.GATEWAY_SERVICE);
38
+ if (getGatewayDebugSetting()) {
39
+ logger.debug('Creating new MCP server with handlers', LOG_MODULES.GATEWAY_SERVICE);
40
+ }
38
41
  const server = new McpServer({
39
42
  name: MCP_HUB_LITE_SERVER,
40
43
  version: this.appVersion
@@ -44,19 +47,29 @@ export class GatewayService {
44
47
  resources: {}
45
48
  }
46
49
  });
47
- logger.debug('MCP server created successfully', LOG_MODULES.GATEWAY_SERVICE);
50
+ if (getGatewayDebugSetting()) {
51
+ logger.debug('MCP server created successfully', LOG_MODULES.GATEWAY_SERVICE);
52
+ }
48
53
  this.registerHandlers(server);
49
- logger.debug('Handlers registered successfully on MCP server', LOG_MODULES.GATEWAY_SERVICE);
54
+ if (getGatewayDebugSetting()) {
55
+ logger.debug('Handlers registered successfully on MCP server', LOG_MODULES.GATEWAY_SERVICE);
56
+ }
50
57
  return server;
51
58
  }
52
59
  registerHandlers(server) {
53
- logger.debug('Registering handlers on MCP server', LOG_MODULES.GATEWAY_SERVICE);
60
+ if (getGatewayDebugSetting()) {
61
+ logger.debug('Registering handlers on MCP server', LOG_MODULES.GATEWAY_SERVICE);
62
+ }
54
63
  // Local toolMap for this connection
55
64
  const toolMap = new Map();
56
- logger.debug('Created local toolMap for connection', LOG_MODULES.GATEWAY_SERVICE);
65
+ if (getGatewayDebugSetting()) {
66
+ logger.debug('Created local toolMap for connection', LOG_MODULES.GATEWAY_SERVICE);
67
+ }
57
68
  try {
58
69
  registerInitializeHandlers(server);
59
- logger.debug('Initialize handlers registered successfully', LOG_MODULES.GATEWAY_SERVICE);
70
+ if (getGatewayDebugSetting()) {
71
+ logger.debug('Initialize handlers registered successfully', LOG_MODULES.GATEWAY_SERVICE);
72
+ }
60
73
  }
61
74
  catch (error) {
62
75
  logger.error('Failed to register initialize handlers:', error, LOG_MODULES.GATEWAY_SERVICE);
@@ -64,7 +77,9 @@ export class GatewayService {
64
77
  }
65
78
  try {
66
79
  registerResourcesHandlers(server);
67
- logger.debug('Resources handlers registered successfully', LOG_MODULES.GATEWAY_SERVICE);
80
+ if (getGatewayDebugSetting()) {
81
+ logger.debug('Resources handlers registered successfully', LOG_MODULES.GATEWAY_SERVICE);
82
+ }
68
83
  }
69
84
  catch (error) {
70
85
  logger.error('Failed to register resources handlers:', error, LOG_MODULES.GATEWAY_SERVICE);
@@ -72,7 +87,9 @@ export class GatewayService {
72
87
  }
73
88
  try {
74
89
  registerSystemToolsHandlers(server);
75
- logger.debug('System tools handlers registered successfully', LOG_MODULES.GATEWAY_SERVICE);
90
+ if (getGatewayDebugSetting()) {
91
+ logger.debug('System tools handlers registered successfully', LOG_MODULES.GATEWAY_SERVICE);
92
+ }
76
93
  }
77
94
  catch (error) {
78
95
  logger.error('Failed to register system tools handlers:', error, LOG_MODULES.GATEWAY_SERVICE);
@@ -80,13 +97,17 @@ export class GatewayService {
80
97
  }
81
98
  try {
82
99
  registerCallToolHandler(server, toolMap);
83
- logger.debug('Call tool handler registered successfully', LOG_MODULES.GATEWAY_SERVICE);
100
+ if (getGatewayDebugSetting()) {
101
+ logger.debug('Call tool handler registered successfully', LOG_MODULES.GATEWAY_SERVICE);
102
+ }
84
103
  }
85
104
  catch (error) {
86
105
  logger.error('Failed to register call tool handler:', error, LOG_MODULES.GATEWAY_SERVICE);
87
106
  throw error;
88
107
  }
89
- logger.debug('All handlers registered successfully', LOG_MODULES.GATEWAY_SERVICE);
108
+ if (getGatewayDebugSetting()) {
109
+ logger.debug('All handlers registered successfully', LOG_MODULES.GATEWAY_SERVICE);
110
+ }
90
111
  }
91
112
  /**
92
113
  * Generates a unified list of gateway tools with proper naming and collision resolution.
@@ -1 +1 @@
1
- {"version":3,"file":"global-transport.d.ts","sourceRoot":"","sources":["../../../../../src/services/gateway/global-transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAMnG;;;;;;GAMG;AACH,wBAAsB,sBAAsB;;;GA+D3C"}
1
+ {"version":3,"file":"global-transport.d.ts","sourceRoot":"","sources":["../../../../../src/services/gateway/global-transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAUnG;;;;;;GAMG;AACH,wBAAsB,sBAAsB;;;GAqE3C"}
@@ -7,7 +7,7 @@
7
7
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
8
  import { gateway } from './gateway.service.js';
9
9
  import { logger, LOG_MODULES } from '../../utils/logger/index.js';
10
- import { stringifyForLogging, getMcpCommDebugSetting } from '../../utils/json-utils.js';
10
+ import { stringifyForLogging, getMcpCommDebugSetting, getGatewayDebugSetting } from '../../utils/json-utils.js';
11
11
  import { formatMcpMessageForLogging, logNotificationMessage } from '../../utils/logger/log-output.js';
12
12
  /**
13
13
  * Creates a new MCP transport and server instance for a single request session.
@@ -21,14 +21,16 @@ export async function createSessionTransport() {
21
21
  const server = gateway.createConnectionServer();
22
22
  // Set up message logging (use empty string for sessionId in per-request mode)
23
23
  transport.onmessage = (message) => {
24
- logger.debug(`Session transport onmessage called with: ${stringifyForLogging(message)}`, LOG_MODULES.GATEWAY);
25
24
  try {
26
25
  if (getMcpCommDebugSetting()) {
26
+ logger.debug(`Session transport onmessage called with: ${stringifyForLogging(message)}`, LOG_MODULES.COMMUNICATION);
27
27
  const logMessage = formatMcpMessageForLogging(message);
28
28
  logger.debug(`MCP message received: ${logMessage}`, LOG_MODULES.COMMUNICATION);
29
29
  }
30
30
  logNotificationMessage(message, ''); // Empty sessionId for per-request
31
- logger.debug(`Session transport onmessage completed successfully`, LOG_MODULES.GATEWAY);
31
+ if (getGatewayDebugSetting()) {
32
+ logger.debug(`Session transport onmessage completed successfully`, LOG_MODULES.GATEWAY);
33
+ }
32
34
  }
33
35
  catch (error) {
34
36
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -51,10 +53,14 @@ export async function createSessionTransport() {
51
53
  };
52
54
  }
53
55
  // Connect server to transport
54
- logger.debug('About to connect session server to transport', LOG_MODULES.GATEWAY);
56
+ if (getGatewayDebugSetting()) {
57
+ logger.debug('About to connect session server to transport', LOG_MODULES.GATEWAY);
58
+ }
55
59
  try {
56
60
  await server.connect(transport);
57
- logger.info('MCP session transport initialized (per-request mode)', LOG_MODULES.GATEWAY);
61
+ if (getGatewayDebugSetting()) {
62
+ logger.debug('MCP session transport initialized (per-request mode)', LOG_MODULES.GATEWAY);
63
+ }
58
64
  }
59
65
  catch (error) {
60
66
  const errorMessage = error instanceof Error ? error.message : String(error);