@juspay/neurolink 7.38.1 → 7.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { EventEmitter } from "events";
10
10
  import { ToolDiscoveryService } from "./toolDiscoveryService.js";
11
+ import type { HITLManager } from "../hitl/hitlManager.js";
11
12
  import type { ExternalMCPServerInstance, ExternalMCPServerHealth, ExternalMCPConfigValidation, ExternalMCPOperationResult, ExternalMCPManagerConfig, ExternalMCPToolInfo } from "../types/externalMcp.js";
12
13
  import type { MCPServerInfo } from "../types/mcpTypes.js";
13
14
  import type { JsonObject } from "../types/common.js";
@@ -18,9 +19,19 @@ export declare class ExternalServerManager extends EventEmitter {
18
19
  private isShuttingDown;
19
20
  private toolDiscovery;
20
21
  private enableMainRegistryIntegration;
22
+ private hitlManager?;
21
23
  constructor(config?: ExternalMCPManagerConfig, options?: {
22
24
  enableMainRegistryIntegration?: boolean;
23
25
  });
26
+ /**
27
+ * Set HITL manager for human-in-the-loop safety mechanisms
28
+ * @param hitlManager - HITL manager instance (optional, can be undefined to disable)
29
+ */
30
+ setHITLManager(hitlManager?: HITLManager): void;
31
+ /**
32
+ * Get current HITL manager
33
+ */
34
+ getHITLManager(): HITLManager | undefined;
24
35
  /**
25
36
  * Load MCP server configurations from .mcp-config.json file with parallel loading support
26
37
  * Automatically registers servers found in the configuration
@@ -11,6 +11,7 @@ import { mcpLogger } from "../utils/logger.js";
11
11
  import { MCPClientFactory } from "./mcpClientFactory.js";
12
12
  import { ToolDiscoveryService } from "./toolDiscoveryService.js";
13
13
  import { toolRegistry } from "./toolRegistry.js";
14
+ import { HITLUserRejectedError, HITLTimeoutError } from "../hitl/hitlErrors.js";
14
15
  import { detectCategory } from "../utils/mcpDefaults.js";
15
16
  import { isObject, isNonNullObject } from "../utils/typeUtils.js";
16
17
  /**
@@ -76,6 +77,7 @@ export class ExternalServerManager extends EventEmitter {
76
77
  isShuttingDown = false;
77
78
  toolDiscovery;
78
79
  enableMainRegistryIntegration;
80
+ hitlManager; // Optional HITL manager for safety mechanisms
79
81
  constructor(config = {}, options = {}) {
80
82
  super();
81
83
  // Set defaults for configuration
@@ -106,6 +108,25 @@ export class ExternalServerManager extends EventEmitter {
106
108
  process.on("SIGTERM", () => this.shutdown());
107
109
  process.on("beforeExit", () => this.shutdown());
108
110
  }
111
+ /**
112
+ * Set HITL manager for human-in-the-loop safety mechanisms
113
+ * @param hitlManager - HITL manager instance (optional, can be undefined to disable)
114
+ */
115
+ setHITLManager(hitlManager) {
116
+ this.hitlManager = hitlManager;
117
+ if (hitlManager && hitlManager.isEnabled()) {
118
+ mcpLogger.info("[ExternalServerManager] HITL safety mechanisms enabled for external tool execution");
119
+ }
120
+ else {
121
+ mcpLogger.debug("[ExternalServerManager] HITL safety mechanisms disabled or not configured");
122
+ }
123
+ }
124
+ /**
125
+ * Get current HITL manager
126
+ */
127
+ getHITLManager() {
128
+ return this.hitlManager;
129
+ }
109
130
  /**
110
131
  * Load MCP server configurations from .mcp-config.json file with parallel loading support
111
132
  * Automatically registers servers found in the configuration
@@ -1116,8 +1137,54 @@ export class ExternalServerManager extends EventEmitter {
1116
1137
  }
1117
1138
  const startTime = Date.now();
1118
1139
  try {
1119
- // Execute tool through discovery service
1120
- const result = await this.toolDiscovery.executeTool(toolName, serverId, instance.client, parameters, {
1140
+ // HITL Safety Check: Request confirmation if required
1141
+ let finalParameters = parameters;
1142
+ if (this.hitlManager && this.hitlManager.isEnabled()) {
1143
+ const requiresConfirmation = this.hitlManager.requiresConfirmation(toolName, parameters);
1144
+ if (requiresConfirmation) {
1145
+ mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' on server '${serverId}' requires HITL confirmation`);
1146
+ try {
1147
+ const confirmationResult = await this.hitlManager.requestConfirmation(toolName, parameters, {
1148
+ serverId: serverId,
1149
+ sessionId: `external-${serverId}-${Date.now()}`,
1150
+ userId: undefined, // External tools don't have user context by default
1151
+ });
1152
+ if (!confirmationResult.approved) {
1153
+ // User rejected the tool execution
1154
+ throw new HITLUserRejectedError(`External tool execution rejected by user: ${confirmationResult.reason || "No reason provided"}`, toolName, confirmationResult.reason);
1155
+ }
1156
+ // User approved - use modified arguments if provided
1157
+ if (confirmationResult.modifiedArguments !== undefined) {
1158
+ finalParameters =
1159
+ confirmationResult.modifiedArguments;
1160
+ mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' arguments modified by user`);
1161
+ }
1162
+ mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' approved for execution (response time: ${confirmationResult.responseTime}ms)`);
1163
+ }
1164
+ catch (error) {
1165
+ if (error instanceof HITLTimeoutError) {
1166
+ // Timeout occurred - user didn't respond in time
1167
+ mcpLogger.warn(`[ExternalServerManager] External tool '${toolName}' execution timed out waiting for user confirmation`);
1168
+ throw error;
1169
+ }
1170
+ else if (error instanceof HITLUserRejectedError) {
1171
+ // User explicitly rejected
1172
+ mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' execution rejected by user`);
1173
+ throw error;
1174
+ }
1175
+ else {
1176
+ // Other HITL error (configuration, system error, etc.)
1177
+ mcpLogger.error(`[ExternalServerManager] HITL confirmation failed for external tool '${toolName}':`, error);
1178
+ throw new Error(`HITL confirmation failed: ${error instanceof Error ? error.message : String(error)}`);
1179
+ }
1180
+ }
1181
+ }
1182
+ else {
1183
+ mcpLogger.debug(`[ExternalServerManager] External tool '${toolName}' does not require HITL confirmation`);
1184
+ }
1185
+ }
1186
+ // Execute tool through discovery service (with potentially modified parameters)
1187
+ const result = await this.toolDiscovery.executeTool(toolName, serverId, instance.client, finalParameters, {
1121
1188
  timeout: options?.timeout || this.config.defaultTimeout,
1122
1189
  });
1123
1190
  const duration = Date.now() - startTime;
@@ -6,6 +6,7 @@ import type { ExecutionContext, ToolInfo } from "./contracts/mcpContract.js";
6
6
  import type { ToolResult } from "./factory.js";
7
7
  import type { MCPServerInfo } from "../types/mcpTypes.js";
8
8
  import { MCPRegistry } from "./registry.js";
9
+ import type { HITLManager } from "../hitl/hitlManager.js";
9
10
  interface ToolImplementation {
10
11
  execute: (params: unknown, context?: ExecutionContext) => Promise<unknown> | unknown;
11
12
  description?: string;
@@ -32,7 +33,17 @@ export declare class MCPToolRegistry extends MCPRegistry {
32
33
  private toolImplementations;
33
34
  private toolExecutionStats;
34
35
  private builtInServerInfos;
36
+ private hitlManager?;
35
37
  constructor();
38
+ /**
39
+ * Set HITL manager for human-in-the-loop safety mechanisms
40
+ * @param hitlManager - HITL manager instance (optional, can be undefined to disable)
41
+ */
42
+ setHITLManager(hitlManager?: HITLManager): void;
43
+ /**
44
+ * Get current HITL manager
45
+ */
46
+ getHITLManager(): HITLManager | undefined;
36
47
  /**
37
48
  * Register all direct tools from directAgentTools
38
49
  */
@@ -9,17 +9,38 @@ import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
9
9
  import { directAgentTools } from "../agent/directTools.js";
10
10
  import { detectCategory, createMCPServerInfo } from "../utils/mcpDefaults.js";
11
11
  import { FlexibleToolValidator } from "./flexibleToolValidator.js";
12
+ import { HITLUserRejectedError, HITLTimeoutError } from "../hitl/hitlErrors.js";
12
13
  export class MCPToolRegistry extends MCPRegistry {
13
14
  tools = new Map();
14
15
  toolImplementations = new Map(); // Store actual tool implementations
15
16
  toolExecutionStats = new Map();
16
17
  builtInServerInfos = []; // DIRECT storage for MCPServerInfo
18
+ hitlManager; // Optional HITL manager for safety mechanisms
17
19
  constructor() {
18
20
  super();
19
21
  if (!shouldDisableBuiltinTools()) {
20
22
  this.registerDirectTools();
21
23
  }
22
24
  }
25
+ /**
26
+ * Set HITL manager for human-in-the-loop safety mechanisms
27
+ * @param hitlManager - HITL manager instance (optional, can be undefined to disable)
28
+ */
29
+ setHITLManager(hitlManager) {
30
+ this.hitlManager = hitlManager;
31
+ if (hitlManager && hitlManager.isEnabled()) {
32
+ registryLogger.info("HITL safety mechanisms enabled for tool execution");
33
+ }
34
+ else {
35
+ registryLogger.debug("HITL safety mechanisms disabled or not configured");
36
+ }
37
+ }
38
+ /**
39
+ * Get current HITL manager
40
+ */
41
+ getHITLManager() {
42
+ return this.hitlManager;
43
+ }
23
44
  /**
24
45
  * Register all direct tools from directAgentTools
25
46
  */
@@ -237,9 +258,54 @@ export class MCPToolRegistry extends MCPRegistry {
237
258
  if (!toolImpl || typeof toolImpl?.execute !== "function") {
238
259
  throw new Error(`Tool '${toolName}' implementation not found or not executable`);
239
260
  }
240
- // Execute the actual tool
241
- registryLogger.debug(`Executing tool '${toolName}' with args:`, args);
242
- const toolResult = await toolImpl.execute(args, execContext);
261
+ // HITL Safety Check: Request confirmation if required
262
+ let finalArgs = args;
263
+ if (this.hitlManager && this.hitlManager.isEnabled()) {
264
+ const requiresConfirmation = this.hitlManager.requiresConfirmation(toolName, args);
265
+ if (requiresConfirmation) {
266
+ registryLogger.info(`Tool '${toolName}' requires HITL confirmation`);
267
+ try {
268
+ const confirmationResult = await this.hitlManager.requestConfirmation(toolName, args, {
269
+ serverId: tool.serverId,
270
+ sessionId: execContext.sessionId,
271
+ userId: execContext.userId,
272
+ });
273
+ if (!confirmationResult.approved) {
274
+ // User rejected the tool execution
275
+ throw new HITLUserRejectedError(`Tool execution rejected by user: ${confirmationResult.reason || "No reason provided"}`, toolName, confirmationResult.reason);
276
+ }
277
+ // User approved - use modified arguments if provided
278
+ if (confirmationResult.modifiedArguments !== undefined) {
279
+ finalArgs = confirmationResult.modifiedArguments;
280
+ registryLogger.info(`Tool '${toolName}' arguments modified by user`);
281
+ }
282
+ registryLogger.info(`Tool '${toolName}' approved for execution (response time: ${confirmationResult.responseTime}ms)`);
283
+ }
284
+ catch (error) {
285
+ if (error instanceof HITLTimeoutError) {
286
+ // Timeout occurred - user didn't respond in time
287
+ registryLogger.warn(`Tool '${toolName}' execution timed out waiting for user confirmation`);
288
+ throw error;
289
+ }
290
+ else if (error instanceof HITLUserRejectedError) {
291
+ // User explicitly rejected
292
+ registryLogger.info(`Tool '${toolName}' execution rejected by user`);
293
+ throw error;
294
+ }
295
+ else {
296
+ // Other HITL error (configuration, system error, etc.)
297
+ registryLogger.error(`HITL confirmation failed for tool '${toolName}':`, error);
298
+ throw new Error(`HITL confirmation failed: ${error instanceof Error ? error.message : String(error)}`);
299
+ }
300
+ }
301
+ }
302
+ else {
303
+ registryLogger.debug(`Tool '${toolName}' does not require HITL confirmation`);
304
+ }
305
+ }
306
+ // Execute the actual tool (with potentially modified arguments)
307
+ registryLogger.debug(`Executing tool '${toolName}' with args:`, finalArgs);
308
+ const toolResult = await toolImpl.execute(finalArgs, execContext);
243
309
  // Properly wrap raw results in ToolResult format
244
310
  let result;
245
311
  // Check if result is already a ToolResult object
@@ -16,6 +16,7 @@ import type { BatchOperationResult } from "./types/typeAliases.js";
16
16
  import type { ConversationMemoryConfig, ChatMessage } from "./types/conversation.js";
17
17
  import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
18
18
  import { RedisConversationMemoryManager } from "./core/redisConversationMemoryManager.js";
19
+ import type { HITLConfig } from "./hitl/types.js";
19
20
  import type { ExternalMCPServerInstance, ExternalMCPOperationResult, ExternalMCPToolInfo } from "./types/externalMcp.js";
20
21
  export interface ProviderStatus {
21
22
  provider: string;
@@ -68,6 +69,7 @@ export declare class NeuroLink {
68
69
  private conversationMemoryNeedsInit;
69
70
  private conversationMemoryConfig?;
70
71
  private enableOrchestration;
72
+ private hitlManager?;
71
73
  /**
72
74
  * Context storage for tool execution
73
75
  * This context will be merged with any runtime context passed by the AI model
@@ -82,6 +84,11 @@ export declare class NeuroLink {
82
84
  * @param config.conversationMemory.maxSessions - Maximum number of concurrent sessions (default: 100)
83
85
  * @param config.conversationMemory.maxTurnsPerSession - Maximum conversation turns per session (default: 50)
84
86
  * @param config.enableOrchestration - Whether to enable smart model orchestration (default: false)
87
+ * @param config.hitl - Configuration for Human-in-the-Loop safety features
88
+ * @param config.hitl.enabled - Whether to enable HITL tool confirmation (default: false)
89
+ * @param config.hitl.dangerousActions - Keywords that trigger confirmation (default: ['delete', 'remove', 'drop'])
90
+ * @param config.hitl.timeout - Confirmation timeout in milliseconds (default: 30000)
91
+ * @param config.hitl.allowArgumentModification - Allow users to modify tool parameters (default: true)
85
92
  *
86
93
  * @example
87
94
  * ```typescript
@@ -101,15 +108,27 @@ export declare class NeuroLink {
101
108
  * const neurolink = new NeuroLink({
102
109
  * enableOrchestration: true
103
110
  * });
111
+ *
112
+ * // With HITL safety features
113
+ * const neurolink = new NeuroLink({
114
+ * hitl: {
115
+ * enabled: true,
116
+ * dangerousActions: ['delete', 'remove', 'drop', 'truncate'],
117
+ * timeout: 30000,
118
+ * allowArgumentModification: true
119
+ * }
120
+ * });
104
121
  * ```
105
122
  *
106
123
  * @throws {Error} When provider registry setup fails
107
124
  * @throws {Error} When conversation memory initialization fails (if enabled)
108
125
  * @throws {Error} When external server manager initialization fails
126
+ * @throws {Error} When HITL configuration is invalid (if enabled)
109
127
  */
110
128
  constructor(config?: {
111
129
  conversationMemory?: Partial<ConversationMemoryConfig>;
112
130
  enableOrchestration?: boolean;
131
+ hitl?: HITLConfig;
113
132
  });
114
133
  /**
115
134
  * Initialize provider registry with security settings
@@ -119,6 +138,14 @@ export declare class NeuroLink {
119
138
  * Initialize conversation memory if enabled
120
139
  */
121
140
  private initializeConversationMemory;
141
+ /**
142
+ * Initialize HITL (Human-in-the-Loop) if enabled
143
+ */
144
+ private initializeHITL;
145
+ /**
146
+ * Set up HITL event forwarding to main emitter
147
+ */
148
+ private setupHITLEventForwarding;
122
149
  /**
123
150
  * Initialize external server manager with event handlers
124
151
  */
@@ -33,6 +33,7 @@ import { ErrorFactory, NeuroLinkError, withTimeout, withRetry, isRetriableError,
33
33
  import { EventEmitter } from "events";
34
34
  import { getConversationMessages, storeConversationTurn, } from "./utils/conversationMemory.js";
35
35
  import { ExternalServerManager } from "./mcp/externalServerManager.js";
36
+ import { HITLManager } from "./hitl/hitlManager.js";
36
37
  // Import direct tools server for automatic registration
37
38
  import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
38
39
  // Import orchestration components
@@ -84,6 +85,8 @@ export class NeuroLink {
84
85
  conversationMemoryConfig;
85
86
  // Add orchestration property
86
87
  enableOrchestration;
88
+ // HITL (Human-in-the-Loop) support
89
+ hitlManager;
87
90
  /**
88
91
  * Context storage for tool execution
89
92
  * This context will be merged with any runtime context passed by the AI model
@@ -98,6 +101,11 @@ export class NeuroLink {
98
101
  * @param config.conversationMemory.maxSessions - Maximum number of concurrent sessions (default: 100)
99
102
  * @param config.conversationMemory.maxTurnsPerSession - Maximum conversation turns per session (default: 50)
100
103
  * @param config.enableOrchestration - Whether to enable smart model orchestration (default: false)
104
+ * @param config.hitl - Configuration for Human-in-the-Loop safety features
105
+ * @param config.hitl.enabled - Whether to enable HITL tool confirmation (default: false)
106
+ * @param config.hitl.dangerousActions - Keywords that trigger confirmation (default: ['delete', 'remove', 'drop'])
107
+ * @param config.hitl.timeout - Confirmation timeout in milliseconds (default: 30000)
108
+ * @param config.hitl.allowArgumentModification - Allow users to modify tool parameters (default: true)
101
109
  *
102
110
  * @example
103
111
  * ```typescript
@@ -117,11 +125,22 @@ export class NeuroLink {
117
125
  * const neurolink = new NeuroLink({
118
126
  * enableOrchestration: true
119
127
  * });
128
+ *
129
+ * // With HITL safety features
130
+ * const neurolink = new NeuroLink({
131
+ * hitl: {
132
+ * enabled: true,
133
+ * dangerousActions: ['delete', 'remove', 'drop', 'truncate'],
134
+ * timeout: 30000,
135
+ * allowArgumentModification: true
136
+ * }
137
+ * });
120
138
  * ```
121
139
  *
122
140
  * @throws {Error} When provider registry setup fails
123
141
  * @throws {Error} When conversation memory initialization fails (if enabled)
124
142
  * @throws {Error} When external server manager initialization fails
143
+ * @throws {Error} When HITL configuration is invalid (if enabled)
125
144
  */
126
145
  constructor(config) {
127
146
  // Initialize orchestration setting
@@ -137,6 +156,7 @@ export class NeuroLink {
137
156
  this.initializeProviderRegistry(constructorId, constructorStartTime, constructorHrTimeStart);
138
157
  this.initializeConversationMemory(config, constructorId, constructorStartTime, constructorHrTimeStart);
139
158
  this.initializeExternalServerManager(constructorId, constructorStartTime, constructorHrTimeStart);
159
+ this.initializeHITL(config, constructorId, constructorStartTime, constructorHrTimeStart);
140
160
  this.logConstructorComplete(constructorId, constructorStartTime, constructorHrTimeStart);
141
161
  }
142
162
  /**
@@ -198,6 +218,132 @@ export class NeuroLink {
198
218
  });
199
219
  }
200
220
  }
221
+ /**
222
+ * Initialize HITL (Human-in-the-Loop) if enabled
223
+ */
224
+ initializeHITL(config, constructorId, constructorStartTime, constructorHrTimeStart) {
225
+ if (config?.hitl?.enabled) {
226
+ const hitlInitStartTime = process.hrtime.bigint();
227
+ logger.debug(`[NeuroLink] 🛡️ LOG_POINT_C015_HITL_INIT_START`, {
228
+ logPoint: "C015_HITL_INIT_START",
229
+ constructorId,
230
+ timestamp: new Date().toISOString(),
231
+ elapsedMs: Date.now() - constructorStartTime,
232
+ elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
233
+ hitlInitStartTimeNs: hitlInitStartTime.toString(),
234
+ hitlConfig: {
235
+ enabled: config.hitl.enabled,
236
+ dangerousActions: config.hitl.dangerousActions || [],
237
+ timeout: config.hitl.timeout || 30000,
238
+ allowArgumentModification: config.hitl.allowArgumentModification ?? true,
239
+ auditLogging: config.hitl.auditLogging ?? false,
240
+ },
241
+ message: "Starting HITL (Human-in-the-Loop) initialization",
242
+ });
243
+ try {
244
+ // Initialize HITL manager
245
+ this.hitlManager = new HITLManager(config.hitl);
246
+ // Inject HITL manager into tool registry
247
+ toolRegistry.setHITLManager(this.hitlManager);
248
+ // Inject HITL manager into external server manager
249
+ this.externalServerManager.setHITLManager(this.hitlManager);
250
+ // Set up HITL event forwarding to main emitter
251
+ this.setupHITLEventForwarding();
252
+ const hitlInitEndTime = process.hrtime.bigint();
253
+ const hitlInitDurationNs = hitlInitEndTime - hitlInitStartTime;
254
+ logger.debug(`[NeuroLink] ✅ LOG_POINT_C016_HITL_INIT_SUCCESS`, {
255
+ logPoint: "C016_HITL_INIT_SUCCESS",
256
+ constructorId,
257
+ timestamp: new Date().toISOString(),
258
+ elapsedMs: Date.now() - constructorStartTime,
259
+ elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
260
+ hitlInitDurationNs: hitlInitDurationNs.toString(),
261
+ hitlInitDurationMs: Number(hitlInitDurationNs) / NANOSECOND_TO_MS_DIVISOR,
262
+ hasHitlManager: !!this.hitlManager,
263
+ message: "HITL (Human-in-the-Loop) initialized successfully",
264
+ });
265
+ logger.info(`[NeuroLink] HITL safety features enabled`, {
266
+ dangerousActions: config.hitl.dangerousActions?.length || 0,
267
+ timeout: config.hitl.timeout || 30000,
268
+ allowArgumentModification: config.hitl.allowArgumentModification ?? true,
269
+ auditLogging: config.hitl.auditLogging ?? false,
270
+ });
271
+ }
272
+ catch (error) {
273
+ const hitlInitErrorTime = process.hrtime.bigint();
274
+ const hitlInitDurationNs = hitlInitErrorTime - hitlInitStartTime;
275
+ logger.error(`[NeuroLink] ❌ LOG_POINT_C017_HITL_INIT_ERROR`, {
276
+ logPoint: "C017_HITL_INIT_ERROR",
277
+ constructorId,
278
+ timestamp: new Date().toISOString(),
279
+ elapsedMs: Date.now() - constructorStartTime,
280
+ elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
281
+ hitlInitDurationNs: hitlInitDurationNs.toString(),
282
+ hitlInitDurationMs: Number(hitlInitDurationNs) / NANOSECOND_TO_MS_DIVISOR,
283
+ error: error instanceof Error ? error.message : String(error),
284
+ errorName: error instanceof Error ? error.name : "UnknownError",
285
+ errorStack: error instanceof Error ? error.stack : undefined,
286
+ message: "HITL (Human-in-the-Loop) initialization failed",
287
+ });
288
+ throw error;
289
+ }
290
+ }
291
+ else {
292
+ logger.debug(`[NeuroLink] 🚫 LOG_POINT_C018_HITL_DISABLED`, {
293
+ logPoint: "C018_HITL_DISABLED",
294
+ constructorId,
295
+ timestamp: new Date().toISOString(),
296
+ elapsedMs: Date.now() - constructorStartTime,
297
+ elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
298
+ hasConfig: !!config,
299
+ hasHitlConfig: !!config?.hitl,
300
+ hitlEnabled: config?.hitl?.enabled || false,
301
+ reason: !config
302
+ ? "NO_CONFIG"
303
+ : !config.hitl
304
+ ? "NO_HITL_CONFIG"
305
+ : !config.hitl.enabled
306
+ ? "HITL_DISABLED"
307
+ : "UNKNOWN",
308
+ message: "HITL (Human-in-the-Loop) not enabled - skipping initialization",
309
+ });
310
+ }
311
+ }
312
+ /**
313
+ * Set up HITL event forwarding to main emitter
314
+ */
315
+ setupHITLEventForwarding() {
316
+ if (!this.hitlManager) {
317
+ return;
318
+ }
319
+ // Forward HITL confirmation requests to main emitter
320
+ this.hitlManager.on("hitl:confirmation-request", (event) => {
321
+ logger.debug("Forwarding HITL confirmation request", {
322
+ confirmationId: event.payload?.confirmationId,
323
+ toolName: event.payload?.toolName,
324
+ });
325
+ this.emitter.emit("hitl:confirmation-request", event);
326
+ });
327
+ // Forward HITL timeout events to main emitter
328
+ this.hitlManager.on("hitl:timeout", (event) => {
329
+ logger.debug("Forwarding HITL timeout event", {
330
+ confirmationId: event.payload?.confirmationId,
331
+ toolName: event.payload?.toolName,
332
+ });
333
+ this.emitter.emit("hitl:timeout", event);
334
+ });
335
+ // Listen for confirmation responses from main emitter and forward to HITL manager
336
+ this.emitter.on("hitl:confirmation-response", (event) => {
337
+ const typedEvent = event;
338
+ logger.debug("Received HITL confirmation response", {
339
+ confirmationId: typedEvent.payload?.confirmationId,
340
+ approved: typedEvent.payload?.approved,
341
+ });
342
+ // Forward to HITL manager
343
+ this.hitlManager?.emit("hitl:confirmation-response", typedEvent);
344
+ });
345
+ logger.debug("HITL event forwarding configured successfully");
346
+ }
201
347
  /**
202
348
  * Initialize external server manager with event handlers
203
349
  */
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { EventEmitter } from "events";
10
10
  import { ToolDiscoveryService } from "./toolDiscoveryService.js";
11
+ import type { HITLManager } from "../hitl/hitlManager.js";
11
12
  import type { ExternalMCPServerInstance, ExternalMCPServerHealth, ExternalMCPConfigValidation, ExternalMCPOperationResult, ExternalMCPManagerConfig, ExternalMCPToolInfo } from "../types/externalMcp.js";
12
13
  import type { MCPServerInfo } from "../types/mcpTypes.js";
13
14
  import type { JsonObject } from "../types/common.js";
@@ -18,9 +19,19 @@ export declare class ExternalServerManager extends EventEmitter {
18
19
  private isShuttingDown;
19
20
  private toolDiscovery;
20
21
  private enableMainRegistryIntegration;
22
+ private hitlManager?;
21
23
  constructor(config?: ExternalMCPManagerConfig, options?: {
22
24
  enableMainRegistryIntegration?: boolean;
23
25
  });
26
+ /**
27
+ * Set HITL manager for human-in-the-loop safety mechanisms
28
+ * @param hitlManager - HITL manager instance (optional, can be undefined to disable)
29
+ */
30
+ setHITLManager(hitlManager?: HITLManager): void;
31
+ /**
32
+ * Get current HITL manager
33
+ */
34
+ getHITLManager(): HITLManager | undefined;
24
35
  /**
25
36
  * Load MCP server configurations from .mcp-config.json file with parallel loading support
26
37
  * Automatically registers servers found in the configuration
@@ -11,6 +11,7 @@ import { mcpLogger } from "../utils/logger.js";
11
11
  import { MCPClientFactory } from "./mcpClientFactory.js";
12
12
  import { ToolDiscoveryService } from "./toolDiscoveryService.js";
13
13
  import { toolRegistry } from "./toolRegistry.js";
14
+ import { HITLUserRejectedError, HITLTimeoutError } from "../hitl/hitlErrors.js";
14
15
  import { detectCategory } from "../utils/mcpDefaults.js";
15
16
  import { isObject, isNonNullObject } from "../utils/typeUtils.js";
16
17
  /**
@@ -76,6 +77,7 @@ export class ExternalServerManager extends EventEmitter {
76
77
  isShuttingDown = false;
77
78
  toolDiscovery;
78
79
  enableMainRegistryIntegration;
80
+ hitlManager; // Optional HITL manager for safety mechanisms
79
81
  constructor(config = {}, options = {}) {
80
82
  super();
81
83
  // Set defaults for configuration
@@ -106,6 +108,25 @@ export class ExternalServerManager extends EventEmitter {
106
108
  process.on("SIGTERM", () => this.shutdown());
107
109
  process.on("beforeExit", () => this.shutdown());
108
110
  }
111
+ /**
112
+ * Set HITL manager for human-in-the-loop safety mechanisms
113
+ * @param hitlManager - HITL manager instance (optional, can be undefined to disable)
114
+ */
115
+ setHITLManager(hitlManager) {
116
+ this.hitlManager = hitlManager;
117
+ if (hitlManager && hitlManager.isEnabled()) {
118
+ mcpLogger.info("[ExternalServerManager] HITL safety mechanisms enabled for external tool execution");
119
+ }
120
+ else {
121
+ mcpLogger.debug("[ExternalServerManager] HITL safety mechanisms disabled or not configured");
122
+ }
123
+ }
124
+ /**
125
+ * Get current HITL manager
126
+ */
127
+ getHITLManager() {
128
+ return this.hitlManager;
129
+ }
109
130
  /**
110
131
  * Load MCP server configurations from .mcp-config.json file with parallel loading support
111
132
  * Automatically registers servers found in the configuration
@@ -1116,8 +1137,54 @@ export class ExternalServerManager extends EventEmitter {
1116
1137
  }
1117
1138
  const startTime = Date.now();
1118
1139
  try {
1119
- // Execute tool through discovery service
1120
- const result = await this.toolDiscovery.executeTool(toolName, serverId, instance.client, parameters, {
1140
+ // HITL Safety Check: Request confirmation if required
1141
+ let finalParameters = parameters;
1142
+ if (this.hitlManager && this.hitlManager.isEnabled()) {
1143
+ const requiresConfirmation = this.hitlManager.requiresConfirmation(toolName, parameters);
1144
+ if (requiresConfirmation) {
1145
+ mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' on server '${serverId}' requires HITL confirmation`);
1146
+ try {
1147
+ const confirmationResult = await this.hitlManager.requestConfirmation(toolName, parameters, {
1148
+ serverId: serverId,
1149
+ sessionId: `external-${serverId}-${Date.now()}`,
1150
+ userId: undefined, // External tools don't have user context by default
1151
+ });
1152
+ if (!confirmationResult.approved) {
1153
+ // User rejected the tool execution
1154
+ throw new HITLUserRejectedError(`External tool execution rejected by user: ${confirmationResult.reason || "No reason provided"}`, toolName, confirmationResult.reason);
1155
+ }
1156
+ // User approved - use modified arguments if provided
1157
+ if (confirmationResult.modifiedArguments !== undefined) {
1158
+ finalParameters =
1159
+ confirmationResult.modifiedArguments;
1160
+ mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' arguments modified by user`);
1161
+ }
1162
+ mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' approved for execution (response time: ${confirmationResult.responseTime}ms)`);
1163
+ }
1164
+ catch (error) {
1165
+ if (error instanceof HITLTimeoutError) {
1166
+ // Timeout occurred - user didn't respond in time
1167
+ mcpLogger.warn(`[ExternalServerManager] External tool '${toolName}' execution timed out waiting for user confirmation`);
1168
+ throw error;
1169
+ }
1170
+ else if (error instanceof HITLUserRejectedError) {
1171
+ // User explicitly rejected
1172
+ mcpLogger.info(`[ExternalServerManager] External tool '${toolName}' execution rejected by user`);
1173
+ throw error;
1174
+ }
1175
+ else {
1176
+ // Other HITL error (configuration, system error, etc.)
1177
+ mcpLogger.error(`[ExternalServerManager] HITL confirmation failed for external tool '${toolName}':`, error);
1178
+ throw new Error(`HITL confirmation failed: ${error instanceof Error ? error.message : String(error)}`);
1179
+ }
1180
+ }
1181
+ }
1182
+ else {
1183
+ mcpLogger.debug(`[ExternalServerManager] External tool '${toolName}' does not require HITL confirmation`);
1184
+ }
1185
+ }
1186
+ // Execute tool through discovery service (with potentially modified parameters)
1187
+ const result = await this.toolDiscovery.executeTool(toolName, serverId, instance.client, finalParameters, {
1121
1188
  timeout: options?.timeout || this.config.defaultTimeout,
1122
1189
  });
1123
1190
  const duration = Date.now() - startTime;
@@ -6,6 +6,7 @@ import type { ExecutionContext, ToolInfo } from "./contracts/mcpContract.js";
6
6
  import type { ToolResult } from "./factory.js";
7
7
  import type { MCPServerInfo } from "../types/mcpTypes.js";
8
8
  import { MCPRegistry } from "./registry.js";
9
+ import type { HITLManager } from "../hitl/hitlManager.js";
9
10
  interface ToolImplementation {
10
11
  execute: (params: unknown, context?: ExecutionContext) => Promise<unknown> | unknown;
11
12
  description?: string;
@@ -32,7 +33,17 @@ export declare class MCPToolRegistry extends MCPRegistry {
32
33
  private toolImplementations;
33
34
  private toolExecutionStats;
34
35
  private builtInServerInfos;
36
+ private hitlManager?;
35
37
  constructor();
38
+ /**
39
+ * Set HITL manager for human-in-the-loop safety mechanisms
40
+ * @param hitlManager - HITL manager instance (optional, can be undefined to disable)
41
+ */
42
+ setHITLManager(hitlManager?: HITLManager): void;
43
+ /**
44
+ * Get current HITL manager
45
+ */
46
+ getHITLManager(): HITLManager | undefined;
36
47
  /**
37
48
  * Register all direct tools from directAgentTools
38
49
  */