UNPKG

stellar-cyber-mcp-agents

Version:

Model Context Protocol (MCP) server for Stellar Cyber security operations with specialized multi-agent analysis capabilities

1,116 lines 61 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; // Set environment flag to disable background timers FIRST process.env.MCP_MODE = 'true'; // Keep console.error for debugging, suppress others const originalConsole = global.console; global.console = { ...originalConsole, log: () => { }, info: () => { }, warn: () => { }, error: originalConsole.error, // Keep error logging for debugging debug: () => { }, }; // Global orchestrator instance - initialize once and reuse let globalOrchestrator = null; let orchestratorInitialized = false; async function getOrchestrator() { // Only try to initialize once if (orchestratorInitialized && !globalOrchestrator) { throw new Error('Orchestrator initialization failed previously'); } if (!globalOrchestrator && !orchestratorInitialized) { orchestratorInitialized = true; try { console.error('Lazy loading orchestrator for first tool call...'); // Check if we have required environment variables if (!process.env.STELLAR_API_URL || !process.env.STELLAR_API_TOKEN) { throw new Error('Missing required environment variables: STELLAR_API_URL or STELLAR_API_TOKEN'); } console.error('Importing orchestrator module...'); const orchestratorModule = await import('./orchestrator.js'); console.error('Orchestrator module imported successfully'); const { MultiAgentOrchestrator, createDefaultConfig } = orchestratorModule; const config = createDefaultConfig(); config.stellar.apiUrl = process.env.STELLAR_API_URL; config.stellar.apiToken = process.env.STELLAR_API_TOKEN; // Disable all logging to prevent EPIPE errors config.logging.enableConsole = false; config.logging.enableFile = false; // Disable background timers/intervals to prevent EPIPE errors config.agents.correlation.config.enableCampaignDetection = false; console.error('Creating orchestrator instance...'); globalOrchestrator = new MultiAgentOrchestrator(config); // Add error handlers to prevent crashes if (globalOrchestrator.on) { globalOrchestrator.on('error', (error) => { console.error('Orchestrator runtime error:', error); // Don't crash the server on orchestrator errors }); } console.error('Starting orchestrator...'); await globalOrchestrator.start(); console.error('Orchestrator initialized and started successfully'); } catch (error) { console.error('Failed to initialize orchestrator:', error); orchestratorInitialized = false; globalOrchestrator = null; // Don't throw - let the tool call handle the error gracefully throw new Error(`Orchestrator initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } if (!globalOrchestrator) { throw new Error('Orchestrator not available'); } return globalOrchestrator; } // Helper function to wrap orchestrator calls async function executeOrchestratorMethod(methodName, ...args) { try { const orchestrator = await getOrchestrator(); // Add timeout to prevent hanging operations const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Operation timed out after 60 seconds')), 60000); }); const operationPromise = orchestrator[methodName](...args); const result = await Promise.race([operationPromise, timeoutPromise]); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { // Log error to stderr for debugging console.error(`Orchestrator method ${methodName} failed:`, error); return { content: [ { type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error', tool: methodName, status: 'error' }, null, 2), }, ], }; } } // Create the MCP server const server = new Server({ name: 'stellar-cyber-agents', version: '1.0.0', }, { capabilities: { tools: {}, }, }); // Add server event handlers for debugging server.onerror = (error) => { console.error('MCP Server error:', error); }; // Log when server receives messages const originalSetRequestHandler = server.setRequestHandler; server.setRequestHandler = function (schema, handler) { return originalSetRequestHandler.call(this, schema, async (request, extra) => { console.error(`Received request: ${request.method || 'unknown'}`); try { const result = await handler(request, extra); console.error(`Request ${request.method || 'unknown'} completed successfully`); return result; } catch (error) { console.error(`Request ${request.method || 'unknown'} failed:`, error); throw error; } }); }; // Define available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'test_connection', description: 'Test connection to Stellar Cyber API', inputSchema: { type: 'object', properties: {}, }, }, { name: 'investigate_case', description: 'Investigate a Stellar Cyber case by case ID', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to investigate', }, options: { type: 'object', description: 'Investigation options', properties: {}, }, }, required: ['caseId'], }, }, { name: 'correlate_case', description: 'Find cases related to a given case ID', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to correlate', }, method: { type: 'string', description: 'Correlation method (e.g., observables, timeline)', default: 'observables', }, }, required: ['caseId'], }, }, { name: 'analyze_network', description: 'Analyze network activity for a case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to analyze', }, timeRange: { type: 'object', properties: { start: { type: 'string', description: 'Start time (ISO format)', }, end: { type: 'string', description: 'End time (ISO format)', }, }, required: ['start', 'end'], }, options: { type: 'object', description: 'Analysis options', properties: {}, }, }, required: ['caseId', 'timeRange'], }, }, { name: 'execute_workflow', description: 'Execute a security workflow', inputSchema: { type: 'object', properties: { workflowId: { type: 'string', description: 'The workflow ID to execute', }, input: { type: 'object', description: 'Input data for the workflow', properties: {}, }, }, required: ['workflowId', 'input'], }, }, { name: 'get_system_status', description: 'Get the status of the multi-agent system', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_agent_metrics', description: 'Get performance metrics for all agents', inputSchema: { type: 'object', properties: {}, }, }, { name: 'search_cases', description: 'List and filter Stellar Cyber cases. Returns cases sorted by creation date with optional client-side filtering.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Optional search text to filter cases by name, description, severity, or status. Applied as client-side filter.', }, filters: { type: 'object', description: 'Optional filters to narrow results', properties: { severity: { type: 'string', description: 'Filter by case severity (e.g., high, medium, low)', }, status: { type: 'string', description: 'Filter by case status (e.g., open, closed, in_progress)', }, assignee: { type: 'string', description: 'Filter by assigned user name or email', }, limit: { type: 'number', description: 'Maximum number of cases to retrieve from API (default: 20, max: 20)', }, sort: { type: 'string', description: 'Sort order: -created_at (newest first) or created_at (oldest first). Default: -created_at', }, }, }, }, required: [], }, }, { name: 'get_case_activities', description: 'Get activities/actions performed on a specific case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get activities for', }, }, required: ['caseId'], }, }, { name: 'get_case_observables', description: 'Get observables/IOCs associated with a specific case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get observables for', }, }, required: ['caseId'], }, }, { name: 'get_case_alerts', description: 'Get security alerts associated with a specific case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get alerts for', }, }, required: ['caseId'], }, }, { name: 'get_case_comments', description: 'Get comments/notes for a specific case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get comments for', }, }, required: ['caseId'], }, }, { name: 'get_case_detailed_summary', description: 'Get detailed summary and metadata for a specific case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get detailed summary for', }, }, required: ['caseId'], }, }, { name: 'get_case_scores', description: 'Get risk scores and assessments for a specific case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get scores for', }, }, required: ['caseId'], }, }, { name: 'analyze_case_observables', description: 'Analyze case observables/IOCs and get recommendations', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to analyze observables for', }, }, required: ['caseId'], }, }, { name: 'get_case_timeline', description: 'Get chronological timeline of case events', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get timeline for', }, }, required: ['caseId'], }, }, { name: 'get_case_artifacts', description: 'Get artifacts/files associated with a case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get artifacts for', }, }, required: ['caseId'], }, }, { name: 'suggest_workflow', description: 'Get workflow recommendations based on case characteristics', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to suggest workflow for', }, }, required: ['caseId'], }, }, { name: 'update_case_status', description: 'Update the status of a case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to update', }, status: { type: 'string', description: 'New case status (e.g., open, closed, in_progress)', }, comment: { type: 'string', description: 'Optional comment explaining the status change', }, }, required: ['caseId', 'status'], }, }, { name: 'add_case_comment', description: 'Add a comment/note to a case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to add comment to', }, comment: { type: 'string', description: 'Comment text to add', }, }, required: ['caseId', 'comment'], }, }, { name: 'get_related_cases', description: 'Find cases related to a given case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to find related cases for', }, method: { type: 'string', description: 'Correlation method to use (observables, temporal, behavioral)', default: 'observables', }, }, required: ['caseId'], }, }, { name: 'get_threat_intelligence', description: 'Get threat intelligence information for a case', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to get threat intel for', }, }, required: ['caseId'], }, }, { name: 'detect_campaigns', description: 'Detect threat campaigns across multiple cases', inputSchema: { type: 'object', properties: { criteria: { type: 'object', description: 'Criteria for campaign detection', properties: { timeRange: { type: 'object', properties: { start: { type: 'string', description: 'Start time (ISO format)', }, end: { type: 'string', description: 'End time (ISO format)', }, }, }, minCases: { type: 'number', description: 'Minimum number of cases for a campaign', default: 3, }, similarity: { type: 'number', description: 'Minimum similarity score (0-1)', default: 0.7, }, }, }, }, required: [], }, }, { name: 'analyze_case_similarity', description: 'Analyze similarity between two cases', inputSchema: { type: 'object', properties: { caseId1: { type: 'string', description: 'First case ID to compare', }, caseId2: { type: 'string', description: 'Second case ID to compare', }, }, required: ['caseId1', 'caseId2'], }, }, { name: 'detect_lateral_movement', description: 'Detect lateral movement patterns in network activity', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to analyze', }, timeRange: { type: 'object', properties: { start: { type: 'string', description: 'Start time (ISO format)', }, end: { type: 'string', description: 'End time (ISO format)', }, }, required: ['start', 'end'], }, }, required: ['caseId', 'timeRange'], }, }, { name: 'detect_c2_communication', description: 'Detect command and control communication patterns', inputSchema: { type: 'object', properties: { caseId: { type: 'string', description: 'The case ID to analyze', }, timeRange: { type: 'object', properties: { start: { type: 'string', description: 'Start time (ISO format)', }, end: { type: 'string', description: 'End time (ISO format)', }, }, required: ['start', 'end'], }, }, required: ['caseId', 'timeRange'], }, }, { name: 'hunt_network_threats', description: 'Hunt for network threats based on query criteria', inputSchema: { type: 'object', properties: { query: { type: 'object', description: 'Threat hunting query parameters', properties: { indicators: { type: 'array', items: { type: 'string', }, description: 'IOCs or indicators to hunt for', }, patterns: { type: 'array', items: { type: 'string', }, description: 'Network patterns to detect', }, protocols: { type: 'array', items: { type: 'string', }, description: 'Network protocols to analyze', }, }, }, timeRange: { type: 'object', properties: { start: { type: 'string', description: 'Start time (ISO format)', }, end: { type: 'string', description: 'End time (ISO format)', }, }, required: ['start', 'end'], }, }, required: ['query', 'timeRange'], }, }, { name: 'get_workflow_status', description: 'Get the status of a running workflow', inputSchema: { type: 'object', properties: { workflowId: { type: 'string', description: 'The workflow execution ID', }, }, required: ['workflowId'], }, }, { name: 'cancel_workflow', description: 'Cancel a running workflow', inputSchema: { type: 'object', properties: { workflowId: { type: 'string', description: 'The workflow execution ID to cancel', }, }, required: ['workflowId'], }, }, ], }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'test_connection': { const apiUrl = process.env.STELLAR_API_URL; const apiToken = process.env.STELLAR_API_TOKEN; if (!apiUrl || !apiToken) { return { content: [ { type: 'text', text: JSON.stringify({ status: 'error', message: 'Missing environment variables', details: { STELLAR_API_URL: apiUrl ? 'Set' : 'Missing', STELLAR_API_TOKEN: apiToken ? 'Set' : 'Missing' } }, null, 2), }, ], }; } // Test Stellar Cyber token refresh flow try { // Step 1: Try to get an access token using the provided token (which should be a refresh token) const tokenResponse = await fetch(`${apiUrl}/connect/api/v1/access_token`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}`, 'Content-Type': 'application/json' } }); const tokenResult = { status: tokenResponse.status, statusText: tokenResponse.statusText, ok: tokenResponse.ok }; if (!tokenResponse.ok) { const errorText = await tokenResponse.text(); return { content: [ { type: 'text', text: JSON.stringify({ status: 'error', message: 'Token refresh failed', details: { url: `${apiUrl}/connect/api/v1/access_token`, response: tokenResult, errorBody: errorText, tokenLength: apiToken.length, suggestion: 'Check if the STELLAR_API_TOKEN is a valid refresh token, not an access token' } }, null, 2), }, ], }; } const tokenData = await tokenResponse.json(); // Step 2: Test using the new access token let apiTestResult = null; if (tokenData.access_token) { try { const apiResponse = await fetch(`${apiUrl}/connect/api/v1/cases?limit=1`, { headers: { 'Authorization': `Bearer ${tokenData.access_token}`, 'Content-Type': 'application/json' } }); apiTestResult = { status: apiResponse.status, statusText: apiResponse.statusText, ok: apiResponse.ok }; } catch (error) { apiTestResult = { error: error instanceof Error ? error.message : 'Unknown error' }; } } return { content: [ { type: 'text', text: JSON.stringify({ status: 'success', message: 'Token refresh test completed', details: { tokenRefresh: { url: `${apiUrl}/connect/api/v1/access_token`, response: tokenResult, hasAccessToken: !!tokenData.access_token, accessTokenLength: tokenData.access_token?.length || 0, expiresIn: tokenData.exp || tokenData.expires_in, tokenType: tokenData.token_type }, apiTest: apiTestResult } }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ status: 'error', message: 'Connection test failed', error: error instanceof Error ? error.message : 'Unknown error', details: { url: apiUrl, tokenLength: apiToken.length } }, null, 2), }, ], }; } } case 'investigate_case': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('investigateCase', args.caseId); case 'correlate_case': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('correlateCase', args.caseId); case 'get_system_status': return await executeOrchestratorMethod('getStatus'); case 'analyze_network': if (!args || !args.caseId || !args.timeRange) { throw new Error('caseId and timeRange arguments are required'); } return await executeOrchestratorMethod('analyzeNetwork', args.caseId, args.timeRange); case 'execute_workflow': if (!args || !args.workflowId || !args.input) { throw new Error('workflowId and input arguments are required'); } return await executeOrchestratorMethod('executeWorkflow', args.workflowId, args.input); case 'get_agent_metrics': return await executeOrchestratorMethod('getAgentMetrics'); case 'search_cases': return await executeOrchestratorMethod('searchCases', args?.query || '', args?.filters); case 'get_case_activities': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getCaseActivities', args.caseId); case 'get_case_observables': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getCaseObservables', args.caseId); case 'get_case_alerts': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getCaseAlerts', args.caseId); case 'get_case_comments': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getCaseComments', args.caseId); case 'get_case_detailed_summary': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getCaseDetailedSummary', args.caseId); case 'get_case_scores': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getCaseScores', args.caseId); case 'analyze_case_observables': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('analyzeCaseObservables', args.caseId); case 'get_case_timeline': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getCaseTimeline', args.caseId); case 'get_case_artifacts': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getCaseArtifacts', args.caseId); case 'suggest_workflow': { try { const { MultiAgentOrchestrator, createDefaultConfig } = await import('./orchestrator.js'); const config = createDefaultConfig(); if (process.env.STELLAR_API_URL) config.stellar.apiUrl = process.env.STELLAR_API_URL; if (process.env.STELLAR_API_TOKEN) config.stellar.apiToken = process.env.STELLAR_API_TOKEN; // Disable all logging to prevent EPIPE errors config.logging.enableConsole = false; config.logging.enableFile = false; // Disable background timers/intervals to prevent EPIPE errors config.agents.correlation.config.enableCampaignDetection = false; const orchestrator = new MultiAgentOrchestrator(config); orchestrator.removeAllListeners(); // Prevent console output await orchestrator.start(); if (!args || !args.caseId) { throw new Error('caseId argument is required'); } const result = await orchestrator.suggestWorkflow(args.caseId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error', tool: name, status: 'error' }, null, 2), }, ], }; } } case 'update_case_status': if (!args || !args.caseId || !args.status) { throw new Error('caseId and status arguments are required'); } return await executeOrchestratorMethod('updateCaseStatus', args.caseId, args.status, args.comment); case 'add_case_comment': if (!args || !args.caseId || !args.comment) { throw new Error('caseId and comment arguments are required'); } return await executeOrchestratorMethod('addCaseComment', args.caseId, args.comment); case 'get_related_cases': if (!args || !args.caseId) { throw new Error('caseId argument is required'); } return await executeOrchestratorMethod('getRelatedCases', args.caseId); case 'get_threat_intelligence': { try { const { MultiAgentOrchestrator, createDefaultConfig } = await import('./orchestrator.js'); const config = createDefaultConfig(); if (process.env.STELLAR_API_URL) config.stellar.apiUrl = process.env.STELLAR_API_URL; if (process.env.STELLAR_API_TOKEN) config.stellar.apiToken = process.env.STELLAR_API_TOKEN; // Disable all logging to prevent EPIPE errors config.logging.enableConsole = false; config.logging.enableFile = false; // Disable background timers/intervals to prevent EPIPE errors config.agents.correlation.config.enableCampaignDetection = false; const orchestrator = new MultiAgentOrchestrator(config); orchestrator.removeAllListeners(); // Prevent console output await orchestrator.start(); if (!args || !args.caseId) { throw new Error('caseId argument is required'); } const result = await orchestrator.getThreatIntelligence(args.caseId); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error', tool: name, status: 'error' }, null, 2), }, ], }; } } case 'detect_campaigns': { try { const { MultiAgentOrchestrator, createDefaultConfig } = await import('./orchestrator.js'); const config = createDefaultConfig(); if (process.env.STELLAR_API_URL) config.stellar.apiUrl = process.env.STELLAR_API_URL; if (process.env.STELLAR_API_TOKEN) config.stellar.apiToken = process.env.STELLAR_API_TOKEN; // Disable all logging to prevent EPIPE errors config.logging.enableConsole = false; config.logging.enableFile = false; // Disable background timers/intervals to prevent EPIPE errors config.agents.correlation.config.enableCampaignDetection = false; const orchestrator = new MultiAgentOrchestrator(config); orchestrator.removeAllListeners(); // Prevent console output await orchestrator.start(); const result = await orchestrator.detectCampaigns(args?.criteria || {}); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error', tool: name, status: 'error' }, null, 2), }, ], }; } } case 'analyze_case_similarity': if (!args || !args.caseId1 || !args.caseId2) { throw new Error('caseId1 and caseId2 arguments are required'); } return await executeOrchestratorMethod('analyzeCaseSimilarity', args.caseId1, args.caseId2); case 'detect_lateral_movement': { try { const { MultiAgentOrchestrator, createDefaultConfig } = await import('./orchestrator.js'); const config = createDefaultConfig(); if (process.env.STELLAR_API_URL) config.stellar.apiUrl = process.env.STELLAR_API_URL; if (process.env.STELLAR_API_TOKEN) config.stellar.apiToken = process.env.STELLAR_API_TOKEN; // Disable all logging to prevent EPIPE errors config.logging.enableConsole = false; config.logging.enableFile = false; // Disable background timers/intervals to prevent EPIPE errors config.agents.correlation.config.enableCampaignDetection = false; const orchestrator = new MultiAgentOrchestrator(config); orchestrator.removeAllListeners(); // Prevent console output await orchestrator.start(); if (!args || !args.caseId || !args.timeRange) { throw new Error('caseId and timeRange arguments are required'); } const result = await orchestrator.detectLateralMovement(args.caseId, args.timeRange); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error', tool: name, status: 'error' }, null, 2), }, ], }; } } case 'detect_c2_communication': { try { const { MultiAgentOrchestrator, createDefaultConfig } = await import('./orchestrator.js'); const config = createDefaultConfig(); if (process.env.STELLAR_API_URL) config.stellar.apiUrl = process.env.STELLAR_API_URL; if (process.env.STELLAR_API_TOKEN) config.stellar.apiToken = process.env.STELLAR_API_TOKEN; // Disable all logging to prevent EPIPE errors config.logging.enableConsole = false; config.logging.enableFile = false; // Disable background timers/intervals to prevent EPIPE errors config.agents.correlation.config.enableCampaignDetection = false; const orchestrator = new MultiAgentOrchestrator(config); orchestrator.removeAllListeners(); // Prevent console output await orchestrator.start(); if (!args || !args.caseId || !args.timeRange) { throw new Error('caseId and timeRange arguments are required'); } const result = await orchestrator.detectC2Communication(args.caseId, args.timeRange); return { content: [ { type: 'text', text: JSON.stringify(re