@tenex-chat/backend 0.9.1
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/README.md +194 -0
- package/dist/backend-wrapper.cjs +3 -0
- package/dist/src/index.js +331928 -0
- package/package.json +103 -0
- package/src/agents/AgentRegistry.ts +418 -0
- package/src/agents/AgentStorage.ts +1133 -0
- package/src/agents/ConfigResolver.ts +229 -0
- package/src/agents/agent-installer.ts +236 -0
- package/src/agents/agent-loader.ts +241 -0
- package/src/agents/constants.ts +82 -0
- package/src/agents/errors.ts +48 -0
- package/src/agents/execution/AgentExecutor.ts +561 -0
- package/src/agents/execution/ExecutionContextFactory.ts +112 -0
- package/src/agents/execution/MessageCompiler.ts +597 -0
- package/src/agents/execution/MessageSyncer.ts +100 -0
- package/src/agents/execution/PostCompletionChecker.ts +278 -0
- package/src/agents/execution/ProgressMonitor.ts +50 -0
- package/src/agents/execution/RALResolver.ts +177 -0
- package/src/agents/execution/SessionManager.ts +181 -0
- package/src/agents/execution/StreamCallbacks.ts +312 -0
- package/src/agents/execution/StreamExecutionHandler.ts +579 -0
- package/src/agents/execution/StreamSetup.ts +313 -0
- package/src/agents/execution/ToolEventHandlers.ts +239 -0
- package/src/agents/execution/ToolExecutionTracker.ts +498 -0
- package/src/agents/execution/ToolResultUtils.ts +97 -0
- package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
- package/src/agents/execution/constants.ts +16 -0
- package/src/agents/execution/index.ts +3 -0
- package/src/agents/execution/types.ts +96 -0
- package/src/agents/execution/utils.ts +26 -0
- package/src/agents/index.ts +4 -0
- package/src/agents/script-installer.ts +266 -0
- package/src/agents/supervision/SupervisorLLMService.ts +253 -0
- package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
- package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
- package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
- package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
- package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
- package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
- package/src/agents/supervision/heuristics/index.ts +5 -0
- package/src/agents/supervision/index.ts +28 -0
- package/src/agents/supervision/registerHeuristics.ts +110 -0
- package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
- package/src/agents/supervision/types.ts +171 -0
- package/src/agents/tool-names.ts +46 -0
- package/src/agents/tool-normalization.ts +184 -0
- package/src/agents/types/index.ts +2 -0
- package/src/agents/types/runtime.ts +74 -0
- package/src/agents/types/storage.ts +145 -0
- package/src/commands/agent/import/index.ts +6 -0
- package/src/commands/agent/import/openclaw-distiller.ts +57 -0
- package/src/commands/agent/import/openclaw-reader.ts +141 -0
- package/src/commands/agent/import/openclaw.ts +154 -0
- package/src/commands/agent/index.ts +6 -0
- package/src/commands/agent.ts +215 -0
- package/src/commands/daemon.ts +198 -0
- package/src/commands/doctor.ts +134 -0
- package/src/commands/setup/embed.ts +228 -0
- package/src/commands/setup/global-system-prompt.ts +223 -0
- package/src/commands/setup/image.ts +179 -0
- package/src/commands/setup/index.ts +16 -0
- package/src/commands/setup/interactive.ts +95 -0
- package/src/commands/setup/llm.ts +38 -0
- package/src/commands/setup/onboarding.ts +294 -0
- package/src/commands/setup/providers.ts +27 -0
- package/src/constants.ts +34 -0
- package/src/conversations/ConversationDiskReader.ts +148 -0
- package/src/conversations/ConversationRegistry.ts +728 -0
- package/src/conversations/ConversationStore.ts +868 -0
- package/src/conversations/MessageBuilder.ts +866 -0
- package/src/conversations/executionTime.ts +62 -0
- package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
- package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
- package/src/conversations/formatters/index.ts +9 -0
- package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
- package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
- package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
- package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
- package/src/conversations/index.ts +2 -0
- package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
- package/src/conversations/search/ConversationIndexManager.ts +393 -0
- package/src/conversations/search/QueryParser.ts +114 -0
- package/src/conversations/search/SearchEngine.ts +175 -0
- package/src/conversations/search/SnippetExtractor.ts +345 -0
- package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
- package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
- package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
- package/src/conversations/search/embeddings/index.ts +18 -0
- package/src/conversations/search/index.ts +49 -0
- package/src/conversations/search/types.ts +124 -0
- package/src/conversations/services/CategoryManager.ts +160 -0
- package/src/conversations/services/ConversationResolver.ts +296 -0
- package/src/conversations/services/ConversationSummarizer.ts +234 -0
- package/src/conversations/services/MetadataDebounceManager.ts +188 -0
- package/src/conversations/services/index.ts +2 -0
- package/src/conversations/types.ts +148 -0
- package/src/conversations/utils/content-utils.ts +69 -0
- package/src/conversations/utils/image-placeholder.ts +281 -0
- package/src/conversations/utils/image-url-utils.ts +171 -0
- package/src/conversations/utils/multimodal-content.ts +90 -0
- package/src/conversations/utils/tool-result-truncator.ts +159 -0
- package/src/daemon/Daemon.ts +1883 -0
- package/src/daemon/ProjectRuntime.ts +657 -0
- package/src/daemon/RestartState.ts +152 -0
- package/src/daemon/RuntimeLifecycle.ts +268 -0
- package/src/daemon/SubscriptionManager.ts +305 -0
- package/src/daemon/UnixSocketTransport.ts +318 -0
- package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
- package/src/daemon/index.ts +9 -0
- package/src/daemon/routing/DaemonRouter.ts +491 -0
- package/src/daemon/types.ts +150 -0
- package/src/daemon/utils/routing-log.ts +76 -0
- package/src/daemon/utils/telemetry.ts +173 -0
- package/src/event-handler/agentDeletion.ts +383 -0
- package/src/event-handler/index.ts +749 -0
- package/src/event-handler/newConversation.ts +165 -0
- package/src/event-handler/project.ts +166 -0
- package/src/event-handler/reply.ts +18 -0
- package/src/events/NDKAgentDefinition.ts +292 -0
- package/src/events/NDKAgentLesson.ts +106 -0
- package/src/events/NDKEventMetadata.ts +34 -0
- package/src/events/NDKMCPTool.ts +60 -0
- package/src/events/NDKProjectStatus.ts +384 -0
- package/src/events/index.ts +4 -0
- package/src/index.ts +126 -0
- package/src/lib/agent-home.ts +334 -0
- package/src/lib/error-formatter.ts +200 -0
- package/src/lib/fs/filesystem.ts +128 -0
- package/src/lib/fs/index.ts +1 -0
- package/src/lib/json-parser.ts +30 -0
- package/src/lib/string.ts +15 -0
- package/src/lib/time.ts +74 -0
- package/src/llm/ChunkHandler.ts +277 -0
- package/src/llm/FinishHandler.ts +250 -0
- package/src/llm/LLMConfigEditor.ts +154 -0
- package/src/llm/LLMServiceFactory.ts +230 -0
- package/src/llm/MessageProcessor.ts +90 -0
- package/src/llm/RecordingState.ts +37 -0
- package/src/llm/StreamPublisher.ts +40 -0
- package/src/llm/TracingUtils.ts +77 -0
- package/src/llm/chunk-validators.ts +57 -0
- package/src/llm/constants.ts +6 -0
- package/src/llm/index.ts +12 -0
- package/src/llm/meta/MetaModelResolver.ts +352 -0
- package/src/llm/meta/index.ts +11 -0
- package/src/llm/middleware/flight-recorder.ts +188 -0
- package/src/llm/providers/MockProvider.ts +332 -0
- package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
- package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
- package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
- package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
- package/src/llm/providers/agent/index.ts +10 -0
- package/src/llm/providers/base/AgentProvider.ts +107 -0
- package/src/llm/providers/base/BaseProvider.ts +114 -0
- package/src/llm/providers/base/StandardProvider.ts +38 -0
- package/src/llm/providers/base/index.ts +9 -0
- package/src/llm/providers/index.ts +106 -0
- package/src/llm/providers/key-manager.ts +238 -0
- package/src/llm/providers/ollama-models.ts +105 -0
- package/src/llm/providers/openrouter-models.ts +102 -0
- package/src/llm/providers/provider-ids.ts +18 -0
- package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
- package/src/llm/providers/registry/index.ts +7 -0
- package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
- package/src/llm/providers/standard/OllamaProvider.ts +59 -0
- package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
- package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
- package/src/llm/providers/standard/index.ts +10 -0
- package/src/llm/providers/types.ts +194 -0
- package/src/llm/providers/usage-metadata.ts +78 -0
- package/src/llm/service.ts +713 -0
- package/src/llm/types.ts +167 -0
- package/src/llm/utils/ConfigurationManager.ts +650 -0
- package/src/llm/utils/ConfigurationTester.ts +229 -0
- package/src/llm/utils/ModelSelector.ts +212 -0
- package/src/llm/utils/ProviderConfigUI.ts +177 -0
- package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
- package/src/llm/utils/codex-models.ts +53 -0
- package/src/llm/utils/context-window-cache.ts +30 -0
- package/src/llm/utils/models-dev-cache.ts +267 -0
- package/src/llm/utils/provider-setup.ts +50 -0
- package/src/llm/utils/tool-errors.ts +78 -0
- package/src/llm/utils/usage.ts +74 -0
- package/src/logging/EventRoutingLogger.ts +205 -0
- package/src/nostr/AgentEventDecoder.ts +357 -0
- package/src/nostr/AgentEventEncoder.ts +677 -0
- package/src/nostr/AgentProfilePublisher.ts +657 -0
- package/src/nostr/AgentPublisher.ts +437 -0
- package/src/nostr/BlossomService.ts +226 -0
- package/src/nostr/InterventionPublisher.ts +132 -0
- package/src/nostr/TagExtractor.ts +228 -0
- package/src/nostr/collectEvents.ts +83 -0
- package/src/nostr/constants.ts +38 -0
- package/src/nostr/encryption.ts +26 -0
- package/src/nostr/index.ts +31 -0
- package/src/nostr/keys.ts +17 -0
- package/src/nostr/kinds.ts +37 -0
- package/src/nostr/ndkClient.ts +72 -0
- package/src/nostr/relays.ts +43 -0
- package/src/nostr/trace-context.ts +39 -0
- package/src/nostr/types.ts +227 -0
- package/src/nostr/utils.ts +84 -0
- package/src/prompts/core/FragmentRegistry.ts +30 -0
- package/src/prompts/core/PromptBuilder.ts +98 -0
- package/src/prompts/core/index.ts +3 -0
- package/src/prompts/core/types.ts +13 -0
- package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
- package/src/prompts/fragments/01-agent-identity.ts +69 -0
- package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
- package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
- package/src/prompts/fragments/04-relay-configuration.ts +38 -0
- package/src/prompts/fragments/05-delegation-chain.ts +45 -0
- package/src/prompts/fragments/06-agent-todos.ts +74 -0
- package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
- package/src/prompts/fragments/07-meta-project-context.ts +234 -0
- package/src/prompts/fragments/08-active-conversations.ts +382 -0
- package/src/prompts/fragments/09-recent-conversations.ts +153 -0
- package/src/prompts/fragments/10-referenced-article.ts +21 -0
- package/src/prompts/fragments/11-nudges.ts +134 -0
- package/src/prompts/fragments/12-skills.ts +127 -0
- package/src/prompts/fragments/13-available-nudges.ts +122 -0
- package/src/prompts/fragments/15-available-agents.ts +53 -0
- package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
- package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
- package/src/prompts/fragments/20-voice-mode.ts +62 -0
- package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
- package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
- package/src/prompts/fragments/25-rag-instructions.ts +333 -0
- package/src/prompts/fragments/26-mcp-resources.ts +237 -0
- package/src/prompts/fragments/27-memorized-reports.ts +77 -0
- package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
- package/src/prompts/fragments/29-rag-collections.ts +50 -0
- package/src/prompts/fragments/30-worktree-context.ts +98 -0
- package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
- package/src/prompts/fragments/32-process-metrics.ts +72 -0
- package/src/prompts/fragments/debug-mode.ts +48 -0
- package/src/prompts/fragments/delegation-completion.ts +44 -0
- package/src/prompts/fragments/index.ts +91 -0
- package/src/prompts/index.ts +21 -0
- package/src/prompts/utils/systemPromptBuilder.ts +777 -0
- package/src/scripts/migrate-prefix-index.ts +157 -0
- package/src/services/AgentDefinitionMonitor.ts +701 -0
- package/src/services/ConfigService.ts +723 -0
- package/src/services/CooldownRegistry.ts +199 -0
- package/src/services/LLMOperationsRegistry.ts +424 -0
- package/src/services/OwnerAgentListService.ts +354 -0
- package/src/services/PubkeyService.ts +308 -0
- package/src/services/agents/AgentMetadataStore.ts +72 -0
- package/src/services/agents/AgentResolution.ts +59 -0
- package/src/services/agents/EscalationService.ts +281 -0
- package/src/services/agents/NDKAgentDiscovery.ts +95 -0
- package/src/services/agents/index.ts +7 -0
- package/src/services/agents-md/AgentsMdService.ts +184 -0
- package/src/services/agents-md/SystemReminderInjector.ts +238 -0
- package/src/services/agents-md/index.ts +11 -0
- package/src/services/apns/APNsClient.ts +203 -0
- package/src/services/apns/APNsService.ts +358 -0
- package/src/services/apns/index.ts +11 -0
- package/src/services/apns/types.ts +80 -0
- package/src/services/compression/CompressionService.ts +445 -0
- package/src/services/compression/compression-schema.ts +28 -0
- package/src/services/compression/compression-types.ts +74 -0
- package/src/services/compression/compression-utils.ts +587 -0
- package/src/services/config/types.ts +394 -0
- package/src/services/dispatch/AgentDispatchService.ts +937 -0
- package/src/services/dispatch/AgentRouter.ts +181 -0
- package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
- package/src/services/embedding/EmbeddingProvider.ts +188 -0
- package/src/services/embedding/index.ts +5 -0
- package/src/services/event-context/EventContextService.ts +108 -0
- package/src/services/event-context/index.ts +2 -0
- package/src/services/heuristics/ContextBuilder.ts +106 -0
- package/src/services/heuristics/HeuristicEngine.ts +200 -0
- package/src/services/heuristics/formatters.ts +58 -0
- package/src/services/heuristics/index.ts +12 -0
- package/src/services/heuristics/rules/index.ts +25 -0
- package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
- package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
- package/src/services/heuristics/types.ts +144 -0
- package/src/services/image/ImageGenerationService.ts +389 -0
- package/src/services/image/index.ts +12 -0
- package/src/services/intervention/InterventionService.ts +1352 -0
- package/src/services/intervention/index.ts +7 -0
- package/src/services/mcp/MCPManager.ts +683 -0
- package/src/services/mcp/McpNotificationDelivery.ts +139 -0
- package/src/services/mcp/McpSubscriptionService.ts +653 -0
- package/src/services/mcp/mcpInstaller.ts +130 -0
- package/src/services/nip46/Nip46SigningLog.ts +81 -0
- package/src/services/nip46/Nip46SigningService.ts +467 -0
- package/src/services/nip46/index.ts +4 -0
- package/src/services/nudge/NudgeService.ts +224 -0
- package/src/services/nudge/NudgeWhitelistService.ts +382 -0
- package/src/services/nudge/index.ts +5 -0
- package/src/services/nudge/types.ts +83 -0
- package/src/services/projects/ProjectContext.ts +672 -0
- package/src/services/projects/ProjectContextStore.ts +102 -0
- package/src/services/projects/index.ts +6 -0
- package/src/services/prompt-compiler/index.ts +15 -0
- package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
- package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
- package/src/services/pubkey-gate/index.ts +1 -0
- package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
- package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
- package/src/services/rag/RAGDatabaseService.ts +173 -0
- package/src/services/rag/RAGOperations.ts +682 -0
- package/src/services/rag/RAGService.ts +240 -0
- package/src/services/rag/RagSubscriptionService.ts +618 -0
- package/src/services/rag/rag-utils.ts +174 -0
- package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
- package/src/services/ral/RALRegistry.ts +2782 -0
- package/src/services/ral/index.ts +4 -0
- package/src/services/ral/types.ts +292 -0
- package/src/services/reports/LocalReportStore.ts +380 -0
- package/src/services/reports/ReportEmbeddingService.ts +430 -0
- package/src/services/reports/ReportService.ts +440 -0
- package/src/services/reports/articleUtils.ts +52 -0
- package/src/services/reports/index.ts +7 -0
- package/src/services/scheduling/SchedulerService.ts +1057 -0
- package/src/services/scheduling/errors.ts +14 -0
- package/src/services/scheduling/index.ts +7 -0
- package/src/services/scheduling/utils.ts +77 -0
- package/src/services/search/SearchProviderRegistry.ts +78 -0
- package/src/services/search/UnifiedSearchService.ts +218 -0
- package/src/services/search/index.ts +47 -0
- package/src/services/search/projectFilter.ts +22 -0
- package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
- package/src/services/search/providers/LessonSearchProvider.ts +75 -0
- package/src/services/search/providers/ReportSearchProvider.ts +49 -0
- package/src/services/search/types.ts +144 -0
- package/src/services/skill/SkillService.ts +482 -0
- package/src/services/skill/index.ts +2 -0
- package/src/services/skill/types.ts +70 -0
- package/src/services/status/OperationsStatusService.ts +276 -0
- package/src/services/status/ProjectStatusService.ts +522 -0
- package/src/services/status/index.ts +11 -0
- package/src/services/storage/PrefixKVStore.ts +242 -0
- package/src/services/storage/index.ts +1 -0
- package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
- package/src/services/system-reminder/index.ts +7 -0
- package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
- package/src/services/trust-pubkeys/index.ts +2 -0
- package/src/telemetry/ConversationSpanManager.ts +111 -0
- package/src/telemetry/EventLoopMonitor.ts +206 -0
- package/src/telemetry/LLMSpanRegistry.ts +20 -0
- package/src/telemetry/NostrSpanProcessor.ts +89 -0
- package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
- package/src/telemetry/diagnostics.ts +27 -0
- package/src/telemetry/setup.ts +120 -0
- package/src/tools/implementations/agents_discover.ts +121 -0
- package/src/tools/implementations/agents_hire.ts +127 -0
- package/src/tools/implementations/agents_list.ts +96 -0
- package/src/tools/implementations/agents_publish.ts +611 -0
- package/src/tools/implementations/agents_read.ts +173 -0
- package/src/tools/implementations/agents_write.ts +200 -0
- package/src/tools/implementations/ask.ts +411 -0
- package/src/tools/implementations/change_model.ts +141 -0
- package/src/tools/implementations/conversation_get.ts +661 -0
- package/src/tools/implementations/conversation_list.ts +377 -0
- package/src/tools/implementations/conversation_search.ts +370 -0
- package/src/tools/implementations/delegate.ts +327 -0
- package/src/tools/implementations/delegate_crossproject.ts +209 -0
- package/src/tools/implementations/delegate_followup.ts +300 -0
- package/src/tools/implementations/fs_edit.ts +162 -0
- package/src/tools/implementations/fs_glob.ts +182 -0
- package/src/tools/implementations/fs_grep.ts +513 -0
- package/src/tools/implementations/fs_read.ts +332 -0
- package/src/tools/implementations/fs_write.ts +113 -0
- package/src/tools/implementations/generate_image.ts +259 -0
- package/src/tools/implementations/home_fs.ts +515 -0
- package/src/tools/implementations/kill.ts +651 -0
- package/src/tools/implementations/learn.ts +166 -0
- package/src/tools/implementations/lesson-formatter.ts +38 -0
- package/src/tools/implementations/lesson_delete.ts +164 -0
- package/src/tools/implementations/lesson_get.ts +105 -0
- package/src/tools/implementations/lessons_list.ts +153 -0
- package/src/tools/implementations/mcp_resource_read.ts +161 -0
- package/src/tools/implementations/mcp_subscribe.ts +158 -0
- package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
- package/src/tools/implementations/nostr_fetch.ts +149 -0
- package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
- package/src/tools/implementations/project_list.ts +146 -0
- package/src/tools/implementations/rag_add_documents.ts +573 -0
- package/src/tools/implementations/rag_create_collection.ts +65 -0
- package/src/tools/implementations/rag_delete_collection.ts +68 -0
- package/src/tools/implementations/rag_list_collections.ts +77 -0
- package/src/tools/implementations/rag_query.ts +107 -0
- package/src/tools/implementations/rag_subscription_create.ts +105 -0
- package/src/tools/implementations/rag_subscription_delete.ts +80 -0
- package/src/tools/implementations/rag_subscription_get.ts +123 -0
- package/src/tools/implementations/rag_subscription_list.ts +128 -0
- package/src/tools/implementations/report_delete.ts +79 -0
- package/src/tools/implementations/report_read.ts +160 -0
- package/src/tools/implementations/report_write.ts +278 -0
- package/src/tools/implementations/reports_list.ts +77 -0
- package/src/tools/implementations/schedule_task.ts +104 -0
- package/src/tools/implementations/schedule_task_cancel.ts +62 -0
- package/src/tools/implementations/schedule_task_once.ts +128 -0
- package/src/tools/implementations/schedule_tasks_list.ts +79 -0
- package/src/tools/implementations/search.ts +160 -0
- package/src/tools/implementations/shell.ts +553 -0
- package/src/tools/implementations/todo.ts +260 -0
- package/src/tools/implementations/upload_blob.ts +381 -0
- package/src/tools/implementations/web_fetch.ts +153 -0
- package/src/tools/implementations/web_search.ts +250 -0
- package/src/tools/registry.ts +670 -0
- package/src/tools/types.ts +177 -0
- package/src/tools/utils.ts +256 -0
- package/src/types/event-ids.ts +320 -0
- package/src/types/index.ts +46 -0
- package/src/utils/agentFetcher.ts +107 -0
- package/src/utils/cli-error.ts +29 -0
- package/src/utils/conversation-id.ts +27 -0
- package/src/utils/conversation-utils.ts +1 -0
- package/src/utils/delegation-chain.ts +357 -0
- package/src/utils/error-handler.ts +42 -0
- package/src/utils/git/gitignore.ts +69 -0
- package/src/utils/git/index.ts +2 -0
- package/src/utils/git/initializeGitRepo.ts +204 -0
- package/src/utils/git/worktree.ts +260 -0
- package/src/utils/lessonFormatter.ts +70 -0
- package/src/utils/lessonTrust.ts +24 -0
- package/src/utils/lockfile.ts +123 -0
- package/src/utils/logger.ts +149 -0
- package/src/utils/nostr-entity-parser.ts +365 -0
- package/src/utils/process.ts +49 -0
- package/src/wrapper.ts +262 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPManager - Official MCP SDK Integration
|
|
3
|
+
*
|
|
4
|
+
* Uses the official @modelcontextprotocol/sdk for full MCP spec compliance
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import type { MCPServerConfig, TenexMCP } from "@/services/config/types";
|
|
9
|
+
import { formatAnyError } from "@/lib/error-formatter";
|
|
10
|
+
import { logger } from "@/utils/logger";
|
|
11
|
+
import { trace } from "@opentelemetry/api";
|
|
12
|
+
import { config as configService } from "@/services/ConfigService";
|
|
13
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
14
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
15
|
+
import type {
|
|
16
|
+
Tool as MCPTool,
|
|
17
|
+
ReadResourceResult,
|
|
18
|
+
Resource,
|
|
19
|
+
ResourceTemplate,
|
|
20
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
21
|
+
import { ResourceUpdatedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
import { jsonSchema, tool } from "ai";
|
|
23
|
+
import type { Tool as CoreTool } from "ai";
|
|
24
|
+
|
|
25
|
+
type MCPToolSet = Record<string, CoreTool<Record<string, unknown>, string>>;
|
|
26
|
+
|
|
27
|
+
interface MCPClientEntry {
|
|
28
|
+
client: Client;
|
|
29
|
+
transport: StdioClientTransport;
|
|
30
|
+
serverName: string;
|
|
31
|
+
config: MCPServerConfig;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type ResourceNotificationHandler = (notification: { uri: string }) => void | Promise<void>;
|
|
35
|
+
|
|
36
|
+
export class MCPManager {
|
|
37
|
+
private clients: Map<string, MCPClientEntry> = new Map();
|
|
38
|
+
private isInitialized = false;
|
|
39
|
+
private metadataPath?: string;
|
|
40
|
+
private workingDirectory?: string;
|
|
41
|
+
private cachedTools: MCPToolSet = {};
|
|
42
|
+
/** Per-server list of notification handlers (dispatcher pattern to avoid clobbering) */
|
|
43
|
+
private resourceNotificationHandlers: Map<string, ResourceNotificationHandler[]> = new Map();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Convert an MCP tool (JSON Schema) to an AI SDK tool.
|
|
47
|
+
* Uses `jsonSchema()` to pass the MCP JSON Schema directly to the AI SDK.
|
|
48
|
+
*/
|
|
49
|
+
private convertMCPToolToAISdkTool(
|
|
50
|
+
mcpTool: MCPTool,
|
|
51
|
+
serverName: string,
|
|
52
|
+
toolName: string
|
|
53
|
+
): CoreTool<Record<string, unknown>, string> {
|
|
54
|
+
return tool<Record<string, unknown>, string>({
|
|
55
|
+
description: mcpTool.description || `Tool ${toolName} from ${serverName}`,
|
|
56
|
+
inputSchema: jsonSchema<Record<string, unknown>>(mcpTool.inputSchema as any),
|
|
57
|
+
execute: async (args) => {
|
|
58
|
+
const entry = this.clients.get(serverName);
|
|
59
|
+
if (!entry) {
|
|
60
|
+
throw new Error(`MCP server '${serverName}' not found`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const callResult = await entry.client.callTool({
|
|
65
|
+
name: toolName,
|
|
66
|
+
arguments: args
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Extract text content from MCP CallToolResult
|
|
70
|
+
if (callResult.content && Array.isArray(callResult.content)) {
|
|
71
|
+
const textContent = callResult.content
|
|
72
|
+
.filter((c): c is { type: 'text'; text: string } =>
|
|
73
|
+
typeof c === 'object' && 'text' in c
|
|
74
|
+
)
|
|
75
|
+
.map(c => c.text)
|
|
76
|
+
.join('\n');
|
|
77
|
+
return textContent || JSON.stringify(callResult);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return JSON.stringify(callResult);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.error(
|
|
83
|
+
`Failed to call MCP tool '${toolName}':`,
|
|
84
|
+
formatAnyError(error)
|
|
85
|
+
);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Initialize MCP manager with project paths
|
|
94
|
+
* @param metadataPath The project metadata path (~/.tenex/projects/{dTag}) for config loading
|
|
95
|
+
* @param workingDirectory The project working directory (~/tenex/{dTag}) for MCP server CWD
|
|
96
|
+
*/
|
|
97
|
+
async initialize(metadataPath?: string, workingDirectory?: string): Promise<void> {
|
|
98
|
+
if (this.isInitialized) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
this.metadataPath = metadataPath;
|
|
104
|
+
this.workingDirectory = workingDirectory;
|
|
105
|
+
|
|
106
|
+
// Load and merge global + project MCP configs
|
|
107
|
+
const globalPath = configService.getGlobalPath();
|
|
108
|
+
const globalMCP = await configService.loadTenexMCP(globalPath);
|
|
109
|
+
const projectMCP = metadataPath
|
|
110
|
+
? await configService.loadTenexMCP(metadataPath)
|
|
111
|
+
: { servers: {}, enabled: true };
|
|
112
|
+
|
|
113
|
+
const mergedMCP: TenexMCP = {
|
|
114
|
+
servers: { ...globalMCP.servers, ...projectMCP.servers },
|
|
115
|
+
enabled: projectMCP.enabled !== undefined ? projectMCP.enabled : globalMCP.enabled,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (!mergedMCP.enabled) {
|
|
119
|
+
this.isInitialized = true;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (mergedMCP.servers && Object.keys(mergedMCP.servers).length > 0) {
|
|
124
|
+
await this.startServers(mergedMCP);
|
|
125
|
+
await this.refreshToolCache();
|
|
126
|
+
}
|
|
127
|
+
this.isInitialized = true;
|
|
128
|
+
|
|
129
|
+
trace.getActiveSpan()?.addEvent("mcp.initialized", {
|
|
130
|
+
"servers.count": this.clients.size,
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.error("Failed to initialize MCP manager:", error);
|
|
134
|
+
// Don't throw - allow the system to continue without MCP
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async startServers(mcpConfig: TenexMCP): Promise<void> {
|
|
139
|
+
const startPromises = Object.entries(mcpConfig.servers)
|
|
140
|
+
.filter(([name]) => {
|
|
141
|
+
if (!name || name.trim() === "") {
|
|
142
|
+
logger.warn("Skipping MCP server with empty or invalid name");
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
})
|
|
147
|
+
.map(([name, config]) =>
|
|
148
|
+
this.startServer(name, config).catch((error) => {
|
|
149
|
+
logger.error(`Failed to start MCP server '${name}':`, formatAnyError(error));
|
|
150
|
+
// Continue with other servers
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await Promise.all(startPromises);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async startServer(name: string, config: MCPServerConfig): Promise<void> {
|
|
158
|
+
if (this.clients.has(name)) {
|
|
159
|
+
logger.warn(`MCP server '${name}' is already running`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// SECURITY CHECK: Enforce allowedPaths
|
|
164
|
+
if (config.allowedPaths && config.allowedPaths.length > 0 && this.workingDirectory) {
|
|
165
|
+
const resolvedWorkingDir = path.resolve(this.workingDirectory);
|
|
166
|
+
// Filter out undefined/null values from allowedPaths
|
|
167
|
+
const validAllowedPaths = config.allowedPaths.filter(
|
|
168
|
+
(p): p is string => typeof p === "string" && p.length > 0
|
|
169
|
+
);
|
|
170
|
+
const isAllowed = validAllowedPaths.some((allowedPath) => {
|
|
171
|
+
const resolvedAllowedPath = path.resolve(allowedPath);
|
|
172
|
+
return (
|
|
173
|
+
resolvedWorkingDir.startsWith(resolvedAllowedPath) ||
|
|
174
|
+
resolvedAllowedPath.startsWith(resolvedWorkingDir)
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (!isAllowed) {
|
|
179
|
+
logger.warn(
|
|
180
|
+
`Skipping MCP server '${name}' due to path restrictions. Working directory '${this.workingDirectory}' is not in allowedPaths: ${validAllowedPaths.join(", ")}`
|
|
181
|
+
);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const mergedEnv: Record<string, string> = {};
|
|
187
|
+
// Only include defined environment variables
|
|
188
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
189
|
+
if (value !== undefined) {
|
|
190
|
+
mergedEnv[key] = value;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Override with config env
|
|
194
|
+
if (config.env) {
|
|
195
|
+
Object.assign(mergedEnv, config.env);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
trace.getActiveSpan()?.addEvent("mcp.server_starting", {
|
|
199
|
+
"server.name": name,
|
|
200
|
+
"server.command": config.command,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Create transport
|
|
204
|
+
const transport = new StdioClientTransport({
|
|
205
|
+
command: config.command,
|
|
206
|
+
args: config.args,
|
|
207
|
+
env: mergedEnv,
|
|
208
|
+
cwd: this.workingDirectory,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
// Create client
|
|
213
|
+
const client = new Client(
|
|
214
|
+
{
|
|
215
|
+
name: `tenex-${name}`,
|
|
216
|
+
version: '1.0.0'
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
capabilities: {}
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Connect to server
|
|
224
|
+
await client.connect(transport);
|
|
225
|
+
|
|
226
|
+
// Health check - try listing tools with timeout
|
|
227
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
228
|
+
setTimeout(() => reject(new Error("Health check timeout")), 5000)
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
await Promise.race([client.listTools(), timeoutPromise]);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
logger.error(`MCP server '${name}' failed health check:`, error);
|
|
235
|
+
await client.close();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Store client entry
|
|
240
|
+
this.clients.set(name, {
|
|
241
|
+
client,
|
|
242
|
+
transport,
|
|
243
|
+
serverName: name,
|
|
244
|
+
config,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
logger.info(`MCP server '${name}' started successfully`);
|
|
248
|
+
|
|
249
|
+
// Emit telemetry
|
|
250
|
+
trace.getActiveSpan()?.addEvent("mcp.server_started", {
|
|
251
|
+
"server.name": name,
|
|
252
|
+
});
|
|
253
|
+
} catch (error) {
|
|
254
|
+
logger.error(`Failed to start MCP server '${name}':`, formatAnyError(error));
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private async refreshToolCache(): Promise<void> {
|
|
260
|
+
const tools: MCPToolSet = {};
|
|
261
|
+
|
|
262
|
+
for (const [serverName, entry] of this.clients) {
|
|
263
|
+
try {
|
|
264
|
+
// List tools from MCP server (official SDK method)
|
|
265
|
+
const { tools: mcpTools } = await entry.client.listTools();
|
|
266
|
+
|
|
267
|
+
// Convert each MCP tool to AI SDK tool
|
|
268
|
+
for (const mcpTool of mcpTools) {
|
|
269
|
+
const namespacedName = `mcp__${serverName}__${mcpTool.name}`;
|
|
270
|
+
tools[namespacedName] = this.convertMCPToolToAISdkTool(
|
|
271
|
+
mcpTool,
|
|
272
|
+
serverName,
|
|
273
|
+
mcpTool.name
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
trace.getActiveSpan()?.addEvent("mcp.tools_discovered", {
|
|
278
|
+
"server.name": serverName,
|
|
279
|
+
"tools.count": mcpTools.length,
|
|
280
|
+
});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
logger.error(
|
|
283
|
+
`Failed to get tools from MCP server '${serverName}':`,
|
|
284
|
+
formatAnyError(error)
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.cachedTools = tools;
|
|
290
|
+
trace.getActiveSpan()?.addEvent("mcp.tools_cached", {
|
|
291
|
+
"tools.total": Object.keys(tools).length,
|
|
292
|
+
"servers.count": this.clients.size,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Refresh the tool cache
|
|
298
|
+
*/
|
|
299
|
+
async refreshTools(): Promise<void> {
|
|
300
|
+
await this.refreshToolCache();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get all cached MCP tools as an object keyed by tool name
|
|
305
|
+
*/
|
|
306
|
+
getCachedTools(): MCPToolSet {
|
|
307
|
+
return this.cachedTools;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async shutdown(): Promise<void> {
|
|
311
|
+
const shutdownPromises: Promise<void>[] = [];
|
|
312
|
+
|
|
313
|
+
for (const [name, entry] of this.clients) {
|
|
314
|
+
shutdownPromises.push(this.shutdownServer(name, entry));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
await Promise.all(shutdownPromises);
|
|
318
|
+
this.clients.clear();
|
|
319
|
+
this.cachedTools = {};
|
|
320
|
+
// Clear notification handlers so reload() re-registers them on new clients
|
|
321
|
+
this.resourceNotificationHandlers.clear();
|
|
322
|
+
this.isInitialized = false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private async shutdownServer(name: string, entry: MCPClientEntry): Promise<void> {
|
|
326
|
+
try {
|
|
327
|
+
// Official SDK: close() closes both client and transport
|
|
328
|
+
await entry.client.close();
|
|
329
|
+
|
|
330
|
+
trace.getActiveSpan()?.addEvent("mcp.server_shutdown", {
|
|
331
|
+
"server.name": name,
|
|
332
|
+
});
|
|
333
|
+
} catch (error) {
|
|
334
|
+
logger.error(`Error shutting down MCP server '${name}':`, formatAnyError(error));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Check if a server is running
|
|
340
|
+
*/
|
|
341
|
+
isServerRunning(name: string): boolean {
|
|
342
|
+
return this.clients.has(name);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get list of running servers
|
|
347
|
+
*/
|
|
348
|
+
getRunningServers(): string[] {
|
|
349
|
+
return Array.from(this.clients.keys());
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get configuration for all running MCP servers.
|
|
354
|
+
* Used to pass server configs to LLM providers (like Claude Code)
|
|
355
|
+
* that need to spawn their own instances of these servers.
|
|
356
|
+
*
|
|
357
|
+
* @returns Record of server name to server configuration
|
|
358
|
+
*/
|
|
359
|
+
getServerConfigs(): Record<string, MCPServerConfig> {
|
|
360
|
+
const configs: Record<string, MCPServerConfig> = {};
|
|
361
|
+
for (const [name, entry] of this.clients) {
|
|
362
|
+
configs[name] = entry.config;
|
|
363
|
+
}
|
|
364
|
+
return configs;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Reload MCP service configuration and restart servers
|
|
369
|
+
* @param metadataPath The project metadata path (~/.tenex/projects/{dTag})
|
|
370
|
+
* @param workingDirectory The project working directory (~/tenex/{dTag})
|
|
371
|
+
*/
|
|
372
|
+
async reload(metadataPath?: string, workingDirectory?: string): Promise<void> {
|
|
373
|
+
trace.getActiveSpan()?.addEvent("mcp.reloading");
|
|
374
|
+
|
|
375
|
+
// Shutdown existing servers
|
|
376
|
+
await this.shutdown();
|
|
377
|
+
|
|
378
|
+
// Re-initialize with the new configuration
|
|
379
|
+
await this.initialize(
|
|
380
|
+
metadataPath || this.metadataPath,
|
|
381
|
+
workingDirectory || this.workingDirectory
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
trace.getActiveSpan()?.addEvent("mcp.reloaded", {
|
|
385
|
+
"servers.running": this.getRunningServers().length,
|
|
386
|
+
"tools.available": Object.keys(this.cachedTools).length,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* List resources from a specific MCP server
|
|
392
|
+
* @param serverName - Name of the MCP server
|
|
393
|
+
* @returns Array of resources from that server
|
|
394
|
+
*/
|
|
395
|
+
async listResources(serverName: string): Promise<Resource[]> {
|
|
396
|
+
const entry = this.clients.get(serverName);
|
|
397
|
+
if (!entry) {
|
|
398
|
+
const validServers = this.getRunningServers();
|
|
399
|
+
const serverList =
|
|
400
|
+
validServers.length > 0
|
|
401
|
+
? `Valid servers: ${validServers.join(", ")}`
|
|
402
|
+
: "No MCP servers are currently running";
|
|
403
|
+
throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const result = await entry.client.listResources();
|
|
408
|
+
return result.resources;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
logger.error(`Failed to list resources from '${serverName}':`, formatAnyError(error));
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* List all resources from all connected MCP servers
|
|
417
|
+
* @returns Map of server names to their resources
|
|
418
|
+
*/
|
|
419
|
+
async listAllResources(): Promise<Map<string, Resource[]>> {
|
|
420
|
+
const resourcesMap = new Map<string, Resource[]>();
|
|
421
|
+
|
|
422
|
+
for (const [serverName] of this.clients) {
|
|
423
|
+
try {
|
|
424
|
+
const resources = await this.listResources(serverName);
|
|
425
|
+
resourcesMap.set(serverName, resources);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
logger.error(
|
|
428
|
+
`Failed to list resources from '${serverName}':`,
|
|
429
|
+
formatAnyError(error)
|
|
430
|
+
);
|
|
431
|
+
// Continue with other servers
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return resourcesMap;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* List resource templates from a specific MCP server
|
|
440
|
+
* @param serverName - Name of the MCP server
|
|
441
|
+
* @returns Array of resource templates from that server
|
|
442
|
+
*/
|
|
443
|
+
async listResourceTemplates(serverName: string): Promise<ResourceTemplate[]> {
|
|
444
|
+
const entry = this.clients.get(serverName);
|
|
445
|
+
if (!entry) {
|
|
446
|
+
const validServers = this.getRunningServers();
|
|
447
|
+
const serverList =
|
|
448
|
+
validServers.length > 0
|
|
449
|
+
? `Valid servers: ${validServers.join(", ")}`
|
|
450
|
+
: "No MCP servers are currently running";
|
|
451
|
+
throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const result = await entry.client.listResourceTemplates();
|
|
456
|
+
return result.resourceTemplates;
|
|
457
|
+
} catch (error) {
|
|
458
|
+
logger.error(
|
|
459
|
+
`Failed to list resource templates from '${serverName}':`,
|
|
460
|
+
formatAnyError(error)
|
|
461
|
+
);
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* List all resource templates from all connected MCP servers
|
|
468
|
+
* @returns Map of server names to their resource templates
|
|
469
|
+
*/
|
|
470
|
+
async listAllResourceTemplates(): Promise<Map<string, ResourceTemplate[]>> {
|
|
471
|
+
const templatesMap = new Map<string, ResourceTemplate[]>();
|
|
472
|
+
|
|
473
|
+
for (const [serverName] of this.clients) {
|
|
474
|
+
try {
|
|
475
|
+
const templates = await this.listResourceTemplates(serverName);
|
|
476
|
+
templatesMap.set(serverName, templates);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
logger.error(
|
|
479
|
+
`Failed to list resource templates from '${serverName}':`,
|
|
480
|
+
formatAnyError(error)
|
|
481
|
+
);
|
|
482
|
+
// Continue with other servers
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return templatesMap;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Read a resource from a specific MCP server
|
|
491
|
+
* @param serverName - Name of the MCP server
|
|
492
|
+
* @param uri - URI of the resource to read
|
|
493
|
+
* @returns Resource content
|
|
494
|
+
*/
|
|
495
|
+
async readResource(
|
|
496
|
+
serverName: string,
|
|
497
|
+
uri: string
|
|
498
|
+
): Promise<ReadResourceResult> {
|
|
499
|
+
const entry = this.clients.get(serverName);
|
|
500
|
+
if (!entry) {
|
|
501
|
+
const validServers = this.getRunningServers();
|
|
502
|
+
const serverList =
|
|
503
|
+
validServers.length > 0
|
|
504
|
+
? `Valid servers: ${validServers.join(", ")}`
|
|
505
|
+
: "No MCP servers are currently running";
|
|
506
|
+
throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
return await entry.client.readResource({ uri });
|
|
511
|
+
} catch (error) {
|
|
512
|
+
logger.error(
|
|
513
|
+
`Failed to read resource '${uri}' from '${serverName}':`,
|
|
514
|
+
formatAnyError(error)
|
|
515
|
+
);
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Get resource context as a formatted string for RAG pattern
|
|
522
|
+
* @param serverName - Name of the MCP server
|
|
523
|
+
* @param resourceUris - Array of resource URIs to fetch
|
|
524
|
+
* @returns Formatted context string
|
|
525
|
+
*/
|
|
526
|
+
async getResourceContext(serverName: string, resourceUris: string[]): Promise<string> {
|
|
527
|
+
const contents: string[] = [];
|
|
528
|
+
|
|
529
|
+
for (const uri of resourceUris) {
|
|
530
|
+
try {
|
|
531
|
+
const result = await this.readResource(serverName, uri);
|
|
532
|
+
|
|
533
|
+
for (const content of result.contents) {
|
|
534
|
+
if ("text" in content) {
|
|
535
|
+
contents.push(`Resource: ${uri}\n${content.text}`);
|
|
536
|
+
} else if ("blob" in content) {
|
|
537
|
+
contents.push(
|
|
538
|
+
`Resource: ${uri}\n[Binary content: ${content.blob.length} bytes]`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
} catch (error) {
|
|
543
|
+
logger.error(`Failed to read resource '${uri}':`, formatAnyError(error));
|
|
544
|
+
// Continue with other resources
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return contents.join("\n\n---\n\n");
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Subscribe to resource updates from an MCP server
|
|
553
|
+
* @param serverName - Name of the MCP server
|
|
554
|
+
* @param resourceUri - URI of the resource to subscribe to
|
|
555
|
+
*/
|
|
556
|
+
async subscribeToResource(serverName: string, resourceUri: string): Promise<void> {
|
|
557
|
+
const entry = this.clients.get(serverName);
|
|
558
|
+
if (!entry) {
|
|
559
|
+
const validServers = this.getRunningServers();
|
|
560
|
+
const serverList =
|
|
561
|
+
validServers.length > 0
|
|
562
|
+
? `Valid servers: ${validServers.join(", ")}`
|
|
563
|
+
: "No MCP servers are currently running";
|
|
564
|
+
throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
// Official SDK method - properly supported
|
|
569
|
+
await entry.client.subscribeResource({ uri: resourceUri });
|
|
570
|
+
|
|
571
|
+
trace.getActiveSpan()?.addEvent("mcp.resource_subscribed", {
|
|
572
|
+
"server.name": serverName,
|
|
573
|
+
"resource.uri": resourceUri,
|
|
574
|
+
});
|
|
575
|
+
} catch (error) {
|
|
576
|
+
logger.error(
|
|
577
|
+
`Failed to subscribe to resource '${resourceUri}' from '${serverName}':`,
|
|
578
|
+
formatAnyError(error)
|
|
579
|
+
);
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Unsubscribe from resource updates
|
|
586
|
+
* @param serverName - Name of the MCP server
|
|
587
|
+
* @param resourceUri - URI of the resource to unsubscribe from
|
|
588
|
+
*/
|
|
589
|
+
async unsubscribeFromResource(serverName: string, resourceUri: string): Promise<void> {
|
|
590
|
+
const entry = this.clients.get(serverName);
|
|
591
|
+
if (!entry) {
|
|
592
|
+
const validServers = this.getRunningServers();
|
|
593
|
+
const serverList =
|
|
594
|
+
validServers.length > 0
|
|
595
|
+
? `Valid servers: ${validServers.join(", ")}`
|
|
596
|
+
: "No MCP servers are currently running";
|
|
597
|
+
throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
// Official SDK method - properly supported
|
|
602
|
+
await entry.client.unsubscribeResource({ uri: resourceUri });
|
|
603
|
+
|
|
604
|
+
trace.getActiveSpan()?.addEvent("mcp.resource_unsubscribed", {
|
|
605
|
+
"server.name": serverName,
|
|
606
|
+
"resource.uri": resourceUri,
|
|
607
|
+
});
|
|
608
|
+
} catch (error) {
|
|
609
|
+
logger.error(
|
|
610
|
+
`Failed to unsubscribe from resource '${resourceUri}' from '${serverName}':`,
|
|
611
|
+
formatAnyError(error)
|
|
612
|
+
);
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Add a handler for resource update notifications on a server.
|
|
619
|
+
* Multiple handlers can be registered per server (dispatcher pattern).
|
|
620
|
+
* Must be called BEFORE subscribeToResource.
|
|
621
|
+
*
|
|
622
|
+
* @returns A removal function to unregister this specific handler
|
|
623
|
+
*/
|
|
624
|
+
addResourceNotificationHandler(
|
|
625
|
+
serverName: string,
|
|
626
|
+
handler: ResourceNotificationHandler
|
|
627
|
+
): () => void {
|
|
628
|
+
const entry = this.clients.get(serverName);
|
|
629
|
+
if (!entry) {
|
|
630
|
+
throw new Error(`MCP server '${serverName}' not found`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Initialize handler list for this server if needed
|
|
634
|
+
if (!this.resourceNotificationHandlers.has(serverName)) {
|
|
635
|
+
this.resourceNotificationHandlers.set(serverName, []);
|
|
636
|
+
|
|
637
|
+
// Register the SDK-level notification handler ONCE per server.
|
|
638
|
+
// This dispatcher fans out to all registered handlers.
|
|
639
|
+
entry.client.setNotificationHandler(
|
|
640
|
+
ResourceUpdatedNotificationSchema,
|
|
641
|
+
async (notification) => {
|
|
642
|
+
const uri = notification.params.uri;
|
|
643
|
+
if (!uri) return;
|
|
644
|
+
|
|
645
|
+
const handlers = this.resourceNotificationHandlers.get(serverName) ?? [];
|
|
646
|
+
for (const h of handlers) {
|
|
647
|
+
try {
|
|
648
|
+
await h({ uri });
|
|
649
|
+
} catch (error) {
|
|
650
|
+
logger.error("Resource notification handler error", {
|
|
651
|
+
server: serverName,
|
|
652
|
+
uri,
|
|
653
|
+
error: error instanceof Error ? error.message : String(error),
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const handlers = this.resourceNotificationHandlers.get(serverName)!;
|
|
662
|
+
handlers.push(handler);
|
|
663
|
+
|
|
664
|
+
trace.getActiveSpan()?.addEvent("mcp.resource_handler_registered", {
|
|
665
|
+
"server.name": serverName,
|
|
666
|
+
"handlers.count": handlers.length,
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
// Return removal function
|
|
670
|
+
return () => {
|
|
671
|
+
const currentHandlers = this.resourceNotificationHandlers.get(serverName);
|
|
672
|
+
if (currentHandlers) {
|
|
673
|
+
const index = currentHandlers.indexOf(handler);
|
|
674
|
+
if (index !== -1) {
|
|
675
|
+
currentHandlers.splice(index, 1);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// MCPManager is now per-project - create instances in ProjectRuntime
|
|
683
|
+
// No more singleton export
|