mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
added some logging + error handling
This commit is contained in:
parent
7d67000d1f
commit
e3cfb405fb
5 changed files with 209 additions and 22 deletions
|
|
@ -14,7 +14,7 @@ import { selectImages } from "../../integrations/misc/process-images"
|
|||
import { getTheme } from "../../integrations/theme/getTheme"
|
||||
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
|
||||
import { McpHub } from "../../services/mcp/McpHub"
|
||||
import { McpMarketplaceCatalog, McpMarketplaceItem } from "../../shared/mcp"
|
||||
import { McpDownloadResponse, McpMarketplaceCatalog, McpMarketplaceItem, McpServer } from "../../shared/mcp"
|
||||
import { FirebaseAuthManager, UserInfo } from "../../services/auth/FirebaseAuthManager"
|
||||
import { ApiProvider, ModelInfo } from "../../shared/api"
|
||||
import { findLast } from "../../shared/array"
|
||||
|
|
@ -404,7 +404,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
items: (response.data || []).map((item: any) => ({
|
||||
...item,
|
||||
githubStars: item.githubStars ?? 0,
|
||||
downloads: item.downloads ?? 0,
|
||||
downloadCount: item.downloadCount ?? 0,
|
||||
tags: item.tags ?? [],
|
||||
})),
|
||||
}
|
||||
|
|
@ -438,30 +438,59 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
|
||||
private async downloadMcp(mcpId: string) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
// First check if we already have this MCP server installed
|
||||
const servers = this.mcpHub?.getServers() || []
|
||||
const isInstalled = servers.some((server: McpServer) => {
|
||||
try {
|
||||
const config = JSON.parse(server.config)
|
||||
const serverConfig = config.mcpServers[server.name]
|
||||
const githubUrl = serverConfig.args?.find((arg: string) => arg.includes("github.com"))
|
||||
return githubUrl?.includes(mcpId)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (isInstalled) {
|
||||
throw new Error("This MCP server is already installed")
|
||||
}
|
||||
|
||||
// Fetch server details from marketplace
|
||||
const response = await axios.post<McpDownloadResponse>(
|
||||
"https://api.cline.bot/v1/mcp/download",
|
||||
{ mcpId },
|
||||
{
|
||||
mcpId,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: { "Content-Type": "application/json" },
|
||||
timeout: 10000,
|
||||
},
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
throw new Error("Invalid response from MCP download API")
|
||||
throw new Error("Invalid response from MCP marketplace API")
|
||||
}
|
||||
|
||||
console.log("[downloadMcp] Response from download API", { response })
|
||||
|
||||
const mcpDetails = response.data
|
||||
|
||||
// Validate required fields
|
||||
if (!mcpDetails.githubUrl) {
|
||||
throw new Error("Missing GitHub URL in MCP download response")
|
||||
}
|
||||
if (!mcpDetails.readmeContent) {
|
||||
throw new Error("Missing README content in MCP download response")
|
||||
}
|
||||
|
||||
// Send details to webview
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpDownloadDetails",
|
||||
mcpDownloadDetails: mcpDetails,
|
||||
})
|
||||
|
||||
// Create a new task for Cline to set up the MCP server
|
||||
// Create task with context from README
|
||||
const task = `Set up the MCP server from ${mcpDetails.githubUrl}. Here's some additional context from the README:\n\n${mcpDetails.readmeContent}`
|
||||
|
||||
// Initialize task and show chat view
|
||||
await this.initClineWithTask(task)
|
||||
await this.postMessageToWebview({
|
||||
type: "action",
|
||||
|
|
@ -469,7 +498,23 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to download MCP:", error)
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to download MCP"
|
||||
let errorMessage = "Failed to download MCP"
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.code === "ECONNABORTED") {
|
||||
errorMessage = "Request timed out. Please try again."
|
||||
} else if (error.response?.status === 404) {
|
||||
errorMessage = "MCP server not found in marketplace."
|
||||
} else if (error.response?.status === 500) {
|
||||
errorMessage = "Internal server error. Please try again later."
|
||||
} else if (!error.response && error.request) {
|
||||
errorMessage = "Network error. Please check your internet connection."
|
||||
}
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
// Show error in both notification and marketplace UI
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpDownloadDetails",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { AutoApprovalSettings } from "./AutoApprovalSettings"
|
|||
import { BrowserSettings } from "./BrowserSettings"
|
||||
import { ChatSettings } from "./ChatSettings"
|
||||
import { HistoryItem } from "./HistoryItem"
|
||||
import { McpServer, McpMarketplaceCatalog, McpMarketplaceItem } from "./mcp"
|
||||
import { McpServer, McpMarketplaceCatalog, McpMarketplaceItem, McpDownloadResponse } from "./mcp"
|
||||
|
||||
// webview will hold state
|
||||
export interface ExtensionMessage {
|
||||
|
|
@ -50,7 +50,7 @@ export interface ExtensionMessage {
|
|||
mcpServers?: McpServer[]
|
||||
mcpMarketplaceCatalog?: McpMarketplaceCatalog
|
||||
error?: string
|
||||
mcpDownloadDetails?: McpMarketplaceItem
|
||||
mcpDownloadDetails?: McpDownloadResponse
|
||||
}
|
||||
|
||||
export type Platform = "aix" | "darwin" | "freebsd" | "linux" | "openbsd" | "sunos" | "win32" | "unknown"
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export interface McpMarketplaceItem {
|
|||
readmeContent?: string
|
||||
isRecommended: boolean
|
||||
githubStars: number
|
||||
downloads: number
|
||||
downloadCount: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
lastGithubSync: string
|
||||
|
|
@ -90,3 +90,13 @@ export interface McpMarketplaceItem {
|
|||
export interface McpMarketplaceCatalog {
|
||||
items: McpMarketplaceItem[]
|
||||
}
|
||||
|
||||
export interface McpDownloadResponse {
|
||||
mcpId: string
|
||||
githubUrl: string
|
||||
name: string
|
||||
author: string
|
||||
description: string
|
||||
readmeContent: string
|
||||
requiresApiKey: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,17 @@ interface McpMarketplaceCardProps {
|
|||
}
|
||||
|
||||
const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps) => {
|
||||
const isInstalled = installedServers.some((server) => server.name === item.mcpId)
|
||||
const isInstalled = installedServers.some((server) => {
|
||||
try {
|
||||
const config = JSON.parse(server.config)
|
||||
const serverConfig = config.mcpServers[server.name]
|
||||
// Extract GitHub URL from args if it's an npm package
|
||||
const githubUrl = serverConfig.args?.find((arg: string) => arg.includes("github.com"))
|
||||
return githubUrl?.includes(item.mcpId) || githubUrl?.includes(item.githubUrl)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
const [isDownloading, setIsDownloading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -94,7 +104,7 @@ const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps)
|
|||
</div>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span className="codicon codicon-cloud-download" />
|
||||
{item.downloads?.toLocaleString() ?? 0}
|
||||
{item.downloadCount?.toLocaleString() ?? 0}
|
||||
</div>
|
||||
{item.requiresApiKey && (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,70 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
import { McpMarketplaceItem } from "../../../../../src/shared/mcp"
|
||||
import { useExtensionState } from "../../../context/ExtensionStateContext"
|
||||
import { vscode } from "../../../utils/vscode"
|
||||
import McpMarketplaceCard from "./McpMarketplaceCard"
|
||||
|
||||
const searchInputStyles = {
|
||||
width: "100%",
|
||||
padding: "4px 8px 4px 28px",
|
||||
background: "var(--vscode-input-background)",
|
||||
border: "1px solid var(--vscode-input-border)",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
borderRadius: "2px",
|
||||
outline: "none",
|
||||
transition: "border-color 0.1s ease-in-out, opacity 0.1s ease-in-out",
|
||||
}
|
||||
|
||||
const selectStyles = {
|
||||
padding: "4px 8px",
|
||||
background: "var(--vscode-dropdown-background)",
|
||||
border: "1px solid var(--vscode-dropdown-border)",
|
||||
color: "var(--vscode-dropdown-foreground)",
|
||||
borderRadius: "2px",
|
||||
outline: "none",
|
||||
transition: "border-color 0.1s ease-in-out, opacity 0.1s ease-in-out",
|
||||
}
|
||||
|
||||
const McpMarketplaceView = () => {
|
||||
const { mcpServers } = useExtensionState()
|
||||
const [items, setItems] = useState<McpMarketplaceItem[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
|
||||
const [sortBy, setSortBy] = useState<"downloadCount" | "stars" | "name">("downloadCount")
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const uniqueCategories = new Set(items.map((item) => item.category))
|
||||
return Array.from(uniqueCategories).sort()
|
||||
}, [items])
|
||||
|
||||
const filteredItems = useMemo(() => {
|
||||
return items
|
||||
.filter((item) => {
|
||||
const matchesSearch =
|
||||
searchQuery === "" ||
|
||||
item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
item.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
item.tags.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
const matchesCategory = !selectedCategory || item.category === selectedCategory
|
||||
return matchesSearch && matchesCategory
|
||||
})
|
||||
.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case "downloadCount":
|
||||
return b.downloadCount - a.downloadCount
|
||||
case "stars":
|
||||
return b.githubStars - a.githubStars
|
||||
case "name":
|
||||
return a.name.localeCompare(b.name)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
})
|
||||
}, [items, searchQuery, selectedCategory, sortBy])
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
|
|
@ -89,13 +143,79 @@ const McpMarketplaceView = () => {
|
|||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "12px", padding: "0 20px" }}>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", marginBottom: "8px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "16px",
|
||||
gap: "16px",
|
||||
}}>
|
||||
<div style={{ display: "flex", gap: "8px", alignItems: "center", flex: 1 }}>
|
||||
<div style={{ position: "relative", flex: 1 }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search MCPs..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="mcp-search-input"
|
||||
style={searchInputStyles}
|
||||
/>
|
||||
<span
|
||||
className="codicon codicon-search"
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: "8px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "var(--vscode-input-placeholderForeground)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
value={selectedCategory || ""}
|
||||
onChange={(e) => setSelectedCategory(e.target.value || null)}
|
||||
className="mcp-select"
|
||||
style={selectStyles}>
|
||||
<option value="">All Categories</option>
|
||||
{categories.map((category) => (
|
||||
<option key={category} value={category}>
|
||||
{category}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value as "downloadCount" | "stars" | "name")}
|
||||
className="mcp-select"
|
||||
style={selectStyles}>
|
||||
<option value="downloadCount">Sort by Downloads</option>
|
||||
<option value="stars">Sort by Stars</option>
|
||||
<option value="name">Sort by Name</option>
|
||||
</select>
|
||||
</div>
|
||||
<VSCodeButton appearance="secondary" onClick={() => fetchMarketplace(true)} disabled={isRefreshing}>
|
||||
<span className="codicon codicon-refresh" style={{ marginRight: "6px" }} />
|
||||
Refresh
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
{items.length === 0 ? (
|
||||
<style>
|
||||
{`
|
||||
.mcp-search-input:focus {
|
||||
border-color: var(--vscode-focusBorder) !important;
|
||||
}
|
||||
.mcp-search-input:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.mcp-select:focus {
|
||||
border-color: var(--vscode-focusBorder) !important;
|
||||
}
|
||||
.mcp-select:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
{filteredItems.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
|
|
@ -105,10 +225,12 @@ const McpMarketplaceView = () => {
|
|||
padding: "20px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
No MCP servers found in the marketplace
|
||||
{searchQuery || selectedCategory
|
||||
? "No matching MCP servers found"
|
||||
: "No MCP servers found in the marketplace"}
|
||||
</div>
|
||||
) : (
|
||||
items.map((item) => <McpMarketplaceCard key={item.mcpId} item={item} installedServers={mcpServers} />)
|
||||
filteredItems.map((item) => <McpMarketplaceCard key={item.mcpId} item={item} installedServers={mcpServers} />)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue