@loop_ouroboros/mcp-hub-lite 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +405 -331
  3. package/dist/client/assets/{HomeView-Bi2bkUKf.js → HomeView-DplI3V-h.js} +1 -1
  4. package/dist/client/assets/{ResourceDetailView-DyuSovH9.js → ResourceDetailView-CeHPn99Y.js} +1 -1
  5. package/dist/client/assets/ResourcesView-C1ObRhYS.js +1 -0
  6. package/dist/client/assets/{ServerDashboard-BGyyZAti.js → ServerDashboard-D7wG4Gvt.js} +1 -1
  7. package/dist/client/assets/ServerDetail-G23phOcJ.js +2 -0
  8. package/dist/client/assets/{ServerListView-yQPVJFHG.js → ServerListView-BFiZLtPO.js} +1 -1
  9. package/dist/client/assets/{ServerStatusTags.vue_vue_type_script_setup_true_lang-C8gQlxGE.js → ServerStatusTags.vue_vue_type_script_setup_true_lang-Deb_SbFw.js} +1 -1
  10. package/dist/client/assets/SettingsView-QBFLZ6fP.js +1 -0
  11. package/dist/client/assets/ToolCallDialog-DYS-ADCL.js +1 -0
  12. package/dist/client/assets/ToolsView-DYwgtm7W.js +1 -0
  13. package/dist/client/assets/_baseClone-DQno9YO3.js +1 -0
  14. package/dist/client/assets/{el-form-item-DfWq_kSy.js → el-form-item-DF0zzQdH.js} +2 -2
  15. package/dist/client/assets/el-input-C_p2Qw42.js +1 -0
  16. package/dist/client/assets/el-loading-BaenpNzU.js +1 -0
  17. package/dist/client/assets/el-overlay-MbIUXSQ7.js +1 -0
  18. package/dist/client/assets/el-radio-group-COnCjCcz.js +1 -0
  19. package/dist/client/assets/el-skeleton-item-qj0eQP4s.js +1 -0
  20. package/dist/client/assets/el-switch-BZbXqB3_.js +1 -0
  21. package/dist/client/assets/el-tab-pane-w7RltRLd.js +1 -0
  22. package/dist/client/assets/el-table-column-OD8zhFcD.js +1 -0
  23. package/dist/client/assets/index-DwhULJXZ.js +2 -0
  24. package/dist/client/assets/{index-Bzz3tYbS.css → index-UtsV0Cvh.css} +1 -1
  25. package/dist/client/assets/{omit-BIIebEYo.js → omit-BAJQlviJ.js} +1 -1
  26. package/dist/client/assets/raf-B1Ry7ruA.js +1 -0
  27. package/dist/client/assets/{vue-vendor-Dwcr0jep.js → vue-vendor-ClSvefnQ.js} +1 -1
  28. package/dist/client/index.html +3 -3
  29. package/dist/server/shared/models/constants.d.ts +5 -0
  30. package/dist/server/shared/models/constants.d.ts.map +1 -1
  31. package/dist/server/shared/models/constants.js +4 -0
  32. package/dist/server/shared/models/server.model.d.ts +14 -0
  33. package/dist/server/shared/models/server.model.d.ts.map +1 -1
  34. package/dist/server/shared/models/server.model.js +27 -4
  35. package/dist/server/src/api/mcp/gateway.d.ts +10 -6
  36. package/dist/server/src/api/mcp/gateway.d.ts.map +1 -1
  37. package/dist/server/src/api/mcp/gateway.js +235 -69
  38. package/dist/server/src/api/web/sessions.d.ts +1 -27
  39. package/dist/server/src/api/web/sessions.d.ts.map +1 -1
  40. package/dist/server/src/api/web/sessions.js +8 -97
  41. package/dist/server/src/app.d.ts.map +1 -1
  42. package/dist/server/src/app.js +5 -0
  43. package/dist/server/src/cli/commands/status.js +39 -1
  44. package/dist/server/src/cli/commands/use-guide.d.ts +0 -8
  45. package/dist/server/src/cli/commands/use-guide.d.ts.map +1 -1
  46. package/dist/server/src/cli/commands/use-guide.js +28 -170
  47. package/dist/server/src/cli/server.d.ts +10 -0
  48. package/dist/server/src/cli/server.d.ts.map +1 -1
  49. package/dist/server/src/cli/server.js +31 -1
  50. package/dist/server/src/server/dev-server.js +2 -0
  51. package/dist/server/src/server/runner.d.ts.map +1 -1
  52. package/dist/server/src/server/runner.js +2 -0
  53. package/dist/server/src/services/connection/connection-manager.d.ts +2 -0
  54. package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
  55. package/dist/server/src/services/connection/connection-manager.js +14 -7
  56. package/dist/server/src/services/gateway/gateway.service.d.ts +13 -0
  57. package/dist/server/src/services/gateway/gateway.service.d.ts.map +1 -1
  58. package/dist/server/src/services/gateway/gateway.service.js +72 -0
  59. package/dist/server/src/services/gateway/global-transport.d.ts +20 -10
  60. package/dist/server/src/services/gateway/global-transport.d.ts.map +1 -1
  61. package/dist/server/src/services/gateway/global-transport.js +50 -34
  62. package/dist/server/src/services/gateway/request-handlers/initialize-handler.d.ts.map +1 -1
  63. package/dist/server/src/services/gateway/request-handlers/initialize-handler.js +22 -6
  64. package/dist/server/src/services/gateway/request-handlers/resources-handler.d.ts.map +1 -1
  65. package/dist/server/src/services/gateway/request-handlers/resources-handler.js +5 -1
  66. package/dist/server/src/services/gateway/session-manager.d.ts +101 -0
  67. package/dist/server/src/services/gateway/session-manager.d.ts.map +1 -0
  68. package/dist/server/src/services/gateway/session-manager.js +256 -0
  69. package/dist/server/src/services/hub-tools/resource-generator.d.ts +1 -1
  70. package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
  71. package/dist/server/src/services/hub-tools/resource-generator.js +11 -9
  72. package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
  73. package/dist/server/src/services/hub-tools.service.js +3 -1
  74. package/dist/server/src/utils/json-utils.d.ts +9 -0
  75. package/dist/server/src/utils/json-utils.d.ts.map +1 -1
  76. package/dist/server/src/utils/json-utils.js +19 -0
  77. package/dist/server/src/utils/logger/index.d.ts +1 -1
  78. package/dist/server/src/utils/logger/index.d.ts.map +1 -1
  79. package/dist/server/src/utils/logger/index.js +1 -1
  80. package/dist/server/src/utils/logger/log-context.d.ts +1 -0
  81. package/dist/server/src/utils/logger/log-context.d.ts.map +1 -1
  82. package/dist/server/src/utils/logger/log-formatter.d.ts.map +1 -1
  83. package/dist/server/src/utils/logger/log-formatter.js +25 -11
  84. package/dist/server/src/utils/logger/log-output.d.ts +17 -1
  85. package/dist/server/src/utils/logger/log-output.d.ts.map +1 -1
  86. package/dist/server/src/utils/logger/log-output.js +46 -40
  87. package/dist/server/src/utils/logger/logger.d.ts.map +1 -1
  88. package/dist/server/src/utils/logger/logger.js +18 -2
  89. package/dist/server/src/utils/request-context.d.ts +8 -70
  90. package/dist/server/src/utils/request-context.d.ts.map +1 -1
  91. package/dist/server/src/utils/request-context.js +11 -70
  92. package/dist/server/tests/unit/config/config.schema.test.js +2 -1
  93. package/dist/server/tests/unit/server/runner.test.js +14 -7
  94. package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts +2 -0
  95. package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts.map +1 -0
  96. package/dist/server/tests/unit/services/gateway-session-mode.test.js +174 -0
  97. package/dist/server/tests/unit/services/hub-tools.service.test.js +4 -4
  98. package/dist/server/tests/unit/utils/config.test.js +14 -7
  99. package/dist/server/tests/unit/utils/log-output.test.d.ts +2 -0
  100. package/dist/server/tests/unit/utils/log-output.test.d.ts.map +1 -0
  101. package/dist/server/tests/unit/utils/log-output.test.js +198 -0
  102. package/dist/server/vitest.config.d.ts.map +1 -1
  103. package/dist/server/vitest.config.js +0 -2
  104. package/package.json +1 -1
  105. package/dist/client/assets/ResourcesView-CU0VbNy5.js +0 -1
  106. package/dist/client/assets/ServerDetail-bcQ8BVXR.js +0 -2
  107. package/dist/client/assets/SettingsView-B1DxbFP3.js +0 -1
  108. package/dist/client/assets/ToolCallDialog-DEapCO06.js +0 -1
  109. package/dist/client/assets/ToolsView-DA0u_bCw.js +0 -1
  110. package/dist/client/assets/_baseClone-B991Lvrt.js +0 -1
  111. package/dist/client/assets/el-input-5YzZrwir.js +0 -1
  112. package/dist/client/assets/el-loading-DE3FcxNH.js +0 -1
  113. package/dist/client/assets/el-overlay-BTeTueuN.js +0 -1
  114. package/dist/client/assets/el-radio-group-Y1E2bxIW.js +0 -1
  115. package/dist/client/assets/el-skeleton-item-DhgR50Jx.js +0 -1
  116. package/dist/client/assets/el-switch-fF--nMSD.js +0 -1
  117. package/dist/client/assets/el-tab-pane-rvS_KTwP.js +0 -1
  118. package/dist/client/assets/el-table-column-B1O8mY47.js +0 -1
  119. package/dist/client/assets/index-DkqV9kH4.js +0 -2
  120. package/dist/client/assets/raf-Cj-gATZv.js +0 -1
