-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Add MCP Marketplace #1731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Add MCP Marketplace #1731
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
d0a46d6
Add MCP marketplace integration and related types
HadasaSchechterV a4f3ce9
Add marketplace tab and related localization for MCP view
HadasaSchechterV 8207d25
Add MCP marketplace view and submission card components
HadasaSchechterV e11b804
Add search and sorting functionality to MCP marketplace view
HadasaSchechterV c9ddca0
Enhance MCP view with tab navigation and improved localization
HadasaSchechterV 9fcb4ee
Fix unit test & localization check
HadasaSchechterV d32eb6e
Merge remote-tracking branch 'origin/main' into mcp-marketplace
HadasaSchechterV f73d3c4
move mcp marketplace from client provider to its own service file
HadasaSchechterV File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,8 @@ import { getNonce } from "./getNonce" | |
| import { getUri } from "./getUri" | ||
| import { telemetryService } from "../../services/telemetry/TelemetryService" | ||
| import { TelemetrySetting } from "../../shared/TelemetrySetting" | ||
| import { McpMarketplaceCatalog, McpServer, McpDownloadResponse } from "../../shared/mcp" | ||
| import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider" | ||
|
|
||
| /** | ||
| * https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts | ||
|
|
@@ -712,6 +714,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { | |
| mcpServers: this.mcpHub.getAllServers(), | ||
| }) | ||
| } | ||
| this.prefetchMcpMarketplace() | ||
|
|
||
| const cacheDir = await this.ensureCacheDirectoryExists() | ||
|
|
||
|
|
@@ -1114,6 +1117,28 @@ export class ClineProvider implements vscode.WebviewViewProvider { | |
| } | ||
| break | ||
| } | ||
|
|
||
| case "fetchMcpMarketplace": { | ||
| await this.fetchMcpMarketplace(message.bool) | ||
| break | ||
| } | ||
| case "downloadMcp": { | ||
| if (message.mcpId) { | ||
| await this.downloadMcp(message.mcpId) | ||
| } | ||
| break | ||
| } | ||
| case "silentlyRefreshMcpMarketplace": { | ||
| await this.silentlyRefreshMcpMarketplace() | ||
| break | ||
| } | ||
| case "openMcpMarketplaceServerDetails": { | ||
| if (message.mcpId) { | ||
| await this.openMcpMarketplaceServerDetails(message.mcpId) | ||
| } | ||
| break | ||
| } | ||
|
|
||
| case "openCustomModesSettings": { | ||
| const customModesFilePath = await this.customModesManager.getCustomModesFilePath() | ||
| if (customModesFilePath) { | ||
|
|
@@ -2094,6 +2119,208 @@ export class ClineProvider implements vscode.WebviewViewProvider { | |
| return undefined | ||
| } | ||
|
|
||
| // MCP Marketplace | ||
|
|
||
| private async fetchMcpMarketplaceFromApi(silent: boolean = false): Promise<McpMarketplaceCatalog | undefined> { | ||
| try { | ||
| const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", { | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }) | ||
|
|
||
| if (!response.data) { | ||
| throw new Error("Invalid response from MCP marketplace API") | ||
| } | ||
|
|
||
| const catalog: McpMarketplaceCatalog = { | ||
| items: (response.data || []).map((item: any) => ({ | ||
| ...item, | ||
| githubStars: item.githubStars ?? 0, | ||
| downloadCount: item.downloadCount ?? 0, | ||
| tags: item.tags ?? [], | ||
| })), | ||
| } | ||
|
|
||
| // Store in global state | ||
| await this.updateGlobalState("mcpMarketplaceCatalog", catalog) | ||
| return catalog | ||
| } catch (error) { | ||
| console.error("Failed to fetch MCP marketplace:", error) | ||
| if (!silent) { | ||
| const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace" | ||
| await this.postMessageToWebview({ | ||
| type: "mcpMarketplaceCatalog", | ||
| error: errorMessage, | ||
| }) | ||
| vscode.window.showErrorMessage(errorMessage) | ||
| } | ||
| return undefined | ||
| } | ||
| } | ||
|
|
||
| async prefetchMcpMarketplace() { | ||
| try { | ||
| await this.fetchMcpMarketplaceFromApi(true) | ||
| } catch (error) { | ||
| console.error("Failed to prefetch MCP marketplace:", error) | ||
| } | ||
| } | ||
|
|
||
| async silentlyRefreshMcpMarketplace() { | ||
| try { | ||
| const catalog = await this.fetchMcpMarketplaceFromApi(true) | ||
| if (catalog) { | ||
| await this.postMessageToWebview({ | ||
| type: "mcpMarketplaceCatalog", | ||
| mcpMarketplaceCatalog: catalog, | ||
| }) | ||
| } | ||
| } catch (error) { | ||
| console.error("Failed to silently refresh MCP marketplace:", error) | ||
| } | ||
| } | ||
|
|
||
| private async fetchMcpMarketplace(forceRefresh: boolean = false) { | ||
| try { | ||
| // Check if we have cached data | ||
| const cachedCatalog = (await this.getGlobalState("mcpMarketplaceCatalog")) as | ||
| | McpMarketplaceCatalog | ||
| | undefined | ||
| if (!forceRefresh && cachedCatalog?.items) { | ||
| await this.postMessageToWebview({ | ||
| type: "mcpMarketplaceCatalog", | ||
| mcpMarketplaceCatalog: cachedCatalog, | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| const catalog = await this.fetchMcpMarketplaceFromApi(false) | ||
| if (catalog) { | ||
| await this.postMessageToWebview({ | ||
| type: "mcpMarketplaceCatalog", | ||
| mcpMarketplaceCatalog: catalog, | ||
| }) | ||
| } | ||
| } catch (error) { | ||
| console.error("Failed to handle cached MCP marketplace:", error) | ||
| const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace" | ||
| await this.postMessageToWebview({ | ||
| type: "mcpMarketplaceCatalog", | ||
| error: errorMessage, | ||
| }) | ||
| vscode.window.showErrorMessage(errorMessage) | ||
| } | ||
| } | ||
|
|
||
| private async downloadMcp(mcpId: string) { | ||
| try { | ||
| // First check if we already have this MCP server installed | ||
| const servers = this.mcpHub?.getServers() || [] | ||
| const isInstalled = servers.some((server: McpServer) => server.name === mcpId) | ||
|
|
||
| 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 }, | ||
| { | ||
| headers: { "Content-Type": "application/json" }, | ||
| timeout: 10000, | ||
| }, | ||
| ) | ||
|
|
||
| if (!response.data) { | ||
| 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 task with context from README | ||
| 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}` | ||
|
|
||
| // Initialize task and show chat view | ||
| await this.initClineWithTask(task) | ||
| await this.postMessageToWebview({ | ||
| type: "action", | ||
| action: "chatButtonClicked", | ||
| }) | ||
| } catch (error) { | ||
| console.error("Failed to download MCP:", error) | ||
| 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", | ||
| error: errorMessage, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| private async openMcpMarketplaceServerDetails(mcpId: string) { | ||
| const response = await fetch(`https://api.cline.bot/v1/mcp/marketplace/item?mcpId=${mcpId}`) | ||
|
||
| const details: McpDownloadResponse = await response.json() | ||
|
|
||
| if (details.readmeContent) { | ||
| // Disable markdown preview markers | ||
| const config = vscode.workspace.getConfiguration("markdown") | ||
| await config.update("preview.markEditorSelection", false, true) | ||
|
|
||
| // Create URI with base64 encoded markdown content | ||
| const uri = vscode.Uri.parse( | ||
| `${DIFF_VIEW_URI_SCHEME}:${details.name} README?${Buffer.from(details.readmeContent).toString("base64")}`, | ||
| ) | ||
|
|
||
| // close existing | ||
| const tabs = vscode.window.tabGroups.all | ||
| .flatMap((tg) => tg.tabs) | ||
| .filter((tab) => tab.label && tab.label.includes("README") && tab.label.includes("Preview")) | ||
| for (const tab of tabs) { | ||
| await vscode.window.tabGroups.close(tab) | ||
| } | ||
|
|
||
| // Show only the preview | ||
| await vscode.commands.executeCommand("markdown.showPreview", uri, { | ||
| sideBySide: true, | ||
| preserveFocus: true, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // OpenRouter | ||
|
|
||
| async handleOpenRouterCallback(code: string) { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid hardcoding external API URLs (e.g.
https://api.cline.bot/v1/mcp/marketplace). Consider placing endpoints in config or environment variables to ease deployment and testing. This follows our secure configuration best practices.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@buzz-code @mrubens i think we can use glama mcp
https://glama.ai/mcp/reference#tag/servers/GET/v1/servers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea, let me look into this.