Compare commits

...

1 Commits

Author SHA1 Message Date
celestial-vault 843ea45b90 migrate download MCP response to proto 2025-07-06 11:31:16 -05:00
5 changed files with 60 additions and 82 deletions
+13 -1
View File
@@ -10,7 +10,7 @@ service McpService {
rpc toggleMcpServer(ToggleMcpServerRequest) returns (McpServers);
rpc updateMcpTimeout(UpdateMcpTimeoutRequest) returns (McpServers);
rpc addRemoteMcpServer(AddRemoteMcpServerRequest) returns (McpServers);
rpc downloadMcp(StringRequest) returns (Empty);
rpc downloadMcp(StringRequest) returns (McpDownloadResponse);
rpc restartMcpServer(StringRequest) returns (McpServers);
rpc deleteMcpServer(StringRequest) returns (McpServers);
rpc toggleToolAutoApprove(ToggleToolAutoApproveRequest) returns (McpServers);
@@ -119,3 +119,15 @@ message McpMarketplaceItem {
message McpMarketplaceCatalog {
repeated McpMarketplaceItem items = 1;
}
message McpDownloadResponse {
string mcp_id = 1;
string github_url = 2;
string name = 3;
string author = 4;
string description = 5;
string readme_content = 6;
string llms_installation_content = 7;
bool requires_api_key = 8;
optional string error = 9;
}
+26 -18
View File
@@ -1,6 +1,7 @@
import { Controller } from ".."
import { Empty, StringRequest } from "../../../shared/proto/common"
import { McpServer, McpDownloadResponse } from "@shared/mcp"
import { StringRequest } from "../../../shared/proto/common"
import { McpDownloadResponse } from "../../../shared/proto/mcp"
import { McpServer } from "@shared/mcp"
import axios from "axios"
import * as vscode from "vscode"
import { sendChatButtonClickedEvent } from "../ui/subscribeToChatButtonClicked"
@@ -9,9 +10,9 @@ import { sendChatButtonClickedEvent } from "../ui/subscribeToChatButtonClicked"
* Download an MCP server from the marketplace
* @param controller The controller instance
* @param request The request containing the MCP ID
* @returns Empty response
* @returns MCP download response with details or error
*/
export async function downloadMcp(controller: Controller, request: StringRequest): Promise<Empty> {
export async function downloadMcp(controller: Controller, request: StringRequest): Promise<McpDownloadResponse> {
try {
// Check if mcpId is provided
if (!request.value) {
@@ -54,12 +55,6 @@ export async function downloadMcp(controller: Controller, request: StringRequest
throw new Error("Missing README content in MCP download response")
}
// Send details to webview
await controller.postMessageToWebview({
type: "mcpDownloadDetails",
mcpDownloadDetails: mcpDetails,
})
// Create task with context from README and added guidelines for MCP server installation
const task = `Set up the MCP server from ${mcpDetails.githubUrl} while adhering to these MCP server installation rules:
- Start by loading the MCP documentation.
@@ -80,8 +75,17 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
await controller.initTask(task)
await sendChatButtonClickedEvent(controller.id)
// Return an empty response - the client only cares if the call succeeded
return Empty.create()
// Return the download details directly
return McpDownloadResponse.create({
mcpId: mcpDetails.mcpId,
githubUrl: mcpDetails.githubUrl,
name: mcpDetails.name,
author: mcpDetails.author,
description: mcpDetails.description,
readmeContent: mcpDetails.readmeContent,
llmsInstallationContent: mcpDetails.llmsInstallationContent,
requiresApiKey: mcpDetails.requiresApiKey,
})
} catch (error) {
console.error("Failed to download MCP:", error)
let errorMessage = "Failed to download MCP"
@@ -100,13 +104,17 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
errorMessage = error.message
}
// Show error in both notification and marketplace UI
vscode.window.showErrorMessage(errorMessage)
await controller.postMessageToWebview({
type: "mcpDownloadDetails",
// Return error in the response instead of throwing
return McpDownloadResponse.create({
mcpId: "",
githubUrl: "",
name: "",
author: "",
description: "",
readmeContent: "",
llmsInstallationContent: "",
requiresApiKey: false,
error: errorMessage,
})
throw error
}
}
+2 -31
View File
@@ -14,37 +14,8 @@ import { UserInfo } from "./UserInfo"
// webview will hold state
export interface ExtensionMessage {
type: "action" | "state" | "selectedImages" | "mcpDownloadDetails" | "grpc_response" // New type for gRPC responses
text?: string
action?: "accountLogoutClicked"
state?: ExtensionState
images?: string[]
files?: string[]
ollamaModels?: string[]
lmStudioModels?: string[]
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
openAiModels?: string[]
mcpServers?: McpServer[]
customToken?: string
mcpMarketplaceCatalog?: McpMarketplaceCatalog
error?: string
mcpDownloadDetails?: McpDownloadResponse
commits?: GitCommit[]
url?: string
isImage?: boolean
success?: boolean
endpoint?: string
isBundled?: boolean
isConnected?: boolean
isRemote?: boolean
host?: string
mentionsRequestId?: string
results?: Array<{
path: string
type: "file" | "folder"
label?: string
}>
tab?: McpViewTab
type: "grpc_response" // New type for gRPC responses
grpc_response?: {
message?: any // JSON serialized protobuf message
request_id: string // Same ID as the request
@@ -1,34 +1,23 @@
import { McpServiceClient } from "@/services/grpc-client"
import { McpMarketplaceItem, McpServer } from "@shared/mcp"
import { StringRequest } from "@shared/proto/common"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useEvent } from "react-use"
import { useEffect, useMemo, useRef, useState } from "react"
import styled from "styled-components"
import { useExtensionState } from "@/context/ExtensionStateContext"
interface McpMarketplaceCardProps {
item: McpMarketplaceItem
installedServers: McpServer[]
setError: (error: string | null) => void
}
const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps) => {
const McpMarketplaceCard = ({ item, installedServers, setError }: McpMarketplaceCardProps) => {
const isInstalled = installedServers.some((server) => server.name === item.mcpId)
const [isDownloading, setIsDownloading] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const githubLinkRef = useRef<HTMLDivElement>(null)
const { onRelinquishControl } = useExtensionState()
const handleMessage = useCallback((event: MessageEvent) => {
const message = event.data
switch (message.type) {
case "mcpDownloadDetails":
setIsDownloading(false)
break
}
}, [])
useEvent("message", handleMessage)
useEffect(() => {
return onRelinquishControl(() => {
setIsLoading(false)
@@ -119,10 +108,21 @@ const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps)
if (!isInstalled && !isDownloading) {
setIsDownloading(true)
try {
await McpServiceClient.downloadMcp(StringRequest.create({ value: item.mcpId }))
const response = await McpServiceClient.downloadMcp(
StringRequest.create({ value: item.mcpId }),
)
if (response.error) {
console.error("MCP download failed:", response.error)
setError(response.error)
} else {
console.log("MCP download successful:", response)
// Clear any previous errors on success
setError(null)
}
} catch (error) {
setIsDownloading(false)
console.error("Failed to download MCP:", error)
} finally {
setIsDownloading(false)
}
}
}}
@@ -59,23 +59,8 @@ const McpMarketplaceView = () => {
}, [items, searchQuery, selectedCategory, sortBy])
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const message = event.data
if (message.type === "mcpDownloadDetails") {
if (message.error) {
setError(message.error)
}
}
}
window.addEventListener("message", handleMessage)
// Fetch marketplace catalog on initial load
fetchMarketplace()
return () => {
window.removeEventListener("message", handleMessage)
}
}, [])
useEffect(() => {
@@ -290,7 +275,9 @@ const McpMarketplaceView = () => {
: "No MCP servers found in the marketplace"}
</div>
) : (
filteredItems.map((item) => <McpMarketplaceCard key={item.mcpId} item={item} installedServers={mcpServers} />)
filteredItems.map((item) => (
<McpMarketplaceCard key={item.mcpId} item={item} installedServers={mcpServers} setError={setError} />
))
)}
<McpSubmitCard />
</div>