@@ -242,13 +242,122 @@ export async function createServer() {
242
242
243
243
// Notify tool discovery manager of configuration changes with changes parameter
244
244
await toolDiscoveryManager . handleConfigurationUpdate ( config , changes ) ;
245
+
246
+ // Auto-spawn stdio processes when servers are added
247
+ if ( changes && changes . addedServers . length > 0 ) {
248
+ for ( const serverName of changes . addedServers ) {
249
+ const serverConfig = config . servers [ serverName ] ;
250
+ const transportType = serverConfig . transport_type || serverConfig . type ;
251
+
252
+ if ( transportType === 'stdio' ) {
253
+ try {
254
+ server . log . info ( {
255
+ operation : 'auto_spawn_stdio_server' ,
256
+ server_name : serverName ,
257
+ transport_type : transportType
258
+ } , `Auto-spawning stdio server: ${ serverName } ` ) ;
259
+
260
+ // Check if already running
261
+ const existing = runtimeState . getProcessByName ( serverName ) ;
262
+ if ( existing && existing . status === 'running' ) {
263
+ server . log . warn ( {
264
+ operation : 'auto_spawn_already_running' ,
265
+ server_name : serverName
266
+ } , `stdio server already running, skipping spawn: ${ serverName } ` ) ;
267
+ continue ;
268
+ }
269
+
270
+ // Build MCP server config for process spawning
271
+ const processConfig = {
272
+ installation_id : serverConfig . installation_id || serverName ,
273
+ installation_name : serverName ,
274
+ team_id : serverConfig . team_id || 'unknown' ,
275
+ command : serverConfig . command ! ,
276
+ args : serverConfig . args ! ,
277
+ env : serverConfig . env || { }
278
+ } ;
279
+
280
+ // Spawn the process
281
+ const processInfo = await processManager . spawnProcess ( processConfig ) ;
282
+
283
+ // Add to runtime state
284
+ runtimeState . addProcess (
285
+ processInfo ,
286
+ processConfig . installation_id ,
287
+ processConfig . installation_name ,
288
+ processConfig . team_id
289
+ ) ;
290
+
291
+ server . log . info ( {
292
+ operation : 'auto_spawn_stdio_success' ,
293
+ server_name : serverName ,
294
+ pid : processInfo . process . pid
295
+ } , `stdio server spawned successfully: ${ serverName } ` ) ;
296
+
297
+ } catch ( error ) {
298
+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
299
+ server . log . error ( {
300
+ operation : 'auto_spawn_stdio_failed' ,
301
+ server_name : serverName ,
302
+ error : errorMessage
303
+ } , `Failed to auto-spawn stdio server: ${ errorMessage } ` ) ;
304
+ }
305
+ }
306
+ }
307
+ }
245
308
} ) ;
246
309
310
+ // Set up automatic tool discovery when stdio processes are spawned
311
+ processManager . on ( 'processSpawned' , async ( processInfo ) => {
312
+ try {
313
+ server . log . info ( {
314
+ operation : 'trigger_stdio_tool_discovery' ,
315
+ installation_name : processInfo . config . installation_name ,
316
+ team_id : processInfo . config . team_id
317
+ } , `Process spawned successfully - triggering tool discovery for ${ processInfo . config . installation_name } ` ) ;
318
+
319
+ // FIXED: Add delay after handshake to allow MCP server to fully initialize
320
+ // Some servers (especially npm-based ones) need time to register all tools
321
+ server . log . debug ( {
322
+ operation : 'tool_discovery_delay' ,
323
+ installation_name : processInfo . config . installation_name ,
324
+ delay_ms : 500
325
+ } , `Waiting 500ms before tool discovery to ensure server is fully initialized` ) ;
326
+
327
+ await new Promise ( resolve => setTimeout ( resolve , 500 ) ) ;
328
+
329
+ await toolDiscoveryManager . discoverStdioTools ( processInfo . config . installation_name ) ;
330
+ } catch ( error ) {
331
+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
332
+ server . log . error ( {
333
+ operation : 'stdio_tool_discovery_error' ,
334
+ installation_name : processInfo . config . installation_name ,
335
+ error : errorMessage
336
+ } , `Failed to discover stdio tools after process spawn: ${ errorMessage } ` ) ;
337
+ }
338
+ } ) ;
339
+
340
+ server . log . info ( {
341
+ operation : 'stdio_tool_discovery_handler_registered'
342
+ } , 'Automatic stdio tool discovery handler registered for process spawn events' ) ;
343
+
344
+
247
345
// Initialize MCP Protocol Handler (after HTTP Proxy Manager and Tool Discovery Manager)
248
346
const mcpProtocolHandler = new McpProtocolHandler ( httpProxyManager , toolDiscoveryManager , server . log ) ;
249
347
250
- // Initialize Command Processor
251
- const commandProcessor = new CommandProcessor ( server . log , dynamicConfigManager ) ;
348
+ // Initialize Command Processor with stdio process management dependencies
349
+ const commandProcessor = new CommandProcessor (
350
+ server . log ,
351
+ dynamicConfigManager ,
352
+ processManager ,
353
+ runtimeState ,
354
+ stdioToolDiscoveryManager
355
+ ) ;
356
+
357
+ server . log . info ( {
358
+ operation : 'command_processor_initialized' ,
359
+ stdio_support : true
360
+ } , 'Command Processor initialized with stdio process management support' ) ;
252
361
253
362
// Initialize Command Polling Service (will be started after registration)
254
363
let commandPollingService : CommandPollingService | undefined ;
@@ -535,6 +644,27 @@ export async function createServer() {
535
644
// Set command processor for process reporting
536
645
heartbeatService . setCommandProcessor ( commandProcessor ) ;
537
646
647
+ // Initialize HeartbeatDataBuilder for normalized heartbeat data
648
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
649
+ const teamIsolationService = ( server as any ) . teamIsolationService ;
650
+ const heartbeatDataBuilder = new ( await import ( './services/heartbeat-data-builder' ) ) . HeartbeatDataBuilder (
651
+ processManager ,
652
+ runtimeState ,
653
+ toolDiscoveryManager ,
654
+ dynamicConfigManager ,
655
+ teamIsolationService ,
656
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
657
+ server . log as any
658
+ ) ;
659
+
660
+ // Set heartbeat data builder for normalized data
661
+ heartbeatService . setHeartbeatDataBuilder ( heartbeatDataBuilder ) ;
662
+
663
+ server . log . info ( {
664
+ operation : 'heartbeat_data_builder_initialized' ,
665
+ satellite_id : satelliteId
666
+ } , 'Heartbeat data builder initialized for normalized heartbeat data' ) ;
667
+
538
668
// Store heartbeat service on server instance for potential future access
539
669
server . decorate ( 'heartbeatService' , heartbeatService ) ;
540
670
0 commit comments