@loop_ouroboros/mcp-hub-lite 1.2.9 → 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 +82 -0
- package/README.md +405 -331
- package/dist/client/assets/{HomeView-CGezWc0j.js → HomeView-DplI3V-h.js} +1 -1
- package/dist/client/assets/{ResourceDetailView-CDmWGdAK.css → ResourceDetailView-BkTSg91z.css} +1 -1
- package/dist/client/assets/ResourceDetailView-CeHPn99Y.js +1 -0
- package/dist/client/assets/ResourcesView-C1ObRhYS.js +1 -0
- package/dist/client/assets/ResourcesView-zgV8Nq7w.css +1 -0
- package/dist/client/assets/{ServerDashboard-g5p4VC_-.js → ServerDashboard-D7wG4Gvt.js} +1 -1
- package/dist/client/assets/{ServerDetail-DCQH8HIb.css → ServerDetail-CPNAFBPM.css} +1 -1
- package/dist/client/assets/ServerDetail-G23phOcJ.js +2 -0
- package/dist/client/assets/{ServerListView-DZsy2gaQ.js → ServerListView-BFiZLtPO.js} +1 -1
- package/dist/client/assets/{ServerStatusTags.vue_vue_type_script_setup_true_lang-DmGg4uuV.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-BQ9UJZ_-.css +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/ToolsView-cO61nMNr.css +1 -0
- package/dist/client/assets/_baseClone-DQno9YO3.js +1 -0
- package/dist/client/assets/{el-form-item-CTsVV8sm.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-BNmwPGMT.css → index-UtsV0Cvh.css} +1 -1
- package/dist/client/assets/{omit-Btci9mp3.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 +8 -0
- package/dist/server/shared/models/constants.d.ts.map +1 -0
- package/dist/server/shared/models/constants.js +6 -0
- package/dist/server/shared/models/index.d.ts +1 -0
- package/dist/server/shared/models/index.d.ts.map +1 -1
- package/dist/server/shared/models/index.js +1 -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/shared/types/index.d.ts +0 -1
- package/dist/server/shared/types/index.d.ts.map +1 -1
- package/dist/server/shared/types/index.js +0 -1
- package/dist/server/src/api/mcp/debug-response-wrapper.js +1 -1
- 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 -87
- package/dist/server/src/api/web/hub-tools.d.ts.map +1 -1
- package/dist/server/src/api/web/hub-tools.js +11 -0
- package/dist/server/src/api/web/mcp-status.js +2 -2
- package/dist/server/src/api/web/search.d.ts +2 -16
- package/dist/server/src/api/web/search.d.ts.map +1 -1
- package/dist/server/src/api/web/search.js +22 -30
- package/dist/server/src/api/web/servers.js +1 -1
- 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/api/ws/events.js +1 -1
- package/dist/server/src/api/ws/ws-handler.js +1 -1
- package/dist/server/src/app.d.ts.map +1 -1
- package/dist/server/src/app.js +6 -1
- package/dist/server/src/cli/commands/status.js +39 -1
- package/dist/server/src/cli/commands/tool-use.d.ts +10 -3
- package/dist/server/src/cli/commands/tool-use.d.ts.map +1 -1
- package/dist/server/src/cli/commands/tool-use.js +69 -30
- 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/config/config-change-logger.js +1 -1
- package/dist/server/src/config/config-loader.js +1 -1
- package/dist/server/src/config/config-manager.js +1 -1
- package/dist/server/src/config/config-migrator.d.ts +4 -48
- package/dist/server/src/config/config-migrator.d.ts.map +1 -1
- package/dist/server/src/config/config-migrator.js +2 -103
- package/dist/server/src/config/config-saver.js +1 -1
- package/dist/server/src/config/server-config-manager.js +1 -1
- package/dist/server/src/models/system-tools.constants.d.ts +2 -1
- package/dist/server/src/models/system-tools.constants.d.ts.map +1 -1
- package/dist/server/src/models/system-tools.constants.js +2 -1
- package/dist/server/src/pid/manager.js +1 -1
- package/dist/server/src/server/dev-server.js +4 -2
- package/dist/server/src/server/runner.d.ts.map +1 -1
- package/dist/server/src/server/runner.js +4 -2
- package/dist/server/src/server/startup.js +2 -2
- 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 +27 -25
- package/dist/server/src/services/connection/tool-cache.d.ts.map +1 -1
- package/dist/server/src/services/connection/tool-cache.js +10 -8
- package/dist/server/src/services/event-bus.service.d.ts +3 -1
- package/dist/server/src/services/event-bus.service.d.ts.map +1 -1
- package/dist/server/src/services/event-bus.service.js +1 -0
- package/dist/server/src/services/gateway/gateway.service.d.ts +14 -0
- package/dist/server/src/services/gateway/gateway.service.d.ts.map +1 -1
- package/dist/server/src/services/gateway/gateway.service.js +101 -7
- 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/call-tool-handler.d.ts +1 -2
- package/dist/server/src/services/gateway/request-handlers/call-tool-handler.d.ts.map +1 -1
- package/dist/server/src/services/gateway/request-handlers/call-tool-handler.js +24 -13
- 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 +12 -4
- 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/gateway/tool-list-generator.d.ts +14 -19
- package/dist/server/src/services/gateway/tool-list-generator.d.ts.map +1 -1
- package/dist/server/src/services/gateway/tool-list-generator.js +221 -80
- package/dist/server/src/services/hub-manager.service.d.ts.map +1 -1
- package/dist/server/src/services/hub-manager.service.js +15 -2
- package/dist/server/src/services/hub-tools/instance-selector.js +1 -1
- package/dist/server/src/services/hub-tools/resource-generator.d.ts +1 -22
- package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools/resource-generator.js +24 -22
- package/dist/server/src/services/hub-tools/server-selector.js +1 -1
- package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools.service.js +18 -13
- package/dist/server/src/services/log-storage.service.js +1 -1
- package/dist/server/src/services/system-tool-handler.js +1 -1
- package/dist/server/src/utils/error-handler.js +1 -1
- package/dist/server/src/utils/index.d.ts +1 -1
- package/dist/server/src/utils/index.d.ts.map +1 -1
- package/dist/server/src/utils/index.js +1 -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 +23 -4
- package/dist/server/src/utils/log-rotator.d.ts +0 -15
- package/dist/server/src/utils/log-rotator.d.ts.map +1 -1
- package/dist/server/src/utils/log-rotator.js +0 -18
- 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/port-checker.js +1 -1
- 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/src/utils/transports/stdio-transport.js +1 -1
- package/dist/server/src/utils/transports/streamable-http-transport.js +1 -1
- package/dist/server/src/utils/transports/transport-factory.d.ts.map +1 -1
- package/dist/server/src/utils/transports/transport-factory.js +26 -3
- package/dist/server/tests/contract/mcp-protocol/initialize.test.js +1 -1
- package/dist/server/tests/contract/mcp-protocol/tools-call.test.js +1 -1
- package/dist/server/tests/contract/mcp-protocol/tools-list.test.js +1 -1
- package/dist/server/tests/integration/gateway/fault-tolerance.test.js +1 -1
- package/dist/server/tests/integration/gateway/mcp-connection.test.js +1 -1
- package/dist/server/tests/types/logger-test-helpers.d.ts +1 -1
- package/dist/server/tests/types/logger-test-helpers.d.ts.map +1 -1
- package/dist/server/tests/unit/config/config-migrator.test.js +45 -105
- package/dist/server/tests/unit/config/config-saver.test.js +1 -1
- package/dist/server/tests/unit/config/config.schema.test.js +2 -1
- package/dist/server/tests/unit/server/runner.test.js +19 -13
- package/dist/server/tests/unit/services/gateway-logging.test.js +1 -1
- 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-manager-service.test.js +4 -5
- package/dist/server/tests/unit/services/hub-tools.service.test.js +82 -6
- 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/tests/unit/utils/log-rotator.test.js +1 -15
- package/dist/server/tests/unit/utils/logger.test.js +1 -1
- package/dist/server/vitest.config.d.ts.map +1 -1
- package/dist/server/vitest.config.js +0 -2
- package/package.json +1 -3
- package/dist/client/assets/ResourceDetailView-Bi5UsbFq.js +0 -1
- package/dist/client/assets/ResourcesView-B9anSm85.js +0 -1
- package/dist/client/assets/ResourcesView-Cc8RHtia.css +0 -1
- package/dist/client/assets/ServerDetail-DMoFqWCp.js +0 -2
- package/dist/client/assets/SettingsView-DQSWb9xM.js +0 -1
- package/dist/client/assets/ToolCallDialog-BEyRp_J3.js +0 -1
- package/dist/client/assets/ToolCallDialog-BhdPX-Kf.css +0 -1
- package/dist/client/assets/ToolsView-BU7PKJwt.js +0 -1
- package/dist/client/assets/ToolsView-BkrQLjH9.css +0 -1
- package/dist/client/assets/_baseClone-DsVtZfPm.js +0 -1
- package/dist/client/assets/el-input-Bh1VGJTU.js +0 -1
- package/dist/client/assets/el-loading-huOeK9cW.js +0 -1
- package/dist/client/assets/el-overlay-CR_KVhLU.js +0 -1
- package/dist/client/assets/el-radio-group-BSbtAW4k.js +0 -1
- package/dist/client/assets/el-skeleton-item-BSxOLPFM.js +0 -1
- package/dist/client/assets/el-switch-BaQUQWTL.js +0 -1
- package/dist/client/assets/el-tab-pane-9JxLgdS7.js +0 -1
- package/dist/client/assets/el-table-column-Du1l9Ww3.js +0 -1
- package/dist/client/assets/index-CsZoFRv1.js +0 -2
- package/dist/client/assets/raf-tUu4BwZS.js +0 -1
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { logger, LOG_MODULES } from '../../../utils/index.js';
|
|
6
|
-
import {
|
|
7
|
-
import { mcpConnectionManager } from '../../mcp-connection-manager.js';
|
|
6
|
+
import { hubToolsService } from '../../hub-tools.service.js';
|
|
8
7
|
import { SystemToolHandler } from '../../system-tool-handler.js';
|
|
9
8
|
import { ErrorHandler } from '../../../utils/error-handler.js';
|
|
10
9
|
import { ToolArgsParser } from '../../../utils/tool-args-parser.js';
|
|
11
10
|
import { SYSTEM_TOOL_NAMES, MCP_HUB_LITE_SERVER } from '../../../models/system-tools.constants.js';
|
|
12
11
|
import { formatToolArgs, formatToolResponse } from '../log-formatter.js';
|
|
13
|
-
import {
|
|
12
|
+
import { getOrBuildGatewayToolMap, getOrBuildGatewayToolsList } from '../tool-list-generator.js';
|
|
14
13
|
/**
|
|
15
14
|
* Type guard to check if a result is a valid CallToolResult.
|
|
16
15
|
*
|
|
@@ -37,7 +36,7 @@ async function executeSystemToolCall(toolName, toolArgs) {
|
|
|
37
36
|
content: [
|
|
38
37
|
{
|
|
39
38
|
type: 'text',
|
|
40
|
-
text:
|
|
39
|
+
text: JSON.stringify(result, null, 2)
|
|
41
40
|
}
|
|
42
41
|
]
|
|
43
42
|
};
|
|
@@ -75,10 +74,10 @@ function getSystemToolName(toolName) {
|
|
|
75
74
|
* @param server - MCP server instance to register handlers on
|
|
76
75
|
* @param toolMap - Tool map for routing tool calls
|
|
77
76
|
*/
|
|
78
|
-
export function registerCallToolHandler(server
|
|
77
|
+
export function registerCallToolHandler(server) {
|
|
79
78
|
// Original list tools handler (for compatibility)
|
|
80
79
|
server.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
81
|
-
const gatewayTools =
|
|
80
|
+
const gatewayTools = getOrBuildGatewayToolsList();
|
|
82
81
|
return {
|
|
83
82
|
tools: gatewayTools
|
|
84
83
|
};
|
|
@@ -88,26 +87,38 @@ export function registerCallToolHandler(server, toolMap) {
|
|
|
88
87
|
const toolName = request.params.name;
|
|
89
88
|
const toolArgs = request.params.arguments || {};
|
|
90
89
|
const systemToolName = getSystemToolName(toolName);
|
|
90
|
+
const gwToolMap = getOrBuildGatewayToolMap();
|
|
91
91
|
// Log incoming tool request with full context
|
|
92
92
|
logger.info(`Tool call REQUEST received: toolName=${toolName}, args=${formatToolArgs(toolArgs)}`, LOG_MODULES.GATEWAY);
|
|
93
|
-
logger.debug(`Tool context: toolMap size=${
|
|
93
|
+
logger.debug(`Tool context: toolMap size=${gwToolMap.size}, available tools=${Array.from(gwToolMap.keys()).slice(0, 10).join(', ')}${gwToolMap.size > 10 ? '...' : ''}`, LOG_MODULES.GATEWAY);
|
|
94
94
|
// Handle system tools
|
|
95
95
|
if (systemToolName) {
|
|
96
96
|
logger.debug(`System tool called: ${systemToolName}, args=${formatToolArgs(toolArgs)}`, LOG_MODULES.GATEWAY);
|
|
97
97
|
return await executeSystemToolCall(systemToolName, toolArgs);
|
|
98
98
|
}
|
|
99
|
-
const target =
|
|
99
|
+
const target = gwToolMap.get(toolName);
|
|
100
100
|
logger.debug(`Tool lookup SUCCESS: toolName=${toolName} -> serverName=${target?.serverName}, serverIndex=${target?.serverIndex}, realToolName=${target?.realToolName}`, LOG_MODULES.GATEWAY);
|
|
101
101
|
if (!target) {
|
|
102
|
-
logger.error(`Tool NOT FOUND: toolName=${toolName}, available tools=${Array.from(
|
|
102
|
+
logger.error(`Tool NOT FOUND: toolName=${toolName}, available tools=${Array.from(gwToolMap.keys()).join(', ')}`, LOG_MODULES.GATEWAY);
|
|
103
103
|
throw new McpError(-32801, `Tool ${toolName} not found`);
|
|
104
104
|
}
|
|
105
|
+
// Extract wrapped arguments (LLM provides serverName/toolName per schema guidance)
|
|
106
|
+
const wrappedArgs = toolArgs;
|
|
107
|
+
const callServerName = wrappedArgs.serverName || target.serverName;
|
|
108
|
+
const callToolName = wrappedArgs.toolName || target.realToolName;
|
|
109
|
+
const callArgs = wrappedArgs.toolArgs || {};
|
|
110
|
+
const callOptions = wrappedArgs.requestOptions || {};
|
|
105
111
|
const startTime = Date.now();
|
|
106
112
|
try {
|
|
107
|
-
logger.debug(`Tool call EXECUTING: serverName=${
|
|
108
|
-
const result = await
|
|
113
|
+
logger.debug(`Tool call EXECUTING: serverName=${callServerName}, toolName=${callToolName}, args=${formatToolArgs(callArgs)}`, LOG_MODULES.GATEWAY);
|
|
114
|
+
const result = await hubToolsService.callTool({
|
|
115
|
+
serverName: callServerName,
|
|
116
|
+
toolName: callToolName,
|
|
117
|
+
toolArgs: callArgs,
|
|
118
|
+
requestOptions: callOptions
|
|
119
|
+
});
|
|
109
120
|
const duration = Date.now() - startTime;
|
|
110
|
-
logger.info(`Tool call SUCCESS: serverName=${
|
|
121
|
+
logger.info(`Tool call SUCCESS: serverName=${callServerName}, toolName=${callToolName}, duration=${duration}ms, response=${formatToolResponse(result)}`, LOG_MODULES.GATEWAY);
|
|
111
122
|
// Wrap the result in a valid CallToolResult structure
|
|
112
123
|
if (typeof result === 'object' && result !== null) {
|
|
113
124
|
return {
|
|
@@ -127,7 +138,7 @@ export function registerCallToolHandler(server, toolMap) {
|
|
|
127
138
|
}
|
|
128
139
|
}
|
|
129
140
|
catch (error) {
|
|
130
|
-
ErrorHandler.handleToolCallError(
|
|
141
|
+
ErrorHandler.handleToolCallError(callServerName, callToolName, error);
|
|
131
142
|
}
|
|
132
143
|
});
|
|
133
144
|
}
|
|
@@ -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,10 +1,9 @@
|
|
|
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
|
-
import { stringifyForLogging } from '../../../utils/json-utils.js';
|
|
8
7
|
import { hubToolsService } from '../../hub-tools.service.js';
|
|
9
8
|
/**
|
|
10
9
|
* Register resources-related handlers on the MCP server.
|
|
@@ -12,6 +11,10 @@ import { hubToolsService } from '../../hub-tools.service.js';
|
|
|
12
11
|
* @param server - MCP server instance to register handlers on
|
|
13
12
|
*/
|
|
14
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
|
+
});
|
|
15
18
|
server.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
16
19
|
try {
|
|
17
20
|
const resources = await hubToolsService.listResources();
|
|
@@ -32,13 +35,18 @@ export function registerResourcesHandlers(server) {
|
|
|
32
35
|
try {
|
|
33
36
|
const { uri } = request.params;
|
|
34
37
|
const content = await hubToolsService.readResource(uri);
|
|
38
|
+
// Preserve original mimeType if content is already in MCP ReadResourceResult format
|
|
39
|
+
const mcpResult = content && typeof content === 'object' && 'contents' in content
|
|
40
|
+
? content
|
|
41
|
+
: null;
|
|
42
|
+
const mimeType = mcpResult?.contents?.[0]?.mimeType || 'application/json';
|
|
35
43
|
// Convert to official MCP format: contents array with required uri field
|
|
36
44
|
return {
|
|
37
45
|
contents: [
|
|
38
46
|
{
|
|
39
47
|
uri,
|
|
40
|
-
mimeType
|
|
41
|
-
text: typeof content === 'string' ? content :
|
|
48
|
+
mimeType,
|
|
49
|
+
text: typeof content === 'string' ? content : JSON.stringify(content, null, 2)
|
|
42
50
|
}
|
|
43
51
|
]
|
|
44
52
|
};
|
|
@@ -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"}
|
|
@@ -0,0 +1,256 @@
|
|
|
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 { logger, LOG_MODULES } from '../../utils/logger/index.js';
|
|
9
|
+
import { getGatewayDebugSetting } from '../../utils/json-utils.js';
|
|
10
|
+
import { configManager } from '../../config/config-manager.js';
|
|
11
|
+
const CLEANUP_INTERVAL_MS = 60 * 1000; // every minute
|
|
12
|
+
const PING_COOLDOWN_MS = 30 * 1000; // min interval between pings
|
|
13
|
+
const PING_TIMEOUT_MS = 10 * 1000; // per-ping timeout
|
|
14
|
+
export class SessionManager {
|
|
15
|
+
sessions = new Map();
|
|
16
|
+
cleanupTimer = null;
|
|
17
|
+
pendingClientMetadata = new WeakMap();
|
|
18
|
+
/**
|
|
19
|
+
* Registers a newly created session.
|
|
20
|
+
*/
|
|
21
|
+
addSession(sessionId, transport, server) {
|
|
22
|
+
const pendingMeta = this.pendingClientMetadata.get(server);
|
|
23
|
+
this.pendingClientMetadata.delete(server);
|
|
24
|
+
this.sessions.set(sessionId, {
|
|
25
|
+
sessionId,
|
|
26
|
+
transport,
|
|
27
|
+
server,
|
|
28
|
+
createdAt: Date.now(),
|
|
29
|
+
lastAccessedAt: Date.now(),
|
|
30
|
+
isClosing: false,
|
|
31
|
+
activeSseCount: 0,
|
|
32
|
+
lastPingedAt: 0,
|
|
33
|
+
pingPending: false,
|
|
34
|
+
clientName: pendingMeta?.clientName,
|
|
35
|
+
clientVersion: pendingMeta?.clientVersion,
|
|
36
|
+
protocolVersion: pendingMeta?.protocolVersion,
|
|
37
|
+
createdByMethod: 'POST',
|
|
38
|
+
lastMethod: 'POST'
|
|
39
|
+
});
|
|
40
|
+
if (getGatewayDebugSetting()) {
|
|
41
|
+
logger.debug(`Session registered: ${sessionId} (total: ${this.sessions.size}, activeSseCount: 0)`, LOG_MODULES.GATEWAY);
|
|
42
|
+
}
|
|
43
|
+
this.ensureCleanupTimer();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Looks up an existing session by ID and updates lastAccessedAt.
|
|
47
|
+
*/
|
|
48
|
+
getSession(sessionId) {
|
|
49
|
+
const session = this.sessions.get(sessionId);
|
|
50
|
+
if (session && !session.isClosing) {
|
|
51
|
+
session.lastAccessedAt = Date.now();
|
|
52
|
+
return session;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Marks an SSE stream as opened for the given session.
|
|
58
|
+
* Prevents the session from being cleaned up while the stream is active.
|
|
59
|
+
*/
|
|
60
|
+
markSseOpened(sessionId) {
|
|
61
|
+
const session = this.sessions.get(sessionId);
|
|
62
|
+
if (session && !session.isClosing) {
|
|
63
|
+
session.activeSseCount++;
|
|
64
|
+
if (getGatewayDebugSetting()) {
|
|
65
|
+
logger.debug(`Session ${sessionId} SSE opened (activeSseCount: ${session.activeSseCount})`, LOG_MODULES.GATEWAY);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Marks an SSE stream as closed for the given session.
|
|
71
|
+
*/
|
|
72
|
+
markSseClosed(sessionId) {
|
|
73
|
+
const session = this.sessions.get(sessionId);
|
|
74
|
+
if (session) {
|
|
75
|
+
session.activeSseCount = Math.max(0, session.activeSseCount - 1);
|
|
76
|
+
if (getGatewayDebugSetting()) {
|
|
77
|
+
logger.debug(`Session ${sessionId} SSE closed (activeSseCount: ${session.activeSseCount})`, LOG_MODULES.GATEWAY);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Removes a session (called on DELETE or onsessionclosed).
|
|
83
|
+
*/
|
|
84
|
+
removeSession(sessionId) {
|
|
85
|
+
const session = this.sessions.get(sessionId);
|
|
86
|
+
if (!session || session.isClosing)
|
|
87
|
+
return;
|
|
88
|
+
session.isClosing = true;
|
|
89
|
+
try {
|
|
90
|
+
session.transport.close().catch((err) => {
|
|
91
|
+
logger.debug(`Error closing transport for session ${sessionId}: ${err}`, LOG_MODULES.GATEWAY);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// transport may already be closed
|
|
96
|
+
}
|
|
97
|
+
this.sessions.delete(sessionId);
|
|
98
|
+
if (getGatewayDebugSetting()) {
|
|
99
|
+
logger.debug(`Session removed: ${sessionId} (total: ${this.sessions.size}, age: ${Date.now() - session.createdAt}ms, activeSseCount: ${session.activeSseCount})`, LOG_MODULES.GATEWAY);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
pendingBroadcasts = new Map();
|
|
103
|
+
BROADCAST_DEBOUNCE_MS = 3000;
|
|
104
|
+
/**
|
|
105
|
+
* Broadcasts a notification to all active sessions.
|
|
106
|
+
* Debounced: same-type notifications within BROADCAST_DEBOUNCE_MS are coalesced.
|
|
107
|
+
* @param method - 'tools' for sendToolListChanged, 'resources' for sendResourceListChanged
|
|
108
|
+
*/
|
|
109
|
+
broadcastNotification(method) {
|
|
110
|
+
// Skip if a debounce timer for this method type is already pending
|
|
111
|
+
if (this.pendingBroadcasts.has(method))
|
|
112
|
+
return;
|
|
113
|
+
this.pendingBroadcasts.set(method, setTimeout(() => {
|
|
114
|
+
this.pendingBroadcasts.delete(method);
|
|
115
|
+
if (this.sessions.size === 0)
|
|
116
|
+
return;
|
|
117
|
+
for (const [sessionId, state] of this.sessions) {
|
|
118
|
+
if (state.isClosing)
|
|
119
|
+
continue;
|
|
120
|
+
try {
|
|
121
|
+
if (method === 'tools') {
|
|
122
|
+
state.server.sendToolListChanged();
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
state.server.sendResourceListChanged();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
logger.debug(`Failed to broadcast ${method} notification to session ${sessionId}: ${error}`, LOG_MODULES.GATEWAY);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, this.BROADCAST_DEBOUNCE_MS));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Removes sessions that have been idle longer than SESSION_TIMEOUT_MS.
|
|
136
|
+
*/
|
|
137
|
+
cleanupStaleSessions() {
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const staleIds = [];
|
|
140
|
+
let skippedClosing = 0;
|
|
141
|
+
let skippedSse = 0;
|
|
142
|
+
let pingFailed = 0;
|
|
143
|
+
for (const [id, state] of this.sessions) {
|
|
144
|
+
if (state.isClosing) {
|
|
145
|
+
skippedClosing++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Ping from last cycle timed out — session is dead
|
|
149
|
+
if (state.pingPending) {
|
|
150
|
+
state.pingPending = false;
|
|
151
|
+
pingFailed++;
|
|
152
|
+
staleIds.push(id);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (state.activeSseCount > 0) {
|
|
156
|
+
if (now - state.lastPingedAt < PING_COOLDOWN_MS) {
|
|
157
|
+
skippedSse++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
state.lastPingedAt = now;
|
|
161
|
+
state.pingPending = true;
|
|
162
|
+
// Fire-and-forget concurrent ping with timeout
|
|
163
|
+
const pingWithTimeout = Promise.race([
|
|
164
|
+
state.server.server.ping(),
|
|
165
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Ping timeout')), PING_TIMEOUT_MS))
|
|
166
|
+
]);
|
|
167
|
+
pingWithTimeout.then(() => {
|
|
168
|
+
const s = this.sessions.get(id);
|
|
169
|
+
if (s) {
|
|
170
|
+
s.pingPending = false;
|
|
171
|
+
s.lastAccessedAt = Date.now();
|
|
172
|
+
}
|
|
173
|
+
}, () => {
|
|
174
|
+
// Keep pingPending=true so the next cycle cleans up this dead session
|
|
175
|
+
});
|
|
176
|
+
skippedSse++;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (now - state.lastAccessedAt > configManager.getConfig().security.idleConnectionTimeout) {
|
|
180
|
+
staleIds.push(id);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (getGatewayDebugSetting() && (staleIds.length > 0 || skippedSse > 0 || pingFailed > 0)) {
|
|
184
|
+
logger.debug(`Stale cleanup: total=${this.sessions.size} stale=${staleIds.length} skippedClosing=${skippedClosing} skippedSse=${skippedSse} pingFailed=${pingFailed}`, LOG_MODULES.GATEWAY);
|
|
185
|
+
}
|
|
186
|
+
for (const id of staleIds) {
|
|
187
|
+
logger.info(`Removing stale session: ${id}`, LOG_MODULES.GATEWAY);
|
|
188
|
+
this.removeSession(id);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
ensureCleanupTimer() {
|
|
192
|
+
if (this.cleanupTimer)
|
|
193
|
+
return;
|
|
194
|
+
this.cleanupTimer = setInterval(() => this.cleanupStaleSessions(), CLEANUP_INTERVAL_MS);
|
|
195
|
+
this.cleanupTimer.unref();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Shuts down all sessions. Called on server shutdown.
|
|
199
|
+
*/
|
|
200
|
+
shutdownAll() {
|
|
201
|
+
if (this.cleanupTimer) {
|
|
202
|
+
clearInterval(this.cleanupTimer);
|
|
203
|
+
this.cleanupTimer = null;
|
|
204
|
+
}
|
|
205
|
+
for (const timer of this.pendingBroadcasts.values()) {
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
}
|
|
208
|
+
this.pendingBroadcasts.clear();
|
|
209
|
+
for (const [id] of this.sessions) {
|
|
210
|
+
this.removeSession(id);
|
|
211
|
+
}
|
|
212
|
+
logger.info('All sessions shut down', LOG_MODULES.GATEWAY);
|
|
213
|
+
}
|
|
214
|
+
/** Returns the current session count (for diagnostics). */
|
|
215
|
+
get sessionCount() {
|
|
216
|
+
return this.sessions.size;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Stores client metadata against a McpServer instance so that
|
|
220
|
+
* addSession() can consume it when the session is registered later.
|
|
221
|
+
*/
|
|
222
|
+
storePendingClientMetadata(server, metadata) {
|
|
223
|
+
this.pendingClientMetadata.set(server, metadata);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Returns metadata for all active sessions. Does not expose internal
|
|
227
|
+
* transport or server references.
|
|
228
|
+
*/
|
|
229
|
+
getAllSessions() {
|
|
230
|
+
const result = [];
|
|
231
|
+
for (const state of this.sessions.values()) {
|
|
232
|
+
result.push({
|
|
233
|
+
sessionId: state.sessionId,
|
|
234
|
+
clientName: state.clientName,
|
|
235
|
+
clientVersion: state.clientVersion,
|
|
236
|
+
protocolVersion: state.protocolVersion,
|
|
237
|
+
createdByMethod: state.createdByMethod,
|
|
238
|
+
lastMethod: state.lastMethod,
|
|
239
|
+
createdAt: new Date(state.createdAt).toLocaleString(),
|
|
240
|
+
lastAccessedAt: new Date(state.lastAccessedAt).toLocaleString(),
|
|
241
|
+
isClosing: state.isClosing,
|
|
242
|
+
activeSseCount: state.activeSseCount
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
/** Updates the last HTTP method seen for a session. */
|
|
248
|
+
updateSessionMethod(sessionId, method) {
|
|
249
|
+
const session = this.sessions.get(sessionId);
|
|
250
|
+
if (session) {
|
|
251
|
+
session.lastMethod = method;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** Singleton instance. */
|
|
256
|
+
export const sessionManager = new SessionManager();
|
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gateway tool list generator with collision resolution.
|
|
2
|
+
* Gateway tool list generator with collision resolution and global caching.
|
|
3
3
|
*/
|
|
4
4
|
import type { ToolMapEntry, GatewayTool } from './types.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
* @param {Map<string, ToolMapEntry>} toolMap - Map to populate with gateway tool name to actual tool mappings
|
|
20
|
-
* @returns {Array<GatewayTool>} Array of gateway tools with resolved names and descriptions
|
|
21
|
-
*/
|
|
22
|
-
export declare function generateGatewayToolsList(toolMap: Map<string, ToolMapEntry>): Array<GatewayTool>;
|
|
5
|
+
export declare function generateGatewayToolsList(toolMap: Map<string, ToolMapEntry>): GatewayTool[];
|
|
6
|
+
/** Lazy get — first-request fallback when cache hasn't been built yet */
|
|
7
|
+
export declare function getOrBuildGatewayToolMap(): Map<string, ToolMapEntry>;
|
|
8
|
+
/** Lazy get — first-request fallback when cache hasn't been built yet */
|
|
9
|
+
export declare function getOrBuildGatewayToolsList(): GatewayTool[];
|
|
10
|
+
/** Lazy get — returns only aggregated external tools (excludes system tools) */
|
|
11
|
+
export declare function getExternalGatewayTools(): GatewayTool[];
|
|
12
|
+
/** Full rebuild from global state — used by TOOLS_UPDATED / SERVER_DISCONNECTED */
|
|
13
|
+
export declare function rebuildFromScratch(): void;
|
|
14
|
+
/** Incremental add — update raw data layer, re-resolve names only */
|
|
15
|
+
export declare function addToCache(serverName: string, toolNames: string[]): void;
|
|
16
|
+
/** Incremental remove — update raw data layer, re-resolve names only */
|
|
17
|
+
export declare function removeFromCache(serverName: string, toolNames: string[]): void;
|
|
23
18
|
//# sourceMappingURL=tool-list-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-list-generator.d.ts","sourceRoot":"","sources":["../../../../../src/services/gateway/tool-list-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-list-generator.d.ts","sourceRoot":"","sources":["../../../../../src/services/gateway/tool-list-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAwM5D,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,WAAW,EAAE,CAG1F;AAID,yEAAyE;AACzE,wBAAgB,wBAAwB,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAKpE;AAED,yEAAyE;AACzE,wBAAgB,0BAA0B,IAAI,WAAW,EAAE,CAK1D;AAED,gFAAgF;AAChF,wBAAgB,uBAAuB,IAAI,WAAW,EAAE,CAEvD;AAED,mFAAmF;AACnF,wBAAgB,kBAAkB,IAAI,IAAI,CASzC;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAoDxE;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAqC7E"}
|