@@ -61,6 +61,7 @@ import { getNonce } from "./getNonce"
6161import { getUri } from "./getUri"
6262import { telemetryService } from "../../services/telemetry/TelemetryService"
6363import { 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}
0 commit comments