@@ -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;AAsBpE,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;;IAQpC,OAAO,CAAC,aAAa;IA0BrB,OAAO,CAAC,wBAAwB;IA2BhC,OAAO,CAAC,gBAAgB;IAkDxB;;;;;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;AAwBpE,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;;IASpC,OAAO,CAAC,aAAa;IA0BrB,OAAO,CAAC,wBAAwB;IA2BhC,OAAO,CAAC,gBAAgB;IAkDxB;;;;;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;;OAEG;IACI,SAAS,IAAI,SAAS;IAI7B;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAgEzB;;;;OAIG;IACU,KAAK;CAKnB;AAED,eAAO,MAAM,OAAO,gBAAuB,CAAC"}
@@ -26,6 +26,8 @@ import { registerInitializeHandlers, registerResourcesHandlers, registerSystemTo
26
26
  import { getAppVersion } from '../../utils/version.js';
27
27
  import { eventBus, EventTypes } from '../event-bus.service.js';
28
28
  import { hubManager } from '../hub-manager.service.js';
29
+ import { mcpConnectionManager } from '../connection/index.js';
30
+ import { sessionManager } from './session-manager.js';
29
31
  import { generateGatewayToolsList, rebuildFromScratch, addToCache, removeFromCache } from './tool-list-generator.js';
30
32
  import { formatToolArgs, formatToolResponse } from './log-formatter.js';
31
33
  export class GatewayService {
@@ -36,6 +38,7 @@ export class GatewayService {
36
38
  this.appVersion = getAppVersion();
37
39
  this.server = this.createServerWithHandlers();
38
40
  this.initToolCache();
41
+ this.initNotifications();
39
42
  }
40
43
  initToolCache() {
41
44
  rebuildFromScratch();
@@ -166,6 +169,75 @@ export class GatewayService {
166
169
  createConnectionServer() {
167
170
  return this.createServerWithHandlers();
168
171
  }
172
+ /**
173
+ * Returns the singleton McpServer instance used for HTTP transport.
174
+ */
175
+ getServer() {
176
+ return this.server;
177
+ }
178
+ /**
179
+ * Subscribes to EventBus events and pushes MCP notifications
180
+ * (notifications/resources/list_changed, notifications/tools/list_changed)
181
+ * to connected SSE clients via the singleton transport.
182
+ *
183
+ * Tools are aggregated by ServerName (multi-instance deduplication).
184
+ * Resources are per-instance (Category 1: hub://servers/{name}, Category 2: hub://servers/{name}/{idx}/{path}).
185
+ */
186
+ initNotifications() {
187
+ // --- Resources list_changed ---
188
+ // Category 1: new/removed server config
189
+ eventBus.subscribe(EventTypes.SERVER_ADDED, () => {
190
+ sessionManager.broadcastNotification('resources');
191
+ });
192
+ eventBus.subscribe(EventTypes.SERVER_DELETED, () => {
193
+ sessionManager.broadcastNotification('resources');
194
+ });
195
+ // Category 1 + 2: any instance connect/disconnect changes resource list
196
+ eventBus.subscribe(EventTypes.SERVER_CONNECTED, () => {
197
+ sessionManager.broadcastNotification('resources');
198
+ });
199
+ eventBus.subscribe(EventTypes.SERVER_DISCONNECTED, () => {
200
+ sessionManager.broadcastNotification('resources');
201
+ });
202
+ // Category 2: backend server resources refreshed
203
+ eventBus.subscribe(EventTypes.RESOURCES_UPDATED, () => {
204
+ sessionManager.broadcastNotification('resources');
205
+ });
206
+ // --- Tools list_changed ---
207
+ // Only notify when the first instance connects or last instance disconnects
208
+ eventBus.subscribe(EventTypes.SERVER_CONNECTED, (rawData) => {
209
+ const data = rawData;
210
+ const indexes = mcpConnectionManager.getConnectedIndexes(data.serverName);
211
+ if (indexes.length === 1) {
212
+ const serverConfig = hubManager.getServerByName(data.serverName);
213
+ if (serverConfig?.template?.aggregatedTools?.length) {
214
+ sessionManager.broadcastNotification('tools');
215
+ }
216
+ }
217
+ });
218
+ eventBus.subscribe(EventTypes.SERVER_DISCONNECTED, (rawData) => {
219
+ const data = rawData;
220
+ const indexes = mcpConnectionManager.getConnectedIndexes(data.serverName);
221
+ if (indexes.length === 0) {
222
+ const serverConfig = hubManager.getServerByName(data.serverName);
223
+ if (serverConfig?.template?.aggregatedTools?.length) {
224
+ sessionManager.broadcastNotification('tools');
225
+ }
226
+ }
227
+ });
228
+ // Backend server tools refreshed while connected
229
+ eventBus.subscribe(EventTypes.TOOLS_UPDATED, (rawData) => {
230
+ const data = rawData;
231
+ const serverConfig = hubManager.getServerByName(data.serverName);
232
+ if (serverConfig?.template?.aggregatedTools?.length) {
233
+ sessionManager.broadcastNotification('tools');
234
+ }
235
+ });
236
+ // Aggregated tools config changed
237
+ eventBus.subscribe(EventTypes.AGGREGATED_TOOLS_CHANGED, () => {
238
+ sessionManager.broadcastNotification('tools');
239
+ });
240
+ }
169
241
  /**
170
242
  * Starts the MCP gateway service on stdio transport.
171
243
  *
@@ -1,19 +1,29 @@
1
1
  /**
2
- * MCP transport factory for per-request transport instances.
2
+ * MCP transport utilities stateful session-based mode.
3
3
  *
4
- * This module provides a factory function to create isolated MCP transport and server instances
5
- * for each HTTP request, ensuring proper state isolation between concurrent clients.
4
+ * Each client session owns a StreamableHTTPServerTransport + McpServer pair.
5
+ * SDK's stateful mode (sessionIdGenerator) handles SSE stream setup and session management.
6
+ * Notifications are broadcast via SessionManager across all active sessions.
6
7
  */
7
8
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
9
  /**
9
- * Creates a new MCP transport and server instance for a single request session.
10
- * Each call returns isolated instances that should be cleaned up after the request completes.
11
- *
12
- * @returns {Promise<{ transport: StreamableHTTPServerTransport, server: import('@modelcontextprotocol/sdk/server/mcp.js').McpServer }>}
13
- * Object containing the transport and server instances
10
+ * Sets up debug logging on a transport instance.
11
+ */
12
+ export declare function setupTransportLogging(transport: StreamableHTTPServerTransport): void;
13
+ /**
14
+ * Initializes the global transport layer.
15
+ * With stateful session-based transport, this is a no-op.
16
+ * Kept for backward compatibility with app.ts startup sequence.
17
+ */
18
+ export declare function initGlobalTransport(): void;
19
+ /**
20
+ * Creates a new MCP transport and server instance for a single request (stateless mode).
21
+ * Each call returns isolated instances that should be GC'd after the request completes.
22
+ * This restores the v1.3.0 per-request transport pattern for clients like CherryStudio
23
+ * that do not properly support stateful sessions.
14
24
  */
15
- export declare function createSessionTransport(): Promise<{
25
+ export declare function createPerRequestTransport(): Promise<{
16
26
  transport: StreamableHTTPServerTransport;
17
- server: import("@modelcontextprotocol/sdk/server/mcp").McpServer;
27
+ server: import('@modelcontextprotocol/sdk/server/mcp.js').McpServer;
18
28
  }>;
19
29
  //# sourceMappingURL=global-transport.d.ts.map
@@ -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;AAUnG;;;;;;GAMG;AACH,wBAAsB,sBAAsB;;;GAqE3C"}
1
+ {"version":3,"file":"global-transport.d.ts","sourceRoot":"","sources":["../../../../../src/services/gateway/global-transport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAuBnG;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAmCpF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC;IACzD,SAAS,EAAE,6BAA6B,CAAC;IACzC,MAAM,EAAE,OAAO,yCAAyC,EAAE,SAAS,CAAC;CACrE,CAAC,CAWD"}
@@ -1,72 +1,88 @@
1
1
  /**
2
- * MCP transport factory for per-request transport instances.
2
+ * MCP transport utilities stateful session-based mode.
3
3
  *
4
- * This module provides a factory function to create isolated MCP transport and server instances
5
- * for each HTTP request, ensuring proper state isolation between concurrent clients.
4
+ * Each client session owns a StreamableHTTPServerTransport + McpServer pair.
5
+ * SDK's stateful mode (sessionIdGenerator) handles SSE stream setup and session management.
6
+ * Notifications are broadcast via SessionManager across all active sessions.
6
7
  */
7
8
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
- import { gateway } from './gateway.service.js';
9
9
  import { logger, LOG_MODULES } from '../../utils/logger/index.js';
10
10
  import { stringifyForLogging, getMcpCommDebugSetting, getGatewayDebugSetting } from '../../utils/json-utils.js';
11
11
  import { formatMcpMessageForLogging, logNotificationMessage } from '../../utils/logger/log-output.js';
12
+ import { gateway } from './gateway.service.js';
12
13
  /**
13
- * Creates a new MCP transport and server instance for a single request session.
14
- * Each call returns isolated instances that should be cleaned up after the request completes.
15
- *
16
- * @returns {Promise<{ transport: StreamableHTTPServerTransport, server: import('@modelcontextprotocol/sdk/server/mcp.js').McpServer }>}
17
- * Object containing the transport and server instances
14
+ * Build log options from trace context stored on transport.
15
+ * Trace context is injected by gateway.ts before handleRequest() as an ALS fallback
16
+ * because the MCP SDK defers send()/onmessage calls and ALS context is lost.
18
17
  */
19
- export async function createSessionTransport() {
20
- const transport = new StreamableHTTPServerTransport();
21
- const server = gateway.createConnectionServer();
22
- // Set up message logging (use empty string for sessionId in per-request mode)
18
+ function traceLogOpts(transport) {
19
+ const ctx = transport;
20
+ const sid = ctx._traceSessionId;
21
+ const tid = ctx._traceId;
22
+ if (!sid && !tid)
23
+ return LOG_MODULES.COMMUNICATION;
24
+ return { module: 'Communication', ...(sid && { sessionId: sid }), ...(tid && { traceId: tid }) };
25
+ }
26
+ /**
27
+ * Sets up debug logging on a transport instance.
28
+ */
29
+ export function setupTransportLogging(transport) {
23
30
  transport.onmessage = (message) => {
24
31
  try {
25
32
  if (getMcpCommDebugSetting()) {
26
- logger.debug(`Session transport onmessage called with: ${stringifyForLogging(message)}`, LOG_MODULES.COMMUNICATION);
33
+ const opts = traceLogOpts(transport);
34
+ logger.debug(`Transport onmessage: ${stringifyForLogging(message)}`, opts);
27
35
  const logMessage = formatMcpMessageForLogging(message);
28
- logger.debug(`MCP message received: ${logMessage}`, LOG_MODULES.COMMUNICATION);
36
+ logger.debug(`MCP message received: ${logMessage}`, opts);
29
37
  }
30
- logNotificationMessage(message, ''); // Empty sessionId for per-request
38
+ logNotificationMessage(message, '');
31
39
  if (getGatewayDebugSetting()) {
32
- logger.debug(`Session transport onmessage completed successfully`, LOG_MODULES.GATEWAY);
40
+ logger.debug('Transport onmessage completed', LOG_MODULES.GATEWAY);
33
41
  }
34
42
  }
35
43
  catch (error) {
36
44
  const errorMessage = error instanceof Error ? error.message : String(error);
37
- logger.error(`Error in session transport onmessage handler: ${errorMessage}`, LOG_MODULES.GATEWAY);
45
+ logger.error(`Error in transport onmessage: ${errorMessage}`, LOG_MODULES.GATEWAY);
38
46
  logger.error(`Message that caused error: ${stringifyForLogging(message)}`, LOG_MODULES.GATEWAY);
39
47
  }
40
48
  };
41
- // Wrap send method for debug logging
42
49
  if (getMcpCommDebugSetting()) {
43
50
  const originalSend = transport.send;
44
51
  transport.send = async (message, options) => {
45
52
  try {
46
53
  const logMessage = formatMcpMessageForLogging(message);
47
- logger.debug(`MCP message sent: ${logMessage}`, LOG_MODULES.COMMUNICATION);
54
+ logger.debug(`MCP message sent: ${logMessage}`, traceLogOpts(transport));
48
55
  }
49
56
  catch {
50
- logger.debug(`MCP message sent: [Error formatting response]`, LOG_MODULES.COMMUNICATION);
57
+ logger.debug('MCP message sent: [Error formatting response]', LOG_MODULES.COMMUNICATION);
51
58
  }
52
59
  return await originalSend.call(transport, message, options);
53
60
  };
54
61
  }
55
- // Connect server to transport
62
+ }
63
+ /**
64
+ * Initializes the global transport layer.
65
+ * With stateful session-based transport, this is a no-op.
66
+ * Kept for backward compatibility with app.ts startup sequence.
67
+ */
68
+ export function initGlobalTransport() {
56
69
  if (getGatewayDebugSetting()) {
57
- logger.debug('About to connect session server to transport', LOG_MODULES.GATEWAY);
58
- }
59
- try {
60
- await server.connect(transport);
61
- if (getGatewayDebugSetting()) {
62
- logger.debug('MCP session transport initialized (per-request mode)', LOG_MODULES.GATEWAY);
63
- }
70
+ logger.debug('Global transport layer initialized (stateful session mode)', LOG_MODULES.GATEWAY);
64
71
  }
65
- catch (error) {
66
- const errorMessage = error instanceof Error ? error.message : String(error);
67
- logger.error(`Failed to connect session server to transport: ${errorMessage}`, LOG_MODULES.GATEWAY);
68
- logger.error(`Transport connection error details: ${stringifyForLogging(error)}`, LOG_MODULES.GATEWAY);
69
- throw error;
72
+ }
73
+ /**
74
+ * Creates a new MCP transport and server instance for a single request (stateless mode).
75
+ * Each call returns isolated instances that should be GC'd after the request completes.
76
+ * This restores the v1.3.0 per-request transport pattern for clients like CherryStudio
77
+ * that do not properly support stateful sessions.
78
+ */
79
+ export async function createPerRequestTransport() {
80
+ const transport = new StreamableHTTPServerTransport();
81
+ const server = gateway.createConnectionServer();
82
+ setupTransportLogging(transport);
83
+ await server.connect(transport);
84
+ if (getGatewayDebugSetting()) {
85
+ logger.debug('MCP per-request transport initialized (stateless mode)', LOG_MODULES.GATEWAY);
70
86
  }
71
87
  return { transport, server };
72
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"initialize-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/services/gateway/request-handlers/initialize-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYpE;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsElE"}
1
+ {"version":3,"file":"initialize-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/services/gateway/request-handlers/initialize-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAapE;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuFlE"}
@@ -1,7 +1,8 @@
1
1
  import { logger, LOG_MODULES } from '../../../utils/logger/index.js';
2
- import { getGatewayDebugSetting } from '../../../utils/json-utils.js';
2
+ import { getGatewayDebugSetting, stringifyForLogging } from '../../../utils/json-utils.js';
3
3
  import { MCP_HUB_LITE_SERVER } from '../../../models/system-tools.constants.js';
4
4
  import { getAppVersion, getProtocolVersion } from '../../../utils/version.js';
5
+ import { sessionManager } from '../../gateway/session-manager.js';
5
6
  import { InitializedNotificationSchema, InitializeRequestSchema, PingRequestSchema } from './initialize.constants.js';
6
7
  /**
7
8
  * Register initialize and ping handlers on the MCP server.
@@ -14,6 +15,11 @@ export function registerInitializeHandlers(server) {
14
15
  const { name, version } = request.params.clientInfo;
15
16
  const protocolVersion = request.params?.protocolVersion || getProtocolVersion();
16
17
  const clientCapabilities = request.params?.capabilities;
18
+ sessionManager.storePendingClientMetadata(server, {
19
+ clientName: name,
20
+ clientVersion: version,
21
+ protocolVersion
22
+ });
17
23
  if (getGatewayDebugSetting()) {
18
24
  logger.debug(`Initialized client: Name=${name}, Version=${version}, ProtocolVersion=${protocolVersion}`, LOG_MODULES.GATEWAY);
19
25
  }
@@ -30,15 +36,25 @@ export function registerInitializeHandlers(server) {
30
36
  capabilities: {
31
37
  tools: {
32
38
  list: true,
33
- execute: true
39
+ execute: true,
40
+ listChanged: true
34
41
  },
35
42
  resources: {
36
43
  list: true,
37
- read: true
44
+ read: true,
45
+ listChanged: true
38
46
  },
39
47
  logging: {},
40
48
  experimental: {}
41
- }
49
+ },
50
+ instructions: 'MCP Hub Lite is a lightweight MCP gateway that aggregates multiple backend MCP servers into a unified interface. ' +
51
+ 'HOW TO USE: 1) Start with resources/list to discover available servers and the use guide. ' +
52
+ '2) Use list_servers to see all connected servers. ' +
53
+ '3) Preview a server’s tools via resources/read on hub://servers/{name}. ' +
54
+ '4) Use list_tools for the full tool list, get_tool when you need inputSchema, then call_tool to execute. ' +
55
+ '5) Use search_tools to find tools by name or description across all servers. ' +
56
+ 'MULTI-INSTANCE SERVERS: Use list_tags to view available instance tags, then pass matching tags via requestOptions.tags when calling call_tool. ' +
57
+ 'System tools (list_servers, list_tools, get_tool, call_tool, search_tools, list_tags, update_server_description) must be called directly, not through call_tool.'
42
58
  };
43
59
  });
44
60
  server.server.setRequestHandler(PingRequestSchema, async () => {
@@ -47,7 +63,7 @@ export function registerInitializeHandlers(server) {
47
63
  server.server.setNotificationHandler(InitializedNotificationSchema, async (notification) => {
48
64
  if (getGatewayDebugSetting()) {
49
65
  logger.debug('Received initialized notification from client', LOG_MODULES.GATEWAY);
50
- logger.debug(`Initialized notification details: ${JSON.stringify(notification)}`, LOG_MODULES.GATEWAY);
66
+ logger.debug(`Initialized notification details: ${stringifyForLogging(notification)}`, LOG_MODULES.GATEWAY);
51
67
  }
52
68
  try {
53
69
  // Process the notification
@@ -58,7 +74,7 @@ export function registerInitializeHandlers(server) {
58
74
  catch (error) {
59
75
  const errorMessage = error instanceof Error ? error.message : String(error);
60
76
  logger.error(`Error processing initialized notification: ${errorMessage}`, LOG_MODULES.GATEWAY);
61
- logger.error(`Notification that caused error: ${JSON.stringify(notification)}`, LOG_MODULES.GATEWAY);
77
+ logger.error(`Notification that caused error: ${stringifyForLogging(notification)}`, LOG_MODULES.GATEWAY);
62
78
  throw error; // Re-throw to see if this is the source of the problem
63
79
  }
64
80
  });
@@ -1 +1 @@
1
- {"version":3,"file":"resources-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/services/gateway/request-handlers/resources-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiDjE"}
1
+ {"version":3,"file":"resources-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/services/gateway/request-handlers/resources-handler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsDjE"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Resources-related request handlers for Gateway service.
3
3
  */
4
- import { ListResourcesRequestSchema, ReadResourceRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
4
+ import { ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { logger } from '../../../utils/index.js';
6
6
  import { LOG_MODULES } from '../../../utils/logger/log-modules.js';
7
7
  import { hubToolsService } from '../../hub-tools.service.js';
@@ -11,6 +11,10 @@ import { hubToolsService } from '../../hub-tools.service.js';
11
11
  * @param server - MCP server instance to register handlers on
12
12
  */
13
13
  export function registerResourcesHandlers(server) {
14
+ // Resource templates — gateway does not use templates, return empty list
15
+ server.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
16
+ return { resourceTemplates: [] };
17
+ });
14
18
  server.server.setRequestHandler(ListResourcesRequestSchema, async () => {
15
19
  try {
16
20
  const resources = await hubToolsService.listResources();
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Session Manager for stateful Streamable HTTP MCP transport.
3
+ *
4
+ * Each client session owns a StreamableHTTPServerTransport + McpServer pair.
5
+ * Sessions are identified by mcp-session-id header (generated by the SDK).
6
+ * Notifications are broadcast to all active sessions via their McpServer instances.
7
+ */
8
+ import type { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
9
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
+ interface SessionState {
11
+ sessionId: string;
12
+ transport: StreamableHTTPServerTransport;
13
+ server: McpServer;
14
+ createdAt: number;
15
+ lastAccessedAt: number;
16
+ isClosing: boolean;
17
+ activeSseCount: number;
18
+ lastPingedAt: number;
19
+ pingPending: boolean;
20
+ clientName?: string;
21
+ clientVersion?: string;
22
+ protocolVersion?: string;
23
+ createdByMethod?: string;
24
+ lastMethod?: string;
25
+ }
26
+ export declare class SessionManager {
27
+ private sessions;
28
+ private cleanupTimer;
29
+ private pendingClientMetadata;
30
+ /**
31
+ * Registers a newly created session.
32
+ */
33
+ addSession(sessionId: string, transport: StreamableHTTPServerTransport, server: McpServer): void;
34
+ /**
35
+ * Looks up an existing session by ID and updates lastAccessedAt.
36
+ */
37
+ getSession(sessionId: string): SessionState | undefined;
38
+ /**
39
+ * Marks an SSE stream as opened for the given session.
40
+ * Prevents the session from being cleaned up while the stream is active.
41
+ */
42
+ markSseOpened(sessionId: string): void;
43
+ /**
44
+ * Marks an SSE stream as closed for the given session.
45
+ */
46
+ markSseClosed(sessionId: string): void;
47
+ /**
48
+ * Removes a session (called on DELETE or onsessionclosed).
49
+ */
50
+ removeSession(sessionId: string): void;
51
+ private pendingBroadcasts;
52
+ private readonly BROADCAST_DEBOUNCE_MS;
53
+ /**
54
+ * Broadcasts a notification to all active sessions.
55
+ * Debounced: same-type notifications within BROADCAST_DEBOUNCE_MS are coalesced.
56
+ * @param method - 'tools' for sendToolListChanged, 'resources' for sendResourceListChanged
57
+ */
58
+ broadcastNotification(method: 'tools' | 'resources'): void;
59
+ /**
60
+ * Removes sessions that have been idle longer than SESSION_TIMEOUT_MS.
61
+ */
62
+ private cleanupStaleSessions;
63
+ private ensureCleanupTimer;
64
+ /**
65
+ * Shuts down all sessions. Called on server shutdown.
66
+ */
67
+ shutdownAll(): void;
68
+ /** Returns the current session count (for diagnostics). */
69
+ get sessionCount(): number;
70
+ /**
71
+ * Stores client metadata against a McpServer instance so that
72
+ * addSession() can consume it when the session is registered later.
73
+ */
74
+ storePendingClientMetadata(server: McpServer, metadata: {
75
+ clientName: string;
76
+ clientVersion: string;
77
+ protocolVersion: string;
78
+ }): void;
79
+ /**
80
+ * Returns metadata for all active sessions. Does not expose internal
81
+ * transport or server references.
82
+ */
83
+ getAllSessions(): Array<{
84
+ sessionId: string;
85
+ clientName?: string;
86
+ clientVersion?: string;
87
+ protocolVersion?: string;
88
+ createdByMethod?: string;
89
+ lastMethod?: string;
90
+ createdAt: string;
91
+ lastAccessedAt: string;
92
+ isClosing: boolean;
93
+ activeSseCount: number;
94
+ }>;
95
+ /** Updates the last HTTP method seen for a session. */
96
+ updateSessionMethod(sessionId: string, method: string): void;
97
+ }
98
+ /** Singleton instance. */
99
+ export declare const sessionManager: SessionManager;
100
+ export {};
101
+ //# sourceMappingURL=session-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../../../../src/services/gateway/session-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACxG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,6BAA6B,CAAC;IACzC,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,YAAY,CAA+C;IAEnE,OAAO,CAAC,qBAAqB,CAGzB;IAEJ;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,6BAA6B,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI;IA6BhG;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IASvD;;;OAGG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAatC;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAatC;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAuBtC,OAAO,CAAC,iBAAiB,CAAoD;IAC7E,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAQ;IAE9C;;;;OAIG;IACH,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI;IA4B1D;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiE5B,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,WAAW,IAAI,IAAI;IAenB,2DAA2D;IAC3D,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,0BAA0B,CACxB,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAC/E,IAAI;IAIP;;;OAGG;IACH,cAAc,IAAI,KAAK,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,OAAO,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IAmBF,uDAAuD;IACvD,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAM7D;AAED,0BAA0B;AAC1B,eAAO,MAAM,cAAc,gBAAuB,CAAC"}