Skip to content

Commit 4ff4734

Browse files
committed
Add windsurf support
1 parent c085d7b commit 4ff4734

File tree

9 files changed

+612
-29
lines changed

9 files changed

+612
-29
lines changed

src/mcp/cli.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
install,
1515
remove,
1616
list_servers,
17-
config,
17+
edit,
1818
status,
1919
toggle,
2020
server,
@@ -54,15 +54,16 @@ def main(ctx, help_flag):
5454
" ██║ ╚═╝ ██║╚██████╗██║ ",
5555
" ╚═╝ ╚═╝ ╚═════╝╚═╝ ",
5656
"",
57-
f"The missing Model Context Protocol Manager v{__version__}",
58-
"The USB Port Manager for AI Applications"
57+
f"v{__version__}",
58+
"Model Context Protocol Manager for all AI apps",
59+
"Supports Claude Desktop, Windsurf, and more"
5960
]
6061

6162
# No need to convert to joined string since we're formatting directly in the panel
6263

6364
# Create a panel with styled content
6465
panel = Panel(
65-
f"[bold green]{logo[0]}\n{logo[1]}\n{logo[2]}\n{logo[3]}\n{logo[4]}\n{logo[5]}[/]\n\n[bold yellow]{logo[7]}[/]\n[italic blue]{logo[8]}[/]",
66+
f"[bold green]{logo[0]}\n{logo[1]}\n{logo[2]}\n{logo[3]}\n{logo[4]}\n{logo[5]}[/]\n\n[bold yellow]{logo[7]}[/]\n[italic blue]{logo[8]}[/]\n[bold magenta]{logo[9]}[/]",
6667
border_style="bold cyan",
6768
expand=False,
6869
padding=(0, 2),
@@ -71,8 +72,20 @@ def main(ctx, help_flag):
7172
# Print the panel
7273
console.print(panel)
7374

75+
# Get information about installed clients
76+
from mcp.utils.client_detector import detect_installed_clients
77+
installed_clients = detect_installed_clients()
78+
7479
# Display active client information and main help
75-
console.print(f"[bold magenta]Active client:[/] [yellow]{active_client}[/]")
80+
client_status = "[green]✓[/]" if installed_clients.get(active_client, False) else "[yellow]⚠[/]"
81+
console.print(f"[bold magenta]Active client:[/] [yellow]{active_client}[/] {client_status}")
82+
83+
# Display all supported clients with their installation status
84+
console.print("[bold]Supported clients:[/]")
85+
for client, installed in installed_clients.items():
86+
status = "[green]Installed[/]" if installed else "[gray]Not installed[/]"
87+
active_marker = "[bold cyan]➤[/] " if client == active_client else " "
88+
console.print(f"{active_marker}{client}: {status}")
7689
console.print("")
7790

7891
# Display usage info
@@ -91,7 +104,7 @@ def main(ctx, help_flag):
91104
console.print("[bold]Commands:[/]")
92105
commands_table = Table(show_header=False, box=None, padding=(0, 2, 0, 0))
93106
commands_table.add_row(" [cyan]client[/]", "Manage the active MCP client.")
94-
commands_table.add_row(" [cyan]config[/]", "View or edit the active MCP client's configuration file.")
107+
commands_table.add_row(" [cyan]edit[/]", "View or edit the active MCP client's configuration file.")
95108
commands_table.add_row(" [cyan]list[/]", "List all installed MCP servers.")
96109
commands_table.add_row(" [cyan]remove[/]", "Remove an installed MCP server.")
97110
commands_table.add_row(" [cyan]server[/]", "Manage MCP server processes.")
@@ -116,7 +129,7 @@ def main(ctx, help_flag):
116129
main.add_command(install.install)
117130
main.add_command(remove.remove)
118131
main.add_command(list_servers.list)
119-
main.add_command(config.config)
132+
main.add_command(edit.edit)
120133
main.add_command(status.status)
121134
main.add_command(toggle.toggle)
122135
main.add_command(server.server)

src/mcp/clients/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
"""
22
Client integrations for MCP - manages client-specific configurations
33
"""
4+
5+
from mcp.clients.claude_desktop import ClaudeDesktopManager
6+
from mcp.clients.windsurf import WindsurfManager
7+
8+
__all__ = ['ClaudeDesktopManager', 'WindsurfManager']

src/mcp/clients/windsurf.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
Windsurf integration utilities for MCP
3+
"""
4+
5+
import os
6+
import json
7+
import logging
8+
from typing import Dict, Optional, Any
9+
import platform
10+
11+
logger = logging.getLogger(__name__)
12+
13+
# Windsurf config paths based on platform
14+
if platform.system() == "Darwin": # macOS
15+
WINDSURF_CONFIG_PATH = os.path.expanduser("~/.codeium/windsurf/mcp_config.json")
16+
elif platform.system() == "Windows":
17+
WINDSURF_CONFIG_PATH = os.path.join(os.environ.get("LOCALAPPDATA", ""), "Codeium", "windsurf", "mcp_config.json")
18+
else:
19+
# Linux
20+
WINDSURF_CONFIG_PATH = os.path.expanduser("~/.codeium/windsurf/mcp_config.json")
21+
22+
class WindsurfManager:
23+
"""Manages Windsurf MCP server configurations"""
24+
25+
def __init__(self, config_path: str = WINDSURF_CONFIG_PATH):
26+
self.config_path = config_path
27+
self._config = None
28+
29+
def _load_config(self) -> Dict[str, Any]:
30+
"""Load Windsurf configuration file"""
31+
if not os.path.exists(self.config_path):
32+
logger.warning(f"Windsurf config file not found at: {self.config_path}")
33+
return {"mcpServers": {}}
34+
35+
try:
36+
with open(self.config_path, 'r') as f:
37+
return json.load(f)
38+
except json.JSONDecodeError:
39+
logger.error(f"Error parsing Windsurf config file: {self.config_path}")
40+
return {"mcpServers": {}}
41+
42+
def _save_config(self, config: Dict[str, Any]) -> bool:
43+
"""Save configuration to Windsurf config file"""
44+
try:
45+
# Create directory if it doesn't exist
46+
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
47+
48+
with open(self.config_path, 'w') as f:
49+
json.dump(config, f, indent=2)
50+
return True
51+
except Exception as e:
52+
logger.error(f"Error saving Windsurf config: {str(e)}")
53+
return False
54+
55+
def get_servers(self) -> Dict[str, Any]:
56+
"""Get all MCP servers configured in Windsurf"""
57+
config = self._load_config()
58+
return config.get("mcpServers", {})
59+
60+
def get_server(self, server_name: str) -> Optional[Dict[str, Any]]:
61+
"""Get a specific MCP server configuration"""
62+
servers = self.get_servers()
63+
return servers.get(server_name)
64+
65+
def add_server(self, server_name: str, server_config: Dict[str, Any]) -> bool:
66+
"""Add or update an MCP server in Windsurf config"""
67+
config = self._load_config()
68+
69+
# Initialize mcpServers if it doesn't exist
70+
if "mcpServers" not in config:
71+
config["mcpServers"] = {}
72+
73+
# Add or update the server
74+
config["mcpServers"][server_name] = server_config
75+
76+
return self._save_config(config)
77+
78+
def remove_server(self, server_name: str) -> bool:
79+
"""Remove an MCP server from Windsurf config"""
80+
config = self._load_config()
81+
82+
if "mcpServers" not in config or server_name not in config["mcpServers"]:
83+
logger.warning(f"Server not found in Windsurf config: {server_name}")
84+
return False
85+
86+
# Remove the server
87+
del config["mcpServers"][server_name]
88+
89+
return self._save_config(config)
90+
91+
def is_windsurf_installed(self) -> bool:
92+
"""Check if Windsurf is installed"""
93+
# Check for the presence of the Windsurf directory
94+
windsurf_dir = os.path.dirname(self.config_path)
95+
return os.path.isdir(windsurf_dir)

src/mcp/commands/edit.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""
2+
Edit command for MCP (formerly config)
3+
"""
4+
5+
import os
6+
import json
7+
import click
8+
import subprocess
9+
from rich.console import Console
10+
from rich.panel import Panel
11+
from rich.prompt import Confirm
12+
13+
from mcp.clients.claude_desktop import ClaudeDesktopManager
14+
from mcp.clients.windsurf import WindsurfManager
15+
from mcp.utils.config import ConfigManager
16+
17+
console = Console()
18+
config_manager = ConfigManager()
19+
claude_manager = ClaudeDesktopManager()
20+
windsurf_manager = WindsurfManager()
21+
22+
@click.command()
23+
@click.option("--edit", is_flag=True, help="Open the active client's config in default editor")
24+
@click.option("--create", is_flag=True, help="Create a basic config file if it doesn't exist")
25+
def edit(edit, create):
26+
"""View or edit the active MCP client's configuration file.
27+
28+
The edit command operates on the currently active MCP client (set via 'mcp client').
29+
By default, this is Claude Desktop, but can be changed to other supported clients.
30+
31+
Examples:
32+
mcp edit # Show current client's config file location and content
33+
mcp edit --edit # Open the config file in your default editor
34+
mcp edit --create # Create a basic config file if it doesn't exist
35+
"""
36+
# Get the active client and its corresponding manager
37+
active_client = config_manager.get_active_client()
38+
39+
# Select appropriate client manager based on active client
40+
if active_client == "claude-desktop":
41+
client_manager = claude_manager
42+
client_name = "Claude Desktop"
43+
install_url = "https://claude.ai/download"
44+
is_installed_method = client_manager.is_claude_desktop_installed
45+
elif active_client == "windsurf":
46+
client_manager = windsurf_manager
47+
client_name = "Windsurf"
48+
install_url = "https://codeium.com/windsurf/download"
49+
is_installed_method = client_manager.is_windsurf_installed
50+
else:
51+
console.print(f"[bold red]Error:[/] Unsupported active client: {active_client}")
52+
console.print("Please switch to a supported client using 'mcp client <client-name>'")
53+
return
54+
55+
# Get the client config file path
56+
config_path = client_manager.config_path
57+
58+
# Check if the client is installed
59+
if not is_installed_method():
60+
console.print(f"[bold red]Error:[/] {client_name} installation not detected.")
61+
console.print(f"Please download and install {client_name} from {install_url}")
62+
return
63+
64+
# Check if config file exists
65+
config_exists = os.path.exists(config_path)
66+
67+
# Display the config file information
68+
console.print(f"[bold]{client_name} config file:[/] {config_path}")
69+
console.print(f"[bold]Status:[/] {'[green]Exists[/]' if config_exists else '[yellow]Does not exist[/]'}\n")
70+
71+
# Create config file if requested and it doesn't exist
72+
if not config_exists and create:
73+
console.print(f"[bold yellow]Creating new {client_name} config file...[/]")
74+
75+
# Create a basic config template
76+
basic_config = {
77+
"mcpServers": {
78+
"filesystem": {
79+
"command": "npx",
80+
"args": [
81+
"-y",
82+
"@modelcontextprotocol/server-filesystem",
83+
os.path.expanduser("~/Desktop"),
84+
os.path.expanduser("~/Downloads")
85+
]
86+
}
87+
}
88+
}
89+
90+
# Create the directory if it doesn't exist
91+
os.makedirs(os.path.dirname(config_path), exist_ok=True)
92+
93+
# Write the template to file
94+
try:
95+
with open(config_path, 'w') as f:
96+
json.dump(basic_config, f, indent=2)
97+
console.print("[green]Successfully created config file![/]\n")
98+
config_exists = True
99+
except Exception as e:
100+
console.print(f"[bold red]Error creating config file:[/] {str(e)}")
101+
return
102+
103+
# Show the current configuration if it exists
104+
if config_exists:
105+
try:
106+
with open(config_path, 'r') as f:
107+
config_content = f.read()
108+
109+
# Display the content
110+
console.print("[bold]Current configuration:[/]")
111+
panel = Panel(config_content, title=f"{client_name} Config", expand=False)
112+
console.print(panel)
113+
114+
# Count the configured servers
115+
try:
116+
config_json = json.loads(config_content)
117+
server_count = len(config_json.get("mcpServers", {}))
118+
console.print(f"[bold]Configured servers:[/] {server_count}")
119+
120+
# Display detailed information for each server
121+
if server_count > 0:
122+
console.print("\n[bold]MCP Server Details:[/]")
123+
for server_name, server_config in config_json.get("mcpServers", {}).items():
124+
console.print(f"\n[bold cyan]{server_name}[/]")
125+
console.print(f" Command: [green]{server_config.get('command', 'N/A')}[/]")
126+
127+
# Display arguments
128+
args = server_config.get('args', [])
129+
if args:
130+
console.print(" Arguments:")
131+
for i, arg in enumerate(args):
132+
console.print(f" {i}: [yellow]{arg}[/]")
133+
134+
# Display environment variables
135+
env_vars = server_config.get('env', {})
136+
if env_vars:
137+
console.print(" Environment Variables:")
138+
for key, value in env_vars.items():
139+
console.print(f" [bold blue]{key}[/] = [green]\"{value}\"[/]")
140+
else:
141+
console.print(" Environment Variables: [italic]None[/]")
142+
143+
# Add a separator line
144+
console.print(" " + "-" * 50)
145+
146+
except json.JSONDecodeError:
147+
console.print("[yellow]Warning: Config file contains invalid JSON[/]")
148+
149+
except Exception as e:
150+
console.print(f"[bold red]Error reading config file:[/] {str(e)}")
151+
152+
# Prompt to edit if file exists, or if we need to create it
153+
should_edit = edit
154+
if not should_edit and config_exists:
155+
should_edit = Confirm.ask("Would you like to open this file in your default editor?")
156+
elif not config_exists and not create:
157+
should_create = Confirm.ask("Config file doesn't exist. Would you like to create it?")
158+
if should_create:
159+
# Create a basic config template
160+
basic_config = {
161+
"mcpServers": {}
162+
}
163+
164+
# Create the directory if it doesn't exist
165+
os.makedirs(os.path.dirname(config_path), exist_ok=True)
166+
167+
# Write the template to file
168+
try:
169+
with open(config_path, 'w') as f:
170+
json.dump(basic_config, f, indent=2)
171+
console.print("[green]Successfully created config file![/]")
172+
should_edit = Confirm.ask("Would you like to open it in your default editor?")
173+
config_exists = True
174+
except Exception as e:
175+
console.print(f"[bold red]Error creating config file:[/] {str(e)}")
176+
return
177+
178+
# Open in default editor if requested
179+
if should_edit and config_exists:
180+
try:
181+
console.print("[bold green]Opening config file in your default editor...[/]")
182+
183+
# Use appropriate command based on platform
184+
if os.name == 'nt': # Windows
185+
os.startfile(config_path)
186+
elif os.name == 'posix': # macOS and Linux
187+
subprocess.run(['open', config_path] if os.uname().sysname == 'Darwin' else ['xdg-open', config_path])
188+
189+
console.print(f"[italic]After editing, {client_name} must be restarted for changes to take effect.[/]")
190+
except Exception as e:
191+
console.print(f"[bold red]Error opening editor:[/] {str(e)}")
192+
console.print(f"You can manually edit the file at: {config_path}")

0 commit comments

Comments
 (0)