Browse Source

feat: Add error console to MCP servers - Edited with Roo Code and Anthropic Claude 3.5 (#2722)

Co-authored-by: cte <[email protected]>
robertheadley 9 months ago
parent
commit
78b2083467

+ 15 - 5
package-lock.json

@@ -14,7 +14,7 @@
 				"@aws-sdk/client-bedrock-runtime": "^3.779.0",
 				"@google/genai": "^0.12.0",
 				"@mistralai/mistralai": "^1.3.6",
-				"@modelcontextprotocol/sdk": "^1.7.0",
+				"@modelcontextprotocol/sdk": "^1.9.0",
 				"@types/clone-deep": "^4.0.4",
 				"@types/pdf-parse": "^1.1.4",
 				"@types/tmp": "^0.2.6",
@@ -6629,17 +6629,18 @@
 			"integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
 		},
 		"node_modules/@modelcontextprotocol/sdk": {
-			"version": "1.7.0",
-			"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz",
-			"integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==",
+			"version": "1.9.0",
+			"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz",
+			"integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==",
 			"license": "MIT",
 			"dependencies": {
 				"content-type": "^1.0.5",
 				"cors": "^2.8.5",
+				"cross-spawn": "^7.0.3",
 				"eventsource": "^3.0.2",
 				"express": "^5.0.1",
 				"express-rate-limit": "^7.5.0",
-				"pkce-challenge": "^4.1.0",
+				"pkce-challenge": "^5.0.0",
 				"raw-body": "^3.0.0",
 				"zod": "^3.23.8",
 				"zod-to-json-schema": "^3.24.1"
@@ -6648,6 +6649,15 @@
 				"node": ">=18"
 			}
 		},
+		"node_modules/@modelcontextprotocol/sdk/node_modules/pkce-challenge": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
+			"integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
+			"license": "MIT",
+			"engines": {
+				"node": ">=16.20.0"
+			}
+		},
 		"node_modules/@modelcontextprotocol/sdk/node_modules/zod": {
 			"version": "3.24.2",
 			"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",

+ 1 - 1
package.json

@@ -370,7 +370,7 @@
 		"@aws-sdk/client-bedrock-runtime": "^3.779.0",
 		"@google/genai": "^0.12.0",
 		"@mistralai/mistralai": "^1.3.6",
-		"@modelcontextprotocol/sdk": "^1.7.0",
+		"@modelcontextprotocol/sdk": "^1.9.0",
 		"@types/clone-deep": "^4.0.4",
 		"@types/pdf-parse": "^1.1.4",
 		"@types/tmp": "^0.2.6",

+ 25 - 6
src/services/mcp/McpHub.ts

@@ -541,6 +541,7 @@ export class McpHub {
 					disabled: config.disabled,
 					source,
 					projectPath: source === "project" ? vscode.workspace.workspaceFolders?.[0]?.uri.fsPath : undefined,
+					errorHistory: [],
 				},
 				client,
 				transport,
@@ -567,13 +568,31 @@ export class McpHub {
 		}
 	}
 
