@@ -67,8 +67,7 @@ import { getUri } from "./getUri"
6767import { telemetryService } from "../../services/telemetry/TelemetryService"
6868import { TelemetrySetting } from "../../shared/TelemetrySetting"
6969import { 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