Skip to content

Commit f73d3c4

Browse files
move mcp marketplace from client provider to its own service file
1 parent d32eb6e commit f73d3c4

File tree

2 files changed

+219
-209
lines changed

2 files changed

+219
-209
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 9 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ import { getUri } from "./getUri"
6767
import { telemetryService } from "../../services/telemetry/TelemetryService"
6868
import { TelemetrySetting } from "../../shared/TelemetrySetting"
6969
import { getWorkspacePath } from "../../utils/path"
70-
import { McpMarketplaceCatalog, McpServer, McpDownloadResponse } from "../../shared/mcp"
71-
import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider"
70+
import { McpMarketplaceService } from "../../services/mcp/McpMarketplaceService"
7271

7372
/**
7473
* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -93,6 +92,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
9392
private contextProxy: ContextProxy
9493
configManager: ConfigManager
9594
customModesManager: CustomModesManager
95+
private mcpMarketplaceService: McpMarketplaceService
9696
get cwd() {
9797
return getWorkspacePath()
9898
}
@@ -124,6 +124,8 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
124124
.catch((error) => {
125125
this.outputChannel.appendLine(`Failed to initialize MCP Hub: ${error}`)
126126
})
127+
128+
this.mcpMarketplaceService = new McpMarketplaceService(this)
127129
}
128130

129131
// Adds a new Cline instance to clineStack, marking the start of a new task.
@@ -746,7 +748,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
746748
mcpServers: this.mcpHub.getAllServers(),
747749
})
748750
}
749-
this.prefetchMcpMarketplace()
751+
this.mcpMarketplaceService.prefetchMcpMarketplace()
750752

751753
const cacheDir = await this.ensureCacheDirectoryExists()
752754

@@ -1194,22 +1196,22 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
11941196
}
11951197

11961198
case "fetchMcpMarketplace": {
1197-
await this.fetchMcpMarketplace(message.bool)
1199+
await this.mcpMarketplaceService.fetchMcpMarketplace(message.bool)
11981200
break
11991201
}
12001202
case "downloadMcp": {
12011203
if (message.mcpId) {
1202-
await this.downloadMcp(message.mcpId)
1204+
await this.mcpMarketplaceService.downloadMcp(this.mcpHub, message.mcpId)
12031205
}
12041206
break
12051207
}
12061208
case "silentlyRefreshMcpMarketplace": {
1207-
await this.silentlyRefreshMcpMarketplace()
1209+
await this.mcpMarketplaceService.silentlyRefreshMcpMarketplace()
12081210
break
12091211
}
12101212
case "openMcpMarketplaceServerDetails": {
12111213
if (message.mcpId) {
1212-
await this.openMcpMarketplaceServerDetails(message.mcpId)
1214+
await this.mcpMarketplaceService.openMcpMarketplaceServerDetails(message.mcpId)
12131215
}
12141216
break
12151217
}
@@ -2272,208 +2274,6 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
22722274
return undefined
22732275
}
22742276

2275-
// MCP Marketplace
2276-
2277-
private async fetchMcpMarketplaceFromApi(silent: boolean = false): Promise<McpMarketplaceCatalog | undefined> {
2278-
try {
2279-
const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
2280-
headers: {
2281-
"Content-Type": "application/json",
2282-
},
2283-
})
2284-
2285-
if (!response.data) {
2286-
throw new Error("Invalid response from MCP marketplace API")
2287-
}
2288-
2289-
const catalog: McpMarketplaceCatalog = {
2290-
items: (response.data || []).map((item: any) => ({
2291-
...item,
2292-
githubStars: item.githubStars ?? 0,
2293-
downloadCount: item.downloadCount ?? 0,
2294-
tags: item.tags ?? [],
2295-
})),
2296-
}
2297-
2298-
// Store in global state
2299-
await this.updateGlobalState("mcpMarketplaceCatalog", catalog)
2300-
return catalog
2301-
} catch (error) {
2302-
console.error("Failed to fetch MCP marketplace:", error)
2303-
if (!silent) {
2304-
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
2305-
await this.postMessageToWebview({
2306-
type: "mcpMarketplaceCatalog",
2307-
error: errorMessage,
2308-
})
2309-
vscode.window.showErrorMessage(errorMessage)
2310-
}
2311-
return undefined
2312-
}
2313-
}
2314-
2315-
async prefetchMcpMarketplace() {
2316-
try {
2317-
await this.fetchMcpMarketplaceFromApi(true)
2318-
} catch (error) {
2319-
console.error("Failed to prefetch MCP marketplace:", error)
2320-
}
2321-
}
2322-
2323-
async silentlyRefreshMcpMarketplace() {
2324-
try {
2325-
const catalog = await this.fetchMcpMarketplaceFromApi(true)
2326-
if (catalog) {
2327-
await this.postMessageToWebview({
2328-
type: "mcpMarketplaceCatalog",
2329-
mcpMarketplaceCatalog: catalog,
2330-
})
2331-
}
2332-
} catch (error) {
2333-
console.error("Failed to silently refresh MCP marketplace:", error)
2334-
}
2335-
}
2336-
2337-
private async fetchMcpMarketplace(forceRefresh: boolean = false) {
2338-
try {
2339-
// Check if we have cached data
2340-
const cachedCatalog = (await this.getGlobalState("mcpMarketplaceCatalog")) as
2341-
| McpMarketplaceCatalog
2342-
| undefined
2343-
if (!forceRefresh && cachedCatalog?.items) {
2344-
await this.postMessageToWebview({
2345-
type: "mcpMarketplaceCatalog",
2346-
mcpMarketplaceCatalog: cachedCatalog,
2347-
})
2348-
return
2349-
}
2350-
2351-
const catalog = await this.fetchMcpMarketplaceFromApi(false)
2352-
if (catalog) {
2353-
await this.postMessageToWebview({
2354-
type: "mcpMarketplaceCatalog",
2355-
mcpMarketplaceCatalog: catalog,
2356-
})
2357-
}
2358-
} catch (error) {
2359-
console.error("Failed to handle cached MCP marketplace:", error)
2360-
const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace"
2361-
await this.postMessageToWebview({
2362-
type: "mcpMarketplaceCatalog",
2363-
error: errorMessage,
2364-
})
2365-
vscode.window.showErrorMessage(errorMessage)
2366-
}
2367-
}
2368-
2369-
private async downloadMcp(mcpId: string) {
2370-
try {
2371-
// First check if we already have this MCP server installed
2372-
const servers = this.mcpHub?.getServers() || []
2373-
const isInstalled = servers.some((server: McpServer) => server.name === mcpId)
2374-
2375-
if (isInstalled) {
2376-
throw new Error("This MCP server is already installed")
2377-
}
2378-
2379-
// Fetch server details from marketplace
2380-
const response = await axios.post<McpDownloadResponse>(
2381-
"https://api.cline.bot/v1/mcp/download",
2382-
{ mcpId },
2383-
{
2384-
headers: { "Content-Type": "application/json" },
2385-
timeout: 10000,
2386-
},
2387-
)
2388-
2389-
if (!response.data) {
2390-
throw new Error("Invalid response from MCP marketplace API")
2391-
}
2392-
2393-
console.log("[downloadMcp] Response from download API", { response })
2394-
2395-
const mcpDetails = response.data
2396-
2397-
// Validate required fields
2398-
if (!mcpDetails.githubUrl) {
2399-
throw new Error("Missing GitHub URL in MCP download response")
2400-
}
2401-
if (!mcpDetails.readmeContent) {
2402-
throw new Error("Missing README content in MCP download response")
2403-
}
2404-
2405-
// Send details to webview
2406-
await this.postMessageToWebview({
2407-
type: "mcpDownloadDetails",
2408-
mcpDownloadDetails: mcpDetails,
2409-
})
2410-
2411-
// Create task with context from README
2412-
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}`
2413-
2414-
// Initialize task and show chat view
2415-
await this.initClineWithTask(task)
2416-
await this.postMessageToWebview({
2417-
type: "action",
2418-
action: "chatButtonClicked",
2419-
})
2420-
} catch (error) {
2421-
console.error("Failed to download MCP:", error)
2422-
let errorMessage = "Failed to download MCP"
2423-
2424-
if (axios.isAxiosError(error)) {
2425-
if (error.code === "ECONNABORTED") {
2426-
errorMessage = "Request timed out. Please try again."
2427-
} else if (error.response?.status === 404) {
2428-
errorMessage = "MCP server not found in marketplace."
2429-
} else if (error.response?.status === 500) {
2430-
errorMessage = "Internal server error. Please try again later."
2431-
} else if (!error.response && error.request) {
2432-
errorMessage = "Network error. Please check your internet connection."
2433-
}
2434-
} else if (error instanceof Error) {
2435-
errorMessage = error.message
2436-
}
2437-
2438-
// Show error in both notification and marketplace UI
2439-
vscode.window.showErrorMessage(errorMessage)
2440-
await this.postMessageToWebview({
2441-
type: "mcpDownloadDetails",
2442-
error: errorMessage,
2443-
})
2444-
}
2445-
}
2446-
2447-
private async openMcpMarketplaceServerDetails(mcpId: string) {
2448-
const response = await fetch(`https://api.cline.bot/v1/mcp/marketplace/item?mcpId=${mcpId}`)
2449-
const details: McpDownloadResponse = await response.json()
2450-
2451-
if (details.readmeContent) {
2452-
// Disable markdown preview markers
2453-
const config = vscode.workspace.getConfiguration("markdown")
2454-
await config.update("preview.markEditorSelection", false, true)
2455-
2456-
// Create URI with base64 encoded markdown content
2457-
const uri = vscode.Uri.parse(
2458-
`${DIFF_VIEW_URI_SCHEME}:${details.name} README?${Buffer.from(details.readmeContent).toString("base64")}`,
2459-
)
2460-
2461-
// close existing
2462-
const tabs = vscode.window.tabGroups.all
2463-
.flatMap((tg) => tg.tabs)
2464-
.filter((tab) => tab.label && tab.label.includes("README") && tab.label.includes("Preview"))
2465-
for (const tab of tabs) {
2466-
await vscode.window.tabGroups.close(tab)
2467-
}
2468-
2469-
// Show only the preview
2470-
await vscode.commands.executeCommand("markdown.showPreview", uri, {
2471-
sideBySide: true,
2472-
preserveFocus: true,
2473-
})
2474-
}
2475-
}
2476-
24772277
// OpenRouter
24782278

24792279
async handleOpenRouterCallback(code: string) {

0 commit comments

Comments
 (0)