-	private appendErrorMessage(connection: McpConnection, error: string) {
+	private appendErrorMessage(connection: McpConnection, error: string, level: "error" | "warn" | "info" = "error") {
 		const MAX_ERROR_LENGTH = 1000
-		const newError = connection.server.error ? `${connection.server.error}\n${error}` : error
-		connection.server.error =
-			newError.length > MAX_ERROR_LENGTH
-				? `${newError.substring(0, MAX_ERROR_LENGTH)}...(error message truncated)`
-				: newError
+		const truncatedError =
+			error.length > MAX_ERROR_LENGTH
+				? `${error.substring(0, MAX_ERROR_LENGTH)}...(error message truncated)`
+				: error
+
+		// Add to error history
+		if (!connection.server.errorHistory) {
+			connection.server.errorHistory = []
+		}
+
+		connection.server.errorHistory.push({
+			message: truncatedError,
+			timestamp: Date.now(),
+			level,
+		})
+
+		// Keep only the last 100 errors
+		if (connection.server.errorHistory.length > 100) {
+			connection.server.errorHistory = connection.server.errorHistory.slice(-100)
+		}
+
+		// Update current error display
+		connection.server.error = truncatedError
 	}
 
 	/**

+ 12 - 0
src/shared/mcp.ts

@@ -1,8 +1,15 @@
+export type McpErrorEntry = {
+	message: string
+	timestamp: number
+	level: "error" | "warn" | "info"
+}
+
 export type McpServer = {
 	name: string
 	config: string
 	status: "connected" | "connecting" | "disconnected"
 	error?: string
+	errorHistory?: McpErrorEntry[]
 	tools?: McpTool[]
 	resources?: McpResource[]
 	resourceTemplates?: McpResourceTemplate[]
@@ -55,6 +62,11 @@ export type McpToolCallResponse = {
 				data: string
 				mimeType: string
 		  }
+		| {
+				type: "audio"
+				data: string
+				mimeType: string
+		  }
 		| {
 				type: "resource"
 				resource: {

+ 11 - 0
webview-ui/package-lock.json

@@ -27,6 +27,7 @@
 				"class-variance-authority": "^0.7.1",
 				"clsx": "^2.1.1",
 				"cmdk": "^1.0.0",
+				"date-fns": "^4.1.0",
 				"debounce": "^2.1.1",
 				"fast-deep-equal": "^3.1.3",
 				"fzf": "^0.5.2",
@@ -10190,6 +10191,16 @@
 				"url": "https://github.com/sponsors/ljharb"
 			}
 		},
+		"node_modules/date-fns": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+			"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+			"license": "MIT",
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/kossnocorp"
+			}
+		},
 		"node_modules/dayjs": {
 			"version": "1.11.13",
 			"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",

+ 1 - 0
webview-ui/package.json

@@ -36,6 +36,7 @@
 		"class-variance-authority": "^0.7.1",
 		"clsx": "^2.1.1",
 		"cmdk": "^1.0.0",
+		"date-fns": "^4.1.0",
 		"debounce": "^2.1.1",
 		"fast-deep-equal": "^3.1.3",
 		"fzf": "^0.5.2",

+ 32 - 0
webview-ui/src/components/mcp/McpErrorRow.tsx

@@ -0,0 +1,32 @@
+import { useMemo } from "react"
+import { formatRelative } from "date-fns"
+
+import type { McpErrorEntry } from "@roo/shared/mcp"
+
+type McpErrorRowProps = {
+	error: McpErrorEntry
+}
+
+export const McpErrorRow = ({ error }: McpErrorRowProps) => {
+	const color = useMemo(() => {
+		switch (error.level) {
+			case "error":
+				return "var(--vscode-testing-iconFailed)"
+			case "warn":
+				return "var(--vscode-charts-yellow)"
+			case "info":
+				return "var(--vscode-testing-iconPassed)"
+		}
+	}, [error.level])
+
+	return (
+		<div className="text-sm bg-vscode-textCodeBlock-background border-l-2 p-2" style={{ borderColor: color }}>
+			<div className="mb-1" style={{ color }}>
+				{error.message}
+			</div>
+			<div className="text-xs text-vscode-descriptionForeground">
+				{formatRelative(error.timestamp, new Date())}
+			</div>
+		</div>
+	)
+}

+ 22 - 0
webview-ui/src/components/mcp/McpView.tsx

@@ -29,6 +29,7 @@ import { Tab, TabContent, TabHeader } from "../common/Tab"
 import McpToolRow from "./McpToolRow"
 import McpResourceRow from "./McpResourceRow"
 import McpEnabledToggle from "./McpEnabledToggle"
+import { McpErrorRow } from "./McpErrorRow"
 
 type McpViewProps = {
 	onDone: () => void
@@ -42,6 +43,7 @@ const McpView = ({ onDone }: McpViewProps) => {
 		enableMcpServerCreation,
 		setEnableMcpServerCreation,
 	} = useExtensionState()
+
 	const { t } = useAppTranslation()
 
 	return (
@@ -330,6 +332,9 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
 								{t("mcp:tabs.resources")} (
 								{[...(server.resourceTemplates || []), ...(server.resources || [])].length || 0})
 							</VSCodePanelTab>
+							<VSCodePanelTab id="errors">
+								{t("mcp:tabs.errors")} ({server.errorHistory?.length || 0})
+							</VSCodePanelTab>
 
 							<VSCodePanelView id="tools-view">
 								{server.tools && server.tools.length > 0 ? (
@@ -372,6 +377,23 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
 									</div>
 								)}
 							</VSCodePanelView>
+
+							<VSCodePanelView id="errors-view">
+								{server.errorHistory && server.errorHistory.length > 0 ? (
+									<div
+										style={{ display: "flex", flexDirection: "column", gap: "8px", width: "100%" }}>
+										{[...server.errorHistory]
+											.sort((a, b) => b.timestamp - a.timestamp)
+											.map((error, index) => (
+												<McpErrorRow key={`${error.timestamp}-${index}`} error={error} />
+											))}
+									</div>
+								) : (
+									<div style={{ padding: "10px 0", color: "var(--vscode-descriptionForeground)" }}>
+										{t("mcp:emptyState.noErrors")}
+									</div>
+								)}
+							</VSCodePanelView>
 						</VSCodePanels>
 
 						{/* Network Timeout */}

