@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.
- package/CHANGELOG.md +49 -0
- package/README.md +405 -331
- package/dist/client/assets/{HomeView-Bi2bkUKf.js → HomeView-DplI3V-h.js} +1 -1
- package/dist/client/assets/{ResourceDetailView-DyuSovH9.js → ResourceDetailView-CeHPn99Y.js} +1 -1
- package/dist/client/assets/ResourcesView-C1ObRhYS.js +1 -0
- package/dist/client/assets/{ServerDashboard-BGyyZAti.js → ServerDashboard-D7wG4Gvt.js} +1 -1
- package/dist/client/assets/ServerDetail-G23phOcJ.js +2 -0
- package/dist/client/assets/{ServerListView-yQPVJFHG.js → ServerListView-BFiZLtPO.js} +1 -1
- 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
- package/dist/client/assets/SettingsView-QBFLZ6fP.js +1 -0
- package/dist/client/assets/ToolCallDialog-DYS-ADCL.js +1 -0
- package/dist/client/assets/ToolsView-DYwgtm7W.js +1 -0
- package/dist/client/assets/_baseClone-DQno9YO3.js +1 -0
- package/dist/client/assets/{el-form-item-DfWq_kSy.js → el-form-item-DF0zzQdH.js} +2 -2
- package/dist/client/assets/el-input-C_p2Qw42.js +1 -0
- package/dist/client/assets/el-loading-BaenpNzU.js +1 -0
- package/dist/client/assets/el-overlay-MbIUXSQ7.js +1 -0
- package/dist/client/assets/el-radio-group-COnCjCcz.js +1 -0
- package/dist/client/assets/el-skeleton-item-qj0eQP4s.js +1 -0
- package/dist/client/assets/el-switch-BZbXqB3_.js +1 -0
- package/dist/client/assets/el-tab-pane-w7RltRLd.js +1 -0
- package/dist/client/assets/el-table-column-OD8zhFcD.js +1 -0
- package/dist/client/assets/index-DwhULJXZ.js +2 -0
- package/dist/client/assets/{index-Bzz3tYbS.css → index-UtsV0Cvh.css} +1 -1
- package/dist/client/assets/{omit-BIIebEYo.js → omit-BAJQlviJ.js} +1 -1
- package/dist/client/assets/raf-B1Ry7ruA.js +1 -0
- package/dist/client/assets/{vue-vendor-Dwcr0jep.js → vue-vendor-ClSvefnQ.js} +1 -1
- package/dist/client/index.html +3 -3
- package/dist/server/shared/models/constants.d.ts +5 -0
- package/dist/server/shared/models/constants.d.ts.map +1 -1
- package/dist/server/shared/models/constants.js +4 -0
- package/dist/server/shared/models/server.model.d.ts +14 -0
- package/dist/server/shared/models/server.model.d.ts.map +1 -1
- package/dist/server/shared/models/server.model.js +27 -4
- package/dist/server/src/api/mcp/gateway.d.ts +10 -6
- package/dist/server/src/api/mcp/gateway.d.ts.map +1 -1
- package/dist/server/src/api/mcp/gateway.js +235 -69
- package/dist/server/src/api/web/sessions.d.ts +1 -27
- package/dist/server/src/api/web/sessions.d.ts.map +1 -1
- package/dist/server/src/api/web/sessions.js +8 -97
- package/dist/server/src/app.d.ts.map +1 -1
- package/dist/server/src/app.js +5 -0
- package/dist/server/src/cli/commands/status.js +39 -1
- package/dist/server/src/cli/commands/use-guide.d.ts +0 -8
- package/dist/server/src/cli/commands/use-guide.d.ts.map +1 -1
- package/dist/server/src/cli/commands/use-guide.js +28 -170
- package/dist/server/src/cli/server.d.ts +10 -0
- package/dist/server/src/cli/server.d.ts.map +1 -1
- package/dist/server/src/cli/server.js +31 -1
- package/dist/server/src/server/dev-server.js +2 -0
- package/dist/server/src/server/runner.d.ts.map +1 -1
- package/dist/server/src/server/runner.js +2 -0
- package/dist/server/src/services/connection/connection-manager.d.ts +2 -0
- package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
- package/dist/server/src/services/connection/connection-manager.js +14 -7
- package/dist/server/src/services/gateway/gateway.service.d.ts +13 -0
- package/dist/server/src/services/gateway/gateway.service.d.ts.map +1 -1
- package/dist/server/src/services/gateway/gateway.service.js +72 -0
- package/dist/server/src/services/gateway/global-transport.d.ts +20 -10
- package/dist/server/src/services/gateway/global-transport.d.ts.map +1 -1
- package/dist/server/src/services/gateway/global-transport.js +50 -34
- package/dist/server/src/services/gateway/request-handlers/initialize-handler.d.ts.map +1 -1
- package/dist/server/src/services/gateway/request-handlers/initialize-handler.js +22 -6
- package/dist/server/src/services/gateway/request-handlers/resources-handler.d.ts.map +1 -1
- package/dist/server/src/services/gateway/request-handlers/resources-handler.js +5 -1
- package/dist/server/src/services/gateway/session-manager.d.ts +101 -0
- package/dist/server/src/services/gateway/session-manager.d.ts.map +1 -0
- package/dist/server/src/services/gateway/session-manager.js +256 -0
- package/dist/server/src/services/hub-tools/resource-generator.d.ts +1 -1
- package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools/resource-generator.js +11 -9
- package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools.service.js +3 -1
- package/dist/server/src/utils/json-utils.d.ts +9 -0
- package/dist/server/src/utils/json-utils.d.ts.map +1 -1
- package/dist/server/src/utils/json-utils.js +19 -0
- package/dist/server/src/utils/logger/index.d.ts +1 -1
- package/dist/server/src/utils/logger/index.d.ts.map +1 -1
- package/dist/server/src/utils/logger/index.js +1 -1
- package/dist/server/src/utils/logger/log-context.d.ts +1 -0
- package/dist/server/src/utils/logger/log-context.d.ts.map +1 -1
- package/dist/server/src/utils/logger/log-formatter.d.ts.map +1 -1
- package/dist/server/src/utils/logger/log-formatter.js +25 -11
- package/dist/server/src/utils/logger/log-output.d.ts +17 -1
- package/dist/server/src/utils/logger/log-output.d.ts.map +1 -1
- package/dist/server/src/utils/logger/log-output.js +46 -40
- package/dist/server/src/utils/logger/logger.d.ts.map +1 -1
- package/dist/server/src/utils/logger/logger.js +18 -2
- package/dist/server/src/utils/request-context.d.ts +8 -70
- package/dist/server/src/utils/request-context.d.ts.map +1 -1
- package/dist/server/src/utils/request-context.js +11 -70
- package/dist/server/tests/unit/config/config.schema.test.js +2 -1
- package/dist/server/tests/unit/server/runner.test.js +14 -7
- package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts +2 -0
- package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts.map +1 -0
- package/dist/server/tests/unit/services/gateway-session-mode.test.js +174 -0
- package/dist/server/tests/unit/services/hub-tools.service.test.js +4 -4
- package/dist/server/tests/unit/utils/config.test.js +14 -7
- package/dist/server/tests/unit/utils/log-output.test.d.ts +2 -0
- package/dist/server/tests/unit/utils/log-output.test.d.ts.map +1 -0
- package/dist/server/tests/unit/utils/log-output.test.js +198 -0
- package/dist/server/vitest.config.d.ts.map +1 -1
- package/dist/server/vitest.config.js +0 -2
- package/package.json +1 -1
- package/dist/client/assets/ResourcesView-CU0VbNy5.js +0 -1
- package/dist/client/assets/ServerDetail-bcQ8BVXR.js +0 -2
- package/dist/client/assets/SettingsView-B1DxbFP3.js +0 -1
- package/dist/client/assets/ToolCallDialog-DEapCO06.js +0 -1
- package/dist/client/assets/ToolsView-DA0u_bCw.js +0 -1
- package/dist/client/assets/_baseClone-B991Lvrt.js +0 -1
- package/dist/client/assets/el-input-5YzZrwir.js +0 -1
- package/dist/client/assets/el-loading-DE3FcxNH.js +0 -1
- package/dist/client/assets/el-overlay-BTeTueuN.js +0 -1
- package/dist/client/assets/el-radio-group-Y1E2bxIW.js +0 -1
- package/dist/client/assets/el-skeleton-item-DhgR50Jx.js +0 -1
- package/dist/client/assets/el-switch-fF--nMSD.js +0 -1
- package/dist/client/assets/el-tab-pane-rvS_KTwP.js +0 -1
- package/dist/client/assets/el-table-column-B1O8mY47.js +0 -1
- package/dist/client/assets/index-DkqV9kH4.js +0 -2
- 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;
|
|
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
|
|
2
|
+
* MCP transport utilities — stateful session-based mode.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
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
|
|
25
|
+
export declare function createPerRequestTransport(): Promise<{
|
|
16
26
|
transport: StreamableHTTPServerTransport;
|
|
17
|
-
server: import(
|
|
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
|
|
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
|
|
2
|
+
* MCP transport utilities — stateful session-based mode.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
14
|
-
*
|
|
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
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
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
|
-
|
|
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}`,
|
|
36
|
+
logger.debug(`MCP message received: ${logMessage}`, opts);
|
|
29
37
|
}
|
|
30
|
-
logNotificationMessage(message, '');
|
|
38
|
+
logNotificationMessage(message, '');
|
|
31
39
|
if (getGatewayDebugSetting()) {
|
|
32
|
-
logger.debug(
|
|
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
|
|
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}`,
|
|
54
|
+
logger.debug(`MCP message sent: ${logMessage}`, traceLogOpts(transport));
|
|
48
55
|
}
|
|
49
56
|
catch {
|
|
50
|
-
logger.debug(
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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;
|
|
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: ${
|
|
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: ${
|
|
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;
|
|
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"}
|