Skip to content

Commit d0a46d6

Browse files
Add MCP marketplace integration and related types
1 parent 8265520 commit d0a46d6

File tree

6 files changed

+221
-1
lines changed

6 files changed

+221
-1
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import { getNonce } from "./getNonce"
6161
import { getUri } from "./getUri"
6262
import { telemetryService } from "../../services/telemetry/TelemetryService"
6363
import { TelemetrySetting } from "../../shared/TelemetrySetting"
64+
import { McpMarketplaceCatalog, McpServer, McpDownloadResponse } from "../../shared/mcp"
6465

6566
/**
6667
* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -2667,4 +2668,175 @@ export class ClineProvider implements vscode.WebviewViewProvider {
26672668

26682669
return properties
26692670
}
2671+
2672+
// MCP Marketplace functions
2673+
private async fetchMcpMarketplaceFromApi(silent: boolean = false): Promise<McpMarketplaceCatalog | undefined> {
2674+
try {
2675+
const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
2676+
headers: {
2677+
"Content-Type": "application/json",
2678+
},
2679+
})
2680+
2681+
if (!response.data) {
2682+
throw new Error("Invalid response from MCP marketplace API")
2683+
}
2684+
2685+
const catalog: McpMarketplaceCatalog = {
2686+
items: (response.data || []).map((item: any) => ({
2687+
...item,
2688+
githubStars: item.githubStars ?? 0,
2689+
downloadCount: item.downloadCount ?? 0,
2690+
tags: item.tags ?? [],
2691+
})),
2692+
}
2693+
2694+
// Store in global state
2695+
await this.updateGlobalState("mcpMarketplaceCatalog", catalog)
2696+
return catalog
2697+
} catch (error) {
2698+
console.error("Failed to fetch MCP marketplace:", error)
2699+
if (!silent) {
2700+
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
2701+
await this.postMessageToWebview({
2702+
type: "mcpMarketplaceCatalog",
2703+
error: errorMessage,
2704+
})
2705+
vscode.window.showErrorMessage(errorMessage)
2706+
}
2707+
return undefined
2708+
}
2709+
}
2710+
2711+
async prefetchMcpMarketplace() {
2712+
try {
2713+
await this.fetchMcpMarketplaceFromApi(true)
2714+
} catch (error) {
2715+
console.error("Failed to prefetch MCP marketplace:", error)
2716+
}
2717+
}
2718+
2719+
async silentlyRefreshMcpMarketplace() {
2720+
try {
2721+
const catalog = await this.fetchMcpMarketplaceFromApi(true)
2722+
if (catalog) {
2723+
await this.postMessageToWebview({
2724+
type: "mcpMarketplaceCatalog",
2725+
mcpMarketplaceCatalog: catalog,
2726+
})
2727+
}
2728+
} catch (error) {
2729+
console.error("Failed to silently refresh MCP marketplace:", error)
2730+
}
2731+
}
2732+
2733+
private async fetchMcpMarketplace(forceRefresh: boolean = false) {
2734+
try {
2735+
// Check if we have cached data
2736+
const cachedCatalog = (await this.getGlobalState("mcpMarketplaceCatalog")) as
2737+
| McpMarketplaceCatalog
2738+
| undefined
2739+
if (!forceRefresh && cachedCatalog?.items) {
2740+
await this.postMessageToWebview({
2741+
type: "mcpMarketplaceCatalog",
2742+
mcpMarketplaceCatalog: cachedCatalog,
2743+
})
2744+
return
2745+
}
2746+
2747+
const catalog = await this.fetchMcpMarketplaceFromApi(false)
2748+
if (catalog) {
2749+
await this.postMessageToWebview({
2750+
type: "mcpMarketplaceCatalog",
2751+
mcpMarketplaceCatalog: catalog,
2752+
})
2753+
}
2754+
} catch (error) {
2755+
console.error("Failed to handle cached MCP marketplace:", error)
2756+
const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace"
2757+
await this.postMessageToWebview({
2758+
type: "mcpMarketplaceCatalog",
2759+
error: errorMessage,
2760+
})
2761+
vscode.window.showErrorMessage(errorMessage)
2762+
}
2763+
}
2764+
2765+
private async downloadMcp(mcpId: string) {
2766+
try {
2767+
// First check if we already have this MCP server installed
2768+
const servers = this.mcpHub?.getServers() || []
2769+
const isInstalled = servers.some((server: McpServer) => server.name === mcpId)
2770+
2771+
if (isInstalled) {
2772+
throw new Error("This MCP server is already installed")
2773+
}
2774+
2775+
// Fetch server details from marketplace
2776+
const response = await axios.post<McpDownloadResponse>(
2777+
"https://api.cline.bot/v1/mcp/download",
2778+
{ mcpId },
2779+
{
2780+
headers: { "Content-Type": "application/json" },
2781+
timeout: 10000,
2782+
},
2783+
)
2784+
2785+
if (!response.data) {
2786+
throw new Error("Invalid response from MCP marketplace API")
2787+
}
2788+
2789+
console.log("[downloadMcp] Response from download API", { response })
2790+
2791+
const mcpDetails = response.data
2792+
2793+
// Validate required fields
2794+
if (!mcpDetails.githubUrl) {
2795+
throw new Error("Missing GitHub URL in MCP download response")
2796+
}
2797+
if (!mcpDetails.readmeContent) {
2798+
throw new Error("Missing README content in MCP download response")
2799+
}
2800+
2801+
// Send details to webview
2802+
await this.postMessageToWebview({
2803+
type: "mcpDownloadDetails",
2804+
mcpDownloadDetails: mcpDetails,
2805+
})
2806+
2807+
// Create task with context from README
2808+
const task = `Set up the MCP server from ${mcpDetails.githubUrl}. Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json. Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
2809+
2810+
// Initialize task and show chat view
2811+
await this.initClineWithTask(task)
2812+
await this.postMessageToWebview({
2813+
type: "action",
2814+
action: "chatButtonClicked",
2815+
})
2816+
} catch (error) {
2817+
console.error("Failed to download MCP:", error)
2818+
let errorMessage = "Failed to download MCP"
2819+
2820+
if (axios.isAxiosError(error)) {
2821+
if (error.code === "ECONNABORTED") {
2822+
errorMessage = "Request timed out. Please try again."
2823+
} else if (error.response?.status === 404) {
2824+
errorMessage = "MCP server not found in marketplace."
2825+
} else if (error.response?.status === 500) {
2826+
errorMessage = "Internal server error. Please try again later."
2827+
} else if (!error.response && error.request) {
2828+
errorMessage = "Network error. Please check your internet connection."
2829+
}
2830+
} else if (error instanceof Error) {
2831+
errorMessage = error.message
2832+
}
2833+
2834+
// Show error in both notification and marketplace UI
2835+
vscode.window.showErrorMessage(errorMessage)
2836+
await this.postMessageToWebview({
2837+
type: "mcpDownloadDetails",
2838+
error: errorMessage,
2839+
})
2840+
}
2841+
}
26702842
}

src/exports/roo-code.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export type GlobalStateKey =
219219
| "showRooIgnoredFiles"
220220
| "remoteBrowserEnabled"
221221
| "language"
222+
| "mcpMarketplaceCatalog"
222223

223224
export type ConfigurationKey = GlobalStateKey | SecretKey
224225

src/shared/ExtensionMessage.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ApiConfiguration, ApiProvider, ModelInfo } from "./api"
22
import { HistoryItem } from "./HistoryItem"
3-
import { McpServer } from "./mcp"
3+
import { McpServer, McpMarketplaceCatalog, McpDownloadResponse } from "./mcp"
44
import { GitCommit } from "../utils/git"
55
import { Mode, CustomModePrompts, ModeConfig } from "./modes"
66
import { CustomSupportPrompts } from "./support-prompt"
@@ -36,6 +36,8 @@ export interface ExtensionMessage {
3636
| "requestyModels"
3737
| "openAiModels"
3838
| "mcpServers"
39+
| "mcpMarketplaceCatalog"
40+
| "mcpDownloadDetails"
3941
| "enhancedPrompt"
4042
| "commitSearchResults"
4143
| "listApiConfig"
@@ -81,6 +83,8 @@ export interface ExtensionMessage {
8183
requestyModels?: Record<string, ModelInfo>
8284
openAiModels?: string[]
8385
mcpServers?: McpServer[]
86+
mcpMarketplaceCatalog?: McpMarketplaceCatalog
87+
mcpDownloadDetails?: McpDownloadResponse
8488
commits?: GitCommit[]
8589
listApiConfig?: ApiConfigMeta[]
8690
mode?: Mode
@@ -90,6 +94,7 @@ export interface ExtensionMessage {
9094
values?: Record<string, any>
9195
requestId?: string
9296
promptText?: string
97+
error?: string
9398
}
9499

95100
export interface ApiConfigMeta {

src/shared/WebviewMessage.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ export interface WebviewMessage {
108108
| "browserConnectionResult"
109109
| "remoteBrowserEnabled"
110110
| "language"
111+
| "fetchMcpMarketplace"
112+
| "downloadMcp"
113+
| "openMcpMarketplaceServerDetails"
114+
| "silentlyRefreshMcpMarketplace"
111115
text?: string
112116
disabled?: boolean
113117
askResponse?: ClineAskResponse
@@ -132,6 +136,7 @@ export interface WebviewMessage {
132136
payload?: WebViewMessagePayload
133137
source?: "global" | "project"
134138
requestId?: string
139+
mcpId?: string
135140
}
136141

137142
export const checkoutDiffPayloadSchema = z.object({

src/shared/globalState.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export const GLOBAL_STATE_KEYS = [
118118
"remoteBrowserEnabled",
119119
"language",
120120
"maxWorkspaceFiles",
121+
"mcpMarketplaceCatalog",
121122
] as const
122123

123124
type CheckGlobalStateKeysExhaustiveness =

src/shared/mcp.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,39 @@ export type McpToolCallResponse = {
6565
>
6666
isError?: boolean
6767
}
68+
69+
export interface McpMarketplaceItem {
70+
mcpId: string
71+
githubUrl: string
72+
name: string
73+
author: string
74+
description: string
75+
codiconIcon: string
76+
logoUrl: string
77+
category: string
78+
tags: string[]
79+
requiresApiKey: boolean
80+
readmeContent?: string
81+
llmsInstallationContent?: string
82+
isRecommended: boolean
83+
githubStars: number
84+
downloadCount: number
85+
createdAt: string
86+
updatedAt: string
87+
lastGithubSync: string
88+
}
89+
90+
export interface McpMarketplaceCatalog {
91+
items: McpMarketplaceItem[]
92+
}
93+
94+
export interface McpDownloadResponse {
95+
mcpId: string
96+
githubUrl: string
97+
name: string
98+
author: string
99+
description: string
100+
readmeContent: string
101+
llmsInstallationContent: string
102+
requiresApiKey: boolean
103+
}

0 commit comments

Comments
 (0)