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

Co-authored-by: cte <cestreich@gmail.com>
This commit is contained in:
robertheadley 2025-05-06 02:17:20 -05:00 committed by GitHub
parent 82dd3d5c4b
commit 78b2083467
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 199 additions and 44 deletions

20
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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
}
/**

View file

@ -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: {

View file

@ -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",

View file

@ -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",

View file

@ -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>
)
}

View file

@ -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 */}

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

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

View file

@ -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",

View file

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

View file

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

View file

@ -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",

View file

@ -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",

View file

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

View file

@ -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ı",

View file

@ -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",

View file

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

View file

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