Skip to content

Commit 5f67f93

Browse files
committed
feat(satellite): implement heartbeat data builder for normalized metrics
1 parent db3e4eb commit 5f67f93

File tree

4 files changed

+494
-2
lines changed

4 files changed

+494
-2
lines changed

services/satellite/src/server.ts

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,122 @@ export async function createServer() {
242242

243243
// Notify tool discovery manager of configuration changes with changes parameter
244244
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+
}
245308
});
246309

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+
247345
// Initialize MCP Protocol Handler (after HTTP Proxy Manager and Tool Discovery Manager)
248346
const mcpProtocolHandler = new McpProtocolHandler(httpProxyManager, toolDiscoveryManager, server.log);
249347

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');
252361

253362
// Initialize Command Polling Service (will be started after registration)
254363
let commandPollingService: CommandPollingService | undefined;
@@ -535,6 +644,27 @@ export async function createServer() {
535644
// Set command processor for process reporting
536645
heartbeatService.setCommandProcessor(commandProcessor);
537646

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+
538668
// Store heartbeat service on server instance for potential future access
539669
server.decorate('heartbeatService', heartbeatService);
540670

0 commit comments

Comments
 (0)