@mxf-dev/core 2.0.4 → 3.0.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.
- package/dist/config/ModelContextLimits.d.ts.map +1 -1
- package/dist/config/ModelContextLimits.js +7 -1
- package/dist/config/ModelContextLimits.js.map +1 -1
- package/dist/constants/CoreTools.d.ts +6 -5
- package/dist/constants/CoreTools.d.ts.map +1 -1
- package/dist/constants/CoreTools.js +6 -5
- package/dist/constants/CoreTools.js.map +1 -1
- package/dist/constants/DefaultPhaseProfiles.d.ts.map +1 -1
- package/dist/constants/DefaultPhaseProfiles.js +29 -0
- package/dist/constants/DefaultPhaseProfiles.js.map +1 -1
- package/dist/constants/ReservedIdentities.d.ts +14 -0
- package/dist/constants/ReservedIdentities.d.ts.map +1 -0
- package/dist/constants/ReservedIdentities.js +42 -0
- package/dist/constants/ReservedIdentities.js.map +1 -0
- package/dist/database/adapters/mongodb/MongoKnowledgeGraphRepository.d.ts +16 -16
- package/dist/database/adapters/mongodb/MongoKnowledgeGraphRepository.d.ts.map +1 -1
- package/dist/database/adapters/mongodb/MongoKnowledgeGraphRepository.js +98 -36
- package/dist/database/adapters/mongodb/MongoKnowledgeGraphRepository.js.map +1 -1
- package/dist/events/ClientEventBus.d.ts +28 -0
- package/dist/events/ClientEventBus.d.ts.map +1 -1
- package/dist/events/ClientEventBus.js +87 -40
- package/dist/events/ClientEventBus.js.map +1 -1
- package/dist/events/EventBus.d.ts +19 -1
- package/dist/events/EventBus.d.ts.map +1 -1
- package/dist/events/EventBus.js +23 -0
- package/dist/events/EventBus.js.map +1 -1
- package/dist/events/EventBusBase.d.ts +37 -6
- package/dist/events/EventBusBase.d.ts.map +1 -1
- package/dist/events/EventBusBase.js +75 -27
- package/dist/events/EventBusBase.js.map +1 -1
- package/dist/events/EventNames.d.ts +49 -36
- package/dist/events/EventNames.d.ts.map +1 -1
- package/dist/events/EventNames.js +38 -36
- package/dist/events/EventNames.js.map +1 -1
- package/dist/events/PublicEvents.d.ts +5 -1
- package/dist/events/PublicEvents.d.ts.map +1 -1
- package/dist/events/PublicEvents.js +7 -0
- package/dist/events/PublicEvents.js.map +1 -1
- package/dist/events/ServerEventBus.d.ts +7 -54
- package/dist/events/ServerEventBus.d.ts.map +1 -1
- package/dist/events/ServerEventBus.js +10 -96
- package/dist/events/ServerEventBus.js.map +1 -1
- package/dist/events/event-definitions/ControlLoopEvents.d.ts +35 -0
- package/dist/events/event-definitions/ControlLoopEvents.d.ts.map +1 -1
- package/dist/events/event-definitions/ControlLoopEvents.js +6 -0
- package/dist/events/event-definitions/ControlLoopEvents.js.map +1 -1
- package/dist/events/event-definitions/SdkEvents.d.ts +55 -0
- package/dist/events/event-definitions/SdkEvents.d.ts.map +1 -0
- package/dist/events/event-definitions/SdkEvents.js +36 -0
- package/dist/events/event-definitions/SdkEvents.js.map +1 -0
- package/dist/events/event-definitions/TaskEvents.d.ts +2 -7
- package/dist/events/event-definitions/TaskEvents.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/AgentInterfaces.d.ts +5 -0
- package/dist/interfaces/AgentInterfaces.d.ts.map +1 -1
- package/dist/interfaces/ChannelConfig.d.ts +2 -2
- package/dist/interfaces/ChannelConfig.d.ts.map +1 -1
- package/dist/interfaces/ConversationMessage.d.ts +1 -1
- package/dist/interfaces/ConversationMessage.d.ts.map +1 -1
- package/dist/interfaces/IMemoryPersistence.d.ts +47 -1
- package/dist/interfaces/IMemoryPersistence.d.ts.map +1 -1
- package/dist/interfaces/SocketServiceInterface.d.ts +3 -2
- package/dist/interfaces/SocketServiceInterface.d.ts.map +1 -1
- package/dist/interfaces/TaskInterfaces.d.ts +14 -1
- package/dist/interfaces/TaskInterfaces.d.ts.map +1 -1
- package/dist/models/agent.d.ts +0 -1
- package/dist/models/agent.d.ts.map +1 -1
- package/dist/models/agent.js +0 -5
- package/dist/models/agent.js.map +1 -1
- package/dist/models/agentIdentityReservation.d.ts +42 -0
- package/dist/models/agentIdentityReservation.d.ts.map +1 -0
- package/dist/models/agentIdentityReservation.js +52 -0
- package/dist/models/agentIdentityReservation.js.map +1 -0
- package/dist/models/channel.d.ts +2 -0
- package/dist/models/channel.d.ts.map +1 -1
- package/dist/models/channel.js.map +1 -1
- package/dist/models/channelKey.d.ts +1 -0
- package/dist/models/channelKey.d.ts.map +1 -1
- package/dist/models/channelKey.js +5 -0
- package/dist/models/channelKey.js.map +1 -1
- package/dist/models/memory.d.ts +1 -1
- package/dist/models/memory.d.ts.map +1 -1
- package/dist/models/memory.js +9 -12
- package/dist/models/memory.js.map +1 -1
- package/dist/models/memoryStrata.d.ts +3 -16
- package/dist/models/memoryStrata.d.ts.map +1 -1
- package/dist/models/memoryStrata.js +3 -5
- package/dist/models/memoryStrata.js.map +1 -1
- package/dist/models/plan.d.ts +2 -2
- package/dist/models/plan.d.ts.map +1 -1
- package/dist/models/plan.js +1 -1
- package/dist/models/plan.js.map +1 -1
- package/dist/models/taskEffectiveness.d.ts +1 -0
- package/dist/models/taskEffectiveness.d.ts.map +1 -1
- package/dist/models/taskEffectiveness.js +7 -1
- package/dist/models/taskEffectiveness.js.map +1 -1
- package/dist/models/user.d.ts +4 -0
- package/dist/models/user.d.ts.map +1 -1
- package/dist/models/user.js +8 -0
- package/dist/models/user.js.map +1 -1
- package/dist/protocols/mcp/LlmTestEnvironmentGuard.d.ts +16 -0
- package/dist/protocols/mcp/LlmTestEnvironmentGuard.d.ts.map +1 -0
- package/dist/protocols/mcp/LlmTestEnvironmentGuard.js +25 -0
- package/dist/protocols/mcp/LlmTestEnvironmentGuard.js.map +1 -0
- package/dist/protocols/mcp/McpServerTypes.d.ts +9 -0
- package/dist/protocols/mcp/McpServerTypes.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/AnthropicMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/AnthropicMcpClient.js +1 -0
- package/dist/protocols/mcp/providers/AnthropicMcpClient.js.map +1 -1
- package/dist/protocols/mcp/providers/AzureOpenAiMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/AzureOpenAiMcpClient.js +1 -0
- package/dist/protocols/mcp/providers/AzureOpenAiMcpClient.js.map +1 -1
- package/dist/protocols/mcp/providers/BaseMcpClient.d.ts +4 -0
- package/dist/protocols/mcp/providers/BaseMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/BaseMcpClient.js +10 -0
- package/dist/protocols/mcp/providers/BaseMcpClient.js.map +1 -1
- package/dist/protocols/mcp/providers/CustomMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/CustomMcpClient.js +7 -0
- package/dist/protocols/mcp/providers/CustomMcpClient.js.map +1 -1
- package/dist/protocols/mcp/providers/GeminiMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/GeminiMcpClient.js +1 -0
- package/dist/protocols/mcp/providers/GeminiMcpClient.js.map +1 -1
- package/dist/protocols/mcp/providers/OllamaMcpClient.d.ts +1 -0
- package/dist/protocols/mcp/providers/OllamaMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/OllamaMcpClient.js +1 -0
- package/dist/protocols/mcp/providers/OllamaMcpClient.js.map +1 -1
- package/dist/protocols/mcp/providers/OpenAiMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/OpenAiMcpClient.js +1 -0
- package/dist/protocols/mcp/providers/OpenAiMcpClient.js.map +1 -1
- package/dist/protocols/mcp/providers/OpenRouterMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/OpenRouterMcpClient.js +2 -0
- package/dist/protocols/mcp/providers/OpenRouterMcpClient.js.map +1 -1
- package/dist/protocols/mcp/providers/XaiMcpClient.d.ts.map +1 -1
- package/dist/protocols/mcp/providers/XaiMcpClient.js +1 -0
- package/dist/protocols/mcp/providers/XaiMcpClient.js.map +1 -1
- package/dist/protocols/mcp/security/BrowserTargetGuard.d.ts +14 -0
- package/dist/protocols/mcp/security/BrowserTargetGuard.d.ts.map +1 -0
- package/dist/protocols/mcp/security/BrowserTargetGuard.js +55 -0
- package/dist/protocols/mcp/security/BrowserTargetGuard.js.map +1 -0
- package/dist/protocols/mcp/security/ExternalMcpRegistrationPolicy.d.ts +24 -0
- package/dist/protocols/mcp/security/ExternalMcpRegistrationPolicy.d.ts.map +1 -0
- package/dist/protocols/mcp/security/ExternalMcpRegistrationPolicy.js +33 -0
- package/dist/protocols/mcp/security/ExternalMcpRegistrationPolicy.js.map +1 -0
- package/dist/protocols/mcp/security/HttpTargetGuard.d.ts.map +1 -1
- package/dist/protocols/mcp/security/HttpTargetGuard.js +68 -6
- package/dist/protocols/mcp/security/HttpTargetGuard.js.map +1 -1
- package/dist/protocols/mcp/security/McpSecurityGuard.d.ts.map +1 -1
- package/dist/protocols/mcp/security/McpSecurityGuard.js +7 -5
- package/dist/protocols/mcp/security/McpSecurityGuard.js.map +1 -1
- package/dist/protocols/mcp/security/McpToolPolicy.d.ts +18 -0
- package/dist/protocols/mcp/security/McpToolPolicy.d.ts.map +1 -1
- package/dist/protocols/mcp/security/McpToolPolicy.js +60 -0
- package/dist/protocols/mcp/security/McpToolPolicy.js.map +1 -1
- package/dist/protocols/mcp/services/ExternalMcpServerManager.d.ts +118 -5
- package/dist/protocols/mcp/services/ExternalMcpServerManager.d.ts.map +1 -1
- package/dist/protocols/mcp/services/ExternalMcpServerManager.js +499 -98
- package/dist/protocols/mcp/services/ExternalMcpServerManager.js.map +1 -1
- package/dist/protocols/mcp/services/ExternalServerConfigs.d.ts +2 -2
- package/dist/protocols/mcp/services/ExternalServerConfigs.d.ts.map +1 -1
- package/dist/protocols/mcp/services/ExternalServerConfigs.js +25 -8
- package/dist/protocols/mcp/services/ExternalServerConfigs.js.map +1 -1
- package/dist/protocols/mcp/tools/AnalyticsTools.d.ts +3 -37
- package/dist/protocols/mcp/tools/AnalyticsTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/AnalyticsTools.js +134 -614
- package/dist/protocols/mcp/tools/AnalyticsTools.js.map +1 -1
- package/dist/protocols/mcp/tools/CodeAnalysisTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/CodeAnalysisTools.js +16 -13
- package/dist/protocols/mcp/tools/CodeAnalysisTools.js.map +1 -1
- package/dist/protocols/mcp/tools/EffectivenessTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/EffectivenessTools.js +94 -30
- package/dist/protocols/mcp/tools/EffectivenessTools.js.map +1 -1
- package/dist/protocols/mcp/tools/GitTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/GitTools.js +9 -5
- package/dist/protocols/mcp/tools/GitTools.js.map +1 -1
- package/dist/protocols/mcp/tools/InfrastructureTools.d.ts +10 -40
- package/dist/protocols/mcp/tools/InfrastructureTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/InfrastructureTools.js +29 -70
- package/dist/protocols/mcp/tools/InfrastructureTools.js.map +1 -1
- package/dist/protocols/mcp/tools/JsonTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/JsonTools.js +4 -4
- package/dist/protocols/mcp/tools/JsonTools.js.map +1 -1
- package/dist/protocols/mcp/tools/KnowledgeGraphTools.d.ts +55 -592
- package/dist/protocols/mcp/tools/KnowledgeGraphTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/KnowledgeGraphTools.js +169 -119
- package/dist/protocols/mcp/tools/KnowledgeGraphTools.js.map +1 -1
- package/dist/protocols/mcp/tools/MemorySearchTools.d.ts +6 -6
- package/dist/protocols/mcp/tools/MemorySearchTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/MemorySearchTools.js +107 -39
- package/dist/protocols/mcp/tools/MemorySearchTools.js.map +1 -1
- package/dist/protocols/mcp/tools/MemoryUtilityTools.d.ts +4 -5
- package/dist/protocols/mcp/tools/MemoryUtilityTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/MemoryUtilityTools.js +59 -208
- package/dist/protocols/mcp/tools/MemoryUtilityTools.js.map +1 -1
- package/dist/protocols/mcp/tools/PlanningTools.d.ts +2 -1
- package/dist/protocols/mcp/tools/PlanningTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/PlanningTools.js +111 -49
- package/dist/protocols/mcp/tools/PlanningTools.js.map +1 -1
- package/dist/protocols/mcp/tools/ProjectContextTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/ProjectContextTools.js +18 -12
- package/dist/protocols/mcp/tools/ProjectContextTools.js.map +1 -1
- package/dist/protocols/mcp/tools/SafetyTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/SafetyTools.js +16 -13
- package/dist/protocols/mcp/tools/SafetyTools.js.map +1 -1
- package/dist/protocols/mcp/tools/SearchProjectTools.d.ts +24 -7
- package/dist/protocols/mcp/tools/SearchProjectTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/SearchProjectTools.js +226 -126
- package/dist/protocols/mcp/tools/SearchProjectTools.js.map +1 -1
- package/dist/protocols/mcp/tools/TaskPlanningTools.d.ts +0 -32
- package/dist/protocols/mcp/tools/TaskPlanningTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/TaskPlanningTools.js +363 -450
- package/dist/protocols/mcp/tools/TaskPlanningTools.js.map +1 -1
- package/dist/protocols/mcp/tools/TestTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/TestTools.js +18 -11
- package/dist/protocols/mcp/tools/TestTools.js.map +1 -1
- package/dist/protocols/mcp/tools/TypeScriptTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/TypeScriptTools.js +22 -12
- package/dist/protocols/mcp/tools/TypeScriptTools.js.map +1 -1
- package/dist/protocols/mcp/tools/UserInputTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/UserInputTools.js +14 -1
- package/dist/protocols/mcp/tools/UserInputTools.js.map +1 -1
- package/dist/protocols/mcp/tools/UserMemoryTools.d.ts +3 -1
- package/dist/protocols/mcp/tools/UserMemoryTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/UserMemoryTools.js +27 -62
- package/dist/protocols/mcp/tools/UserMemoryTools.js.map +1 -1
- package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.js +14 -60
- package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.js.map +1 -1
- package/dist/protocols/mcp/tools/shell/ShellSandbox.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/shell/ShellSandbox.js +9 -27
- package/dist/protocols/mcp/tools/shell/ShellSandbox.js.map +1 -1
- package/dist/repositories/interfaces/IKnowledgeGraphRepository.d.ts +40 -30
- package/dist/repositories/interfaces/IKnowledgeGraphRepository.d.ts.map +1 -1
- package/dist/schemas/EventPayloadSchema.d.ts +28 -0
- package/dist/schemas/EventPayloadSchema.d.ts.map +1 -1
- package/dist/schemas/EventPayloadSchema.js +30 -1
- package/dist/schemas/EventPayloadSchema.js.map +1 -1
- package/dist/schemas/MxpProtocolSchemas.d.ts +6 -6
- package/dist/services/AgentPerformanceService.d.ts +11 -0
- package/dist/services/AgentPerformanceService.d.ts.map +1 -1
- package/dist/services/AgentPerformanceService.js +59 -33
- package/dist/services/AgentPerformanceService.js.map +1 -1
- package/dist/services/AutoCorrectionService.d.ts +3 -0
- package/dist/services/AutoCorrectionService.d.ts.map +1 -1
- package/dist/services/AutoCorrectionService.js +19 -2
- package/dist/services/AutoCorrectionService.js.map +1 -1
- package/dist/services/BackgroundTaskManager.d.ts +23 -7
- package/dist/services/BackgroundTaskManager.d.ts.map +1 -1
- package/dist/services/BackgroundTaskManager.js +137 -57
- package/dist/services/BackgroundTaskManager.js.map +1 -1
- package/dist/services/BrowserManager.d.ts +2 -0
- package/dist/services/BrowserManager.d.ts.map +1 -1
- package/dist/services/BrowserManager.js +18 -15
- package/dist/services/BrowserManager.js.map +1 -1
- package/dist/services/ChannelContextMemoryOperations.d.ts +8 -1
- package/dist/services/ChannelContextMemoryOperations.d.ts.map +1 -1
- package/dist/services/ChannelContextMemoryOperations.js +89 -165
- package/dist/services/ChannelContextMemoryOperations.js.map +1 -1
- package/dist/services/ChannelContextMessageOperations.d.ts +4 -3
- package/dist/services/ChannelContextMessageOperations.d.ts.map +1 -1
- package/dist/services/ChannelContextMessageOperations.js +72 -212
- package/dist/services/ChannelContextMessageOperations.js.map +1 -1
- package/dist/services/MemoryService.d.ts +52 -21
- package/dist/services/MemoryService.d.ts.map +1 -1
- package/dist/services/MemoryService.js +775 -548
- package/dist/services/MemoryService.js.map +1 -1
- package/dist/services/PatternLearningService.d.ts +5 -0
- package/dist/services/PatternLearningService.d.ts.map +1 -1
- package/dist/services/PatternLearningService.js +26 -3
- package/dist/services/PatternLearningService.js.map +1 -1
- package/dist/services/PerformanceOptimizationService.d.ts +2 -1
- package/dist/services/PerformanceOptimizationService.d.ts.map +1 -1
- package/dist/services/PerformanceOptimizationService.js +21 -6
- package/dist/services/PerformanceOptimizationService.js.map +1 -1
- package/dist/services/PredictiveAnalyticsService.d.ts +1 -0
- package/dist/services/PredictiveAnalyticsService.d.ts.map +1 -1
- package/dist/services/PredictiveAnalyticsService.js +18 -6
- package/dist/services/PredictiveAnalyticsService.js.map +1 -1
- package/dist/services/ProactiveValidationService.d.ts +6 -0
- package/dist/services/ProactiveValidationService.d.ts.map +1 -1
- package/dist/services/ProactiveValidationService.js +58 -39
- package/dist/services/ProactiveValidationService.js.map +1 -1
- package/dist/services/TaskEffectivenessService.d.ts +55 -31
- package/dist/services/TaskEffectivenessService.d.ts.map +1 -1
- package/dist/services/TaskEffectivenessService.js +243 -125
- package/dist/services/TaskEffectivenessService.js.map +1 -1
- package/dist/services/ToolExecutionInterceptor.d.ts +4 -0
- package/dist/services/ToolExecutionInterceptor.d.ts.map +1 -1
- package/dist/services/ToolExecutionInterceptor.js +31 -5
- package/dist/services/ToolExecutionInterceptor.js.map +1 -1
- package/dist/services/UserInputRequestManager.d.ts +13 -3
- package/dist/services/UserInputRequestManager.d.ts.map +1 -1
- package/dist/services/UserInputRequestManager.js +42 -6
- package/dist/services/UserInputRequestManager.js.map +1 -1
- package/dist/services/UserMemoryService.d.ts +2 -0
- package/dist/services/UserMemoryService.d.ts.map +1 -1
- package/dist/services/UserMemoryService.js +9 -4
- package/dist/services/UserMemoryService.js.map +1 -1
- package/dist/services/ValidationAnalyticsService.d.ts +4 -0
- package/dist/services/ValidationAnalyticsService.d.ts.map +1 -1
- package/dist/services/ValidationAnalyticsService.js +30 -8
- package/dist/services/ValidationAnalyticsService.js.map +1 -1
- package/dist/services/ValidationCacheService.d.ts +3 -1
- package/dist/services/ValidationCacheService.d.ts.map +1 -1
- package/dist/services/ValidationCacheService.js +25 -4
- package/dist/services/ValidationCacheService.js.map +1 -1
- package/dist/services/ValidationMiddleware.d.ts +7 -0
- package/dist/services/ValidationMiddleware.d.ts.map +1 -1
- package/dist/services/ValidationMiddleware.js +53 -10
- package/dist/services/ValidationMiddleware.js.map +1 -1
- package/dist/services/ValidationPerformanceService.d.ts +3 -0
- package/dist/services/ValidationPerformanceService.d.ts.map +1 -1
- package/dist/services/ValidationPerformanceService.js +28 -11
- package/dist/services/ValidationPerformanceService.js.map +1 -1
- package/dist/services/WebSearchService.d.ts.map +1 -1
- package/dist/services/WebSearchService.js +9 -0
- package/dist/services/WebSearchService.js.map +1 -1
- package/dist/services/dag/TaskDagService.d.ts +3 -0
- package/dist/services/dag/TaskDagService.d.ts.map +1 -1
- package/dist/services/dag/TaskDagService.js +18 -0
- package/dist/services/dag/TaskDagService.js.map +1 -1
- package/dist/services/kg/EntityQValueManager.d.ts +3 -1
- package/dist/services/kg/EntityQValueManager.d.ts.map +1 -1
- package/dist/services/kg/EntityQValueManager.js +10 -10
- package/dist/services/kg/EntityQValueManager.js.map +1 -1
- package/dist/services/kg/GraphSurpriseCalculator.d.ts +0 -4
- package/dist/services/kg/GraphSurpriseCalculator.d.ts.map +1 -1
- package/dist/services/kg/GraphSurpriseCalculator.js +0 -9
- package/dist/services/kg/GraphSurpriseCalculator.js.map +1 -1
- package/dist/services/kg/KnowledgeGraphService.d.ts +12 -12
- package/dist/services/kg/KnowledgeGraphService.d.ts.map +1 -1
- package/dist/services/kg/KnowledgeGraphService.js +43 -51
- package/dist/services/kg/KnowledgeGraphService.js.map +1 -1
- package/dist/services/kg/OrparGraphIntegration.d.ts.map +1 -1
- package/dist/services/kg/OrparGraphIntegration.js +6 -5
- package/dist/services/kg/OrparGraphIntegration.js.map +1 -1
- package/dist/services/orpar-memory/CycleConsolidationTrigger.d.ts +1 -0
- package/dist/services/orpar-memory/CycleConsolidationTrigger.d.ts.map +1 -1
- package/dist/services/orpar-memory/CycleConsolidationTrigger.js +8 -1
- package/dist/services/orpar-memory/CycleConsolidationTrigger.js.map +1 -1
- package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts +8 -1
- package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts.map +1 -1
- package/dist/services/orpar-memory/OrparMemoryCoordinator.js +93 -45
- package/dist/services/orpar-memory/OrparMemoryCoordinator.js.map +1 -1
- package/dist/services/orpar-memory/SurpriseOrparAdapter.d.ts +1 -0
- package/dist/services/orpar-memory/SurpriseOrparAdapter.d.ts.map +1 -1
- package/dist/services/orpar-memory/SurpriseOrparAdapter.js +8 -1
- package/dist/services/orpar-memory/SurpriseOrparAdapter.js.map +1 -1
- package/dist/types/ChannelContext.d.ts +2 -1
- package/dist/types/ChannelContext.d.ts.map +1 -1
- package/dist/types/EffectivenessTypes.d.ts +4 -1
- package/dist/types/EffectivenessTypes.d.ts.map +1 -1
- package/dist/types/TaskCompletionTypes.d.ts +7 -5
- package/dist/types/TaskCompletionTypes.d.ts.map +1 -1
- package/dist/types/TaskTypes.d.ts +8 -1
- package/dist/types/TaskTypes.d.ts.map +1 -1
- package/dist/utils/GlobMatcher.d.ts +37 -0
- package/dist/utils/GlobMatcher.d.ts.map +1 -0
- package/dist/utils/GlobMatcher.js +131 -0
- package/dist/utils/GlobMatcher.js.map +1 -0
- package/package.json +1 -1
- package/src/config/ModelContextLimits.ts +7 -1
- package/src/constants/CoreTools.ts +6 -5
- package/src/constants/DefaultPhaseProfiles.ts +29 -0
- package/src/constants/ReservedIdentities.ts +51 -0
- package/src/database/adapters/mongodb/MongoKnowledgeGraphRepository.ts +115 -36
- package/src/events/ClientEventBus.ts +117 -48
- package/src/events/EventBus.ts +32 -2
- package/src/events/EventBusBase.ts +132 -39
- package/src/events/EventNames.ts +43 -36
- package/src/events/PublicEvents.ts +8 -0
- package/src/events/ServerEventBus.ts +9 -128
- package/src/events/event-definitions/ControlLoopEvents.ts +12 -0
- package/src/events/event-definitions/SdkEvents.ts +58 -0
- package/src/events/event-definitions/TaskEvents.ts +2 -2
- package/src/index.ts +15 -0
- package/src/interfaces/AgentInterfaces.ts +5 -0
- package/src/interfaces/ChannelConfig.ts +2 -2
- package/src/interfaces/ConversationMessage.ts +5 -0
- package/src/interfaces/IMemoryPersistence.ts +55 -1
- package/src/interfaces/SocketServiceInterface.ts +3 -2
- package/src/interfaces/TaskInterfaces.ts +16 -1
- package/src/models/agent.ts +0 -6
- package/src/models/agentIdentityReservation.ts +77 -0
- package/src/models/channel.ts +2 -0
- package/src/models/channelKey.ts +10 -0
- package/src/models/memory.ts +18 -19
- package/src/models/memoryStrata.ts +9 -23
- package/src/models/plan.ts +3 -3
- package/src/models/taskEffectiveness.ts +9 -2
- package/src/models/user.ts +12 -0
- package/src/protocols/mcp/LlmTestEnvironmentGuard.ts +30 -0
- package/src/protocols/mcp/McpServerTypes.ts +9 -0
- package/src/protocols/mcp/providers/AnthropicMcpClient.ts +2 -0
- package/src/protocols/mcp/providers/AzureOpenAiMcpClient.ts +1 -0
- package/src/protocols/mcp/providers/BaseMcpClient.ts +13 -0
- package/src/protocols/mcp/providers/CustomMcpClient.ts +7 -0
- package/src/protocols/mcp/providers/GeminiMcpClient.ts +2 -0
- package/src/protocols/mcp/providers/OllamaMcpClient.ts +3 -1
- package/src/protocols/mcp/providers/OpenAiMcpClient.ts +2 -0
- package/src/protocols/mcp/providers/OpenRouterMcpClient.ts +3 -0
- package/src/protocols/mcp/providers/XaiMcpClient.ts +2 -0
- package/src/protocols/mcp/security/BrowserTargetGuard.ts +64 -0
- package/src/protocols/mcp/security/ExternalMcpRegistrationPolicy.ts +38 -0
- package/src/protocols/mcp/security/HttpTargetGuard.ts +77 -6
- package/src/protocols/mcp/security/McpSecurityGuard.ts +7 -5
- package/src/protocols/mcp/security/McpToolPolicy.ts +74 -0
- package/src/protocols/mcp/services/ExternalMcpServerManager.ts +656 -111
- package/src/protocols/mcp/services/ExternalServerConfigs.ts +26 -8
- package/src/protocols/mcp/tools/AnalyticsTools.ts +167 -641
- package/src/protocols/mcp/tools/CodeAnalysisTools.ts +44 -14
- package/src/protocols/mcp/tools/EffectivenessTools.ts +185 -45
- package/src/protocols/mcp/tools/GitTools.ts +15 -5
- package/src/protocols/mcp/tools/InfrastructureTools.ts +42 -90
- package/src/protocols/mcp/tools/JsonTools.ts +7 -4
- package/src/protocols/mcp/tools/KnowledgeGraphTools.ts +313 -164
- package/src/protocols/mcp/tools/MemorySearchTools.ts +158 -46
- package/src/protocols/mcp/tools/MemoryUtilityTools.ts +85 -232
- package/src/protocols/mcp/tools/PlanningTools.ts +137 -59
- package/src/protocols/mcp/tools/ProjectContextTools.ts +33 -12
- package/src/protocols/mcp/tools/SafetyTools.ts +49 -13
- package/src/protocols/mcp/tools/SearchProjectTools.ts +294 -153
- package/src/protocols/mcp/tools/TaskPlanningTools.ts +437 -514
- package/src/protocols/mcp/tools/TestTools.ts +41 -11
- package/src/protocols/mcp/tools/TypeScriptTools.ts +61 -12
- package/src/protocols/mcp/tools/UserInputTools.ts +15 -1
- package/src/protocols/mcp/tools/UserMemoryTools.ts +27 -62
- package/src/protocols/mcp/tools/shell/ShellExecuteHandler.ts +26 -84
- package/src/protocols/mcp/tools/shell/ShellSandbox.ts +13 -31
- package/src/repositories/interfaces/IKnowledgeGraphRepository.ts +45 -27
- package/src/schemas/EventPayloadSchema.ts +45 -1
- package/src/services/AgentPerformanceService.ts +85 -36
- package/src/services/AutoCorrectionService.ts +25 -6
- package/src/services/BackgroundTaskManager.ts +183 -76
- package/src/services/BrowserManager.ts +20 -16
- package/src/services/ChannelContextMemoryOperations.ts +169 -202
- package/src/services/ChannelContextMessageOperations.ts +112 -274
- package/src/services/MemoryService.ts +1053 -760
- package/src/services/PatternLearningService.ts +32 -7
- package/src/services/PerformanceOptimizationService.ts +26 -9
- package/src/services/PredictiveAnalyticsService.ts +23 -10
- package/src/services/ProactiveValidationService.ts +66 -40
- package/src/services/TaskEffectivenessService.ts +421 -206
- package/src/services/ToolExecutionInterceptor.ts +40 -9
- package/src/services/UserInputRequestManager.ts +51 -6
- package/src/services/UserMemoryService.ts +12 -3
- package/src/services/ValidationAnalyticsService.ts +36 -11
- package/src/services/ValidationCacheService.ts +30 -7
- package/src/services/ValidationMiddleware.ts +62 -14
- package/src/services/ValidationPerformanceService.ts +47 -25
- package/src/services/WebSearchService.ts +16 -4
- package/src/services/dag/TaskDagService.ts +20 -1
- package/src/services/kg/EntityQValueManager.ts +21 -10
- package/src/services/kg/GraphSurpriseCalculator.ts +0 -14
- package/src/services/kg/KnowledgeGraphService.ts +56 -48
- package/src/services/kg/OrparGraphIntegration.ts +11 -5
- package/src/services/orpar-memory/CycleConsolidationTrigger.ts +10 -1
- package/src/services/orpar-memory/OrparMemoryCoordinator.ts +142 -71
- package/src/services/orpar-memory/SurpriseOrparAdapter.ts +10 -1
- package/src/types/ChannelContext.ts +3 -1
- package/src/types/EffectivenessTypes.ts +6 -2
- package/src/types/TaskCompletionTypes.ts +9 -9
- package/src/types/TaskTypes.ts +12 -1
- package/src/utils/GlobMatcher.ts +157 -0
|
@@ -43,11 +43,30 @@ import { AutoCorrectionService } from '../../../services/AutoCorrectionService.j
|
|
|
43
43
|
import { IToolEventEmitter } from './IToolEventEmitter.js';
|
|
44
44
|
import { Events } from '../../../events/EventNames.js';
|
|
45
45
|
import { v4 as uuidv4 } from 'uuid';
|
|
46
|
+
import { assertUnsafeStdioMcpEnabled } from '../security/ExternalMcpRegistrationPolicy.js';
|
|
46
47
|
|
|
47
48
|
// Create logger and validator instances
|
|
48
49
|
const logger = new Logger('info', 'ExternalMcpServerManager', 'server');
|
|
49
50
|
const validator = createStrictValidator('ExternalMcpServerManager');
|
|
50
51
|
|
|
52
|
+
interface ExternalMcpRegistrationRequestData extends Partial<ExternalServerConfig> {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
channelId?: string;
|
|
56
|
+
keepAliveMinutes?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ExternalMcpUnregistrationRequestData {
|
|
60
|
+
serverId: string;
|
|
61
|
+
channelId?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface ExternalMcpLifecycleRequestPayload<TData> {
|
|
65
|
+
agentId: AgentId;
|
|
66
|
+
channelId: ChannelId;
|
|
67
|
+
data: TData;
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
/**
|
|
52
71
|
* Configuration for an external MCP server
|
|
53
72
|
*/
|
|
@@ -64,6 +83,10 @@ export interface ExternalServerConfig {
|
|
|
64
83
|
command: string;
|
|
65
84
|
/** Arguments for the command */
|
|
66
85
|
args: string[];
|
|
86
|
+
/** Transport requested by a remote registration. Omitted means stdio. */
|
|
87
|
+
transport?: 'stdio' | 'http';
|
|
88
|
+
/** Endpoint for an HTTP transport registration. */
|
|
89
|
+
url?: string;
|
|
67
90
|
/** Working directory for the process */
|
|
68
91
|
workingDirectory?: string;
|
|
69
92
|
/** Environment variables for the process */
|
|
@@ -109,6 +132,10 @@ export interface ExternalMcpTool {
|
|
|
109
132
|
description: string;
|
|
110
133
|
inputSchema: Record<string, any>;
|
|
111
134
|
serverId: string;
|
|
135
|
+
/** Authoritative visibility scope assigned when the server was registered. */
|
|
136
|
+
scope: 'global' | 'channel' | 'agent';
|
|
137
|
+
/** Channel or agent identifier for non-global scopes. */
|
|
138
|
+
scopeId?: string;
|
|
112
139
|
}
|
|
113
140
|
|
|
114
141
|
/**
|
|
@@ -129,6 +156,23 @@ const REQUEST_TIMEOUTS_MS = {
|
|
|
129
156
|
ping: 5000
|
|
130
157
|
} as const;
|
|
131
158
|
|
|
159
|
+
/** Delay before restarting a crashed server. */
|
|
160
|
+
const DEFAULT_RESTART_DELAY_MS = 2000;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Consecutive failed health probes after which a running server is declared
|
|
164
|
+
* dead and restarted. One failure can be a slow reply; two in a row on a
|
|
165
|
+
* connection that answers in milliseconds when healthy means the connection
|
|
166
|
+
* is gone.
|
|
167
|
+
*/
|
|
168
|
+
const HEALTH_FAILURES_BEFORE_RESTART = 2;
|
|
169
|
+
|
|
170
|
+
/** A caller waiting for a starting server to finish its handshake and discovery. */
|
|
171
|
+
interface ReadyWaiter {
|
|
172
|
+
resolve: () => void;
|
|
173
|
+
reject: (error: Error) => void;
|
|
174
|
+
}
|
|
175
|
+
|
|
132
176
|
/** MCP protocol version this client negotiates in the initialize handshake. */
|
|
133
177
|
const MCP_PROTOCOL_VERSION = '2024-11-05';
|
|
134
178
|
|
|
@@ -158,6 +202,18 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
158
202
|
pending: Map<string, PendingRequest>;
|
|
159
203
|
/** Partial line left over from the last stdout chunk. */
|
|
160
204
|
stdoutBuffer: string;
|
|
205
|
+
/**
|
|
206
|
+
* Set by stopServer() before it kills the process, so the exit handler
|
|
207
|
+
* can tell an intentional stop from a crash. A crash restarts (when
|
|
208
|
+
* configured); an intentional stop never does.
|
|
209
|
+
*/
|
|
210
|
+
expectedExit?: boolean;
|
|
211
|
+
/** SIGKILL escalation timer set by stopServer; cleared when the process exits. */
|
|
212
|
+
forceKillTimer?: NodeJS.Timeout;
|
|
213
|
+
/** Callers awaiting handshake + tool discovery of a starting server. */
|
|
214
|
+
readyWaiters?: ReadyWaiter[];
|
|
215
|
+
/** Consecutive failed health probes; reset on any successful probe. */
|
|
216
|
+
consecutiveHealthFailures?: number;
|
|
161
217
|
}> = new Map();
|
|
162
218
|
|
|
163
219
|
// Scope tracking for channel/agent-scoped servers
|
|
@@ -176,6 +232,13 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
176
232
|
};
|
|
177
233
|
}> = new Map();
|
|
178
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Channel ids which crossed their terminal deletion boundary in this
|
|
237
|
+
* process. Channel ids are reserved in persistence, so this tombstone never
|
|
238
|
+
* needs to be removed and closes register/delete races in both directions.
|
|
239
|
+
*/
|
|
240
|
+
private retiredChannelIds = new Set<string>();
|
|
241
|
+
|
|
179
242
|
private autoCorrectionService: AutoCorrectionService;
|
|
180
243
|
|
|
181
244
|
/** Injectable event emitter — decouples from direct EventBus.server dependency.
|
|
@@ -183,17 +246,39 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
183
246
|
* Client-side: uses ClientToolEventEmitter (wraps EventBus.client + socketEmit). */
|
|
184
247
|
private toolEventEmitter: IToolEventEmitter | null = null;
|
|
185
248
|
|
|
249
|
+
/** EventBus subscriptions owned by this manager instance. */
|
|
250
|
+
private eventSubscriptions: Array<{ unsubscribe: () => void }> = [];
|
|
251
|
+
|
|
252
|
+
/** Delay before restarting a crashed server. Overridable for tests. */
|
|
253
|
+
private restartDelayMs: number = DEFAULT_RESTART_DELAY_MS;
|
|
254
|
+
|
|
255
|
+
/** Per-method JSON-RPC reply timeouts. Overridable for tests and tuning. */
|
|
256
|
+
private requestTimeouts: Record<keyof typeof REQUEST_TIMEOUTS_MS, number> = { ...REQUEST_TIMEOUTS_MS };
|
|
257
|
+
|
|
186
258
|
/**
|
|
187
259
|
* @param options.toolEventEmitter - Injectable event emitter for decoupling from EventBus.server
|
|
188
260
|
* @param options.skipServerEventHandlers - Skip setting up EventBus.server listeners (for client-side usage)
|
|
261
|
+
* @param options.restartDelayMs - Delay before restarting a crashed server (default 2000)
|
|
262
|
+
* @param options.requestTimeoutsMs - Per-method JSON-RPC reply timeout overrides
|
|
189
263
|
*/
|
|
190
|
-
constructor(options?: {
|
|
264
|
+
constructor(options?: {
|
|
265
|
+
toolEventEmitter?: IToolEventEmitter;
|
|
266
|
+
skipServerEventHandlers?: boolean;
|
|
267
|
+
restartDelayMs?: number;
|
|
268
|
+
requestTimeoutsMs?: Partial<Record<keyof typeof REQUEST_TIMEOUTS_MS, number>>;
|
|
269
|
+
}) {
|
|
191
270
|
super();
|
|
192
271
|
this.autoCorrectionService = AutoCorrectionService.getInstance();
|
|
193
272
|
|
|
194
273
|
if (options?.toolEventEmitter) {
|
|
195
274
|
this.toolEventEmitter = options.toolEventEmitter;
|
|
196
275
|
}
|
|
276
|
+
if (options?.restartDelayMs !== undefined) {
|
|
277
|
+
this.restartDelayMs = options.restartDelayMs;
|
|
278
|
+
}
|
|
279
|
+
if (options?.requestTimeoutsMs) {
|
|
280
|
+
this.requestTimeouts = { ...this.requestTimeouts, ...options.requestTimeoutsMs };
|
|
281
|
+
}
|
|
197
282
|
|
|
198
283
|
// Set up event listeners for SDK-initiated server registration
|
|
199
284
|
// Skipped when running client-side (no EventBus.server listeners needed)
|
|
@@ -208,9 +293,16 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
208
293
|
private setupEventHandlers(): void {
|
|
209
294
|
|
|
210
295
|
// Handle external server registration requests from SDK
|
|
211
|
-
EventBus.server.on(Events.Mcp.EXTERNAL_SERVER_REGISTER, async (
|
|
296
|
+
this.eventSubscriptions.push(EventBus.server.on(Events.Mcp.EXTERNAL_SERVER_REGISTER, async (
|
|
297
|
+
payload: ExternalMcpLifecycleRequestPayload<ExternalMcpRegistrationRequestData>
|
|
298
|
+
) => {
|
|
212
299
|
try {
|
|
213
300
|
|
|
301
|
+
// EventBus registrations originate outside this manager (normally
|
|
302
|
+
// from a socket). They are caller-controlled and stdio means host
|
|
303
|
+
// process execution, so require the operator's explicit opt-in.
|
|
304
|
+
assertUnsafeStdioMcpEnabled(payload.data?.transport);
|
|
305
|
+
|
|
214
306
|
const serverConfig = {
|
|
215
307
|
id: payload.data.id,
|
|
216
308
|
name: payload.data.name,
|
|
@@ -262,15 +354,16 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
262
354
|
}
|
|
263
355
|
});
|
|
264
356
|
}
|
|
265
|
-
});
|
|
357
|
+
}));
|
|
266
358
|
|
|
267
359
|
// Handle external server unregistration requests from SDK
|
|
268
|
-
EventBus.server.on(Events.Mcp.EXTERNAL_SERVER_UNREGISTER, async (
|
|
360
|
+
this.eventSubscriptions.push(EventBus.server.on(Events.Mcp.EXTERNAL_SERVER_UNREGISTER, async (
|
|
361
|
+
payload: ExternalMcpLifecycleRequestPayload<ExternalMcpUnregistrationRequestData>
|
|
362
|
+
) => {
|
|
269
363
|
try {
|
|
270
364
|
|
|
271
365
|
const serverId = payload.data.serverId;
|
|
272
|
-
await this.
|
|
273
|
-
this.servers.delete(serverId);
|
|
366
|
+
await this.unregisterServer(serverId);
|
|
274
367
|
|
|
275
368
|
// Emit success response
|
|
276
369
|
EventBus.server.emit(Events.Mcp.EXTERNAL_SERVER_UNREGISTERED, {
|
|
@@ -288,7 +381,9 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
288
381
|
|
|
289
382
|
|
|
290
383
|
} catch (error) {
|
|
291
|
-
|
|
384
|
+
// Interpolate the message: passing the raw error as a second console
|
|
385
|
+
// argument makes Bun print a source-code excerpt instead of the message.
|
|
386
|
+
logger.error(`Error unregistering external server: ${error instanceof Error ? error.message : String(error)}`);
|
|
292
387
|
|
|
293
388
|
// Emit error response
|
|
294
389
|
EventBus.server.emit(Events.Mcp.EXTERNAL_SERVER_UNREGISTERED, {
|
|
@@ -304,58 +399,50 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
304
399
|
}
|
|
305
400
|
});
|
|
306
401
|
}
|
|
307
|
-
});
|
|
402
|
+
}));
|
|
308
403
|
|
|
309
404
|
// Handle channel-scoped server registration requests
|
|
310
|
-
EventBus.server.on(Events.Mcp.CHANNEL_SERVER_REGISTER, async (
|
|
405
|
+
this.eventSubscriptions.push(EventBus.server.on(Events.Mcp.CHANNEL_SERVER_REGISTER, async (
|
|
406
|
+
payload: ExternalMcpLifecycleRequestPayload<ExternalMcpRegistrationRequestData>
|
|
407
|
+
) => {
|
|
311
408
|
try {
|
|
409
|
+
// Channel membership is not permission to execute a host command.
|
|
410
|
+
// The HTTP/socket entry point must also establish administrator
|
|
411
|
+
// authority; this is the defense-in-depth feature gate.
|
|
412
|
+
assertUnsafeStdioMcpEnabled(payload.data?.transport);
|
|
312
413
|
logger.info(`[CHANNEL_SERVER_REGISTER] Received registration request: ${JSON.stringify({ agentId: payload.agentId, channelId: payload.channelId, serverId: payload.data?.id })}`);
|
|
313
414
|
|
|
314
415
|
|
|
315
416
|
const channelId = payload.data.channelId || payload.channelId;
|
|
316
|
-
const serverId = `${channelId}:${payload.data.id}`;
|
|
317
417
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
this.serverScopes.set(serverId, {
|
|
337
|
-
scope: 'channel',
|
|
338
|
-
scopeId: channelId,
|
|
339
|
-
connectedAgents: new Set(),
|
|
340
|
-
keepAliveMinutes: payload.data.keepAliveMinutes || 5,
|
|
341
|
-
// Store registration context for deferred success emission
|
|
342
|
-
registrationContext: {
|
|
418
|
+
await this.registerChannelServer(
|
|
419
|
+
channelId,
|
|
420
|
+
{
|
|
421
|
+
id: payload.data.id,
|
|
422
|
+
name: payload.data.name,
|
|
423
|
+
version: payload.data.version || '1.0.0',
|
|
424
|
+
command: payload.data.command || '',
|
|
425
|
+
args: payload.data.args || [],
|
|
426
|
+
autoStart: payload.data.autoStart !== false,
|
|
427
|
+
restartOnCrash: payload.data.restartOnCrash !== false,
|
|
428
|
+
maxRestartAttempts: payload.data.maxRestartAttempts || 3,
|
|
429
|
+
healthCheckInterval: payload.data.healthCheckInterval || 30000,
|
|
430
|
+
startupTimeout: payload.data.startupTimeout || 10000,
|
|
431
|
+
environmentVariables: payload.data.environmentVariables || {},
|
|
432
|
+
keepAliveMinutes: payload.data.keepAliveMinutes
|
|
433
|
+
},
|
|
434
|
+
// Registration context for deferred success emission after tool discovery
|
|
435
|
+
{
|
|
343
436
|
agentId: payload.agentId,
|
|
344
437
|
channelId,
|
|
345
438
|
originalServerId: payload.data.id,
|
|
346
439
|
serverName: payload.data.name
|
|
347
440
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// Register the server (this starts the process and tool discovery)
|
|
351
|
-
// Success event will be emitted after tool discovery completes
|
|
352
|
-
await this.registerServer(serverConfig);
|
|
353
|
-
|
|
354
|
-
logger.info(`[CHANNEL_SERVER_REGISTER] Server ${serverId} registered, waiting for tool discovery before emitting success`);
|
|
441
|
+
);
|
|
355
442
|
|
|
356
443
|
} catch (error) {
|
|
357
444
|
|
|
358
|
-
logger.error(`Error registering channel server
|
|
445
|
+
logger.error(`Error registering channel server: ${error instanceof Error ? error.message : String(error)}`);
|
|
359
446
|
|
|
360
447
|
// Emit error response
|
|
361
448
|
EventBus.server.emit(McpEvents.CHANNEL_SERVER_REGISTRATION_FAILED,
|
|
@@ -373,18 +460,17 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
373
460
|
)
|
|
374
461
|
);
|
|
375
462
|
}
|
|
376
|
-
});
|
|
463
|
+
}));
|
|
377
464
|
|
|
378
465
|
// Handle channel server unregistration requests
|
|
379
|
-
EventBus.server.on(Events.Mcp.CHANNEL_SERVER_UNREGISTER, async (
|
|
466
|
+
this.eventSubscriptions.push(EventBus.server.on(Events.Mcp.CHANNEL_SERVER_UNREGISTER, async (
|
|
467
|
+
payload: ExternalMcpLifecycleRequestPayload<ExternalMcpUnregistrationRequestData>
|
|
468
|
+
) => {
|
|
380
469
|
try {
|
|
381
470
|
|
|
382
471
|
const channelId = payload.data.channelId || payload.channelId;
|
|
383
|
-
const serverId = `${channelId}:${payload.data.serverId}`;
|
|
384
472
|
|
|
385
|
-
await this.
|
|
386
|
-
this.servers.delete(serverId);
|
|
387
|
-
this.serverScopes.delete(serverId);
|
|
473
|
+
await this.unregisterChannelServer(channelId, payload.data.serverId);
|
|
388
474
|
|
|
389
475
|
// Emit success response
|
|
390
476
|
EventBus.server.emit(McpEvents.CHANNEL_SERVER_UNREGISTERED,
|
|
@@ -404,7 +490,7 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
404
490
|
|
|
405
491
|
} catch (error) {
|
|
406
492
|
|
|
407
|
-
logger.error(`Error unregistering channel server
|
|
493
|
+
logger.error(`Error unregistering channel server: ${error instanceof Error ? error.message : String(error)}`);
|
|
408
494
|
|
|
409
495
|
// Emit error response
|
|
410
496
|
EventBus.server.emit(McpEvents.CHANNEL_SERVER_UNREGISTERED,
|
|
@@ -422,7 +508,7 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
422
508
|
)
|
|
423
509
|
);
|
|
424
510
|
}
|
|
425
|
-
});
|
|
511
|
+
}));
|
|
426
512
|
|
|
427
513
|
}
|
|
428
514
|
|
|
@@ -430,6 +516,13 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
430
516
|
* Register a new external server configuration
|
|
431
517
|
*/
|
|
432
518
|
public async registerServer(config: ExternalServerConfig): Promise<void> {
|
|
519
|
+
// This manager currently speaks line-delimited MCP over child-process
|
|
520
|
+
// stdio only. Never let an HTTP-labelled registration fall through to
|
|
521
|
+
// spawn(config.command, ...), which would bypass the stdio feature gate.
|
|
522
|
+
if (config.transport === 'http') {
|
|
523
|
+
throw new Error('HTTP transport registration is not implemented by ExternalMcpServerManager');
|
|
524
|
+
}
|
|
525
|
+
|
|
433
526
|
logger.info(`[REGISTER_SERVER] Registering server ${config.id}: command="${config.command}", args=${JSON.stringify(config.args)}, autoStart=${config.autoStart}`);
|
|
434
527
|
|
|
435
528
|
// Validate configuration
|
|
@@ -470,11 +563,251 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
470
563
|
}
|
|
471
564
|
|
|
472
565
|
/**
|
|
473
|
-
*
|
|
566
|
+
* Register a channel-scoped server: track its scope, then register and
|
|
567
|
+
* (per config) start it. Resolves after the MCP handshake and tool
|
|
568
|
+
* discovery when autoStart is set, so a resolved promise means the tools
|
|
569
|
+
* are in the registry.
|
|
570
|
+
*
|
|
571
|
+
* A re-registration over an existing scope preserves the connected-agent
|
|
572
|
+
* set and clears any pending keepAlive timer — previously the timer
|
|
573
|
+
* reference was silently overwritten while the timer kept running, so a
|
|
574
|
+
* stale keepAlive could stop a freshly re-registered server later.
|
|
575
|
+
*/
|
|
576
|
+
public async registerChannelServer(
|
|
577
|
+
channelId: string,
|
|
578
|
+
config: Omit<ExternalServerConfig, 'id'> & { id: string; keepAliveMinutes?: number },
|
|
579
|
+
registrationContext?: {
|
|
580
|
+
agentId: string;
|
|
581
|
+
channelId: string;
|
|
582
|
+
originalServerId: string;
|
|
583
|
+
serverName: string;
|
|
584
|
+
}
|
|
585
|
+
): Promise<void> {
|
|
586
|
+
validator.assertIsNonEmptyString(channelId, 'channelId must be a non-empty string');
|
|
587
|
+
const serverId = `${channelId}:${config.id}`;
|
|
588
|
+
const keepAliveMinutes = config.keepAliveMinutes || 5;
|
|
589
|
+
|
|
590
|
+
if (this.retiredChannelIds.has(channelId)) {
|
|
591
|
+
throw new Error(`Channel ${channelId} is deleted; MCP servers cannot be registered`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const existingScope = this.serverScopes.get(serverId);
|
|
595
|
+
if (existingScope?.keepAliveTimer) {
|
|
596
|
+
clearTimeout(existingScope.keepAliveTimer);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
this.serverScopes.set(serverId, {
|
|
600
|
+
scope: 'channel',
|
|
601
|
+
scopeId: channelId,
|
|
602
|
+
// Only actual channel agents are counted, not the admin who registers.
|
|
603
|
+
// On re-registration, keep whoever is already connected.
|
|
604
|
+
connectedAgents: existingScope?.connectedAgents ?? new Set(),
|
|
605
|
+
keepAliveMinutes,
|
|
606
|
+
registrationContext
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
logger.info(
|
|
610
|
+
`[CHANNEL_SERVER_REGISTER] Registering channel server ${serverId} ` +
|
|
611
|
+
`(keepAlive ${keepAliveMinutes}min, autoStart ${config.autoStart}, restartOnCrash ${config.restartOnCrash})`
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
const { keepAliveMinutes: _ignored, ...serverConfig } = config;
|
|
615
|
+
try {
|
|
616
|
+
await this.registerServer({ ...serverConfig, id: serverId });
|
|
617
|
+
|
|
618
|
+
// Deletion may have won while process startup/handshake was in
|
|
619
|
+
// flight. Remove the just-created runtime before returning so a
|
|
620
|
+
// late registration cannot resurrect a deleted channel's server.
|
|
621
|
+
if (this.retiredChannelIds.has(channelId)) {
|
|
622
|
+
await this.removeServer(serverId, 'registration raced channel deletion');
|
|
623
|
+
throw new Error(`Channel ${channelId} was deleted during MCP server registration`);
|
|
624
|
+
}
|
|
625
|
+
} catch (error) {
|
|
626
|
+
// Registration failed before the server record existed — do not leave
|
|
627
|
+
// a scope entry behind for agents to "join" (unless one existed before).
|
|
628
|
+
if (!existingScope && !this.servers.has(serverId)) {
|
|
629
|
+
this.serverScopes.delete(serverId);
|
|
630
|
+
}
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Unregister a channel-scoped server: stop the process and remove both the
|
|
637
|
+
* server record and the scope tracking, including any keepAlive timer.
|
|
638
|
+
*
|
|
639
|
+
* Idempotent: unregistering a server that is partially or fully gone cleans
|
|
640
|
+
* up whatever remains and resolves. Production showed the half-removed
|
|
641
|
+
* state — record deleted, scope alive — and an unregister that throws
|
|
642
|
+
* "not found" against it leaves the zombie in place forever.
|
|
643
|
+
*/
|
|
644
|
+
public async unregisterChannelServer(channelId: string, serverId: string): Promise<void> {
|
|
645
|
+
validator.assertIsNonEmptyString(channelId, 'channelId must be a non-empty string');
|
|
646
|
+
validator.assertIsNonEmptyString(serverId, 'serverId must be a non-empty string');
|
|
647
|
+
await this.removeServer(`${channelId}:${serverId}`, 'channel server unregistration');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Permanently retire a channel's MCP runtime in this process.
|
|
652
|
+
*
|
|
653
|
+
* The tombstone is installed before process cleanup, which makes concurrent
|
|
654
|
+
* registrations fail both before and after their asynchronous startup.
|
|
655
|
+
* Every known scoped record is removed, including orphaned records whose
|
|
656
|
+
* scope metadata or server entry is only partially present.
|
|
657
|
+
*/
|
|
658
|
+
public async retireChannel(channelId: string): Promise<void> {
|
|
659
|
+
validator.assertIsNonEmptyString(channelId, 'channelId must be a non-empty string');
|
|
660
|
+
this.retiredChannelIds.add(channelId);
|
|
661
|
+
|
|
662
|
+
const serverIds = new Set<string>();
|
|
663
|
+
for (const [serverId, scopeData] of this.serverScopes.entries()) {
|
|
664
|
+
if (scopeData.scope === 'channel' && scopeData.scopeId === channelId) {
|
|
665
|
+
serverIds.add(serverId);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
for (const serverId of this.servers.keys()) {
|
|
669
|
+
if (serverId.startsWith(`${channelId}:`)) {
|
|
670
|
+
serverIds.add(serverId);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const results = await Promise.allSettled(
|
|
675
|
+
Array.from(serverIds, serverId => (
|
|
676
|
+
this.removeServer(serverId, 'channel deletion')
|
|
677
|
+
))
|
|
678
|
+
);
|
|
679
|
+
const failures = results.filter(
|
|
680
|
+
(result): result is PromiseRejectedResult => result.status === 'rejected'
|
|
681
|
+
);
|
|
682
|
+
if (failures.length > 0) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
`Failed to retire ${failures.length} MCP server(s) for channel ${channelId}`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Unregister a server by its full id (global servers, and the SDK-facing
|
|
691
|
+
* EXTERNAL_SERVER_UNREGISTER path). Also removes scope tracking — this
|
|
692
|
+
* path used to delete only the server record, which was exactly the
|
|
693
|
+
* zombie state observed in production: agents kept "joining" a scope
|
|
694
|
+
* whose server no longer existed.
|
|
695
|
+
*/
|
|
696
|
+
public async unregisterServer(serverId: string): Promise<void> {
|
|
697
|
+
validator.assertIsNonEmptyString(serverId, 'serverId must be a non-empty string');
|
|
698
|
+
await this.removeServer(serverId, 'server unregistration');
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Stop a server (if running) and remove every trace of it: server record,
|
|
703
|
+
* scope entry, keepAlive timer. Never throws for a missing record — it
|
|
704
|
+
* removes what exists and says what it did.
|
|
705
|
+
*/
|
|
706
|
+
private async removeServer(serverId: string, reason: string): Promise<void> {
|
|
707
|
+
const serverData = this.servers.get(serverId);
|
|
708
|
+
const scopeData = this.serverScopes.get(serverId);
|
|
709
|
+
|
|
710
|
+
if (!serverData && !scopeData) {
|
|
711
|
+
logger.warn(`Nothing to unregister for ${serverId} (${reason}) — no record, no scope`);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (scopeData?.keepAliveTimer) {
|
|
716
|
+
clearTimeout(scopeData.keepAliveTimer);
|
|
717
|
+
scopeData.keepAliveTimer = undefined;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (serverData) {
|
|
721
|
+
try {
|
|
722
|
+
await this.stopServer(serverId, undefined, undefined, reason);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
logger.error(
|
|
725
|
+
`Error stopping ${serverId} during ${reason}: ` +
|
|
726
|
+
`${error instanceof Error ? error.message : String(error)} — removing its record anyway`
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
logger.warn(
|
|
731
|
+
`Unregistering ${serverId} (${reason}): server record was already gone, ` +
|
|
732
|
+
`removing the orphaned scope entry`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
this.servers.delete(serverId);
|
|
737
|
+
this.serverScopes.delete(serverId);
|
|
738
|
+
logger.info(`Unregistered server ${serverId} (${reason})`);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Remove a server that cannot be kept alive (restart budget exhausted,
|
|
743
|
+
* unrecoverable spawn failure). Loud by design: this is the path that
|
|
744
|
+
* prevents zombies, so it reports at error level.
|
|
745
|
+
*/
|
|
746
|
+
private removeServerAfterFailure(serverId: string, reason: string): void {
|
|
747
|
+
const serverData = this.servers.get(serverId);
|
|
748
|
+
if (serverData) {
|
|
749
|
+
if (serverData.healthCheckTimer) {
|
|
750
|
+
clearInterval(serverData.healthCheckTimer);
|
|
751
|
+
serverData.healthCheckTimer = undefined;
|
|
752
|
+
}
|
|
753
|
+
if (serverData.startupTimer) {
|
|
754
|
+
clearTimeout(serverData.startupTimer);
|
|
755
|
+
serverData.startupTimer = undefined;
|
|
756
|
+
}
|
|
757
|
+
this.rejectPendingRequests(serverId, reason);
|
|
758
|
+
this.settleReadyWaiters(serverId, new Error(reason));
|
|
759
|
+
if (serverData.process && !serverData.process.killed) {
|
|
760
|
+
serverData.process.kill('SIGKILL');
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const scopeData = this.serverScopes.get(serverId);
|
|
765
|
+
if (scopeData?.keepAliveTimer) {
|
|
766
|
+
clearTimeout(scopeData.keepAliveTimer);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
this.servers.delete(serverId);
|
|
770
|
+
this.serverScopes.delete(serverId);
|
|
771
|
+
|
|
772
|
+
logger.error(
|
|
773
|
+
`Server ${serverId} unregistered: ${reason}. ` +
|
|
774
|
+
`Its tools are removed from the registry; re-register the server to restore them.`
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_STOPPED, serverId);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Settle every caller waiting for a server to become ready.
|
|
782
|
+
*/
|
|
783
|
+
private settleReadyWaiters(serverId: string, error?: Error): void {
|
|
784
|
+
const serverData = this.servers.get(serverId);
|
|
785
|
+
if (!serverData?.readyWaiters?.length) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const waiters = serverData.readyWaiters;
|
|
789
|
+
serverData.readyWaiters = [];
|
|
790
|
+
for (const waiter of waiters) {
|
|
791
|
+
if (error) {
|
|
792
|
+
waiter.reject(error);
|
|
793
|
+
} else {
|
|
794
|
+
waiter.resolve();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Start an external server process.
|
|
801
|
+
*
|
|
802
|
+
* Resolves once the MCP handshake AND tool discovery have completed — a
|
|
803
|
+
* resolved startServer() means the server's tools are in the registry.
|
|
804
|
+
* It used to resolve right after spawn, which let callers (agent join,
|
|
805
|
+
* restart) proceed against a server that had not finished — or would
|
|
806
|
+
* never finish — its handshake.
|
|
474
807
|
*/
|
|
475
808
|
public async startServer(serverId: string, agentId?: AgentId, channelId?: ChannelId): Promise<void> {
|
|
476
809
|
logger.info(`[START_SERVER] Starting server ${serverId}`);
|
|
477
|
-
|
|
810
|
+
|
|
478
811
|
const serverData = this.servers.get(serverId);
|
|
479
812
|
if (!serverData) {
|
|
480
813
|
// Server was unregistered (e.g., during cleanup) - log warning and return gracefully
|
|
@@ -489,9 +822,20 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
489
822
|
return;
|
|
490
823
|
}
|
|
491
824
|
|
|
825
|
+
if (status.status === 'starting') {
|
|
826
|
+
// Another caller is already starting this server — wait for that
|
|
827
|
+
// startup instead of spawning a second process.
|
|
828
|
+
logger.info(`[START_SERVER] Server ${serverId} already starting, awaiting readiness`);
|
|
829
|
+
await new Promise<void>((resolve, reject) => {
|
|
830
|
+
serverData.readyWaiters = serverData.readyWaiters ?? [];
|
|
831
|
+
serverData.readyWaiters.push({ resolve, reject });
|
|
832
|
+
});
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
492
835
|
|
|
493
836
|
// Update status
|
|
494
837
|
status.status = 'starting';
|
|
838
|
+
serverData.expectedExit = false;
|
|
495
839
|
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_SPAWN, serverId, agentId, channelId);
|
|
496
840
|
|
|
497
841
|
try {
|
|
@@ -543,41 +887,99 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
543
887
|
// Start health check monitoring
|
|
544
888
|
this.startHealthChecking(serverId);
|
|
545
889
|
|
|
546
|
-
|
|
547
890
|
} catch (error) {
|
|
548
891
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
549
892
|
logger.error(`❌ Failed to start server ${config.name}: ${errorMessage}`);
|
|
550
893
|
this.handleServerError(serverId, errorMessage, agentId, channelId);
|
|
894
|
+
throw error instanceof Error ? error : new Error(errorMessage);
|
|
551
895
|
}
|
|
896
|
+
|
|
897
|
+
// Wait for the handshake and tool discovery. Settled by
|
|
898
|
+
// initializeMcpConnection() on success, and by handleServerError()
|
|
899
|
+
// or the exit handler on failure — those already report the cause,
|
|
900
|
+
// so a rejection here propagates without being re-handled.
|
|
901
|
+
await new Promise<void>((resolve, reject) => {
|
|
902
|
+
serverData.readyWaiters = serverData.readyWaiters ?? [];
|
|
903
|
+
serverData.readyWaiters.push({ resolve, reject });
|
|
904
|
+
});
|
|
552
905
|
}
|
|
553
906
|
|
|
554
907
|
/**
|
|
555
|
-
* Handle agent joining a channel
|
|
908
|
+
* Handle agent joining a channel — verify each channel server is actually
|
|
909
|
+
* alive before counting the agent as connected.
|
|
910
|
+
*
|
|
911
|
+
* This used to be a blind reference-count bump: a missing server record was
|
|
912
|
+
* silently skipped and a dead process was never probed, so agents "joined"
|
|
913
|
+
* servers that could not serve a single tool call, and the only downstream
|
|
914
|
+
* signal was a NOT FOUND warning when their allowlist resolved.
|
|
556
915
|
*/
|
|
557
916
|
public async onAgentJoinChannel(agentId: string, channelId: string): Promise<void> {
|
|
558
917
|
logger.info(`Agent ${agentId} joining channel ${channelId} - checking for channel servers`);
|
|
559
918
|
|
|
560
919
|
// Find all channel-scoped servers for this channel
|
|
561
920
|
for (const [serverId, scopeData] of this.serverScopes.entries()) {
|
|
562
|
-
if (scopeData.scope
|
|
563
|
-
|
|
564
|
-
|
|
921
|
+
if (scopeData.scope !== 'channel' || scopeData.scopeId !== channelId) {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
565
924
|
|
|
566
|
-
|
|
925
|
+
const serverData = this.servers.get(serverId);
|
|
926
|
+
if (!serverData) {
|
|
927
|
+
// The production zombie state: a scope entry whose server record is
|
|
928
|
+
// gone. There is no config left to restart from, so remove the
|
|
929
|
+
// orphan loudly instead of pretending the agent connected.
|
|
930
|
+
logger.error(
|
|
931
|
+
`Agent ${agentId} tried to join channel server ${serverId}, but its server record is gone ` +
|
|
932
|
+
`(scope entry was orphaned). Removing the orphaned scope — the server must be re-registered.`
|
|
933
|
+
);
|
|
567
934
|
if (scopeData.keepAliveTimer) {
|
|
568
935
|
clearTimeout(scopeData.keepAliveTimer);
|
|
569
|
-
scopeData.keepAliveTimer = undefined;
|
|
570
936
|
}
|
|
937
|
+
this.serverScopes.delete(serverId);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
571
940
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
logger.info(`Starting channel server ${serverId} for agent ${agentId}`);
|
|
941
|
+
try {
|
|
942
|
+
if (serverData.status.status !== 'running') {
|
|
943
|
+
logger.info(`Starting channel server ${serverId} for agent ${agentId} (status: ${serverData.status.status})`);
|
|
576
944
|
await this.startServer(serverId);
|
|
945
|
+
} else {
|
|
946
|
+
// Status says running — prove it. A live entry with a dead child
|
|
947
|
+
// (or a wedged MCP connection) must trigger recovery at join
|
|
948
|
+
// time, not a silent ref-count bump.
|
|
949
|
+
const alive = serverData.process && !serverData.process.killed && serverData.process.exitCode === null;
|
|
950
|
+
if (!alive) {
|
|
951
|
+
logger.warn(`Channel server ${serverId} has no live process at agent join — restarting`);
|
|
952
|
+
await this.restartServer(serverId);
|
|
953
|
+
} else {
|
|
954
|
+
try {
|
|
955
|
+
await this.sendRequest(serverId, 'tools/list');
|
|
956
|
+
} catch (probeError) {
|
|
957
|
+
logger.warn(
|
|
958
|
+
`Channel server ${serverId} did not answer the liveness probe at agent join ` +
|
|
959
|
+
`(${probeError instanceof Error ? probeError.message : String(probeError)}) — restarting`
|
|
960
|
+
);
|
|
961
|
+
await this.restartServer(serverId);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
577
964
|
}
|
|
965
|
+
} catch (error) {
|
|
966
|
+
logger.error(
|
|
967
|
+
`Channel server ${serverId} could not be made available for agent ${agentId}: ` +
|
|
968
|
+
`${error instanceof Error ? error.message : String(error)}`
|
|
969
|
+
);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Only a server that is verifiably up counts the agent as connected
|
|
974
|
+
scopeData.connectedAgents.add(agentId);
|
|
578
975
|
|
|
579
|
-
|
|
976
|
+
// Clear any pending keepAlive timer
|
|
977
|
+
if (scopeData.keepAliveTimer) {
|
|
978
|
+
clearTimeout(scopeData.keepAliveTimer);
|
|
979
|
+
scopeData.keepAliveTimer = undefined;
|
|
580
980
|
}
|
|
981
|
+
|
|
982
|
+
logger.info(`Agent ${agentId} connected to channel server ${serverId} (${scopeData.connectedAgents.size} agents)`);
|
|
581
983
|
}
|
|
582
984
|
}
|
|
583
985
|
|
|
@@ -602,11 +1004,17 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
602
1004
|
logger.info(`Last agent left channel server ${serverId}, starting ${scopeData.keepAliveMinutes}min keepAlive timer`);
|
|
603
1005
|
|
|
604
1006
|
scopeData.keepAliveTimer = setTimeout(async () => {
|
|
1007
|
+
scopeData.keepAliveTimer = undefined;
|
|
1008
|
+
if (!this.servers.has(serverId)) {
|
|
1009
|
+
logger.warn(`KeepAlive expired for ${serverId}, but its server record is already gone — removing the orphaned scope`);
|
|
1010
|
+
this.serverScopes.delete(serverId);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
605
1013
|
logger.info(`KeepAlive expired for server ${serverId}, stopping server`);
|
|
606
1014
|
try {
|
|
607
|
-
await this.stopServer(serverId);
|
|
1015
|
+
await this.stopServer(serverId, undefined, undefined, 'keepAlive expired');
|
|
608
1016
|
} catch (error) {
|
|
609
|
-
logger.error(`Error stopping server ${serverId} after keepAlive
|
|
1017
|
+
logger.error(`Error stopping server ${serverId} after keepAlive: ${error instanceof Error ? error.message : String(error)}`);
|
|
610
1018
|
}
|
|
611
1019
|
}, keepAliveMs);
|
|
612
1020
|
}
|
|
@@ -620,27 +1028,32 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
620
1028
|
public getServersByScope(scope: 'global' | 'channel' | 'agent', scopeId?: string): ExternalServerStatus[] {
|
|
621
1029
|
const servers: ExternalServerStatus[] = [];
|
|
622
1030
|
|
|
623
|
-
for (const [serverId,
|
|
624
|
-
|
|
1031
|
+
for (const [serverId, serverData] of this.servers.entries()) {
|
|
1032
|
+
// Servers registered through the original global registration path
|
|
1033
|
+
// predate serverScopes and are explicitly global, matching the tool
|
|
1034
|
+
// snapshot contract in getAllExternalTools().
|
|
1035
|
+
const scopeData = this.serverScopes.get(serverId);
|
|
1036
|
+
const actualScope = scopeData?.scope ?? 'global';
|
|
1037
|
+
if (actualScope === scope) {
|
|
625
1038
|
// For channel/agent scope, match scopeId
|
|
626
|
-
if ((scope === 'channel' || scope === 'agent') && scopeData
|
|
1039
|
+
if ((scope === 'channel' || scope === 'agent') && scopeData?.scopeId !== scopeId) {
|
|
627
1040
|
continue;
|
|
628
1041
|
}
|
|
629
1042
|
|
|
630
|
-
|
|
631
|
-
if (serverData) {
|
|
632
|
-
servers.push(serverData.status);
|
|
633
|
-
}
|
|
1043
|
+
servers.push(serverData.status);
|
|
634
1044
|
}
|
|
635
1045
|
}
|
|
636
1046
|
|
|
637
|
-
return servers;
|
|
1047
|
+
return servers.sort((a, b) => a.id.localeCompare(b.id));
|
|
638
1048
|
}
|
|
639
1049
|
|
|
640
1050
|
/**
|
|
641
|
-
* Stop an external server process
|
|
1051
|
+
* Stop an external server process.
|
|
1052
|
+
*
|
|
1053
|
+
* An intentional stop: the exit handler will see `expectedExit` and will
|
|
1054
|
+
* neither log the exit as a crash nor restart the process.
|
|
642
1055
|
*/
|
|
643
|
-
public async stopServer(serverId: string, agentId?: AgentId, channelId?: ChannelId): Promise<void> {
|
|
1056
|
+
public async stopServer(serverId: string, agentId?: AgentId, channelId?: ChannelId, reason?: string): Promise<void> {
|
|
644
1057
|
const serverData = this.servers.get(serverId);
|
|
645
1058
|
if (!serverData) {
|
|
646
1059
|
throw new Error(`Server ${serverId} not found`);
|
|
@@ -652,6 +1065,11 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
652
1065
|
return;
|
|
653
1066
|
}
|
|
654
1067
|
|
|
1068
|
+
const droppedTools = status.tools.length;
|
|
1069
|
+
logger.info(
|
|
1070
|
+
`Stopping server ${serverId} (${reason ?? 'no reason given'})` +
|
|
1071
|
+
(droppedTools > 0 ? ` — removing its ${droppedTools} tool(s) from the registry` : '')
|
|
1072
|
+
);
|
|
655
1073
|
|
|
656
1074
|
// Emit stop event
|
|
657
1075
|
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_STOP, serverId, agentId, channelId);
|
|
@@ -669,16 +1087,21 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
669
1087
|
// Fail anything still in flight before we kill the process, so callers get
|
|
670
1088
|
// a clear error rather than waiting out their own timeouts.
|
|
671
1089
|
this.rejectPendingRequests(serverId, 'server is stopping');
|
|
1090
|
+
this.settleReadyWaiters(serverId, new Error(`Server ${serverId} was stopped (${reason ?? 'no reason given'})`));
|
|
672
1091
|
|
|
673
1092
|
// Terminate process
|
|
674
1093
|
if (serverData.process) {
|
|
675
|
-
serverData.
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1094
|
+
serverData.expectedExit = true;
|
|
1095
|
+
const stoppingProcess = serverData.process;
|
|
1096
|
+
stoppingProcess.kill('SIGTERM');
|
|
1097
|
+
|
|
1098
|
+
// Force kill after timeout; the exit handler clears this when the
|
|
1099
|
+
// process goes down on its own.
|
|
1100
|
+
serverData.forceKillTimer = setTimeout(() => {
|
|
1101
|
+
serverData.forceKillTimer = undefined;
|
|
1102
|
+
if (!stoppingProcess.killed && stoppingProcess.exitCode === null) {
|
|
680
1103
|
logger.warn(`Force killing server ${config.name}`);
|
|
681
|
-
|
|
1104
|
+
stoppingProcess.kill('SIGKILL');
|
|
682
1105
|
}
|
|
683
1106
|
}, 5000);
|
|
684
1107
|
}
|
|
@@ -723,10 +1146,16 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
723
1146
|
const tools: ExternalMcpTool[] = [];
|
|
724
1147
|
|
|
725
1148
|
for (const [serverId, serverData] of this.servers) {
|
|
1149
|
+
const scopeData = this.serverScopes.get(serverId);
|
|
1150
|
+
const scope = scopeData?.scope ?? 'global';
|
|
1151
|
+
const scopeId = scopeData?.scopeId;
|
|
1152
|
+
|
|
726
1153
|
for (const tool of serverData.status.tools) {
|
|
727
1154
|
tools.push({
|
|
728
1155
|
...tool,
|
|
729
|
-
serverId
|
|
1156
|
+
serverId,
|
|
1157
|
+
scope,
|
|
1158
|
+
scopeId
|
|
730
1159
|
});
|
|
731
1160
|
}
|
|
732
1161
|
}
|
|
@@ -745,6 +1174,25 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
745
1174
|
|
|
746
1175
|
// Handle process exit
|
|
747
1176
|
process.on('exit', (code, signal) => {
|
|
1177
|
+
// This process is down — its SIGKILL escalation timer is moot.
|
|
1178
|
+
if (serverData.forceKillTimer) {
|
|
1179
|
+
clearTimeout(serverData.forceKillTimer);
|
|
1180
|
+
serverData.forceKillTimer = undefined;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const currentData = this.servers.get(serverId);
|
|
1184
|
+
if (!currentData || currentData.process !== process) {
|
|
1185
|
+
// Exit of a process instance that has already been replaced (restart)
|
|
1186
|
+
// or whose server record is gone (unregistered). Not this record's
|
|
1187
|
+
// state to change.
|
|
1188
|
+
logger.debug(`Ignoring exit of a superseded process for ${serverId} (code ${code ?? 'null'}, signal ${signal ?? 'none'})`);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const wasExpected = currentData.expectedExit === true;
|
|
1193
|
+
currentData.expectedExit = false;
|
|
1194
|
+
|
|
1195
|
+
const droppedTools = status.tools.length;
|
|
748
1196
|
|
|
749
1197
|
status.status = 'stopped';
|
|
750
1198
|
status.pid = undefined;
|
|
@@ -752,30 +1200,77 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
752
1200
|
// perform it again before it can be marked running.
|
|
753
1201
|
status.initialized = false;
|
|
754
1202
|
status.initializing = false;
|
|
1203
|
+
status.tools = [];
|
|
755
1204
|
|
|
756
1205
|
// Anything still waiting on this process will never get a reply.
|
|
757
1206
|
this.rejectPendingRequests(
|
|
758
1207
|
serverId,
|
|
759
1208
|
`server exited (code ${code ?? 'null'}, signal ${signal ?? 'none'})`
|
|
760
1209
|
);
|
|
1210
|
+
this.settleReadyWaiters(
|
|
1211
|
+
serverId,
|
|
1212
|
+
new Error(`Server ${serverId} exited during startup (code ${code ?? 'null'}, signal ${signal ?? 'none'})`)
|
|
1213
|
+
);
|
|
761
1214
|
|
|
762
1215
|
// Clear timers
|
|
763
1216
|
if (serverData.healthCheckTimer) {
|
|
764
1217
|
clearInterval(serverData.healthCheckTimer);
|
|
765
1218
|
serverData.healthCheckTimer = undefined;
|
|
766
1219
|
}
|
|
1220
|
+
if (serverData.startupTimer) {
|
|
1221
|
+
clearTimeout(serverData.startupTimer);
|
|
1222
|
+
serverData.startupTimer = undefined;
|
|
1223
|
+
}
|
|
767
1224
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
1225
|
+
if (wasExpected) {
|
|
1226
|
+
logger.info(`Server ${serverId} exited after stop (code ${code ?? 'null'}, signal ${signal ?? 'none'})`);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Unexpected death. This used to happen in complete silence — no log
|
|
1231
|
+
// line at any level — which is how production servers vanished from
|
|
1232
|
+
// the tool registry with nothing to grep for.
|
|
1233
|
+
logger.error(
|
|
1234
|
+
`Server ${serverId} exited unexpectedly (code ${code ?? 'null'}, signal ${signal ?? 'none'})` +
|
|
1235
|
+
(droppedTools > 0 ? ` — its ${droppedTools} tool(s) are removed from the registry` : '')
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
// Restart on ANY unexpected exit when configured — including a clean
|
|
1239
|
+
// exit code. `code !== 0` used to gate this, leaving a child that
|
|
1240
|
+
// exited 0 dead forever with no restart and no log.
|
|
1241
|
+
if (config.restartOnCrash) {
|
|
1242
|
+
if (status.restartCount < config.maxRestartAttempts) {
|
|
1243
|
+
status.restartCount++;
|
|
1244
|
+
logger.warn(
|
|
1245
|
+
`Restarting server ${serverId} in ${this.restartDelayMs}ms ` +
|
|
1246
|
+
`(attempt ${status.restartCount}/${config.maxRestartAttempts})`
|
|
1247
|
+
);
|
|
1248
|
+
setTimeout(() => {
|
|
1249
|
+
// Check if server still exists before attempting restart
|
|
1250
|
+
// (it may have been unregistered during the delay)
|
|
1251
|
+
if (this.servers.has(serverId)) {
|
|
1252
|
+
this.startServer(serverId).catch(error => {
|
|
1253
|
+
logger.error(
|
|
1254
|
+
`Restart of server ${serverId} failed: ` +
|
|
1255
|
+
`${error instanceof Error ? error.message : String(error)}`
|
|
1256
|
+
);
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
}, this.restartDelayMs);
|
|
1260
|
+
} else {
|
|
1261
|
+
// Out of restart budget: remove the server entirely rather than
|
|
1262
|
+
// leaving a zombie record + scope that agents can "join".
|
|
1263
|
+
this.removeServerAfterFailure(
|
|
1264
|
+
serverId,
|
|
1265
|
+
`crashed and exhausted its ${config.maxRestartAttempts} restart attempt(s)`
|
|
1266
|
+
);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
} else {
|
|
1270
|
+
logger.error(
|
|
1271
|
+
`Server ${serverId} will not be restarted (restartOnCrash is off). ` +
|
|
1272
|
+
`An agent joining its channel will start it again on demand.`
|
|
1273
|
+
);
|
|
779
1274
|
}
|
|
780
1275
|
|
|
781
1276
|
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_STOPPED, serverId);
|
|
@@ -903,7 +1398,7 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
903
1398
|
|
|
904
1399
|
const stdin = serverData.process.stdin;
|
|
905
1400
|
const requestId = uuidv4();
|
|
906
|
-
const timeoutMs =
|
|
1401
|
+
const timeoutMs = this.requestTimeouts[method];
|
|
907
1402
|
|
|
908
1403
|
return new Promise((resolve, reject) => {
|
|
909
1404
|
const timer = setTimeout(() => {
|
|
@@ -1021,11 +1516,33 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
1021
1516
|
|
|
1022
1517
|
try {
|
|
1023
1518
|
await this.sendRequest(serverId, 'tools/list');
|
|
1519
|
+
serverData.consecutiveHealthFailures = 0;
|
|
1024
1520
|
this.emitServerHealthStatus(serverId, 'healthy');
|
|
1025
1521
|
} catch (error) {
|
|
1026
1522
|
const message = error instanceof Error ? error.message : String(error);
|
|
1027
|
-
|
|
1523
|
+
serverData.consecutiveHealthFailures = (serverData.consecutiveHealthFailures ?? 0) + 1;
|
|
1524
|
+
logger.warn(
|
|
1525
|
+
`Health check failed for ${serverId} ` +
|
|
1526
|
+
`(${serverData.consecutiveHealthFailures} consecutive): ${message}`
|
|
1527
|
+
);
|
|
1028
1528
|
this.emitServerHealthStatus(serverId, 'unhealthy');
|
|
1529
|
+
|
|
1530
|
+
// A process that is alive but no longer answering MCP is as dead as a
|
|
1531
|
+
// crashed one — the exit handler will never fire for it. Recover here.
|
|
1532
|
+
if (
|
|
1533
|
+
serverData.consecutiveHealthFailures >= HEALTH_FAILURES_BEFORE_RESTART &&
|
|
1534
|
+
serverData.config.restartOnCrash &&
|
|
1535
|
+
status.status === 'running'
|
|
1536
|
+
) {
|
|
1537
|
+
serverData.consecutiveHealthFailures = 0;
|
|
1538
|
+
logger.error(`Server ${serverId} failed ${HEALTH_FAILURES_BEFORE_RESTART} consecutive health checks — restarting it`);
|
|
1539
|
+
this.restartServer(serverId).catch(restartError => {
|
|
1540
|
+
logger.error(
|
|
1541
|
+
`Health-check restart of ${serverId} failed: ` +
|
|
1542
|
+
`${restartError instanceof Error ? restartError.message : String(restartError)}`
|
|
1543
|
+
);
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1029
1546
|
}
|
|
1030
1547
|
}
|
|
1031
1548
|
|
|
@@ -1173,6 +1690,16 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
1173
1690
|
|
|
1174
1691
|
// Now that the connection is live, find out what the server offers.
|
|
1175
1692
|
await this.discoverServerTools(serverId);
|
|
1693
|
+
|
|
1694
|
+
// A server that came up healthy earns back its full restart budget.
|
|
1695
|
+
// restartCount used to only ever grow, so a server that crashed a few
|
|
1696
|
+
// times over its lifetime — days apart, each recovered — permanently
|
|
1697
|
+
// exhausted its budget and the next crash left it down for good.
|
|
1698
|
+
serverData.status.restartCount = 0;
|
|
1699
|
+
serverData.consecutiveHealthFailures = 0;
|
|
1700
|
+
|
|
1701
|
+
// Whoever awaited startServer() can proceed: the tools are discovered.
|
|
1702
|
+
this.settleReadyWaiters(serverId);
|
|
1176
1703
|
}
|
|
1177
1704
|
|
|
1178
1705
|
/**
|
|
@@ -1217,6 +1744,9 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
1217
1744
|
|
|
1218
1745
|
logger.error(`❌ Server ${serverId} error: ${error}`);
|
|
1219
1746
|
|
|
1747
|
+
// Anyone awaiting this server's startup gets the failure now.
|
|
1748
|
+
this.settleReadyWaiters(serverId, new Error(`Server ${serverId} failed: ${error}`));
|
|
1749
|
+
|
|
1220
1750
|
// Emit error event
|
|
1221
1751
|
this.emitServerErrorEvent(serverId, error, agentId, channelId);
|
|
1222
1752
|
}
|
|
@@ -1342,13 +1872,30 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
1342
1872
|
*/
|
|
1343
1873
|
public async shutdown(): Promise<void> {
|
|
1344
1874
|
|
|
1875
|
+
// Stop accepting lifecycle requests before child-process teardown. Node's
|
|
1876
|
+
// removeAllListeners() below only affects this class's EventEmitter; it
|
|
1877
|
+
// does not detach RxJS EventBus subscriptions.
|
|
1878
|
+
for (const subscription of this.eventSubscriptions) {
|
|
1879
|
+
subscription.unsubscribe();
|
|
1880
|
+
}
|
|
1881
|
+
this.eventSubscriptions = [];
|
|
1882
|
+
|
|
1345
1883
|
const shutdownPromises = Array.from(this.servers.keys()).map(serverId =>
|
|
1346
1884
|
this.stopServer(serverId)
|
|
1347
1885
|
);
|
|
1348
1886
|
|
|
1349
1887
|
await Promise.allSettled(shutdownPromises);
|
|
1350
1888
|
|
|
1889
|
+
// Clear pending keepAlive timers so nothing fires against cleared maps
|
|
1890
|
+
for (const scopeData of this.serverScopes.values()) {
|
|
1891
|
+
if (scopeData.keepAliveTimer) {
|
|
1892
|
+
clearTimeout(scopeData.keepAliveTimer);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1351
1896
|
this.servers.clear();
|
|
1897
|
+
this.serverScopes.clear();
|
|
1898
|
+
this.retiredChannelIds.clear();
|
|
1352
1899
|
this.removeAllListeners();
|
|
1353
1900
|
|
|
1354
1901
|
}
|
|
@@ -1411,26 +1958,24 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
1411
1958
|
}
|
|
1412
1959
|
|
|
1413
1960
|
/**
|
|
1414
|
-
* Restart a server by ID
|
|
1961
|
+
* Restart a server by ID. Resolves after the restarted server has finished
|
|
1962
|
+
* its handshake and tool discovery (see startServer). Throws when the
|
|
1963
|
+
* restart fails, so callers can react instead of proceeding against a dead
|
|
1964
|
+
* server.
|
|
1415
1965
|
*/
|
|
1416
1966
|
public async restartServer(serverId: string): Promise<boolean> {
|
|
1417
|
-
|
|
1967
|
+
logger.info(`Restarting server ${serverId}`);
|
|
1418
1968
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1969
|
+
// Stop the server first
|
|
1970
|
+
await this.stopServer(serverId, undefined, undefined, 'restart');
|
|
1421
1971
|
|
|
1422
|
-
|
|
1423
|
-
|
|
1972
|
+
// Give the old process a moment to release stdio
|
|
1973
|
+
await new Promise(resolve => setTimeout(resolve, this.restartDelayMs));
|
|
1424
1974
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
return true;
|
|
1975
|
+
// Start the server again — resolves after handshake + tool discovery
|
|
1976
|
+
await this.startServer(serverId);
|
|
1429
1977
|
|
|
1430
|
-
|
|
1431
|
-
logger.error(`Failed to restart server ${serverId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1432
|
-
return false;
|
|
1433
|
-
}
|
|
1978
|
+
return true;
|
|
1434
1979
|
}
|
|
1435
1980
|
|
|
1436
1981
|
/**
|