Skip to content

Commit a9f0ec6

Browse files
authored
Merge pull request #8 from pathintegral-institute/cnie-refactor-config
Refactor Config
2 parents e408fc6 + 8eb343b commit a9f0ec6

File tree

13 files changed

+421
-197
lines changed

13 files changed

+421
-197
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ mcpm.sh/
109109

110110
2. Set up a virtual environment with uv
111111
```
112-
uv venv
112+
uv venv --seed
113113
source .venv/bin/activate # On Unix/Mac
114114
```
115115

pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ requires-python = ">=3.10"
1111
license = "MIT"
1212
authors = [{ name = "MCPM Contributors" }]
1313
maintainers = [{ name = "Path Integral Institute" }]
14-
keywords = ["MCPM", "MCP", "Model Context Protocol", "Claude Desktop", "Cursor", "Windsurf"]
14+
keywords = [
15+
"MCPM",
16+
"MCP",
17+
"Model Context Protocol",
18+
"Claude Desktop",
19+
"Cursor",
20+
"Windsurf",
21+
]
1522
dependencies = [
1623
"click>=8.1.3",
1724
"rich>=12.0.0",

src/mcpm/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"""
44

55
# Import version from internal module
6+
# Import router module
7+
from . import router
68
from .version import __version__
79

810
# Define what symbols are exported from this package
9-
__all__ = ["__version__"]
11+
__all__ = ["__version__", "router"]

src/mcpm/cli.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from rich.table import Table
88

99
from mcpm import __version__
10+
from mcpm.clients.client_config import ClientConfigManager
1011
from mcpm.commands import (
1112
add,
1213
client,
@@ -19,10 +20,9 @@
1920
search,
2021
stash,
2122
)
22-
from mcpm.utils.config import ConfigManager
2323

2424
console = Console()
25-
config_manager = ConfigManager()
25+
client_config_manager = ClientConfigManager()
2626

2727
# Set -h as an alias for --help but we'll handle it ourselves
2828
CONTEXT_SETTINGS = dict(help_option_names=[])
@@ -40,7 +40,7 @@ def main(ctx, help_flag):
4040
# Check if a command is being executed (and it's not help, no command, or the client command)
4141
if ctx.invoked_subcommand and ctx.invoked_subcommand != "client" and not help_flag:
4242
# Check if active client is set
43-
active_client = config_manager.get_active_client()
43+
active_client = client_config_manager.get_active_client()
4444
if not active_client:
4545
console.print("[bold red]Error:[/] No active client set.")
4646
console.print("Please run 'mcpm client <client-name>' to set an active client.")
@@ -57,7 +57,7 @@ def main(ctx, help_flag):
5757
# If no command was invoked or help is requested, show our custom help
5858
if ctx.invoked_subcommand is None or help_flag:
5959
# Get active client
60-
active_client = config_manager.get_active_client()
60+
active_client = client_config_manager.get_active_client()
6161

6262
# Create a nice ASCII art banner with proper alignment using Rich
6363
from rich.panel import Panel

src/mcpm/clients/__init__.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
"""
2-
Client integrations for MCPM - manages client-specific configurations
2+
MCPM Client package
3+
4+
Provides client-specific implementations and configuration
35
"""
46

57
from mcpm.clients.base import BaseClientManager
68
from mcpm.clients.claude_desktop import ClaudeDesktopManager
7-
from mcpm.clients.windsurf import WindsurfManager
9+
from mcpm.clients.client_config import ClientConfigManager
810
from mcpm.clients.cursor import CursorManager
11+
from mcpm.clients.windsurf import WindsurfManager
912

10-
__all__ = ["BaseClientManager", "ClaudeDesktopManager", "WindsurfManager", "CursorManager"]
13+
__all__ = [
14+
"BaseClientManager",
15+
"ClaudeDesktopManager",
16+
"WindsurfManager",
17+
"CursorManager",
18+
"ClientConfigManager",
19+
]

src/mcpm/clients/client_config.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
"""
2+
Client configuration management for MCPM
3+
"""
4+
5+
import logging
6+
from typing import Any, Dict, List, Optional
7+
8+
from mcpm.utils.config import ConfigManager
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
class ClientConfigManager:
14+
"""Manages client-specific configuration including active client and stashed servers"""
15+
16+
def __init__(self):
17+
"""Initialize the client config manager"""
18+
self.config_manager = ConfigManager()
19+
self._config = self.config_manager.get_config()
20+
21+
def _refresh_config(self):
22+
"""Refresh the local config cache from the config manager"""
23+
self._config = self.config_manager.get_config()
24+
25+
def get_active_client(self) -> str:
26+
"""Get the name of the currently active client or None if not set"""
27+
self._refresh_config()
28+
return self._config.get("active_client")
29+
30+
def set_active_client(self, client_name: Optional[str]) -> bool:
31+
"""Set the active client
32+
33+
Args:
34+
client_name: Name of client to set as active, or None to clear
35+
36+
Returns:
37+
bool: Success or failure
38+
"""
39+
# If None, remove the active client
40+
if client_name is None:
41+
result = self.config_manager.set_config("active_client", None)
42+
self._refresh_config()
43+
return result
44+
45+
# Get supported clients
46+
from mcpm.utils.client_registry import ClientRegistry
47+
48+
supported_clients = ClientRegistry.get_supported_clients()
49+
50+
if client_name not in supported_clients:
51+
logger.error(f"Unknown client: {client_name}")
52+
return False
53+
54+
# Set the active client
55+
result = self.config_manager.set_config("active_client", client_name)
56+
self._refresh_config()
57+
return result
58+
59+
def get_supported_clients(self) -> List[str]:
60+
"""Get a list of supported client names"""
61+
# Import here to avoid circular imports
62+
from mcpm.utils.client_registry import ClientRegistry
63+
64+
return ClientRegistry.get_supported_clients()
65+
66+
def get_client_manager(self, client_name: str):
67+
"""Get the appropriate client manager for a client
68+
69+
Args:
70+
client_name: Name of the client
71+
72+
Returns:
73+
BaseClientManager or None if client not supported
74+
"""
75+
# Import here to avoid circular imports
76+
from mcpm.utils.client_registry import ClientRegistry
77+
78+
return ClientRegistry.get_client_manager(client_name)
79+
80+
def stash_server(self, client_name: str, server_name: str, server_config: Any) -> bool:
81+
"""Store a disabled server configuration in the global config
82+
83+
Args:
84+
client_name: Name of the client the server belongs to
85+
server_name: Name of the server to stash
86+
server_config: Server configuration to stash (ServerConfig object or dict)
87+
88+
Returns:
89+
bool: Success or failure
90+
"""
91+
# Refresh config to ensure we have the latest state
92+
self._refresh_config()
93+
94+
# Ensure stashed_servers section exists
95+
if "stashed_servers" not in self._config:
96+
self._config["stashed_servers"] = {}
97+
98+
# Ensure client section exists
99+
if client_name not in self._config["stashed_servers"]:
100+
self._config["stashed_servers"][client_name] = {}
101+
102+
# Convert ServerConfig to dict if needed
103+
try:
104+
# If it's a ServerConfig object with to_dict method
105+
if hasattr(server_config, "to_dict") and callable(server_config.to_dict):
106+
server_dict = server_config.to_dict()
107+
else:
108+
# Assume it's already a dict or JSON serializable
109+
server_dict = server_config
110+
111+
# Add the server configuration
112+
stashed_servers = self._config.get("stashed_servers", {})
113+
if client_name not in stashed_servers:
114+
stashed_servers[client_name] = {}
115+
stashed_servers[client_name][server_name] = server_dict
116+
117+
# Use set_config to save the updated stashed_servers
118+
result = self.config_manager.set_config("stashed_servers", stashed_servers)
119+
self._refresh_config()
120+
return result
121+
except Exception as e:
122+
logger.error(f"Failed to save stashed server: {e}")
123+
return False
124+
125+
def pop_server(self, client_name: str, server_name: str) -> Optional[Dict[str, Any]]:
126+
"""Retrieve a stashed server configuration from the global config
127+
128+
Args:
129+
client_name: Name of the client the server belongs to
130+
server_name: Name of the server to retrieve
131+
132+
Returns:
133+
Dict: Server configuration or None if not found
134+
"""
135+
# Refresh config to ensure we have the latest state
136+
self._refresh_config()
137+
138+
# Check if stashed_servers section exists
139+
stashed_servers = self._config.get("stashed_servers", {})
140+
if not stashed_servers:
141+
return None
142+
143+
# Check if client section exists
144+
if client_name not in stashed_servers:
145+
return None
146+
147+
# Check if server exists
148+
if server_name not in stashed_servers[client_name]:
149+
return None
150+
151+
# Get the server configuration
152+
server_config = stashed_servers[client_name][server_name]
153+
154+
# Remove the server from stashed servers
155+
del stashed_servers[client_name][server_name]
156+
157+
# Clean up empty client section if needed
158+
if not stashed_servers[client_name]:
159+
del stashed_servers[client_name]
160+
161+
# Clean up empty stashed_servers section if needed
162+
if not stashed_servers:
163+
# Set to None to remove the key completely
164+
self.config_manager.set_config("stashed_servers", None)
165+
else:
166+
# Update with modified stashed_servers
167+
self.config_manager.set_config("stashed_servers", stashed_servers)
168+
169+
# Refresh config after changes
170+
self._refresh_config()
171+
172+
return server_config
173+
174+
def is_server_stashed(self, client_name: str, server_name: str) -> bool:
175+
"""Check if a server is stashed in the global config
176+
177+
Args:
178+
client_name: Name of the client the server belongs to
179+
server_name: Name of the server to check
180+
181+
Returns:
182+
bool: True if server is stashed, False otherwise
183+
"""
184+
# Refresh config to ensure we have the latest state
185+
self._refresh_config()
186+
187+
# Check if stashed_servers section exists
188+
stashed_servers = self._config.get("stashed_servers", {})
189+
if not stashed_servers:
190+
return False
191+
192+
# Check if client section exists
193+
if client_name not in stashed_servers:
194+
return False
195+
196+
# Check if server exists
197+
return server_name in stashed_servers[client_name]
198+
199+
def get_stashed_servers(self, client_name: str) -> Dict[str, Dict[str, Any]]:
200+
"""Get all stashed servers for a client
201+
202+
Args:
203+
client_name: Name of the client to get stashed servers for
204+
205+
Returns:
206+
Dict: Dictionary of server configurations by name
207+
"""
208+
# Refresh config to ensure we have the latest state
209+
self._refresh_config()
210+
211+
# Check if stashed_servers section exists
212+
stashed_servers = self._config.get("stashed_servers", {})
213+
if not stashed_servers:
214+
return {}
215+
216+
# Check if client section exists
217+
if client_name not in stashed_servers:
218+
return {}
219+
220+
return stashed_servers[client_name]

src/mcpm/commands/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
import click
66
from rich.console import Console
7-
from rich.table import Table
87
from rich.panel import Panel
8+
from rich.table import Table
99

10-
from mcpm.utils.config import ConfigManager
10+
from mcpm.clients.client_config import ClientConfigManager
1111
from mcpm.utils.client_registry import ClientRegistry
1212

1313
console = Console()
14-
config_manager = ConfigManager()
14+
client_config_manager = ClientConfigManager()
1515

1616

1717
@click.command()

src/mcpm/commands/pop.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
"""Pop command for MCPM - restores previously stashed server configuration"""
22

33
import logging
4+
45
import click
56
from rich.console import Console
67

8+
from mcpm.clients.client_config import ClientConfigManager
79
from mcpm.utils.client_registry import ClientRegistry
8-
from mcpm.utils.config import ConfigManager
910

1011
console = Console()
1112
logger = logging.getLogger(__name__)
13+
client_config_manager = ClientConfigManager()
1214

1315

1416
@click.command()
@@ -34,18 +36,15 @@ def pop(server_name):
3436
console.print("Please switch to a supported client using 'mcpm client <client-name>'")
3537
return
3638

37-
# Access the global config manager
38-
config_manager = ConfigManager()
39-
4039
# Check if the server is stashed for this client
41-
if not config_manager.is_server_stashed(client, server_name):
40+
if not client_config_manager.is_server_stashed(client, server_name):
4241
console.print(
4342
f"[bold red]Error:[/] Server '{server_name}' not found in stashed configurations for {client_name}."
4443
)
4544
return
4645

4746
# Get the server configuration from global stashed servers
48-
server_data = config_manager.pop_server(client, server_name)
47+
server_data = client_config_manager.pop_server(client, server_name)
4948
if not server_data:
5049
console.print(f"[bold red]Error:[/] Failed to retrieve stashed configuration for server '{server_name}'.")
5150
return
@@ -60,5 +59,5 @@ def pop(server_name):
6059
console.print("Remember to restart the client for changes to take effect.")
6160
else:
6261
# If adding failed, re-stash the server to avoid data loss
63-
config_manager.stash_server(client, server_name, server_data)
62+
client_config_manager.stash_server(client, server_name, server_data)
6463
console.print(f"[bold red]Failed to restore[/] '{server_name}' for {client_name}.")

0 commit comments

Comments
 (0)