diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6f70a19946..61f9ef1ec2 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -80,6 +80,7 @@ import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker" import { McpHub } from "../../services/mcp/McpHub" import { McpServerManager } from "../../services/mcp/McpServerManager" +import { promptToInstallExaMcp } from "../../services/mcp/promptToInstallExaMcp" import { MarketplaceManager } from "../../services/marketplace" import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService" import { CodeIndexManager } from "../../services/code-index/manager" @@ -345,9 +346,10 @@ export class ClineProvider // Initialize MCP Hub through the singleton manager McpServerManager.getInstance(this.context, this) - .then((hub) => { + .then(async (hub) => { this.mcpHub = hub this.mcpHub.registerClient() + await promptToInstallExaMcp(this.context, hub) }) .catch((error) => { this.log(`Failed to initialize MCP Hub: ${error}`) diff --git a/src/i18n/locales/ca/mcp.json b/src/i18n/locales/ca/mcp.json index 697f78a31b..1bda69158b 100644 --- a/src/i18n/locales/ca/mcp.json +++ b/src/i18n/locales/ca/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Ha fallat la desconnexió de {{count}} servidor(s) MCP. Comprova la sortida per més detalls.", "toolNotFound": "L'eina '{{toolName}}' no existeix al servidor '{{serverName}}'. Eines disponibles: {{availableTools}}", "serverNotFound": "El servidor MCP '{{serverName}}' no està configurat. Servidors disponibles: {{availableServers}}", - "toolDisabled": "L'eina '{{toolName}}' del servidor '{{serverName}}' està desactivada. Eines activades disponibles: {{availableTools}}" + "toolDisabled": "L'eina '{{toolName}}' del servidor '{{serverName}}' està desactivada. Eines activades disponibles: {{availableTools}}", + "exa_install_failed": "No s'ha pogut instal·lar Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Reiniciant el servidor MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "Els servidors MCP ja s'estan actualitzant.", "refreshing_all": "Actualitzant tots els servidors MCP...", "all_refreshed": "Tots els servidors MCP han estat actualitzats.", - "project_config_deleted": "Fitxer de configuració MCP del projecte eliminat. Tots els servidors MCP del projecte han estat desconnectats." + "project_config_deleted": "Fitxer de configuració MCP del projecte eliminat. Tots els servidors MCP del projecte han estat desconnectats.", + "exa_install_prompt": "Vols instal·lar el servidor MCP gratuït d'Exa perquè Zoo pugui cercar i obtenir contingut web?", + "exa_install_action": "Instal·la Exa MCP", + "exa_install_success": "Exa MCP s'ha instal·lat correctament." }, "oauth": { "callback": { diff --git a/src/i18n/locales/de/mcp.json b/src/i18n/locales/de/mcp.json index c21374d958..52c6d38b57 100644 --- a/src/i18n/locales/de/mcp.json +++ b/src/i18n/locales/de/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Fehler beim Trennen von {{count}} MCP-Server(n). Überprüfe die Ausgabe für Details.", "toolNotFound": "Tool '{{toolName}}' existiert nicht auf Server '{{serverName}}'. Verfügbare Tools: {{availableTools}}", "serverNotFound": "MCP-Server '{{serverName}}' ist nicht konfiguriert. Verfügbare Server: {{availableServers}}", - "toolDisabled": "Tool '{{toolName}}' auf Server '{{serverName}}' ist deaktiviert. Verfügbare aktivierte Tools: {{availableTools}}" + "toolDisabled": "Tool '{{toolName}}' auf Server '{{serverName}}' ist deaktiviert. Verfügbare aktivierte Tools: {{availableTools}}", + "exa_install_failed": "Exa MCP konnte nicht installiert werden: {{errorMessage}}" }, "info": { "server_restarting": "MCP-Server {{serverName}} wird neu gestartet...", @@ -23,7 +24,10 @@ "already_refreshing": "MCP-Server werden bereits aktualisiert.", "refreshing_all": "Alle MCP-Server werden aktualisiert...", "all_refreshed": "Alle MCP-Server wurden aktualisiert.", - "project_config_deleted": "Projekt-MCP-Konfigurationsdatei gelöscht. Alle Projekt-MCP-Server wurden getrennt." + "project_config_deleted": "Projekt-MCP-Konfigurationsdatei gelöscht. Alle Projekt-MCP-Server wurden getrennt.", + "exa_install_prompt": "Möchtest du den kostenlosen Exa MCP-Server installieren, damit Zoo das Web durchsuchen und Webinhalte abrufen kann?", + "exa_install_action": "Exa MCP installieren", + "exa_install_success": "Exa MCP wurde erfolgreich installiert." }, "oauth": { "callback": { diff --git a/src/i18n/locales/en/mcp.json b/src/i18n/locales/en/mcp.json index 565fba4caf..1237768a52 100644 --- a/src/i18n/locales/en/mcp.json +++ b/src/i18n/locales/en/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Failed to disconnect {{count}} MCP server(s). Check the output for details.", "toolNotFound": "Tool '{{toolName}}' does not exist on server '{{serverName}}'. Available tools: {{availableTools}}", "serverNotFound": "MCP server '{{serverName}}' is not configured. Available servers: {{availableServers}}", - "toolDisabled": "Tool '{{toolName}}' on server '{{serverName}}' is disabled. Available enabled tools: {{availableTools}}" + "toolDisabled": "Tool '{{toolName}}' on server '{{serverName}}' is disabled. Available enabled tools: {{availableTools}}", + "exa_install_failed": "Failed to install Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Restarting {{serverName}} MCP server...", @@ -23,7 +24,10 @@ "already_refreshing": "MCP servers are already refreshing.", "refreshing_all": "Refreshing all MCP servers...", "all_refreshed": "All MCP servers have been refreshed.", - "project_config_deleted": "Project MCP configuration file deleted. All project MCP servers have been disconnected." + "project_config_deleted": "Project MCP configuration file deleted. All project MCP servers have been disconnected.", + "exa_install_prompt": "Install the free Exa MCP server to give Zoo web search and web fetching capabilities?", + "exa_install_action": "Install Exa MCP", + "exa_install_success": "Exa MCP installed successfully." }, "oauth": { "callback": { diff --git a/src/i18n/locales/es/mcp.json b/src/i18n/locales/es/mcp.json index 8c11fd7364..e62f2eb78a 100644 --- a/src/i18n/locales/es/mcp.json +++ b/src/i18n/locales/es/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Error al desconectar {{count}} servidor(es) MCP. Revisa la salida para más detalles.", "toolNotFound": "La herramienta '{{toolName}}' no existe en el servidor '{{serverName}}'. Herramientas disponibles: {{availableTools}}", "serverNotFound": "El servidor MCP '{{serverName}}' no está configurado. Servidores disponibles: {{availableServers}}", - "toolDisabled": "La herramienta '{{toolName}}' del servidor '{{serverName}}' está desactivada. Herramientas activadas disponibles: {{availableTools}}" + "toolDisabled": "La herramienta '{{toolName}}' del servidor '{{serverName}}' está desactivada. Herramientas activadas disponibles: {{availableTools}}", + "exa_install_failed": "No se pudo instalar Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Reiniciando el servidor MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "Los servidores MCP ya se están actualizando.", "refreshing_all": "Actualizando todos los servidores MCP...", "all_refreshed": "Todos los servidores MCP han sido actualizados.", - "project_config_deleted": "Archivo de configuración MCP del proyecto eliminado. Todos los servidores MCP del proyecto han sido desconectados." + "project_config_deleted": "Archivo de configuración MCP del proyecto eliminado. Todos los servidores MCP del proyecto han sido desconectados.", + "exa_install_prompt": "¿Quieres instalar el servidor MCP gratuito de Exa para que Zoo pueda buscar y obtener contenido web?", + "exa_install_action": "Instalar Exa MCP", + "exa_install_success": "Exa MCP se instaló correctamente." }, "oauth": { "callback": { diff --git a/src/i18n/locales/fr/mcp.json b/src/i18n/locales/fr/mcp.json index 7d302b708e..abb65d674f 100644 --- a/src/i18n/locales/fr/mcp.json +++ b/src/i18n/locales/fr/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Échec de la déconnexion de {{count}} serveur(s) MCP. Vérifiez la sortie pour plus de détails.", "toolNotFound": "L'outil '{{toolName}}' n'existe pas sur le serveur '{{serverName}}'. Outils disponibles : {{availableTools}}", "serverNotFound": "Le serveur MCP '{{serverName}}' n'est pas configuré. Serveurs disponibles : {{availableServers}}", - "toolDisabled": "L'outil '{{toolName}}' sur le serveur '{{serverName}}' est désactivé. Outils activés disponibles : {{availableTools}}" + "toolDisabled": "L'outil '{{toolName}}' sur le serveur '{{serverName}}' est désactivé. Outils activés disponibles : {{availableTools}}", + "exa_install_failed": "Impossible d'installer Exa MCP : {{errorMessage}}" }, "info": { "server_restarting": "Redémarrage du serveur MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "Les serveurs MCP sont déjà en cours de rafraîchissement.", "refreshing_all": "Rafraîchissement de tous les serveurs MCP...", "all_refreshed": "Tous les serveurs MCP ont été rafraîchis.", - "project_config_deleted": "Fichier de configuration MCP du projet supprimé. Tous les serveurs MCP du projet ont été déconnectés." + "project_config_deleted": "Fichier de configuration MCP du projet supprimé. Tous les serveurs MCP du projet ont été déconnectés.", + "exa_install_prompt": "Veux-tu installer le serveur MCP gratuit d'Exa pour permettre à Zoo de rechercher et récupérer du contenu web ?", + "exa_install_action": "Installer Exa MCP", + "exa_install_success": "Exa MCP a été installé." }, "oauth": { "callback": { diff --git a/src/i18n/locales/hi/mcp.json b/src/i18n/locales/hi/mcp.json index 6a93128779..625cf0a3fa 100644 --- a/src/i18n/locales/hi/mcp.json +++ b/src/i18n/locales/hi/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "{{count}} MCP सर्वर डिस्कनेक्ट करने में विफल। विवरण के लिए आउटपुट देखें।", "toolNotFound": "टूल '{{toolName}}' सर्वर '{{serverName}}' पर मौजूद नहीं है। उपलब्ध टूल: {{availableTools}}", "serverNotFound": "MCP सर्वर '{{serverName}}' कॉन्फ़िगर नहीं है। उपलब्ध सर्वर: {{availableServers}}", - "toolDisabled": "सर्वर '{{serverName}}' पर टूल '{{toolName}}' अक्षम है। उपलब्ध सक्षम टूल: {{availableTools}}" + "toolDisabled": "सर्वर '{{serverName}}' पर टूल '{{toolName}}' अक्षम है। उपलब्ध सक्षम टूल: {{availableTools}}", + "exa_install_failed": "Exa MCP इंस्टॉल नहीं हो सका: {{errorMessage}}" }, "info": { "server_restarting": "{{serverName}} MCP सर्वर पुनः प्रारंभ हो रहा है...", @@ -23,7 +24,10 @@ "already_refreshing": "एमसीपी सर्वर पहले से ही रीफ्रेश हो रहे हैं।", "refreshing_all": "सभी एमसीपी सर्वर रीफ्रेश हो रहे हैं...", "all_refreshed": "सभी एमसीपी सर्वर रीफ्रेश हो गए हैं।", - "project_config_deleted": "प्रोजेक्ट एमसीपी कॉन्फ़िगरेशन फ़ाइल हटा दी गई है। सभी प्रोजेक्ट एमसीपी सर्वर डिस्कनेक्ट कर दिए गए हैं।" + "project_config_deleted": "प्रोजेक्ट एमसीपी कॉन्फ़िगरेशन फ़ाइल हटा दी गई है। सभी प्रोजेक्ट एमसीपी सर्वर डिस्कनेक्ट कर दिए गए हैं।", + "exa_install_prompt": "क्या Zoo को वेब खोज और वेब सामग्री प्राप्त करने की सुविधा देने के लिए मुफ़्त Exa MCP सर्वर इंस्टॉल करना है?", + "exa_install_action": "Exa MCP इंस्टॉल करें", + "exa_install_success": "Exa MCP सफलतापूर्वक इंस्टॉल हो गया।" }, "oauth": { "callback": { diff --git a/src/i18n/locales/id/mcp.json b/src/i18n/locales/id/mcp.json index 83dcae0504..ad3b0ff90c 100644 --- a/src/i18n/locales/id/mcp.json +++ b/src/i18n/locales/id/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Gagal memutus koneksi {{count}} server MCP. Periksa output untuk detailnya.", "toolNotFound": "Tool '{{toolName}}' tidak ada di server '{{serverName}}'. Tool yang tersedia: {{availableTools}}", "serverNotFound": "Server MCP '{{serverName}}' tidak dikonfigurasi. Server yang tersedia: {{availableServers}}", - "toolDisabled": "Tool '{{toolName}}' di server '{{serverName}}' dinonaktifkan. Tool aktif yang tersedia: {{availableTools}}" + "toolDisabled": "Tool '{{toolName}}' di server '{{serverName}}' dinonaktifkan. Tool aktif yang tersedia: {{availableTools}}", + "exa_install_failed": "Gagal menginstal Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Merestart server MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "Server MCP sudah sedang di-refresh.", "refreshing_all": "Me-refresh semua server MCP...", "all_refreshed": "Semua server MCP telah di-refresh.", - "project_config_deleted": "File konfigurasi MCP proyek dihapus. Semua server MCP proyek telah diputus koneksinya." + "project_config_deleted": "File konfigurasi MCP proyek dihapus. Semua server MCP proyek telah diputus koneksinya.", + "exa_install_prompt": "Instal server MCP Exa gratis agar Zoo dapat mencari dan mengambil konten web?", + "exa_install_action": "Instal Exa MCP", + "exa_install_success": "Exa MCP berhasil diinstal." }, "oauth": { "callback": { diff --git a/src/i18n/locales/it/mcp.json b/src/i18n/locales/it/mcp.json index 5c82a7369e..7fca8d9289 100644 --- a/src/i18n/locales/it/mcp.json +++ b/src/i18n/locales/it/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Impossibile disconnettere {{count}} server MCP. Controlla l'output per i dettagli.", "toolNotFound": "Lo strumento '{{toolName}}' non esiste sul server '{{serverName}}'. Strumenti disponibili: {{availableTools}}", "serverNotFound": "Il server MCP '{{serverName}}' non è configurato. Server disponibili: {{availableServers}}", - "toolDisabled": "Lo strumento '{{toolName}}' sul server '{{serverName}}' è disabilitato. Strumenti abilitati disponibili: {{availableTools}}" + "toolDisabled": "Lo strumento '{{toolName}}' sul server '{{serverName}}' è disabilitato. Strumenti abilitati disponibili: {{availableTools}}", + "exa_install_failed": "Impossibile installare Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Riavvio del server MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "I server MCP sono già in aggiornamento.", "refreshing_all": "Aggiornamento di tutti i server MCP...", "all_refreshed": "Tutti i server MCP sono stati aggiornati.", - "project_config_deleted": "File di configurazione MCP del progetto eliminato. Tutti i server MCP del progetto sono stati disconnessi." + "project_config_deleted": "File di configurazione MCP del progetto eliminato. Tutti i server MCP del progetto sono stati disconnessi.", + "exa_install_prompt": "Vuoi installare il server MCP gratuito di Exa per consentire a Zoo di cercare e recuperare contenuti web?", + "exa_install_action": "Installa Exa MCP", + "exa_install_success": "Exa MCP è stato installato correttamente." }, "oauth": { "callback": { diff --git a/src/i18n/locales/ja/mcp.json b/src/i18n/locales/ja/mcp.json index e63ca86d56..8c11a73989 100644 --- a/src/i18n/locales/ja/mcp.json +++ b/src/i18n/locales/ja/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "{{count}}個のMCPサーバーの切断に失敗しました。詳細は出力を確認してください。", "toolNotFound": "ツール '{{toolName}}' はサーバー '{{serverName}}' に存在しません。利用可能なツール: {{availableTools}}", "serverNotFound": "MCPサーバー '{{serverName}}' は設定されていません。利用可能なサーバー: {{availableServers}}", - "toolDisabled": "サーバー '{{serverName}}' のツール '{{toolName}}' は無効です。利用可能な有効なツール: {{availableTools}}" + "toolDisabled": "サーバー '{{serverName}}' のツール '{{toolName}}' は無効です。利用可能な有効なツール: {{availableTools}}", + "exa_install_failed": "Exa MCPをインストールできませんでした: {{errorMessage}}" }, "info": { "server_restarting": "MCPサーバー{{serverName}}を再起動中...", @@ -23,7 +24,10 @@ "already_refreshing": "MCPサーバーはすでに更新中です。", "refreshing_all": "すべてのMCPサーバーを更新しています...", "all_refreshed": "すべてのMCPサーバーが更新されました。", - "project_config_deleted": "プロジェクトMCP設定ファイルが削除されました。すべてのプロジェクトMCPサーバーが切断されました。" + "project_config_deleted": "プロジェクトMCP設定ファイルが削除されました。すべてのプロジェクトMCPサーバーが切断されました。", + "exa_install_prompt": "無料のExa MCPサーバーをインストールして、ZooでWeb検索とWebコンテンツ取得を利用しますか?", + "exa_install_action": "Exa MCPをインストール", + "exa_install_success": "Exa MCPをインストールしました。" }, "oauth": { "callback": { diff --git a/src/i18n/locales/ko/mcp.json b/src/i18n/locales/ko/mcp.json index f31e234764..85c146a344 100644 --- a/src/i18n/locales/ko/mcp.json +++ b/src/i18n/locales/ko/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "{{count}}개의 MCP 서버 연결 해제 실패. 자세한 내용은 출력을 확인하세요.", "toolNotFound": "도구 '{{toolName}}'이(가) 서버 '{{serverName}}'에 존재하지 않습니다. 사용 가능한 도구: {{availableTools}}", "serverNotFound": "MCP 서버 '{{serverName}}'이(가) 구성되지 않았습니다. 사용 가능한 서버: {{availableServers}}", - "toolDisabled": "서버 '{{serverName}}'의 도구 '{{toolName}}'이(가) 비활성화되었습니다. 사용 가능한 활성화된 도구: {{availableTools}}" + "toolDisabled": "서버 '{{serverName}}'의 도구 '{{toolName}}'이(가) 비활성화되었습니다. 사용 가능한 활성화된 도구: {{availableTools}}", + "exa_install_failed": "Exa MCP를 설치하지 못했습니다: {{errorMessage}}" }, "info": { "server_restarting": "{{serverName}} MCP 서버를 재시작하는 중...", @@ -23,7 +24,10 @@ "already_refreshing": "MCP 서버가 이미 새로 고쳐지고 있습니다.", "refreshing_all": "모든 MCP 서버를 새로 고치는 중...", "all_refreshed": "모든 MCP 서버가 새로 고쳐졌습니다.", - "project_config_deleted": "프로젝트 MCP 구성 파일이 삭제되었습니다. 모든 프로젝트 MCP 서버가 연결 해제되었습니다." + "project_config_deleted": "프로젝트 MCP 구성 파일이 삭제되었습니다. 모든 프로젝트 MCP 서버가 연결 해제되었습니다.", + "exa_install_prompt": "무료 Exa MCP 서버를 설치하여 Zoo에서 웹 검색과 웹 콘텐츠 가져오기를 사용하시겠어요?", + "exa_install_action": "Exa MCP 설치", + "exa_install_success": "Exa MCP가 설치되었습니다." }, "oauth": { "callback": { diff --git a/src/i18n/locales/nl/mcp.json b/src/i18n/locales/nl/mcp.json index da502e075f..1bbbf5c7a9 100644 --- a/src/i18n/locales/nl/mcp.json +++ b/src/i18n/locales/nl/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Loskoppelen van {{count}} MCP-server(s) mislukt. Controleer de uitvoer voor details.", "toolNotFound": "Tool '{{toolName}}' bestaat niet op server '{{serverName}}'. Beschikbare tools: {{availableTools}}", "serverNotFound": "MCP-server '{{serverName}}' is niet geconfigureerd. Beschikbare servers: {{availableServers}}", - "toolDisabled": "Tool '{{toolName}}' op server '{{serverName}}' is uitgeschakeld. Beschikbare ingeschakelde tools: {{availableTools}}" + "toolDisabled": "Tool '{{toolName}}' op server '{{serverName}}' is uitgeschakeld. Beschikbare ingeschakelde tools: {{availableTools}}", + "exa_install_failed": "Exa MCP kon niet worden geïnstalleerd: {{errorMessage}}" }, "info": { "server_restarting": "{{serverName}} MCP-server wordt opnieuw gestart...", @@ -23,7 +24,10 @@ "already_refreshing": "MCP-servers worden al vernieuwd.", "refreshing_all": "Alle MCP-servers worden vernieuwd...", "all_refreshed": "Alle MCP-servers zijn vernieuwd.", - "project_config_deleted": "Project MCP-configuratiebestand verwijderd. Alle project MCP-servers zijn losgekoppeld." + "project_config_deleted": "Project MCP-configuratiebestand verwijderd. Alle project MCP-servers zijn losgekoppeld.", + "exa_install_prompt": "Wil je de gratis Exa MCP-server installeren zodat Zoo het web kan doorzoeken en webinhoud kan ophalen?", + "exa_install_action": "Exa MCP installeren", + "exa_install_success": "Exa MCP is geïnstalleerd." }, "oauth": { "callback": { diff --git a/src/i18n/locales/pl/mcp.json b/src/i18n/locales/pl/mcp.json index 8ca454a735..23b6edf5de 100644 --- a/src/i18n/locales/pl/mcp.json +++ b/src/i18n/locales/pl/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Nie udało się odłączyć {{count}} serwera(ów) MCP. Sprawdź dane wyjściowe, aby uzyskać szczegóły.", "toolNotFound": "Narzędzie '{{toolName}}' nie istnieje na serwerze '{{serverName}}'. Dostępne narzędzia: {{availableTools}}", "serverNotFound": "Serwer MCP '{{serverName}}' nie jest skonfigurowany. Dostępne serwery: {{availableServers}}", - "toolDisabled": "Narzędzie '{{toolName}}' na serwerze '{{serverName}}' jest wyłączone. Dostępne włączone narzędzia: {{availableTools}}" + "toolDisabled": "Narzędzie '{{toolName}}' na serwerze '{{serverName}}' jest wyłączone. Dostępne włączone narzędzia: {{availableTools}}", + "exa_install_failed": "Nie udało się zainstalować Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Ponowne uruchamianie serwera MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "Serwery MCP są już odświeżane.", "refreshing_all": "Odświeżanie wszystkich serwerów MCP...", "all_refreshed": "Wszystkie serwery MCP zostały odświeżone.", - "project_config_deleted": "Plik konfiguracyjny MCP projektu został usunięty. Wszystkie serwery MCP projektu zostały odłączone." + "project_config_deleted": "Plik konfiguracyjny MCP projektu został usunięty. Wszystkie serwery MCP projektu zostały odłączone.", + "exa_install_prompt": "Zainstalować bezpłatny serwer Exa MCP, aby Zoo mógł przeszukiwać internet i pobierać treści z sieci?", + "exa_install_action": "Zainstaluj Exa MCP", + "exa_install_success": "Exa MCP został zainstalowany." }, "oauth": { "callback": { diff --git a/src/i18n/locales/pt-BR/mcp.json b/src/i18n/locales/pt-BR/mcp.json index 03d848d20f..67c4ed891a 100644 --- a/src/i18n/locales/pt-BR/mcp.json +++ b/src/i18n/locales/pt-BR/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Falha ao desconectar {{count}} servidor(es) MCP. Verifique a saída para detalhes.", "toolNotFound": "A ferramenta '{{toolName}}' não existe no servidor '{{serverName}}'. Ferramentas disponíveis: {{availableTools}}", "serverNotFound": "O servidor MCP '{{serverName}}' não está configurado. Servidores disponíveis: {{availableServers}}", - "toolDisabled": "A ferramenta '{{toolName}}' no servidor '{{serverName}}' está desabilitada. Ferramentas habilitadas disponíveis: {{availableTools}}" + "toolDisabled": "A ferramenta '{{toolName}}' no servidor '{{serverName}}' está desabilitada. Ferramentas habilitadas disponíveis: {{availableTools}}", + "exa_install_failed": "Não foi possível instalar o Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Reiniciando o servidor MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "Os servidores MCP já estão atualizando.", "refreshing_all": "Atualizando todos os servidores MCP...", "all_refreshed": "Todos os servidores MCP foram atualizados.", - "project_config_deleted": "Arquivo de configuração MCP do projeto excluído. Todos os servidores MCP do projeto foram desconectados." + "project_config_deleted": "Arquivo de configuração MCP do projeto excluído. Todos os servidores MCP do projeto foram desconectados.", + "exa_install_prompt": "Quer instalar o servidor MCP gratuito da Exa para que o Zoo possa pesquisar e buscar conteúdo na web?", + "exa_install_action": "Instalar Exa MCP", + "exa_install_success": "Exa MCP instalado com sucesso." }, "oauth": { "callback": { diff --git a/src/i18n/locales/ru/mcp.json b/src/i18n/locales/ru/mcp.json index 98ad90dff5..b6c45a42fc 100644 --- a/src/i18n/locales/ru/mcp.json +++ b/src/i18n/locales/ru/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Не удалось отключить {{count}} MCP сервер(ов). Проверьте вывод для получения подробностей.", "toolNotFound": "Инструмент '{{toolName}}' не существует на сервере '{{serverName}}'. Доступные инструменты: {{availableTools}}", "serverNotFound": "MCP сервер '{{serverName}}' не настроен. Доступные серверы: {{availableServers}}", - "toolDisabled": "Инструмент '{{toolName}}' на сервере '{{serverName}}' отключен. Доступные включенные инструменты: {{availableTools}}" + "toolDisabled": "Инструмент '{{toolName}}' на сервере '{{serverName}}' отключен. Доступные включенные инструменты: {{availableTools}}", + "exa_install_failed": "Не удалось установить Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Перезапуск сервера MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "MCP серверы уже обновляются.", "refreshing_all": "Обновление всех MCP серверов...", "all_refreshed": "Все MCP серверы обновлены.", - "project_config_deleted": "Файл конфигурации MCP проекта удален. Все MCP серверы проекта отключены." + "project_config_deleted": "Файл конфигурации MCP проекта удален. Все MCP серверы проекта отключены.", + "exa_install_prompt": "Установить бесплатный сервер Exa MCP, чтобы Zoo мог искать и получать контент из интернета?", + "exa_install_action": "Установить Exa MCP", + "exa_install_success": "Exa MCP успешно установлен." }, "oauth": { "callback": { diff --git a/src/i18n/locales/tr/mcp.json b/src/i18n/locales/tr/mcp.json index f3c39ebf60..a05ac5d392 100644 --- a/src/i18n/locales/tr/mcp.json +++ b/src/i18n/locales/tr/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "{{count}} MCP sunucusu bağlantısı kesilemedi. Ayrıntılar için çıktıyı kontrol edin.", "toolNotFound": "Araç '{{toolName}}' sunucu '{{serverName}}' üzerinde mevcut değil. Mevcut araçlar: {{availableTools}}", "serverNotFound": "MCP sunucusu '{{serverName}}' yapılandırılmamış. Mevcut sunucular: {{availableServers}}", - "toolDisabled": "Sunucu '{{serverName}}' üzerindeki araç '{{toolName}}' devre dışı. Mevcut etkin araçlar: {{availableTools}}" + "toolDisabled": "Sunucu '{{serverName}}' üzerindeki araç '{{toolName}}' devre dışı. Mevcut etkin araçlar: {{availableTools}}", + "exa_install_failed": "Exa MCP yüklenemedi: {{errorMessage}}" }, "info": { "server_restarting": "{{serverName}} MCP sunucusu yeniden başlatılıyor...", @@ -23,7 +24,10 @@ "already_refreshing": "MCP sunucuları zaten yenileniyor.", "refreshing_all": "Tüm MCP sunucuları yenileniyor...", "all_refreshed": "Tüm MCP sunucuları yenilendi.", - "project_config_deleted": "Proje MCP yapılandırma dosyası silindi. Tüm proje MCP sunucuları bağlantısı kesildi." + "project_config_deleted": "Proje MCP yapılandırma dosyası silindi. Tüm proje MCP sunucuları bağlantısı kesildi.", + "exa_install_prompt": "Zoo'ya web arama ve web içeriği getirme özellikleri kazandırmak için ücretsiz Exa MCP sunucusunu yüklemek ister misin?", + "exa_install_action": "Exa MCP'yi yükle", + "exa_install_success": "Exa MCP başarıyla yüklendi." }, "oauth": { "callback": { diff --git a/src/i18n/locales/vi/mcp.json b/src/i18n/locales/vi/mcp.json index 83c5f80cfb..26cc7e13fe 100644 --- a/src/i18n/locales/vi/mcp.json +++ b/src/i18n/locales/vi/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "Không thể ngắt kết nối {{count}} máy chủ MCP. Kiểm tra đầu ra để biết chi tiết.", "toolNotFound": "Công cụ '{{toolName}}' không tồn tại trên máy chủ '{{serverName}}'. Công cụ có sẵn: {{availableTools}}", "serverNotFound": "Máy chủ MCP '{{serverName}}' chưa được cấu hình. Máy chủ có sẵn: {{availableServers}}", - "toolDisabled": "Công cụ '{{toolName}}' trên máy chủ '{{serverName}}' đã bị vô hiệu hóa. Công cụ đã kích hoạt có sẵn: {{availableTools}}" + "toolDisabled": "Công cụ '{{toolName}}' trên máy chủ '{{serverName}}' đã bị vô hiệu hóa. Công cụ đã kích hoạt có sẵn: {{availableTools}}", + "exa_install_failed": "Không thể cài đặt Exa MCP: {{errorMessage}}" }, "info": { "server_restarting": "Đang khởi động lại máy chủ MCP {{serverName}}...", @@ -23,7 +24,10 @@ "already_refreshing": "Các máy chủ MCP đã đang làm mới.", "refreshing_all": "Đang làm mới tất cả các máy chủ MCP...", "all_refreshed": "Tất cả các máy chủ MCP đã được làm mới.", - "project_config_deleted": "Tệp cấu hình MCP của dự án đã bị xóa. Tất cả các máy chủ MCP của dự án đã bị ngắt kết nối." + "project_config_deleted": "Tệp cấu hình MCP của dự án đã bị xóa. Tất cả các máy chủ MCP của dự án đã bị ngắt kết nối.", + "exa_install_prompt": "Cài đặt máy chủ Exa MCP miễn phí để Zoo có thể tìm kiếm và lấy nội dung web?", + "exa_install_action": "Cài đặt Exa MCP", + "exa_install_success": "Đã cài đặt Exa MCP thành công." }, "oauth": { "callback": { diff --git a/src/i18n/locales/zh-CN/mcp.json b/src/i18n/locales/zh-CN/mcp.json index acf32fb1d7..7f1ec176f2 100644 --- a/src/i18n/locales/zh-CN/mcp.json +++ b/src/i18n/locales/zh-CN/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "断开 {{count}} 个 MCP 服务器失败。请查看输出了解详情。", "toolNotFound": "工具 '{{toolName}}' 在服务器 '{{serverName}}' 上不存在。可用工具: {{availableTools}}", "serverNotFound": "MCP 服务器 '{{serverName}}' 未配置。可用服务器: {{availableServers}}", - "toolDisabled": "服务器 '{{serverName}}' 上的工具 '{{toolName}}' 已禁用。可用的已启用工具: {{availableTools}}" + "toolDisabled": "服务器 '{{serverName}}' 上的工具 '{{toolName}}' 已禁用。可用的已启用工具: {{availableTools}}", + "exa_install_failed": "无法安装 Exa MCP:{{errorMessage}}" }, "info": { "server_restarting": "正在重启{{serverName}}MCP服务器...", @@ -23,7 +24,10 @@ "already_refreshing": "MCP 服务器已在刷新中。", "refreshing_all": "正在刷新所有 MCP 服务器...", "all_refreshed": "所有 MCP 服务器已刷新。", - "project_config_deleted": "项目MCP配置文件已删除。所有项目MCP服务器已断开连接。" + "project_config_deleted": "项目MCP配置文件已删除。所有项目MCP服务器已断开连接。", + "exa_install_prompt": "是否安装免费的 Exa MCP 服务器,让 Zoo 能够搜索和获取网页内容?", + "exa_install_action": "安装 Exa MCP", + "exa_install_success": "Exa MCP 安装成功。" }, "oauth": { "callback": { diff --git a/src/i18n/locales/zh-TW/mcp.json b/src/i18n/locales/zh-TW/mcp.json index 9fa07b5f11..36791002c0 100644 --- a/src/i18n/locales/zh-TW/mcp.json +++ b/src/i18n/locales/zh-TW/mcp.json @@ -11,7 +11,8 @@ "disconnect_servers_partial": "斷開 {{count}} 個 MCP 伺服器失敗。請查看輸出了解詳情。", "toolNotFound": "工具 '{{toolName}}' 在伺服器 '{{serverName}}' 上不存在。可用工具: {{availableTools}}", "serverNotFound": "MCP 伺服器 '{{serverName}}' 未設定。可用伺服器: {{availableServers}}", - "toolDisabled": "伺服器 '{{serverName}}' 上的工具 '{{toolName}}' 已停用。可用的已啟用工具: {{availableTools}}" + "toolDisabled": "伺服器 '{{serverName}}' 上的工具 '{{toolName}}' 已停用。可用的已啟用工具: {{availableTools}}", + "exa_install_failed": "無法安裝 Exa MCP:{{errorMessage}}" }, "info": { "server_restarting": "正在重啟{{serverName}}MCP 伺服器...", @@ -23,7 +24,10 @@ "already_refreshing": "MCP 伺服器已在重新整理中。", "refreshing_all": "正在重新整理所有 MCP 伺服器...", "all_refreshed": "所有 MCP 伺服器已重新整理。", - "project_config_deleted": "專案MCP設定檔案已刪除。所有專案MCP伺服器已斷開連接。" + "project_config_deleted": "專案MCP設定檔案已刪除。所有專案MCP伺服器已斷開連接。", + "exa_install_prompt": "要安裝免費的 Exa MCP 伺服器,讓 Zoo 能搜尋及擷取網頁內容嗎?", + "exa_install_action": "安裝 Exa MCP", + "exa_install_success": "Exa MCP 已成功安裝。" }, "oauth": { "callback": { diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 4c66a2ca60..2c53b09d0a 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -152,6 +152,12 @@ const McpSettingsSchema = z.object({ mcpServers: z.record(ServerConfigSchema), }) +export const EXA_MCP_SERVER_CONFIG = { + type: "streamable-http", + url: "https://mcp.exa.ai/mcp", + alwaysAllow: ["web_search_exa", "web_fetch_exa"], +} as const + export class McpHub { private providerRef: WeakRef private disposables: vscode.Disposable[] = [] @@ -504,18 +510,56 @@ export class McpHub { ) const fileExists = await fileExistsAtPath(mcpSettingsFilePath) if (!fileExists) { - await fs.writeFile( - mcpSettingsFilePath, - `{ - "mcpServers": { - - } -}`, - ) + await safeWriteJson(mcpSettingsFilePath, { mcpServers: {} }, { prettyPrint: true }) } return mcpSettingsFilePath } + public hasExaServer(): boolean { + return this.connections.some((connection) => { + if (connection.server.name.toLowerCase() === "exa") { + return true + } + + try { + const config: unknown = JSON.parse(connection.server.config) + return ( + typeof config === "object" && + config !== null && + "url" in config && + config.url === EXA_MCP_SERVER_CONFIG.url + ) + } catch { + return false + } + }) + } + + public async installExaServer(): Promise { + if (this.hasExaServer()) { + return + } + + const configPath = await this.getMcpSettingsFilePath() + const content = await fs.readFile(configPath, "utf-8") + const config: unknown = JSON.parse(content) + + if (!config || typeof config !== "object") { + throw new Error("Invalid config structure") + } + + const mcpSettings = config as { mcpServers?: Record } + mcpSettings.mcpServers ??= {} + + if (Object.keys(mcpSettings.mcpServers).some((name) => name.toLowerCase() === "exa")) { + return + } + + mcpSettings.mcpServers.exa = EXA_MCP_SERVER_CONFIG + await safeWriteJson(configPath, { mcpServers: mcpSettings.mcpServers }, { prettyPrint: true }) + await this.updateServerConnections(mcpSettings.mcpServers, "global") + } + private async watchMcpSettingsFile(): Promise { // Skip if test environment is detected or VSCode APIs are not available if (process.env.NODE_ENV === "test" || !vscode.workspace.createFileSystemWatcher) { diff --git a/src/services/mcp/__tests__/McpHub.spec.ts b/src/services/mcp/__tests__/McpHub.spec.ts index 576514c5df..28c00c2911 100644 --- a/src/services/mcp/__tests__/McpHub.spec.ts +++ b/src/services/mcp/__tests__/McpHub.spec.ts @@ -1,4 +1,6 @@ import * as fs from "fs/promises" +import * as path from "path" +import * as vscode from "vscode" import type { Mock } from "vitest" import type { ExtensionContext, Uri } from "vscode" @@ -8,6 +10,7 @@ import type { ClineProvider } from "../../../core/webview/ClineProvider" import type { McpHub as McpHubType, McpConnection, ConnectedMcpConnection, DisconnectedMcpConnection } from "../McpHub" import { ServerConfigSchema, McpHub } from "../McpHub" import { OAUTH_FLOW_TIMEOUT_MS } from "../constants" +import { EXA_MCP_PROMPT_SHOWN_KEY, promptToInstallExaMcp } from "../promptToInstallExaMcp" import { t } from "../../../i18n" // Mock fs/promises before importing anything that uses it. @@ -192,6 +195,229 @@ describe("McpHub", () => { } }) + describe("Exa MCP", () => { + const settingsPath = path.join("/mock/settings/path", "mcp_settings.json") + + it("creates an empty settings file when one does not exist", async () => { + vi.mocked(fs.access).mockRejectedValueOnce(new Error("ENOENT")) + + const settingsPath = await mcpHub.getMcpSettingsFilePath() + + expect(settingsPath).toBe(path.join("/mock/settings/path", "mcp_settings.json")) + expect(safeWriteJson).toHaveBeenCalledWith(settingsPath, { mcpServers: {} }, { prettyPrint: true }) + }) + + it("detects Exa configured under a custom server name", () => { + mcpHub.connections = [ + { + type: "disconnected", + server: { + name: "web-search", + config: JSON.stringify({ type: "streamable-http", url: "https://mcp.exa.ai/mcp" }), + status: "disconnected", + source: "global", + }, + client: null, + transport: null, + }, + ] + + expect(mcpHub.hasExaServer()).toBe(true) + }) + + it("detects Exa by server name without parsing its config", () => { + mcpHub.connections = [ + { + type: "disconnected", + server: { + name: "EXA", + config: "not-json", + status: "disconnected", + source: "project", + }, + client: null, + transport: null, + }, + ] + + expect(mcpHub.hasExaServer()).toBe(true) + }) + + it("ignores malformed non-Exa server configs", () => { + mcpHub.connections = [ + { + type: "disconnected", + server: { + name: "other", + config: "not-json", + status: "disconnected", + source: "global", + }, + client: null, + transport: null, + }, + ] + + expect(mcpHub.hasExaServer()).toBe(false) + }) + + it("installs Exa without replacing existing servers", async () => { + const existingConfig = { + mcpServers: { + existing: { type: "stdio", command: "node" }, + }, + } + vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(existingConfig)) + vi.spyOn(mcpHub, "updateServerConnections").mockResolvedValue(undefined) + + await mcpHub.installExaServer() + + expect(safeWriteJson).toHaveBeenCalledWith( + settingsPath, + { + mcpServers: { + existing: { type: "stdio", command: "node" }, + exa: { + type: "streamable-http", + url: "https://mcp.exa.ai/mcp", + alwaysAllow: ["web_search_exa", "web_fetch_exa"], + }, + }, + }, + { prettyPrint: true }, + ) + }) + + it("does not install Exa when the hub already contains it", async () => { + vi.spyOn(mcpHub, "hasExaServer").mockReturnValue(true) + const readCount = vi.mocked(fs.readFile).mock.calls.length + const writeCount = vi.mocked(safeWriteJson).mock.calls.length + + await mcpHub.installExaServer() + + expect(fs.readFile).toHaveBeenCalledTimes(readCount) + expect(safeWriteJson).toHaveBeenCalledTimes(writeCount) + }) + + it("does not overwrite an Exa entry found directly in the settings file", async () => { + vi.mocked(fs.readFile).mockResolvedValue( + JSON.stringify({ + mcpServers: { + ExA: { type: "streamable-http", url: "https://custom.example.com/mcp" }, + }, + }), + ) + + await mcpHub.installExaServer() + + expect(safeWriteJson).not.toHaveBeenCalled() + }) + + it("creates the MCP servers object when installing into a valid empty config", async () => { + vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({})) + vi.spyOn(mcpHub, "updateServerConnections").mockResolvedValue(undefined) + + await mcpHub.installExaServer() + + expect(safeWriteJson).toHaveBeenCalledWith( + settingsPath, + { mcpServers: { exa: expect.objectContaining({ url: "https://mcp.exa.ai/mcp" }) } }, + { prettyPrint: true }, + ) + }) + + it("rejects invalid settings structures", async () => { + vi.mocked(fs.readFile).mockResolvedValue("null") + + await expect(mcpHub.installExaServer()).rejects.toThrow("Invalid config structure") + expect(safeWriteJson).not.toHaveBeenCalled() + }) + }) + + describe("Exa MCP installation prompt", () => { + const createPromptContext = (wasPrompted = false) => ({ + globalState: { + get: vi.fn().mockReturnValue(wasPrompted), + update: vi.fn().mockResolvedValue(undefined), + }, + }) + + it("does not prompt when Exa is already configured", async () => { + const context = createPromptContext() + vi.spyOn(mcpHub, "hasExaServer").mockReturnValue(true) + + await promptToInstallExaMcp(context, mcpHub) + + expect(vscode.window.showInformationMessage).not.toHaveBeenCalled() + expect(context.globalState.update).not.toHaveBeenCalled() + }) + + it("does not prompt after it has already been shown", async () => { + const context = createPromptContext(true) + vi.spyOn(mcpHub, "hasExaServer").mockReturnValue(false) + + await promptToInstallExaMcp(context, mcpHub) + + expect(vscode.window.showInformationMessage).not.toHaveBeenCalled() + }) + + it("records dismissal so the prompt is only shown once", async () => { + const context = createPromptContext() + vi.spyOn(mcpHub, "hasExaServer").mockReturnValue(false) + vi.mocked(vscode.window.showInformationMessage).mockResolvedValue(undefined) + const installSpy = vi.spyOn(mcpHub, "installExaServer").mockResolvedValue(undefined) + + await promptToInstallExaMcp(context, mcpHub) + + expect(context.globalState.update).toHaveBeenCalledWith(EXA_MCP_PROMPT_SHOWN_KEY, true) + expect(installSpy).not.toHaveBeenCalled() + }) + + it("installs Exa when the user accepts", async () => { + const context = createPromptContext() + vi.spyOn(mcpHub, "hasExaServer").mockReturnValue(false) + vi.mocked(vscode.window.showInformationMessage).mockResolvedValueOnce({ + title: t("mcp:info.exa_install_action"), + }) + const installSpy = vi.spyOn(mcpHub, "installExaServer").mockResolvedValue(undefined) + + await promptToInstallExaMcp(context, mcpHub) + + expect(installSpy).toHaveBeenCalledOnce() + expect(context.globalState.update).toHaveBeenCalledWith(EXA_MCP_PROMPT_SHOWN_KEY, true) + }) + + it("shows the installation error when installation fails", async () => { + const context = createPromptContext() + vi.spyOn(mcpHub, "hasExaServer").mockReturnValue(false) + vi.mocked(vscode.window.showInformationMessage).mockResolvedValueOnce({ + title: t("mcp:info.exa_install_action"), + }) + vi.spyOn(mcpHub, "installExaServer").mockRejectedValue(new Error("network unavailable")) + + await promptToInstallExaMcp(context, mcpHub) + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + t("mcp:errors.exa_install_failed", { errorMessage: "network unavailable" }), + ) + }) + + it("formats non-Error installation failures", async () => { + const context = createPromptContext() + vi.spyOn(mcpHub, "hasExaServer").mockReturnValue(false) + vi.mocked(vscode.window.showInformationMessage).mockResolvedValueOnce({ + title: t("mcp:info.exa_install_action"), + }) + vi.spyOn(mcpHub, "installExaServer").mockRejectedValue("installation failed") + + await promptToInstallExaMcp(context, mcpHub) + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( + t("mcp:errors.exa_install_failed", { errorMessage: "installation failed" }), + ) + }) + }) + describe("Discriminated union type handling", () => { it("should create connected connections with proper type", async () => { // Mock StdioClientTransport diff --git a/src/services/mcp/promptToInstallExaMcp.ts b/src/services/mcp/promptToInstallExaMcp.ts new file mode 100644 index 0000000000..7229bfbdac --- /dev/null +++ b/src/services/mcp/promptToInstallExaMcp.ts @@ -0,0 +1,33 @@ +import * as vscode from "vscode" + +import { t } from "../../i18n" +import type { McpHub } from "./McpHub" + +export const EXA_MCP_PROMPT_SHOWN_KEY = "exaMcpInstallPromptShown" + +type ExaMcpPromptContext = { + globalState: Pick +} + +export async function promptToInstallExaMcp(context: ExaMcpPromptContext, mcpHub: McpHub): Promise { + if (mcpHub.hasExaServer() || context.globalState.get(EXA_MCP_PROMPT_SHOWN_KEY, false)) { + return + } + + await context.globalState.update(EXA_MCP_PROMPT_SHOWN_KEY, true) + + const installAction: vscode.MessageItem = { title: t("mcp:info.exa_install_action") } + const selection = await vscode.window.showInformationMessage(t("mcp:info.exa_install_prompt"), installAction) + + if (selection?.title !== installAction.title) { + return + } + + try { + await mcpHub.installExaServer() + void vscode.window.showInformationMessage(t("mcp:info.exa_install_success")) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + void vscode.window.showErrorMessage(t("mcp:errors.exa_install_failed", { errorMessage })) + } +}