@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
|
@@ -34,6 +34,7 @@ import { createExternalMcpServerEventPayload, createExternalMcpServerErrorEventP
|
|
|
34
34
|
import { AutoCorrectionService } from '../../../services/AutoCorrectionService.js';
|
|
35
35
|
import { Events } from '../../../events/EventNames.js';
|
|
36
36
|
import { v4 as uuidv4 } from 'uuid';
|
|
37
|
+
import { assertUnsafeStdioMcpEnabled } from '../security/ExternalMcpRegistrationPolicy.js';
|
|
37
38
|
// Create logger and validator instances
|
|
38
39
|
const logger = new Logger('info', 'ExternalMcpServerManager', 'server');
|
|
39
40
|
const validator = createStrictValidator('ExternalMcpServerManager');
|
|
@@ -44,6 +45,15 @@ const REQUEST_TIMEOUTS_MS = {
|
|
|
44
45
|
'tools/call': 30000,
|
|
45
46
|
ping: 5000
|
|
46
47
|
};
|
|
48
|
+
/** Delay before restarting a crashed server. */
|
|
49
|
+
const DEFAULT_RESTART_DELAY_MS = 2000;
|
|
50
|
+
/**
|
|
51
|
+
* Consecutive failed health probes after which a running server is declared
|
|
52
|
+
* dead and restarted. One failure can be a slow reply; two in a row on a
|
|
53
|
+
* connection that answers in milliseconds when healthy means the connection
|
|
54
|
+
* is gone.
|
|
55
|
+
*/
|
|
56
|
+
const HEALTH_FAILURES_BEFORE_RESTART = 2;
|
|
47
57
|
/** MCP protocol version this client negotiates in the initialize handshake. */
|
|
48
58
|
const MCP_PROTOCOL_VERSION = '2024-11-05';
|
|
49
59
|
/** Version this client reports to servers. */
|
|
@@ -55,14 +65,28 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
55
65
|
servers = new Map();
|
|
56
66
|
// Scope tracking for channel/agent-scoped servers
|
|
57
67
|
serverScopes = new Map();
|
|
68
|
+
/**
|
|
69
|
+
* Channel ids which crossed their terminal deletion boundary in this
|
|
70
|
+
* process. Channel ids are reserved in persistence, so this tombstone never
|
|
71
|
+
* needs to be removed and closes register/delete races in both directions.
|
|
72
|
+
*/
|
|
73
|
+
retiredChannelIds = new Set();
|
|
58
74
|
autoCorrectionService;
|
|
59
75
|
/** Injectable event emitter — decouples from direct EventBus.server dependency.
|
|
60
76
|
* Server-side: uses default ServerToolEventEmitter (wraps EventBus.server).
|
|
61
77
|
* Client-side: uses ClientToolEventEmitter (wraps EventBus.client + socketEmit). */
|
|
62
78
|
toolEventEmitter = null;
|
|
79
|
+
/** EventBus subscriptions owned by this manager instance. */
|
|
80
|
+
eventSubscriptions = [];
|
|
81
|
+
/** Delay before restarting a crashed server. Overridable for tests. */
|
|
82
|
+
restartDelayMs = DEFAULT_RESTART_DELAY_MS;
|
|
83
|
+
/** Per-method JSON-RPC reply timeouts. Overridable for tests and tuning. */
|
|
84
|
+
requestTimeouts = { ...REQUEST_TIMEOUTS_MS };
|
|
63
85
|
/**
|
|
64
86
|
* @param options.toolEventEmitter - Injectable event emitter for decoupling from EventBus.server
|
|
65
87
|
* @param options.skipServerEventHandlers - Skip setting up EventBus.server listeners (for client-side usage)
|
|
88
|
+
* @param options.restartDelayMs - Delay before restarting a crashed server (default 2000)
|
|
89
|
+
* @param options.requestTimeoutsMs - Per-method JSON-RPC reply timeout overrides
|
|
66
90
|
*/
|
|
67
91
|
constructor(options) {
|
|
68
92
|
super();
|
|
@@ -70,6 +94,12 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
70
94
|
if (options?.toolEventEmitter) {
|
|
71
95
|
this.toolEventEmitter = options.toolEventEmitter;
|
|
72
96
|
}
|
|
97
|
+
if (options?.restartDelayMs !== undefined) {
|
|
98
|
+
this.restartDelayMs = options.restartDelayMs;
|
|
99
|
+
}
|
|
100
|
+
if (options?.requestTimeoutsMs) {
|
|
101
|
+
this.requestTimeouts = { ...this.requestTimeouts, ...options.requestTimeoutsMs };
|
|
102
|
+
}
|
|
73
103
|
// Set up event listeners for SDK-initiated server registration
|
|
74
104
|
// Skipped when running client-side (no EventBus.server listeners needed)
|
|
75
105
|
if (!options?.skipServerEventHandlers) {
|
|
@@ -81,8 +111,12 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
81
111
|
*/
|
|
82
112
|
setupEventHandlers() {
|
|
83
113
|
// Handle external server registration requests from SDK
|
|
84
|
-
EventBus.server.on(Events.Mcp.EXTERNAL_SERVER_REGISTER, async (payload) => {
|
|
114
|
+
this.eventSubscriptions.push(EventBus.server.on(Events.Mcp.EXTERNAL_SERVER_REGISTER, async (payload) => {
|
|
85
115
|
try {
|
|
116
|
+
// EventBus registrations originate outside this manager (normally
|
|
117
|
+
// from a socket). They are caller-controlled and stdio means host
|
|
118
|
+
// process execution, so require the operator's explicit opt-in.
|
|
119
|
+
assertUnsafeStdioMcpEnabled(payload.data?.transport);
|
|
86
120
|
const serverConfig = {
|
|
87
121
|
id: payload.data.id,
|
|
88
122
|
name: payload.data.name,
|
|
@@ -130,13 +164,12 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
130
164
|
}
|
|
131
165
|
});
|
|
132
166
|
}
|
|
133
|
-
});
|
|
167
|
+
}));
|
|
134
168
|
// Handle external server unregistration requests from SDK
|
|
135
|
-
EventBus.server.on(Events.Mcp.EXTERNAL_SERVER_UNREGISTER, async (payload) => {
|
|
169
|
+
this.eventSubscriptions.push(EventBus.server.on(Events.Mcp.EXTERNAL_SERVER_UNREGISTER, async (payload) => {
|
|
136
170
|
try {
|
|
137
171
|
const serverId = payload.data.serverId;
|
|
138
|
-
await this.
|
|
139
|
-
this.servers.delete(serverId);
|
|
172
|
+
await this.unregisterServer(serverId);
|
|
140
173
|
// Emit success response
|
|
141
174
|
EventBus.server.emit(Events.Mcp.EXTERNAL_SERVER_UNREGISTERED, {
|
|
142
175
|
eventId: uuidv4(),
|
|
@@ -152,7 +185,9 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
152
185
|
});
|
|
153
186
|
}
|
|
154
187
|
catch (error) {
|
|
155
|
-
|
|
188
|
+
// Interpolate the message: passing the raw error as a second console
|
|
189
|
+
// argument makes Bun print a source-code excerpt instead of the message.
|
|
190
|
+
logger.error(`Error unregistering external server: ${error instanceof Error ? error.message : String(error)}`);
|
|
156
191
|
// Emit error response
|
|
157
192
|
EventBus.server.emit(Events.Mcp.EXTERNAL_SERVER_UNREGISTERED, {
|
|
158
193
|
eventId: uuidv4(),
|
|
@@ -167,50 +202,40 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
167
202
|
}
|
|
168
203
|
});
|
|
169
204
|
}
|
|
170
|
-
});
|
|
205
|
+
}));
|
|
171
206
|
// Handle channel-scoped server registration requests
|
|
172
|
-
EventBus.server.on(Events.Mcp.CHANNEL_SERVER_REGISTER, async (payload) => {
|
|
207
|
+
this.eventSubscriptions.push(EventBus.server.on(Events.Mcp.CHANNEL_SERVER_REGISTER, async (payload) => {
|
|
173
208
|
try {
|
|
209
|
+
// Channel membership is not permission to execute a host command.
|
|
210
|
+
// The HTTP/socket entry point must also establish administrator
|
|
211
|
+
// authority; this is the defense-in-depth feature gate.
|
|
212
|
+
assertUnsafeStdioMcpEnabled(payload.data?.transport);
|
|
174
213
|
logger.info(`[CHANNEL_SERVER_REGISTER] Received registration request: ${JSON.stringify({ agentId: payload.agentId, channelId: payload.channelId, serverId: payload.data?.id })}`);
|
|
175
214
|
const channelId = payload.data.channelId || payload.channelId;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
id: serverId,
|
|
215
|
+
await this.registerChannelServer(channelId, {
|
|
216
|
+
id: payload.data.id,
|
|
179
217
|
name: payload.data.name,
|
|
180
218
|
version: payload.data.version || '1.0.0',
|
|
181
219
|
command: payload.data.command || '',
|
|
182
220
|
args: payload.data.args || [],
|
|
183
|
-
transport: (payload.data.transport || 'stdio'),
|
|
184
|
-
url: payload.data.url,
|
|
185
221
|
autoStart: payload.data.autoStart !== false,
|
|
186
222
|
restartOnCrash: payload.data.restartOnCrash !== false,
|
|
187
223
|
maxRestartAttempts: payload.data.maxRestartAttempts || 3,
|
|
188
224
|
healthCheckInterval: payload.data.healthCheckInterval || 30000,
|
|
189
225
|
startupTimeout: payload.data.startupTimeout || 10000,
|
|
190
|
-
environmentVariables: payload.data.environmentVariables || {}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// Store registration context for deferred success emission
|
|
200
|
-
registrationContext: {
|
|
201
|
-
agentId: payload.agentId,
|
|
202
|
-
channelId,
|
|
203
|
-
originalServerId: payload.data.id,
|
|
204
|
-
serverName: payload.data.name
|
|
205
|
-
}
|
|
226
|
+
environmentVariables: payload.data.environmentVariables || {},
|
|
227
|
+
keepAliveMinutes: payload.data.keepAliveMinutes
|
|
228
|
+
},
|
|
229
|
+
// Registration context for deferred success emission after tool discovery
|
|
230
|
+
{
|
|
231
|
+
agentId: payload.agentId,
|
|
232
|
+
channelId,
|
|
233
|
+
originalServerId: payload.data.id,
|
|
234
|
+
serverName: payload.data.name
|
|
206
235
|
});
|
|
207
|
-
// Register the server (this starts the process and tool discovery)
|
|
208
|
-
// Success event will be emitted after tool discovery completes
|
|
209
|
-
await this.registerServer(serverConfig);
|
|
210
|
-
logger.info(`[CHANNEL_SERVER_REGISTER] Server ${serverId} registered, waiting for tool discovery before emitting success`);
|
|
211
236
|
}
|
|
212
237
|
catch (error) {
|
|
213
|
-
logger.error(`Error registering channel server
|
|
238
|
+
logger.error(`Error registering channel server: ${error instanceof Error ? error.message : String(error)}`);
|
|
214
239
|
// Emit error response
|
|
215
240
|
EventBus.server.emit(McpEvents.CHANNEL_SERVER_REGISTRATION_FAILED, createExternalMcpServerEventPayload(McpEvents.CHANNEL_SERVER_REGISTRATION_FAILED, payload.agentId, payload.data?.channelId || payload.channelId, {
|
|
216
241
|
serverId: payload.data?.id,
|
|
@@ -220,15 +245,12 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
220
245
|
error: error instanceof Error ? error.message : String(error)
|
|
221
246
|
}));
|
|
222
247
|
}
|
|
223
|
-
});
|
|
248
|
+
}));
|
|
224
249
|
// Handle channel server unregistration requests
|
|
225
|
-
EventBus.server.on(Events.Mcp.CHANNEL_SERVER_UNREGISTER, async (payload) => {
|
|
250
|
+
this.eventSubscriptions.push(EventBus.server.on(Events.Mcp.CHANNEL_SERVER_UNREGISTER, async (payload) => {
|
|
226
251
|
try {
|
|
227
252
|
const channelId = payload.data.channelId || payload.channelId;
|
|
228
|
-
|
|
229
|
-
await this.stopServer(serverId);
|
|
230
|
-
this.servers.delete(serverId);
|
|
231
|
-
this.serverScopes.delete(serverId);
|
|
253
|
+
await this.unregisterChannelServer(channelId, payload.data.serverId);
|
|
232
254
|
// Emit success response
|
|
233
255
|
EventBus.server.emit(McpEvents.CHANNEL_SERVER_UNREGISTERED, createExternalMcpServerEventPayload(McpEvents.CHANNEL_SERVER_UNREGISTERED, payload.agentId, channelId, {
|
|
234
256
|
serverId: payload.data.serverId,
|
|
@@ -239,7 +261,7 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
239
261
|
}));
|
|
240
262
|
}
|
|
241
263
|
catch (error) {
|
|
242
|
-
logger.error(`Error unregistering channel server
|
|
264
|
+
logger.error(`Error unregistering channel server: ${error instanceof Error ? error.message : String(error)}`);
|
|
243
265
|
// Emit error response
|
|
244
266
|
EventBus.server.emit(McpEvents.CHANNEL_SERVER_UNREGISTERED, createExternalMcpServerEventPayload(McpEvents.CHANNEL_SERVER_UNREGISTERED, payload.agentId, payload.data?.channelId || payload.channelId, {
|
|
245
267
|
serverId: payload.data?.serverId,
|
|
@@ -249,12 +271,18 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
249
271
|
error: error instanceof Error ? error.message : String(error)
|
|
250
272
|
}));
|
|
251
273
|
}
|
|
252
|
-
});
|
|
274
|
+
}));
|
|
253
275
|
}
|
|
254
276
|
/**
|
|
255
277
|
* Register a new external server configuration
|
|
256
278
|
*/
|
|
257
279
|
async registerServer(config) {
|
|
280
|
+
// This manager currently speaks line-delimited MCP over child-process
|
|
281
|
+
// stdio only. Never let an HTTP-labelled registration fall through to
|
|
282
|
+
// spawn(config.command, ...), which would bypass the stdio feature gate.
|
|
283
|
+
if (config.transport === 'http') {
|
|
284
|
+
throw new Error('HTTP transport registration is not implemented by ExternalMcpServerManager');
|
|
285
|
+
}
|
|
258
286
|
logger.info(`[REGISTER_SERVER] Registering server ${config.id}: command="${config.command}", args=${JSON.stringify(config.args)}, autoStart=${config.autoStart}`);
|
|
259
287
|
// Validate configuration
|
|
260
288
|
validator.assertIsNonEmptyString(config.id, 'Server ID must be a non-empty string');
|
|
@@ -287,7 +315,203 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
287
315
|
}
|
|
288
316
|
}
|
|
289
317
|
/**
|
|
290
|
-
*
|
|
318
|
+
* Register a channel-scoped server: track its scope, then register and
|
|
319
|
+
* (per config) start it. Resolves after the MCP handshake and tool
|
|
320
|
+
* discovery when autoStart is set, so a resolved promise means the tools
|
|
321
|
+
* are in the registry.
|
|
322
|
+
*
|
|
323
|
+
* A re-registration over an existing scope preserves the connected-agent
|
|
324
|
+
* set and clears any pending keepAlive timer — previously the timer
|
|
325
|
+
* reference was silently overwritten while the timer kept running, so a
|
|
326
|
+
* stale keepAlive could stop a freshly re-registered server later.
|
|
327
|
+
*/
|
|
328
|
+
async registerChannelServer(channelId, config, registrationContext) {
|
|
329
|
+
validator.assertIsNonEmptyString(channelId, 'channelId must be a non-empty string');
|
|
330
|
+
const serverId = `${channelId}:${config.id}`;
|
|
331
|
+
const keepAliveMinutes = config.keepAliveMinutes || 5;
|
|
332
|
+
if (this.retiredChannelIds.has(channelId)) {
|
|
333
|
+
throw new Error(`Channel ${channelId} is deleted; MCP servers cannot be registered`);
|
|
334
|
+
}
|
|
335
|
+
const existingScope = this.serverScopes.get(serverId);
|
|
336
|
+
if (existingScope?.keepAliveTimer) {
|
|
337
|
+
clearTimeout(existingScope.keepAliveTimer);
|
|
338
|
+
}
|
|
339
|
+
this.serverScopes.set(serverId, {
|
|
340
|
+
scope: 'channel',
|
|
341
|
+
scopeId: channelId,
|
|
342
|
+
// Only actual channel agents are counted, not the admin who registers.
|
|
343
|
+
// On re-registration, keep whoever is already connected.
|
|
344
|
+
connectedAgents: existingScope?.connectedAgents ?? new Set(),
|
|
345
|
+
keepAliveMinutes,
|
|
346
|
+
registrationContext
|
|
347
|
+
});
|
|
348
|
+
logger.info(`[CHANNEL_SERVER_REGISTER] Registering channel server ${serverId} ` +
|
|
349
|
+
`(keepAlive ${keepAliveMinutes}min, autoStart ${config.autoStart}, restartOnCrash ${config.restartOnCrash})`);
|
|
350
|
+
const { keepAliveMinutes: _ignored, ...serverConfig } = config;
|
|
351
|
+
try {
|
|
352
|
+
await this.registerServer({ ...serverConfig, id: serverId });
|
|
353
|
+
// Deletion may have won while process startup/handshake was in
|
|
354
|
+
// flight. Remove the just-created runtime before returning so a
|
|
355
|
+
// late registration cannot resurrect a deleted channel's server.
|
|
356
|
+
if (this.retiredChannelIds.has(channelId)) {
|
|
357
|
+
await this.removeServer(serverId, 'registration raced channel deletion');
|
|
358
|
+
throw new Error(`Channel ${channelId} was deleted during MCP server registration`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
// Registration failed before the server record existed — do not leave
|
|
363
|
+
// a scope entry behind for agents to "join" (unless one existed before).
|
|
364
|
+
if (!existingScope && !this.servers.has(serverId)) {
|
|
365
|
+
this.serverScopes.delete(serverId);
|
|
366
|
+
}
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Unregister a channel-scoped server: stop the process and remove both the
|
|
372
|
+
* server record and the scope tracking, including any keepAlive timer.
|
|
373
|
+
*
|
|
374
|
+
* Idempotent: unregistering a server that is partially or fully gone cleans
|
|
375
|
+
* up whatever remains and resolves. Production showed the half-removed
|
|
376
|
+
* state — record deleted, scope alive — and an unregister that throws
|
|
377
|
+
* "not found" against it leaves the zombie in place forever.
|
|
378
|
+
*/
|
|
379
|
+
async unregisterChannelServer(channelId, serverId) {
|
|
380
|
+
validator.assertIsNonEmptyString(channelId, 'channelId must be a non-empty string');
|
|
381
|
+
validator.assertIsNonEmptyString(serverId, 'serverId must be a non-empty string');
|
|
382
|
+
await this.removeServer(`${channelId}:${serverId}`, 'channel server unregistration');
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Permanently retire a channel's MCP runtime in this process.
|
|
386
|
+
*
|
|
387
|
+
* The tombstone is installed before process cleanup, which makes concurrent
|
|
388
|
+
* registrations fail both before and after their asynchronous startup.
|
|
389
|
+
* Every known scoped record is removed, including orphaned records whose
|
|
390
|
+
* scope metadata or server entry is only partially present.
|
|
391
|
+
*/
|
|
392
|
+
async retireChannel(channelId) {
|
|
393
|
+
validator.assertIsNonEmptyString(channelId, 'channelId must be a non-empty string');
|
|
394
|
+
this.retiredChannelIds.add(channelId);
|
|
395
|
+
const serverIds = new Set();
|
|
396
|
+
for (const [serverId, scopeData] of this.serverScopes.entries()) {
|
|
397
|
+
if (scopeData.scope === 'channel' && scopeData.scopeId === channelId) {
|
|
398
|
+
serverIds.add(serverId);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
for (const serverId of this.servers.keys()) {
|
|
402
|
+
if (serverId.startsWith(`${channelId}:`)) {
|
|
403
|
+
serverIds.add(serverId);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const results = await Promise.allSettled(Array.from(serverIds, serverId => (this.removeServer(serverId, 'channel deletion'))));
|
|
407
|
+
const failures = results.filter((result) => result.status === 'rejected');
|
|
408
|
+
if (failures.length > 0) {
|
|
409
|
+
throw new Error(`Failed to retire ${failures.length} MCP server(s) for channel ${channelId}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Unregister a server by its full id (global servers, and the SDK-facing
|
|
414
|
+
* EXTERNAL_SERVER_UNREGISTER path). Also removes scope tracking — this
|
|
415
|
+
* path used to delete only the server record, which was exactly the
|
|
416
|
+
* zombie state observed in production: agents kept "joining" a scope
|
|
417
|
+
* whose server no longer existed.
|
|
418
|
+
*/
|
|
419
|
+
async unregisterServer(serverId) {
|
|
420
|
+
validator.assertIsNonEmptyString(serverId, 'serverId must be a non-empty string');
|
|
421
|
+
await this.removeServer(serverId, 'server unregistration');
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Stop a server (if running) and remove every trace of it: server record,
|
|
425
|
+
* scope entry, keepAlive timer. Never throws for a missing record — it
|
|
426
|
+
* removes what exists and says what it did.
|
|
427
|
+
*/
|
|
428
|
+
async removeServer(serverId, reason) {
|
|
429
|
+
const serverData = this.servers.get(serverId);
|
|
430
|
+
const scopeData = this.serverScopes.get(serverId);
|
|
431
|
+
if (!serverData && !scopeData) {
|
|
432
|
+
logger.warn(`Nothing to unregister for ${serverId} (${reason}) — no record, no scope`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (scopeData?.keepAliveTimer) {
|
|
436
|
+
clearTimeout(scopeData.keepAliveTimer);
|
|
437
|
+
scopeData.keepAliveTimer = undefined;
|
|
438
|
+
}
|
|
439
|
+
if (serverData) {
|
|
440
|
+
try {
|
|
441
|
+
await this.stopServer(serverId, undefined, undefined, reason);
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
logger.error(`Error stopping ${serverId} during ${reason}: ` +
|
|
445
|
+
`${error instanceof Error ? error.message : String(error)} — removing its record anyway`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
logger.warn(`Unregistering ${serverId} (${reason}): server record was already gone, ` +
|
|
450
|
+
`removing the orphaned scope entry`);
|
|
451
|
+
}
|
|
452
|
+
this.servers.delete(serverId);
|
|
453
|
+
this.serverScopes.delete(serverId);
|
|
454
|
+
logger.info(`Unregistered server ${serverId} (${reason})`);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Remove a server that cannot be kept alive (restart budget exhausted,
|
|
458
|
+
* unrecoverable spawn failure). Loud by design: this is the path that
|
|
459
|
+
* prevents zombies, so it reports at error level.
|
|
460
|
+
*/
|
|
461
|
+
removeServerAfterFailure(serverId, reason) {
|
|
462
|
+
const serverData = this.servers.get(serverId);
|
|
463
|
+
if (serverData) {
|
|
464
|
+
if (serverData.healthCheckTimer) {
|
|
465
|
+
clearInterval(serverData.healthCheckTimer);
|
|
466
|
+
serverData.healthCheckTimer = undefined;
|
|
467
|
+
}
|
|
468
|
+
if (serverData.startupTimer) {
|
|
469
|
+
clearTimeout(serverData.startupTimer);
|
|
470
|
+
serverData.startupTimer = undefined;
|
|
471
|
+
}
|
|
472
|
+
this.rejectPendingRequests(serverId, reason);
|
|
473
|
+
this.settleReadyWaiters(serverId, new Error(reason));
|
|
474
|
+
if (serverData.process && !serverData.process.killed) {
|
|
475
|
+
serverData.process.kill('SIGKILL');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const scopeData = this.serverScopes.get(serverId);
|
|
479
|
+
if (scopeData?.keepAliveTimer) {
|
|
480
|
+
clearTimeout(scopeData.keepAliveTimer);
|
|
481
|
+
}
|
|
482
|
+
this.servers.delete(serverId);
|
|
483
|
+
this.serverScopes.delete(serverId);
|
|
484
|
+
logger.error(`Server ${serverId} unregistered: ${reason}. ` +
|
|
485
|
+
`Its tools are removed from the registry; re-register the server to restore them.`);
|
|
486
|
+
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_STOPPED, serverId);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Settle every caller waiting for a server to become ready.
|
|
490
|
+
*/
|
|
491
|
+
settleReadyWaiters(serverId, error) {
|
|
492
|
+
const serverData = this.servers.get(serverId);
|
|
493
|
+
if (!serverData?.readyWaiters?.length) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const waiters = serverData.readyWaiters;
|
|
497
|
+
serverData.readyWaiters = [];
|
|
498
|
+
for (const waiter of waiters) {
|
|
499
|
+
if (error) {
|
|
500
|
+
waiter.reject(error);
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
waiter.resolve();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Start an external server process.
|
|
509
|
+
*
|
|
510
|
+
* Resolves once the MCP handshake AND tool discovery have completed — a
|
|
511
|
+
* resolved startServer() means the server's tools are in the registry.
|
|
512
|
+
* It used to resolve right after spawn, which let callers (agent join,
|
|
513
|
+
* restart) proceed against a server that had not finished — or would
|
|
514
|
+
* never finish — its handshake.
|
|
291
515
|
*/
|
|
292
516
|
async startServer(serverId, agentId, channelId) {
|
|
293
517
|
logger.info(`[START_SERVER] Starting server ${serverId}`);
|
|
@@ -302,8 +526,19 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
302
526
|
logger.info(`[START_SERVER] Server ${serverId} already running`);
|
|
303
527
|
return;
|
|
304
528
|
}
|
|
529
|
+
if (status.status === 'starting') {
|
|
530
|
+
// Another caller is already starting this server — wait for that
|
|
531
|
+
// startup instead of spawning a second process.
|
|
532
|
+
logger.info(`[START_SERVER] Server ${serverId} already starting, awaiting readiness`);
|
|
533
|
+
await new Promise((resolve, reject) => {
|
|
534
|
+
serverData.readyWaiters = serverData.readyWaiters ?? [];
|
|
535
|
+
serverData.readyWaiters.push({ resolve, reject });
|
|
536
|
+
});
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
305
539
|
// Update status
|
|
306
540
|
status.status = 'starting';
|
|
541
|
+
serverData.expectedExit = false;
|
|
307
542
|
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_SPAWN, serverId, agentId, channelId);
|
|
308
543
|
try {
|
|
309
544
|
// Spawn the process
|
|
@@ -351,31 +586,85 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
351
586
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
352
587
|
logger.error(`❌ Failed to start server ${config.name}: ${errorMessage}`);
|
|
353
588
|
this.handleServerError(serverId, errorMessage, agentId, channelId);
|
|
589
|
+
throw error instanceof Error ? error : new Error(errorMessage);
|
|
354
590
|
}
|
|
591
|
+
// Wait for the handshake and tool discovery. Settled by
|
|
592
|
+
// initializeMcpConnection() on success, and by handleServerError()
|
|
593
|
+
// or the exit handler on failure — those already report the cause,
|
|
594
|
+
// so a rejection here propagates without being re-handled.
|
|
595
|
+
await new Promise((resolve, reject) => {
|
|
596
|
+
serverData.readyWaiters = serverData.readyWaiters ?? [];
|
|
597
|
+
serverData.readyWaiters.push({ resolve, reject });
|
|
598
|
+
});
|
|
355
599
|
}
|
|
356
600
|
/**
|
|
357
|
-
* Handle agent joining a channel
|
|
601
|
+
* Handle agent joining a channel — verify each channel server is actually
|
|
602
|
+
* alive before counting the agent as connected.
|
|
603
|
+
*
|
|
604
|
+
* This used to be a blind reference-count bump: a missing server record was
|
|
605
|
+
* silently skipped and a dead process was never probed, so agents "joined"
|
|
606
|
+
* servers that could not serve a single tool call, and the only downstream
|
|
607
|
+
* signal was a NOT FOUND warning when their allowlist resolved.
|
|
358
608
|
*/
|
|
359
609
|
async onAgentJoinChannel(agentId, channelId) {
|
|
360
610
|
logger.info(`Agent ${agentId} joining channel ${channelId} - checking for channel servers`);
|
|
361
611
|
// Find all channel-scoped servers for this channel
|
|
362
612
|
for (const [serverId, scopeData] of this.serverScopes.entries()) {
|
|
363
|
-
if (scopeData.scope
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
613
|
+
if (scopeData.scope !== 'channel' || scopeData.scopeId !== channelId) {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
const serverData = this.servers.get(serverId);
|
|
617
|
+
if (!serverData) {
|
|
618
|
+
// The production zombie state: a scope entry whose server record is
|
|
619
|
+
// gone. There is no config left to restart from, so remove the
|
|
620
|
+
// orphan loudly instead of pretending the agent connected.
|
|
621
|
+
logger.error(`Agent ${agentId} tried to join channel server ${serverId}, but its server record is gone ` +
|
|
622
|
+
`(scope entry was orphaned). Removing the orphaned scope — the server must be re-registered.`);
|
|
367
623
|
if (scopeData.keepAliveTimer) {
|
|
368
624
|
clearTimeout(scopeData.keepAliveTimer);
|
|
369
|
-
scopeData.keepAliveTimer = undefined;
|
|
370
625
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
626
|
+
this.serverScopes.delete(serverId);
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
if (serverData.status.status !== 'running') {
|
|
631
|
+
logger.info(`Starting channel server ${serverId} for agent ${agentId} (status: ${serverData.status.status})`);
|
|
375
632
|
await this.startServer(serverId);
|
|
376
633
|
}
|
|
377
|
-
|
|
634
|
+
else {
|
|
635
|
+
// Status says running — prove it. A live entry with a dead child
|
|
636
|
+
// (or a wedged MCP connection) must trigger recovery at join
|
|
637
|
+
// time, not a silent ref-count bump.
|
|
638
|
+
const alive = serverData.process && !serverData.process.killed && serverData.process.exitCode === null;
|
|
639
|
+
if (!alive) {
|
|
640
|
+
logger.warn(`Channel server ${serverId} has no live process at agent join — restarting`);
|
|
641
|
+
await this.restartServer(serverId);
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
try {
|
|
645
|
+
await this.sendRequest(serverId, 'tools/list');
|
|
646
|
+
}
|
|
647
|
+
catch (probeError) {
|
|
648
|
+
logger.warn(`Channel server ${serverId} did not answer the liveness probe at agent join ` +
|
|
649
|
+
`(${probeError instanceof Error ? probeError.message : String(probeError)}) — restarting`);
|
|
650
|
+
await this.restartServer(serverId);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
logger.error(`Channel server ${serverId} could not be made available for agent ${agentId}: ` +
|
|
657
|
+
`${error instanceof Error ? error.message : String(error)}`);
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
// Only a server that is verifiably up counts the agent as connected
|
|
661
|
+
scopeData.connectedAgents.add(agentId);
|
|
662
|
+
// Clear any pending keepAlive timer
|
|
663
|
+
if (scopeData.keepAliveTimer) {
|
|
664
|
+
clearTimeout(scopeData.keepAliveTimer);
|
|
665
|
+
scopeData.keepAliveTimer = undefined;
|
|
378
666
|
}
|
|
667
|
+
logger.info(`Agent ${agentId} connected to channel server ${serverId} (${scopeData.connectedAgents.size} agents)`);
|
|
379
668
|
}
|
|
380
669
|
}
|
|
381
670
|
/**
|
|
@@ -394,12 +683,18 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
394
683
|
const keepAliveMs = (scopeData.keepAliveMinutes || 5) * 60 * 1000;
|
|
395
684
|
logger.info(`Last agent left channel server ${serverId}, starting ${scopeData.keepAliveMinutes}min keepAlive timer`);
|
|
396
685
|
scopeData.keepAliveTimer = setTimeout(async () => {
|
|
686
|
+
scopeData.keepAliveTimer = undefined;
|
|
687
|
+
if (!this.servers.has(serverId)) {
|
|
688
|
+
logger.warn(`KeepAlive expired for ${serverId}, but its server record is already gone — removing the orphaned scope`);
|
|
689
|
+
this.serverScopes.delete(serverId);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
397
692
|
logger.info(`KeepAlive expired for server ${serverId}, stopping server`);
|
|
398
693
|
try {
|
|
399
|
-
await this.stopServer(serverId);
|
|
694
|
+
await this.stopServer(serverId, undefined, undefined, 'keepAlive expired');
|
|
400
695
|
}
|
|
401
696
|
catch (error) {
|
|
402
|
-
logger.error(`Error stopping server ${serverId} after keepAlive
|
|
697
|
+
logger.error(`Error stopping server ${serverId} after keepAlive: ${error instanceof Error ? error.message : String(error)}`);
|
|
403
698
|
}
|
|
404
699
|
}, keepAliveMs);
|
|
405
700
|
}
|
|
@@ -411,24 +706,29 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
411
706
|
*/
|
|
412
707
|
getServersByScope(scope, scopeId) {
|
|
413
708
|
const servers = [];
|
|
414
|
-
for (const [serverId,
|
|
415
|
-
|
|
709
|
+
for (const [serverId, serverData] of this.servers.entries()) {
|
|
710
|
+
// Servers registered through the original global registration path
|
|
711
|
+
// predate serverScopes and are explicitly global, matching the tool
|
|
712
|
+
// snapshot contract in getAllExternalTools().
|
|
713
|
+
const scopeData = this.serverScopes.get(serverId);
|
|
714
|
+
const actualScope = scopeData?.scope ?? 'global';
|
|
715
|
+
if (actualScope === scope) {
|
|
416
716
|
// For channel/agent scope, match scopeId
|
|
417
|
-
if ((scope === 'channel' || scope === 'agent') && scopeData
|
|
717
|
+
if ((scope === 'channel' || scope === 'agent') && scopeData?.scopeId !== scopeId) {
|
|
418
718
|
continue;
|
|
419
719
|
}
|
|
420
|
-
|
|
421
|
-
if (serverData) {
|
|
422
|
-
servers.push(serverData.status);
|
|
423
|
-
}
|
|
720
|
+
servers.push(serverData.status);
|
|
424
721
|
}
|
|
425
722
|
}
|
|
426
|
-
return servers;
|
|
723
|
+
return servers.sort((a, b) => a.id.localeCompare(b.id));
|
|
427
724
|
}
|
|
428
725
|
/**
|
|
429
|
-
* Stop an external server process
|
|
726
|
+
* Stop an external server process.
|
|
727
|
+
*
|
|
728
|
+
* An intentional stop: the exit handler will see `expectedExit` and will
|
|
729
|
+
* neither log the exit as a crash nor restart the process.
|
|
430
730
|
*/
|
|
431
|
-
async stopServer(serverId, agentId, channelId) {
|
|
731
|
+
async stopServer(serverId, agentId, channelId, reason) {
|
|
432
732
|
const serverData = this.servers.get(serverId);
|
|
433
733
|
if (!serverData) {
|
|
434
734
|
throw new Error(`Server ${serverId} not found`);
|
|
@@ -437,6 +737,9 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
437
737
|
if (status.status === 'stopped') {
|
|
438
738
|
return;
|
|
439
739
|
}
|
|
740
|
+
const droppedTools = status.tools.length;
|
|
741
|
+
logger.info(`Stopping server ${serverId} (${reason ?? 'no reason given'})` +
|
|
742
|
+
(droppedTools > 0 ? ` — removing its ${droppedTools} tool(s) from the registry` : ''));
|
|
440
743
|
// Emit stop event
|
|
441
744
|
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_STOP, serverId, agentId, channelId);
|
|
442
745
|
// Clear timers
|
|
@@ -451,14 +754,19 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
451
754
|
// Fail anything still in flight before we kill the process, so callers get
|
|
452
755
|
// a clear error rather than waiting out their own timeouts.
|
|
453
756
|
this.rejectPendingRequests(serverId, 'server is stopping');
|
|
757
|
+
this.settleReadyWaiters(serverId, new Error(`Server ${serverId} was stopped (${reason ?? 'no reason given'})`));
|
|
454
758
|
// Terminate process
|
|
455
759
|
if (serverData.process) {
|
|
456
|
-
serverData.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
760
|
+
serverData.expectedExit = true;
|
|
761
|
+
const stoppingProcess = serverData.process;
|
|
762
|
+
stoppingProcess.kill('SIGTERM');
|
|
763
|
+
// Force kill after timeout; the exit handler clears this when the
|
|
764
|
+
// process goes down on its own.
|
|
765
|
+
serverData.forceKillTimer = setTimeout(() => {
|
|
766
|
+
serverData.forceKillTimer = undefined;
|
|
767
|
+
if (!stoppingProcess.killed && stoppingProcess.exitCode === null) {
|
|
460
768
|
logger.warn(`Force killing server ${config.name}`);
|
|
461
|
-
|
|
769
|
+
stoppingProcess.kill('SIGKILL');
|
|
462
770
|
}
|
|
463
771
|
}, 5000);
|
|
464
772
|
}
|
|
@@ -494,10 +802,15 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
494
802
|
getAllExternalTools() {
|
|
495
803
|
const tools = [];
|
|
496
804
|
for (const [serverId, serverData] of this.servers) {
|
|
805
|
+
const scopeData = this.serverScopes.get(serverId);
|
|
806
|
+
const scope = scopeData?.scope ?? 'global';
|
|
807
|
+
const scopeId = scopeData?.scopeId;
|
|
497
808
|
for (const tool of serverData.status.tools) {
|
|
498
809
|
tools.push({
|
|
499
810
|
...tool,
|
|
500
|
-
serverId
|
|
811
|
+
serverId,
|
|
812
|
+
scope,
|
|
813
|
+
scopeId
|
|
501
814
|
});
|
|
502
815
|
}
|
|
503
816
|
}
|
|
@@ -513,29 +826,79 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
513
826
|
const { config, status } = serverData;
|
|
514
827
|
// Handle process exit
|
|
515
828
|
process.on('exit', (code, signal) => {
|
|
829
|
+
// This process is down — its SIGKILL escalation timer is moot.
|
|
830
|
+
if (serverData.forceKillTimer) {
|
|
831
|
+
clearTimeout(serverData.forceKillTimer);
|
|
832
|
+
serverData.forceKillTimer = undefined;
|
|
833
|
+
}
|
|
834
|
+
const currentData = this.servers.get(serverId);
|
|
835
|
+
if (!currentData || currentData.process !== process) {
|
|
836
|
+
// Exit of a process instance that has already been replaced (restart)
|
|
837
|
+
// or whose server record is gone (unregistered). Not this record's
|
|
838
|
+
// state to change.
|
|
839
|
+
logger.debug(`Ignoring exit of a superseded process for ${serverId} (code ${code ?? 'null'}, signal ${signal ?? 'none'})`);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const wasExpected = currentData.expectedExit === true;
|
|
843
|
+
currentData.expectedExit = false;
|
|
844
|
+
const droppedTools = status.tools.length;
|
|
516
845
|
status.status = 'stopped';
|
|
517
846
|
status.pid = undefined;
|
|
518
847
|
// The handshake does not survive the process. A restarted server has to
|
|
519
848
|
// perform it again before it can be marked running.
|
|
520
849
|
status.initialized = false;
|
|
521
850
|
status.initializing = false;
|
|
851
|
+
status.tools = [];
|
|
522
852
|
// Anything still waiting on this process will never get a reply.
|
|
523
853
|
this.rejectPendingRequests(serverId, `server exited (code ${code ?? 'null'}, signal ${signal ?? 'none'})`);
|
|
854
|
+
this.settleReadyWaiters(serverId, new Error(`Server ${serverId} exited during startup (code ${code ?? 'null'}, signal ${signal ?? 'none'})`));
|
|
524
855
|
// Clear timers
|
|
525
856
|
if (serverData.healthCheckTimer) {
|
|
526
857
|
clearInterval(serverData.healthCheckTimer);
|
|
527
858
|
serverData.healthCheckTimer = undefined;
|
|
528
859
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
860
|
+
if (serverData.startupTimer) {
|
|
861
|
+
clearTimeout(serverData.startupTimer);
|
|
862
|
+
serverData.startupTimer = undefined;
|
|
863
|
+
}
|
|
864
|
+
if (wasExpected) {
|
|
865
|
+
logger.info(`Server ${serverId} exited after stop (code ${code ?? 'null'}, signal ${signal ?? 'none'})`);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
// Unexpected death. This used to happen in complete silence — no log
|
|
869
|
+
// line at any level — which is how production servers vanished from
|
|
870
|
+
// the tool registry with nothing to grep for.
|
|
871
|
+
logger.error(`Server ${serverId} exited unexpectedly (code ${code ?? 'null'}, signal ${signal ?? 'none'})` +
|
|
872
|
+
(droppedTools > 0 ? ` — its ${droppedTools} tool(s) are removed from the registry` : ''));
|
|
873
|
+
// Restart on ANY unexpected exit when configured — including a clean
|
|
874
|
+
// exit code. `code !== 0` used to gate this, leaving a child that
|
|
875
|
+
// exited 0 dead forever with no restart and no log.
|
|
876
|
+
if (config.restartOnCrash) {
|
|
877
|
+
if (status.restartCount < config.maxRestartAttempts) {
|
|
878
|
+
status.restartCount++;
|
|
879
|
+
logger.warn(`Restarting server ${serverId} in ${this.restartDelayMs}ms ` +
|
|
880
|
+
`(attempt ${status.restartCount}/${config.maxRestartAttempts})`);
|
|
881
|
+
setTimeout(() => {
|
|
882
|
+
// Check if server still exists before attempting restart
|
|
883
|
+
// (it may have been unregistered during the delay)
|
|
884
|
+
if (this.servers.has(serverId)) {
|
|
885
|
+
this.startServer(serverId).catch(error => {
|
|
886
|
+
logger.error(`Restart of server ${serverId} failed: ` +
|
|
887
|
+
`${error instanceof Error ? error.message : String(error)}`);
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
}, this.restartDelayMs);
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
// Out of restart budget: remove the server entirely rather than
|
|
894
|
+
// leaving a zombie record + scope that agents can "join".
|
|
895
|
+
this.removeServerAfterFailure(serverId, `crashed and exhausted its ${config.maxRestartAttempts} restart attempt(s)`);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
logger.error(`Server ${serverId} will not be restarted (restartOnCrash is off). ` +
|
|
901
|
+
`An agent joining its channel will start it again on demand.`);
|
|
539
902
|
}
|
|
540
903
|
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_STOPPED, serverId);
|
|
541
904
|
});
|
|
@@ -642,7 +1005,7 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
642
1005
|
}
|
|
643
1006
|
const stdin = serverData.process.stdin;
|
|
644
1007
|
const requestId = uuidv4();
|
|
645
|
-
const timeoutMs =
|
|
1008
|
+
const timeoutMs = this.requestTimeouts[method];
|
|
646
1009
|
return new Promise((resolve, reject) => {
|
|
647
1010
|
const timer = setTimeout(() => {
|
|
648
1011
|
serverData.pending.delete(requestId);
|
|
@@ -735,12 +1098,27 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
735
1098
|
}
|
|
736
1099
|
try {
|
|
737
1100
|
await this.sendRequest(serverId, 'tools/list');
|
|
1101
|
+
serverData.consecutiveHealthFailures = 0;
|
|
738
1102
|
this.emitServerHealthStatus(serverId, 'healthy');
|
|
739
1103
|
}
|
|
740
1104
|
catch (error) {
|
|
741
1105
|
const message = error instanceof Error ? error.message : String(error);
|
|
742
|
-
|
|
1106
|
+
serverData.consecutiveHealthFailures = (serverData.consecutiveHealthFailures ?? 0) + 1;
|
|
1107
|
+
logger.warn(`Health check failed for ${serverId} ` +
|
|
1108
|
+
`(${serverData.consecutiveHealthFailures} consecutive): ${message}`);
|
|
743
1109
|
this.emitServerHealthStatus(serverId, 'unhealthy');
|
|
1110
|
+
// A process that is alive but no longer answering MCP is as dead as a
|
|
1111
|
+
// crashed one — the exit handler will never fire for it. Recover here.
|
|
1112
|
+
if (serverData.consecutiveHealthFailures >= HEALTH_FAILURES_BEFORE_RESTART &&
|
|
1113
|
+
serverData.config.restartOnCrash &&
|
|
1114
|
+
status.status === 'running') {
|
|
1115
|
+
serverData.consecutiveHealthFailures = 0;
|
|
1116
|
+
logger.error(`Server ${serverId} failed ${HEALTH_FAILURES_BEFORE_RESTART} consecutive health checks — restarting it`);
|
|
1117
|
+
this.restartServer(serverId).catch(restartError => {
|
|
1118
|
+
logger.error(`Health-check restart of ${serverId} failed: ` +
|
|
1119
|
+
`${restartError instanceof Error ? restartError.message : String(restartError)}`);
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
744
1122
|
}
|
|
745
1123
|
}
|
|
746
1124
|
/**
|
|
@@ -859,6 +1237,14 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
859
1237
|
this.emitServerEvent(McpEvents.EXTERNAL_SERVER_STARTED, serverId);
|
|
860
1238
|
// Now that the connection is live, find out what the server offers.
|
|
861
1239
|
await this.discoverServerTools(serverId);
|
|
1240
|
+
// A server that came up healthy earns back its full restart budget.
|
|
1241
|
+
// restartCount used to only ever grow, so a server that crashed a few
|
|
1242
|
+
// times over its lifetime — days apart, each recovered — permanently
|
|
1243
|
+
// exhausted its budget and the next crash left it down for good.
|
|
1244
|
+
serverData.status.restartCount = 0;
|
|
1245
|
+
serverData.consecutiveHealthFailures = 0;
|
|
1246
|
+
// Whoever awaited startServer() can proceed: the tools are discovered.
|
|
1247
|
+
this.settleReadyWaiters(serverId);
|
|
862
1248
|
}
|
|
863
1249
|
/**
|
|
864
1250
|
* Ask a server what tools it has.
|
|
@@ -891,6 +1277,8 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
891
1277
|
status.status = 'error';
|
|
892
1278
|
status.lastError = error;
|
|
893
1279
|
logger.error(`❌ Server ${serverId} error: ${error}`);
|
|
1280
|
+
// Anyone awaiting this server's startup gets the failure now.
|
|
1281
|
+
this.settleReadyWaiters(serverId, new Error(`Server ${serverId} failed: ${error}`));
|
|
894
1282
|
// Emit error event
|
|
895
1283
|
this.emitServerErrorEvent(serverId, error, agentId, channelId);
|
|
896
1284
|
}
|
|
@@ -986,9 +1374,24 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
986
1374
|
* Cleanup and shutdown all servers
|
|
987
1375
|
*/
|
|
988
1376
|
async shutdown() {
|
|
1377
|
+
// Stop accepting lifecycle requests before child-process teardown. Node's
|
|
1378
|
+
// removeAllListeners() below only affects this class's EventEmitter; it
|
|
1379
|
+
// does not detach RxJS EventBus subscriptions.
|
|
1380
|
+
for (const subscription of this.eventSubscriptions) {
|
|
1381
|
+
subscription.unsubscribe();
|
|
1382
|
+
}
|
|
1383
|
+
this.eventSubscriptions = [];
|
|
989
1384
|
const shutdownPromises = Array.from(this.servers.keys()).map(serverId => this.stopServer(serverId));
|
|
990
1385
|
await Promise.allSettled(shutdownPromises);
|
|
1386
|
+
// Clear pending keepAlive timers so nothing fires against cleared maps
|
|
1387
|
+
for (const scopeData of this.serverScopes.values()) {
|
|
1388
|
+
if (scopeData.keepAliveTimer) {
|
|
1389
|
+
clearTimeout(scopeData.keepAliveTimer);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
991
1392
|
this.servers.clear();
|
|
1393
|
+
this.serverScopes.clear();
|
|
1394
|
+
this.retiredChannelIds.clear();
|
|
992
1395
|
this.removeAllListeners();
|
|
993
1396
|
}
|
|
994
1397
|
/**
|
|
@@ -1044,22 +1447,20 @@ export class ExternalMcpServerManager extends EventEmitter {
|
|
|
1044
1447
|
}
|
|
1045
1448
|
}
|
|
1046
1449
|
/**
|
|
1047
|
-
* Restart a server by ID
|
|
1450
|
+
* Restart a server by ID. Resolves after the restarted server has finished
|
|
1451
|
+
* its handshake and tool discovery (see startServer). Throws when the
|
|
1452
|
+
* restart fails, so callers can react instead of proceeding against a dead
|
|
1453
|
+
* server.
|
|
1048
1454
|
*/
|
|
1049
1455
|
async restartServer(serverId) {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
catch (error) {
|
|
1060
|
-
logger.error(`Failed to restart server ${serverId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1061
|
-
return false;
|
|
1062
|
-
}
|
|
1456
|
+
logger.info(`Restarting server ${serverId}`);
|
|
1457
|
+
// Stop the server first
|
|
1458
|
+
await this.stopServer(serverId, undefined, undefined, 'restart');
|
|
1459
|
+
// Give the old process a moment to release stdio
|
|
1460
|
+
await new Promise(resolve => setTimeout(resolve, this.restartDelayMs));
|
|
1461
|
+
// Start the server again — resolves after handshake + tool discovery
|
|
1462
|
+
await this.startServer(serverId);
|
|
1463
|
+
return true;
|
|
1063
1464
|
}
|
|
1064
1465
|
/**
|
|
1065
1466
|
* Execute a tool on an external MCP server via JSON-RPC with auto-correction support
|