Skip to content

Commit 0ee3029

Browse files
committed
feat: add VS Code support to MCP init
- Implemented vscodeClient with JSON config file approach - Supports both project-local (.vscode/mcp.json) and global configs - Adds Supabase MCP server with type: http and URL - Merges with existing config if present - Updated help text to mention VS Code support - Checks for 'code' command availability
1 parent 7ade1ff commit 0ee3029

File tree

2 files changed

+112
-46
lines changed

2 files changed

+112
-46
lines changed

cmd/mcp_init.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ var (
1313
Long: `Configure the Supabase MCP server for your AI assistant clients.
1414
1515
This command will detect installed MCP clients and guide you through the setup process.
16-
Currently supports: Claude Code, Cursor (with more clients coming soon).
16+
Currently supports: Claude Code, Cursor, VS Code (with more clients coming soon).
1717
1818
The Supabase MCP server allows AI assistants to interact with your Supabase projects,
1919
providing tools for database operations, edge functions, storage, and more.
@@ -24,7 +24,8 @@ Examples:
2424
2525
# Configure a specific client
2626
supabase mcp init --client claude-code
27-
supabase mcp init --client cursor`,
27+
supabase mcp init --client cursor
28+
supabase mcp init --client vscode`,
2829
RunE: func(cmd *cobra.Command, args []string) error {
2930
client, _ := cmd.Flags().GetString("client")
3031
return mcpinit.Run(cmd.Context(), afero.NewOsFs(), client)

internal/mcp/init/init.go

Lines changed: 109 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ func (b *baseClient) InstallInstructions() string {
6262
var clientRegistry = []Client{
6363
newClaudeCodeClient(),
6464
newCursorClient(),
65-
// Add new clients here in the future:
66-
// newVSCodeClient(),
67-
// newClaudeDesktopClient(),
65+
newVSCodeClient(),
6866
}
6967

7068
func Run(ctx context.Context, fsys afero.Fs, clientFlag string) error {
@@ -301,6 +299,114 @@ func (c *cursorClient) Configure(ctx context.Context, fsys afero.Fs) error {
301299
return nil
302300
}
303301

302+
// vscodeClient implements the Client interface for VS Code
303+
type vscodeClient struct {
304+
baseClient
305+
}
306+
307+
func newVSCodeClient() *vscodeClient {
308+
return &vscodeClient{
309+
baseClient: baseClient{
310+
name: "vscode",
311+
displayName: "VS Code",
312+
installInstructions: "Download from https://code.visualstudio.com",
313+
checkInstalled: func() bool {
314+
return commandExists("code")
315+
},
316+
},
317+
}
318+
}
319+
320+
func (c *vscodeClient) Configure(ctx context.Context, fsys afero.Fs) error {
321+
fmt.Println("Configuring VS Code...")
322+
fmt.Println()
323+
324+
// Prompt for config scope
325+
fmt.Println("Where would you like to add the configuration?")
326+
fmt.Println(" 1. Project-local (in .vscode/mcp.json)")
327+
fmt.Println(" 2. Global (in your home directory)")
328+
fmt.Print("Choice [1]: ")
329+
330+
var choice string
331+
if _, err := fmt.Scanln(&choice); err != nil && err.Error() != "unexpected newline" {
332+
return fmt.Errorf("failed to read choice: %w", err)
333+
}
334+
if choice == "" {
335+
choice = "1"
336+
}
337+
338+
var configPath string
339+
if choice == "2" {
340+
// Global config
341+
homeDir, _ := os.UserHomeDir()
342+
configPath = filepath.Join(homeDir, ".vscode", "mcp.json")
343+
} else {
344+
// Project-local config
345+
cwd, _ := os.Getwd()
346+
configPath = filepath.Join(cwd, ".vscode", "mcp.json")
347+
}
348+
349+
// Prepare the Supabase MCP server config
350+
supabaseConfig := map[string]interface{}{
351+
"type": "http",
352+
"url": "https://mcp.supabase.com/mcp",
353+
}
354+
355+
// Read existing config if it exists
356+
var config map[string]interface{}
357+
existingData, err := afero.ReadFile(fsys, configPath)
358+
if err == nil && len(existingData) > 0 {
359+
if err := json.Unmarshal(existingData, &config); err != nil {
360+
// If existing file is invalid JSON, start fresh
361+
config = make(map[string]interface{})
362+
}
363+
} else {
364+
config = make(map[string]interface{})
365+
}
366+
367+
// Ensure mcpServers exists
368+
mcpServers, ok := config["mcpServers"].(map[string]interface{})
369+
if !ok {
370+
mcpServers = make(map[string]interface{})
371+
config["mcpServers"] = mcpServers
372+
}
373+
374+
// Add or update Supabase server
375+
mcpServers["supabase"] = supabaseConfig
376+
377+
// Ensure directory exists
378+
configDir := filepath.Dir(configPath)
379+
if err := fsys.MkdirAll(configDir, 0755); err != nil {
380+
return fmt.Errorf("failed to create config directory: %w", err)
381+
}
382+
383+
// Write config
384+
configJSON, err := json.MarshalIndent(config, "", " ")
385+
if err != nil {
386+
return fmt.Errorf("failed to marshal config: %w", err)
387+
}
388+
389+
if err := afero.WriteFile(fsys, configPath, configJSON, 0644); err != nil {
390+
return fmt.Errorf("failed to write config file: %w", err)
391+
}
392+
393+
fmt.Println()
394+
fmt.Printf("✓ Successfully configured VS Code at: %s\n", configPath)
395+
fmt.Println()
396+
fmt.Println("Configuration added:")
397+
fmt.Println(`{
398+
"mcpServers": {
399+
"supabase": {
400+
"type": "http",
401+
"url": "https://mcp.supabase.com/mcp"
402+
}
403+
}
404+
}`)
405+
fmt.Println()
406+
fmt.Println("The Supabase MCP server is now available in VS Code!")
407+
return nil
408+
}
409+
304410
// appExists checks if a macOS application is installed
305411
func appExists(appName string) bool {
306412
if runtime.GOOS == "darwin" {
@@ -316,44 +422,3 @@ func appExists(appName string) bool {
316422
}
317423
return false
318424
}
319-
320-
// Example: Adding a new client
321-
//
322-
// 1. Create a struct that embeds baseClient:
323-
//
324-
// type myNewClient struct {
325-
// baseClient
326-
// }
327-
//
328-
// 2. Create a constructor function:
329-
//
330-
// func newMyNewClient() *myNewClient {
331-
// return &myNewClient{
332-
// baseClient: baseClient{
333-
// name: "my-client",
334-
// displayName: "My Client",
335-
// installInstructions: "Installation command or URL",
336-
// checkInstalled: func() bool {
337-
// return commandExists("my-cli") || appExists("MyApp")
338-
// },
339-
// },
340-
// }
341-
// }
342-
//
343-
// 3. Implement the Configure method:
344-
//
345-
// func (c *myNewClient) Configure(ctx context.Context, fsys afero.Fs) error {
346-
// // Your configuration logic here
347-
// // See claudeCodeClient or cursorClient for examples
348-
// return nil
349-
// }
350-
//
351-
// 4. Add to clientRegistry:
352-
//
353-
// var clientRegistry = []Client{
354-
// newClaudeCodeClient(),
355-
// newCursorClient(),
356-
// newMyNewClient(), // Add here
357-
// }
358-
359-

0 commit comments

Comments
 (0)