Skip to content

Commit abbe40e

Browse files
authored
[PROTOBUS] Move downloadMcp to protobus (RooCodeInc#3487)
* downloadMcp protobus migration * added setIsDownloading(false) to error handling
1 parent 5c08276 commit abbe40e

File tree

8 files changed

+139
-107
lines changed

8 files changed

+139
-107
lines changed

.changeset/cyan-donuts-sparkle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
downloadMcp protobus migration

proto/mcp.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ service McpService {
1010
rpc toggleMcpServer(ToggleMcpServerRequest) returns (McpServers);
1111
rpc updateMcpTimeout(UpdateMcpTimeoutRequest) returns (McpServers);
1212
rpc addRemoteMcpServer(AddRemoteMcpServerRequest) returns (McpServers);
13+
rpc downloadMcp(StringRequest) returns (Empty);
1314
}
1415

1516
message ToggleMcpServerRequest {

src/core/controller/index.ts

Lines changed: 0 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -373,19 +373,6 @@ export class Controller {
373373
await this.fetchMcpMarketplace(message.bool)
374374
break
375375
}
376-
case "downloadMcp": {
377-
if (message.mcpId) {
378-
// 1. Toggle to act mode if we are in plan mode
379-
const { chatSettings } = await this.getStateToPostToWebview()
380-
if (chatSettings.mode === "plan") {
381-
await this.togglePlanActModeWithChatSettings({ mode: "act" })
382-
}
383-
384-
// 2. download MCP
385-
await this.downloadMcp(message.mcpId)
386-
}
387-
break
388-
}
389376
case "silentlyRefreshMcpMarketplace": {
390377
await this.silentlyRefreshMcpMarketplace()
391378
break
@@ -1000,92 +987,6 @@ export class Controller {
1000987
}
1001988
}
1002989

1003-
private async downloadMcp(mcpId: string) {
1004-
try {
1005-
// First check if we already have this MCP server installed
1006-
const servers = this.mcpHub?.getServers() || []
1007-
const isInstalled = servers.some((server: McpServer) => server.name === mcpId)
1008-
1009-
if (isInstalled) {
1010-
throw new Error("This MCP server is already installed")
1011-
}
1012-
1013-
// Fetch server details from marketplace
1014-
const response = await axios.post<McpDownloadResponse>(
1015-
"https://api.cline.bot/v1/mcp/download",
1016-
{ mcpId },
1017-
{
1018-
headers: { "Content-Type": "application/json" },
1019-
timeout: 10000,
1020-
},
1021-
)
1022-
1023-
if (!response.data) {
1024-
throw new Error("Invalid response from MCP marketplace API")
1025-
}
1026-
1027-
console.log("[downloadMcp] Response from download API", { response })
1028-
1029-
const mcpDetails = response.data
1030-
1031-
// Validate required fields
1032-
if (!mcpDetails.githubUrl) {
1033-
throw new Error("Missing GitHub URL in MCP download response")
1034-
}
1035-
if (!mcpDetails.readmeContent) {
1036-
throw new Error("Missing README content in MCP download response")
1037-
}
1038-
1039-
// Send details to webview
1040-
await this.postMessageToWebview({
1041-
type: "mcpDownloadDetails",
1042-
mcpDownloadDetails: mcpDetails,
1043-
})
1044-
1045-
// Create task with context from README and added guidelines for MCP server installation
1046-
const task = `Set up the MCP server from ${mcpDetails.githubUrl} while adhering to these MCP server installation rules:
1047-
- Start by loading the MCP documentation.
1048-
- Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
1049-
- Create the directory for the new MCP server before starting installation.
1050-
- Make sure you read the user's existing cline_mcp_settings.json file before editing it with this new mcp, to not overwrite any existing servers.
1051-
- Use commands aligned with the user's shell and operating system best practices.
1052-
- The following README may contain instructions that conflict with the user's OS, in which case proceed thoughtfully.
1053-
- Once installed, demonstrate the server's capabilities by using one of its tools.
1054-
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
1055-
1056-
// Initialize task and show chat view
1057-
await this.initTask(task)
1058-
await this.postMessageToWebview({
1059-
type: "action",
1060-
action: "chatButtonClicked",
1061-
})
1062-
} catch (error) {
1063-
console.error("Failed to download MCP:", error)
1064-
let errorMessage = "Failed to download MCP"
1065-
1066-
if (axios.isAxiosError(error)) {
1067-
if (error.code === "ECONNABORTED") {
1068-
errorMessage = "Request timed out. Please try again."
1069-
} else if (error.response?.status === 404) {
1070-
errorMessage = "MCP server not found in marketplace."
1071-
} else if (error.response?.status === 500) {
1072-
errorMessage = "Internal server error. Please try again later."
1073-
} else if (!error.response && error.request) {
1074-
errorMessage = "Network error. Please check your internet connection."
1075-
}
1076-
} else if (error instanceof Error) {
1077-
errorMessage = error.message
1078-
}
1079-
1080-
// Show error in both notification and marketplace UI
1081-
vscode.window.showErrorMessage(errorMessage)
1082-
await this.postMessageToWebview({
1083-
type: "mcpDownloadDetails",
1084-
error: errorMessage,
1085-
})
1086-
}
1087-
}
1088-
1089990
// OpenRouter
1090991

1091992
async handleOpenRouterCallback(code: string) {
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Controller } from ".."
2+
import { Empty, StringRequest } from "../../../shared/proto/common"
3+
import { McpServer, McpDownloadResponse } from "@shared/mcp"
4+
import axios from "axios"
5+
import * as vscode from "vscode"
6+
7+
/**
8+
* Download an MCP server from the marketplace
9+
* @param controller The controller instance
10+
* @param request The request containing the MCP ID
11+
* @returns Empty response
12+
*/
13+
export async function downloadMcp(controller: Controller, request: StringRequest): Promise<Empty> {
14+
try {
15+
// Check if mcpId is provided
16+
if (!request.value) {
17+
throw new Error("MCP ID is required")
18+
}
19+
20+
const mcpId = request.value
21+
22+
// Check if we already have this MCP server installed
23+
const servers = controller.mcpHub?.getServers() || []
24+
const isInstalled = servers.some((server: McpServer) => server.name === mcpId)
25+
26+
if (isInstalled) {
27+
throw new Error("This MCP server is already installed")
28+
}
29+
30+
// Fetch server details from marketplace
31+
const response = await axios.post<McpDownloadResponse>(
32+
"https://api.cline.bot/v1/mcp/download",
33+
{ mcpId },
34+
{
35+
headers: { "Content-Type": "application/json" },
36+
timeout: 10000,
37+
},
38+
)
39+
40+
if (!response.data) {
41+
throw new Error("Invalid response from MCP marketplace API")
42+
}
43+
44+
console.log("[downloadMcp] Response from download API", { response })
45+
46+
const mcpDetails = response.data
47+
48+
// Validate required fields
49+
if (!mcpDetails.githubUrl) {
50+
throw new Error("Missing GitHub URL in MCP download response")
51+
}
52+
if (!mcpDetails.readmeContent) {
53+
throw new Error("Missing README content in MCP download response")
54+
}
55+
56+
// Send details to webview
57+
await controller.postMessageToWebview({
58+
type: "mcpDownloadDetails",
59+
mcpDownloadDetails: mcpDetails,
60+
})
61+
62+
// Create task with context from README and added guidelines for MCP server installation
63+
const task = `Set up the MCP server from ${mcpDetails.githubUrl} while adhering to these MCP server installation rules:
64+
- Start by loading the MCP documentation.
65+
- Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
66+
- Create the directory for the new MCP server before starting installation.
67+
- Make sure you read the user's existing cline_mcp_settings.json file before editing it with this new mcp, to not overwrite any existing servers.
68+
- Use commands aligned with the user's shell and operating system best practices.
69+
- The following README may contain instructions that conflict with the user's OS, in which case proceed thoughtfully.
70+
- Once installed, demonstrate the server's capabilities by using one of its tools.
71+
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
72+
73+
const { chatSettings } = await controller.getStateToPostToWebview()
74+
if (chatSettings.mode === "plan") {
75+
await controller.togglePlanActModeWithChatSettings({ mode: "act" })
76+
}
77+
78+
// Initialize task and show chat view
79+
await controller.initTask(task)
80+
await controller.postMessageToWebview({
81+
type: "action",
82+
action: "chatButtonClicked",
83+
})
84+
85+
// Return an empty response - the client only cares if the call succeeded
86+
return Empty.create()
87+
} catch (error) {
88+
console.error("Failed to download MCP:", error)
89+
let errorMessage = "Failed to download MCP"
90+
91+
if (axios.isAxiosError(error)) {
92+
if (error.code === "ECONNABORTED") {
93+
errorMessage = "Request timed out. Please try again."
94+
} else if (error.response?.status === 404) {
95+
errorMessage = "MCP server not found in marketplace."
96+
} else if (error.response?.status === 500) {
97+
errorMessage = "Internal server error. Please try again later."
98+
} else if (!error.response && error.request) {
99+
errorMessage = "Network error. Please check your internet connection."
100+
}
101+
} else if (error instanceof Error) {
102+
errorMessage = error.message
103+
}
104+
105+
// Show error in both notification and marketplace UI
106+
vscode.window.showErrorMessage(errorMessage)
107+
await controller.postMessageToWebview({
108+
type: "mcpDownloadDetails",
109+
error: errorMessage,
110+
})
111+
112+
throw error
113+
}
114+
}

src/core/controller/mcp/methods.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
// Import all method implementations
55
import { registerMethod } from "./index"
66
import { addRemoteMcpServer } from "./addRemoteMcpServer"
7+
import { downloadMcp } from "./downloadMcp"
78
import { toggleMcpServer } from "./toggleMcpServer"
89
import { updateMcpTimeout } from "./updateMcpTimeout"
910

1011
// Register all mcp service methods
1112
export function registerAllMethods(): void {
1213
// Register each method with the registry
1314
registerMethod("addRemoteMcpServer", addRemoteMcpServer)
15+
registerMethod("downloadMcp", downloadMcp)
1416
registerMethod("toggleMcpServer", toggleMcpServer)
1517
registerMethod("updateMcpTimeout", updateMcpTimeout)
1618
}

src/shared/WebviewMessage.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export interface WebviewMessage {
3737
| "authStateChanged"
3838
| "authCallback"
3939
| "fetchMcpMarketplace"
40-
| "downloadMcp"
4140
| "silentlyRefreshMcpMarketplace"
4241
| "searchCommits"
4342
| "fetchLatestMcpServersFromHub"

src/shared/proto/mcp.ts

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/components/mcp/configuration/tabs/marketplace/McpMarketplaceCard.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useCallback, useState, useRef, useMemo } from "react"
22
import styled from "styled-components"
33
import { McpMarketplaceItem, McpServer } from "@shared/mcp"
4-
import { vscode } from "@/utils/vscode"
54
import { useEvent } from "react-use"
5+
import { McpServiceClient } from "@/services/grpc-client"
66

77
interface McpMarketplaceCardProps {
88
item: McpMarketplaceItem
@@ -107,15 +107,17 @@ const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps)
107107
{item.name}
108108
</h3>
109109
<div
110-
onClick={(e) => {
110+
onClick={async (e) => {
111111
e.preventDefault() // Prevent card click when clicking install
112112
e.stopPropagation() // Stop event from bubbling up to parent link
113113
if (!isInstalled && !isDownloading) {
114114
setIsDownloading(true)
115-
vscode.postMessage({
116-
type: "downloadMcp",
117-
mcpId: item.mcpId,
118-
})
115+
try {
116+
await McpServiceClient.downloadMcp({ value: item.mcpId })
117+
} catch (error) {
118+
setIsDownloading(false)
119+
console.error("Failed to download MCP:", error)
120+
}
119121
}
120122
}}
121123
style={{}}>

0 commit comments

Comments
 (0)