@@ -4,6 +4,16 @@ import * as vscode from "vscode";
44
55type EditorType = "cursor" | "vscode" | "windsurf" | "antigravity" | "unknown" ;
66
7+ type McpServerDef = {
8+ label : string ;
9+ description : string ;
10+ key : string ;
11+ /** Config entry for Cursor / windsurf / antigravity / Claude (mcpServers format) */
12+ cursorConfig : Record < string , unknown > ;
13+ /** Config entry for VS Code (servers format, "servers" root key) */
14+ vscodeConfig : Record < string , unknown > ;
15+ } ;
16+
717type SkillInfo = {
818 name : string ;
919 description : string ;
@@ -51,9 +61,18 @@ function getMcpFilePath(editor: EditorType): string {
5161const SKILLS_BASE = "https://api.github.com/repos/cloudinary-devs/skills/contents" ;
5262
5363async function githubFetchJson < T > ( url : string ) : Promise < T > {
54- const response = await fetch ( url , {
55- headers : { Accept : "application/vnd.github+json" } ,
56- } ) ;
64+ const headers : Record < string , string > = { Accept : "application/vnd.github+json" } ;
65+
66+ try {
67+ const session = await vscode . authentication . getSession ( "github" , [ "repo" ] , { silent : true } ) ;
68+ if ( session ) {
69+ headers [ "Authorization" ] = `Bearer ${ session . accessToken } ` ;
70+ }
71+ } catch {
72+ // auth not available — proceed unauthenticated
73+ }
74+
75+ const response = await fetch ( url , { headers } ) ;
5776 if ( ! response . ok ) {
5877 throw new Error ( `GitHub API ${ response . status } : ${ url } ` ) ;
5978 }
@@ -281,20 +300,176 @@ async function installForCopilot(
281300 }
282301}
283302
303+ // ── MCP Server definitions ────────────────────────────────────────────────────
304+
305+ const MCP_SERVERS : McpServerDef [ ] = [
306+ {
307+ label : "Cloudinary Asset Management" ,
308+ description : "Browse, upload, and manage media assets" ,
309+ key : "cloudinary-asset-mgmt" ,
310+ cursorConfig : {
311+ command : "npx" ,
312+ args : [ "-y" , "@cloudinary/asset-management" , "mcp" , "start" ] ,
313+ env : {
314+ CLOUDINARY_CLOUD_NAME : "your_cloud_name" ,
315+ CLOUDINARY_API_KEY : "your_api_key" ,
316+ CLOUDINARY_API_SECRET : "your_api_secret" ,
317+ } ,
318+ } ,
319+ vscodeConfig : {
320+ type : "stdio" ,
321+ command : "npx" ,
322+ args : [ "-y" , "@cloudinary/asset-management" , "mcp" , "start" ] ,
323+ env : {
324+ CLOUDINARY_CLOUD_NAME : "${env:CLOUDINARY_CLOUD_NAME}" ,
325+ CLOUDINARY_API_KEY : "${env:CLOUDINARY_API_KEY}" ,
326+ CLOUDINARY_API_SECRET : "${env:CLOUDINARY_API_SECRET}" ,
327+ } ,
328+ } ,
329+ } ,
330+ {
331+ label : "Cloudinary Environment Config" ,
332+ description : "Configure upload presets, transformations, and settings" ,
333+ key : "cloudinary-env-config" ,
334+ cursorConfig : {
335+ command : "npx" ,
336+ args : [ "-y" , "@cloudinary/environment-config" , "mcp" , "start" ] ,
337+ env : {
338+ CLOUDINARY_CLOUD_NAME : "your_cloud_name" ,
339+ CLOUDINARY_API_KEY : "your_api_key" ,
340+ CLOUDINARY_API_SECRET : "your_api_secret" ,
341+ } ,
342+ } ,
343+ vscodeConfig : {
344+ type : "stdio" ,
345+ command : "npx" ,
346+ args : [ "-y" , "@cloudinary/environment-config" , "mcp" , "start" ] ,
347+ env : {
348+ CLOUDINARY_CLOUD_NAME : "${env:CLOUDINARY_CLOUD_NAME}" ,
349+ CLOUDINARY_API_KEY : "${env:CLOUDINARY_API_KEY}" ,
350+ CLOUDINARY_API_SECRET : "${env:CLOUDINARY_API_SECRET}" ,
351+ } ,
352+ } ,
353+ } ,
354+ {
355+ label : "Cloudinary Structured Metadata" ,
356+ description : "Manage structured metadata fields and values" ,
357+ key : "cloudinary-smd" ,
358+ cursorConfig : {
359+ command : "npx" ,
360+ args : [ "-y" , "@cloudinary/structured-metadata" , "mcp" , "start" ] ,
361+ env : {
362+ CLOUDINARY_CLOUD_NAME : "your_cloud_name" ,
363+ CLOUDINARY_API_KEY : "your_api_key" ,
364+ CLOUDINARY_API_SECRET : "your_api_secret" ,
365+ } ,
366+ } ,
367+ vscodeConfig : {
368+ type : "stdio" ,
369+ command : "npx" ,
370+ args : [ "-y" , "@cloudinary/structured-metadata" , "mcp" , "start" ] ,
371+ env : {
372+ CLOUDINARY_CLOUD_NAME : "${env:CLOUDINARY_CLOUD_NAME}" ,
373+ CLOUDINARY_API_KEY : "${env:CLOUDINARY_API_KEY}" ,
374+ CLOUDINARY_API_SECRET : "${env:CLOUDINARY_API_SECRET}" ,
375+ } ,
376+ } ,
377+ } ,
378+ {
379+ label : "Cloudinary Analysis" ,
380+ description : "AI-powered image and video analysis" ,
381+ key : "cloudinary-analysis" ,
382+ cursorConfig : {
383+ command : "npx" ,
384+ args : [ "-y" , "@cloudinary/analysis" , "mcp" , "start" ] ,
385+ env : {
386+ CLOUDINARY_CLOUD_NAME : "your_cloud_name" ,
387+ CLOUDINARY_API_KEY : "your_api_key" ,
388+ CLOUDINARY_API_SECRET : "your_api_secret" ,
389+ } ,
390+ } ,
391+ vscodeConfig : {
392+ type : "stdio" ,
393+ command : "npx" ,
394+ args : [ "-y" , "@cloudinary/analysis" , "mcp" , "start" ] ,
395+ env : {
396+ CLOUDINARY_CLOUD_NAME : "${env:CLOUDINARY_CLOUD_NAME}" ,
397+ CLOUDINARY_API_KEY : "${env:CLOUDINARY_API_KEY}" ,
398+ CLOUDINARY_API_SECRET : "${env:CLOUDINARY_API_SECRET}" ,
399+ } ,
400+ } ,
401+ } ,
402+ {
403+ label : "MediaFlows" ,
404+ description : "AI-powered media workflows and automation" ,
405+ key : "mediaflows" ,
406+ cursorConfig : {
407+ url : "https://mediaflows.mcp.cloudinary.com/v2/mcp" ,
408+ headers : {
409+ "cld-cloud-name" : "your_cloud_name" ,
410+ "cld-api-key" : "your_api_key" ,
411+ "cld-secret" : "your_api_secret" ,
412+ } ,
413+ } ,
414+ vscodeConfig : {
415+ url : "https://mediaflows.mcp.cloudinary.com/v2/mcp" ,
416+ headers : {
417+ "cld-cloud-name" : "your_cloud_name" ,
418+ "cld-api-key" : "your_api_key" ,
419+ "cld-secret" : "your_api_secret" ,
420+ } ,
421+ } ,
422+ } ,
423+ ] ;
424+
284425// ── MCP Config ───────────────────────────────────────────────────────────────
285426
286427async function createMcpConfig (
287428 rootUri : vscode . Uri ,
429+ editor : EditorType ,
288430 mcpFilePath : string ,
289431 createdFiles : string [ ]
290432) : Promise < void > {
433+ const selected = await vscode . window . showQuickPick (
434+ MCP_SERVERS . map ( ( s ) => ( { label : s . label , description : s . description , picked : true } ) ) ,
435+ { canPickMany : true , placeHolder : "Select MCP servers to configure" }
436+ ) ;
437+ if ( ! selected || selected . length === 0 ) { return ; }
438+
439+ const selectedDefs = selected
440+ . map ( ( item ) => MCP_SERVERS . find ( ( s ) => s . label === item . label ) )
441+ . filter ( ( s ) : s is McpServerDef => s !== undefined ) ;
442+
291443 const mcpUri = vscode . Uri . joinPath ( rootUri , mcpFilePath ) ;
292- const written = await writeWithOverwriteCheck (
444+ const isVscode = editor === "vscode" ;
445+ const rootKey = isVscode ? "servers" : "mcpServers" ;
446+
447+ // Read and merge into existing config if present
448+ let config : Record < string , unknown > = { } ;
449+ try {
450+ const bytes = await vscode . workspace . fs . readFile ( mcpUri ) ;
451+ config = JSON . parse ( Buffer . from ( bytes ) . toString ( "utf-8" ) ) ;
452+ } catch {
453+ // new file
454+ }
455+
456+ if ( ! config [ rootKey ] || typeof config [ rootKey ] !== "object" ) {
457+ config [ rootKey ] = { } ;
458+ }
459+ const servers = config [ rootKey ] as Record < string , unknown > ;
460+
461+ for ( const def of selectedDefs ) {
462+ servers [ def . key ] = isVscode ? def . vscodeConfig : def . cursorConfig ;
463+ }
464+
465+ await ensureDir ( vscode . Uri . joinPath ( mcpUri , ".." ) ) ;
466+ await vscode . workspace . fs . writeFile (
293467 mcpUri ,
294- JSON . stringify ( { mcpServers : { } } , null , 2 ) ,
295- mcpFilePath
468+ Buffer . from ( JSON . stringify ( config , null , 2 ) , "utf-8" )
296469 ) ;
297- if ( written ) { createdFiles . push ( mcpFilePath ) ; }
470+ if ( ! createdFiles . includes ( mcpFilePath ) ) {
471+ createdFiles . push ( mcpFilePath ) ;
472+ }
298473}
299474
300475// ── Command registration ─────────────────────────────────────────────────────
@@ -394,7 +569,7 @@ function registerConfigureAiTools(context: vscode.ExtensionContext): void {
394569 // ── Step 3: MCP config flow ────────────────────────────────────────────
395570 if ( options . some ( ( o ) => o . label === "MCP Config" ) ) {
396571 const editor = detectEditor ( ) ;
397- await createMcpConfig ( rootUri , getMcpFilePath ( editor ) , createdFiles ) ;
572+ await createMcpConfig ( rootUri , editor , getMcpFilePath ( editor ) , createdFiles ) ;
398573 }
399574
400575 // ── Step 4: feedback ───────────────────────────────────────────────────
0 commit comments