+ 5 - 2
webview-ui/src/i18n/locales/ca/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Eines",
-		"resources": "Recursos"
+		"resources": "Recursos",
+		"errors": "Errors"
 	},
 	"emptyState": {
 		"noTools": "No s'han trobat eines",
-		"noResources": "No s'han trobat recursos"
+		"noResources": "No s'han trobat recursos",
+		"noLogs": "No s'han trobat registres",
+		"noErrors": "No s'han trobat errors"
 	},
 	"networkTimeout": {
 		"label": "Temps d'espera de xarxa",

+ 5 - 2
webview-ui/src/i18n/locales/de/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Tools",
-		"resources": "Ressourcen"
+		"resources": "Ressourcen",
+		"errors": "Fehler"
 	},
 	"emptyState": {
 		"noTools": "Keine Tools gefunden",
-		"noResources": "Keine Ressourcen gefunden"
+		"noResources": "Keine Ressourcen gefunden",
+		"noLogs": "Keine Logs gefunden",
+		"noErrors": "Keine Fehler gefunden"
 	},
 	"networkTimeout": {
 		"label": "Netzwerk-Timeout",

+ 5 - 2
webview-ui/src/i18n/locales/en/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Tools",
-		"resources": "Resources"
+		"resources": "Resources",
+		"errors": "Errors"
 	},
 	"emptyState": {
 		"noTools": "No tools found",
-		"noResources": "No resources found"
+		"noResources": "No resources found",
+		"noLogs": "No logs found",
+		"noErrors": "No errors found"
 	},
 	"networkTimeout": {
 		"label": "Network Timeout",

+ 5 - 2
webview-ui/src/i18n/locales/es/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Herramientas",
-		"resources": "Recursos"
+		"resources": "Recursos",
+		"errors": "Errores"
 	},
 	"emptyState": {
 		"noTools": "No se encontraron herramientas",
-		"noResources": "No se encontraron recursos"
+		"noResources": "No se encontraron recursos",
+		"noLogs": "No se encontraron registros",
+		"noErrors": "No se encontraron errores"
 	},
 	"networkTimeout": {
 		"label": "Tiempo de espera de red",

+ 5 - 2
webview-ui/src/i18n/locales/fr/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Outils",
-		"resources": "Ressources"
+		"resources": "Ressources",
+		"errors": "Erreurs"
 	},
 	"emptyState": {
 		"noTools": "Aucun outil trouvé",
-		"noResources": "Aucune ressource trouvée"
+		"noResources": "Aucune ressource trouvée",
+		"noLogs": "Aucun journal trouvé",
+		"noErrors": "Aucune erreur trouvée"
 	},
 	"networkTimeout": {
 		"label": "Délai d'attente réseau",

+ 5 - 2
webview-ui/src/i18n/locales/hi/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "उपकरण",
-		"resources": "संसाधन"
+		"resources": "संसाधन",
+		"errors": "त्रुटियाँ"
 	},
 	"emptyState": {
 		"noTools": "कोई उपकरण नहीं मिला",
-		"noResources": "कोई संसाधन नहीं मिला"
+		"noResources": "कोई संसाधन नहीं मिला",
+		"noLogs": "कोई लॉग नहीं मिला",
+		"noErrors": "कोई त्रुटि नहीं मिली"
 	},
 	"networkTimeout": {
 		"label": "नेटवर्क टाइमआउट",

+ 5 - 2
webview-ui/src/i18n/locales/it/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Strumenti",
-		"resources": "Risorse"
+		"resources": "Risorse",
+		"errors": "Errori"
 	},
 	"emptyState": {
 		"noTools": "Nessuno strumento trovato",
-		"noResources": "Nessuna risorsa trovata"
+		"noResources": "Nessuna risorsa trovata",
+		"noLogs": "Nessun log trovato",
+		"noErrors": "Nessun errore trovato"
 	},
 	"networkTimeout": {
 		"label": "Timeout di rete",

+ 5 - 2
webview-ui/src/i18n/locales/ja/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "ツール",
-		"resources": "リソース"
+		"resources": "リソース",
+		"errors": "エラー"
 	},
 	"emptyState": {
 		"noTools": "ツールが見つかりません",
-		"noResources": "リソースが見つかりません"
+		"noResources": "リソースが見つかりません",
+		"noLogs": "ログが見つかりません",
+		"noErrors": "エラーが見つかりません"
 	},
 	"networkTimeout": {
 		"label": "ネットワークタイムアウト",

+ 5 - 2
webview-ui/src/i18n/locales/ko/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "도구",
-		"resources": "리소스"
+		"resources": "리소스",
+		"errors": "오류"
 	},
 	"emptyState": {
 		"noTools": "도구를 찾을 수 없음",
-		"noResources": "리소스를 찾을 수 없음"
+		"noResources": "리소스를 찾을 수 없음",
+		"noLogs": "로그를 찾을 수 없음",
+		"noErrors": "오류를 찾을 수 없음"
 	},
 	"networkTimeout": {
 		"label": "네트워크 타임아웃",

+ 5 - 2
webview-ui/src/i18n/locales/pl/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Narzędzia",
-		"resources": "Zasoby"
+		"resources": "Zasoby",
+		"errors": "Błędy"
 	},
 	"emptyState": {
 		"noTools": "Nie znaleziono narzędzi",
-		"noResources": "Nie znaleziono zasobów"
+		"noResources": "Nie znaleziono zasobów",
+		"noLogs": "Nie znaleziono logów",
+		"noErrors": "Nie znaleziono błędów"
 	},
 	"networkTimeout": {
 		"label": "Limit czasu sieci",

+ 5 - 2
webview-ui/src/i18n/locales/pt-BR/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Ferramentas",
-		"resources": "Recursos"
+		"resources": "Recursos",
+		"errors": "Erros"
 	},
 	"emptyState": {
 		"noTools": "Nenhuma ferramenta encontrada",
-		"noResources": "Nenhum recurso encontrado"
+		"noResources": "Nenhum recurso encontrado",
+		"noLogs": "Nenhum log encontrado",
+		"noErrors": "Nenhum erro encontrado"
 	},
 	"networkTimeout": {
 		"label": "Tempo limite de rede",

+ 5 - 2
webview-ui/src/i18n/locales/ru/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Инструменты",
-		"resources": "Ресурсы"
+		"resources": "Ресурсы",
+		"errors": "Ошибки"
 	},
 	"emptyState": {
 		"noTools": "Инструменты не найдены",
-		"noResources": "Ресурсы не найдены"
+		"noResources": "Ресурсы не найдены",
+		"noLogs": "Логи не найдены",
+		"noErrors": "Ошибки не найдены"
 	},
 	"networkTimeout": {
 		"label": "Тайм-аут сети",

+ 5 - 2
webview-ui/src/i18n/locales/tr/mcp.json

@@ -19,11 +19,14 @@
 	},
 	"tabs": {
 		"tools": "Araçlar",
-		"resources": "Kaynaklar"
+		"resources": "Kaynaklar",
+		"errors": "Hatalar"
 	},
 	"emptyState": {
 		"noTools": "Araç bulunamadı",
-		"noResources": "Kaynak bulunamadı"
+		"noResources": "Kaynak bulunamadı",
+		"noLogs": "Günlük bulunamadı",
+		"noErrors": "Hata bulunamadı"
 	},
 	"networkTimeout": {
 		"label": "Ağ Zaman Aşımı",

+ 5 - 2
webview-ui/src/i18n/locales/vi/mcp.json

@@ -20,11 +20,14 @@
 	},
 	"tabs": {
 		"tools": "Công cụ",
-		"resources": "Tài nguyên"
+		"resources": "Tài nguyên",
+		"errors": "Lỗi"
 	},
 	"emptyState": {
 		"noTools": "Không tìm thấy công cụ nào",
-		"noResources": "Không tìm thấy tài nguyên nào"
+		"noResources": "Không tìm thấy tài nguyên nào",
+		"noLogs": "Không tìm thấy nhật ký",
+		"noErrors": "Không tìm thấy lỗi"
 	},
 	"networkTimeout": {
 		"label": "Thời gian chờ mạng",

+ 5 - 2
webview-ui/src/i18n/locales/zh-CN/mcp.json

@@ -20,11 +20,14 @@
 	},
 	"tabs": {
 		"tools": "工具",
-		"resources": "资源"
+		"resources": "资源",
+		"errors": "错误"
 	},
 	"emptyState": {
 		"noTools": "未找到工具",
-		"noResources": "未找到资源"
+		"noResources": "未找到资源",
+		"noLogs": "未找到日志",
+		"noErrors": "未找到错误"
 	},
 	"networkTimeout": {
 		"label": "请求超时",

+ 5 - 2
webview-ui/src/i18n/locales/zh-TW/mcp.json

@@ -20,11 +20,14 @@
 	},
 	"tabs": {
 		"tools": "工具",
-		"resources": "資源"
+		"resources": "資源",
+		"errors": "錯誤"
 	},
 	"emptyState": {
 		"noTools": "找不到工具",
-		"noResources": "找不到資源"
+		"noResources": "找不到資源",
+		"noLogs": "找不到日誌",
+		"noErrors": "找不到錯誤"
 	},
 	"networkTimeout": {
 		"label": "網路逾時",