Skip to content

Commit d90b1e3

Browse files
authored
Merge pull request #16 from zhujian0805/main
refactor: update MCP clients and tool configurations
2 parents fb57650 + c031548 commit d90b1e3

File tree

12 files changed

+484
-173
lines changed

12 files changed

+484
-173
lines changed

code_assistant_manager/copilot_models.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -129,23 +129,27 @@ def list_models():
129129
load_env()
130130
logger.debug("Environment variables loaded")
131131

132-
github_token = os.environ.get("GITHUB_TOKEN")
133-
if not github_token:
134-
logger.error("GITHUB_TOKEN environment variable is required but not found")
135-
raise SystemExit("GITHUB_TOKEN environment variable is required")
136-
137-
logger.debug("Starting Copilot token refresh loop")
138-
state = {}
139-
start_refresh_loop(github_token, state)
140-
141-
time.sleep(1)
142-
143-
copilot_token = state.get("copilot_token")
144-
if not copilot_token:
145-
logger.debug("No token in state, fetching directly")
146-
info = get_copilot_token(github_token)
147-
copilot_token = info["token"]
148-
state["copilot_token"] = copilot_token
132+
# If EndpointManager provided an API key (proxy mode), we don't need GitHub token auth.
133+
if os.environ.get("api_key"):
134+
copilot_token = ""
135+
else:
136+
github_token = os.environ.get("GITHUB_TOKEN")
137+
if not github_token:
138+
logger.error("GITHUB_TOKEN environment variable is required but not found")
139+
raise SystemExit("GITHUB_TOKEN environment variable is required")
140+
141+
logger.debug("Starting Copilot token refresh loop")
142+
state = {}
143+
start_refresh_loop(github_token, state)
144+
145+
time.sleep(1)
146+
147+
copilot_token = state.get("copilot_token")
148+
if not copilot_token:
149+
logger.debug("No token in state, fetching directly")
150+
info = get_copilot_token(github_token)
151+
copilot_token = info["token"]
152+
state["copilot_token"] = copilot_token
149153

150154
# Get base URL from environment (set by EndpointManager) or from config
151155
base_url = os.environ.get("endpoint")

code_assistant_manager/mcp/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
ClaudeMCPClient,
1111
CodeBuddyMCPClient,
1212
CodexMCPClient,
13+
ContinueMCPClient,
1314
CopilotMCPClient,
1415
CrushMCPClient,
1516
CursorAgentMCPClient,
@@ -42,4 +43,5 @@
4243
"QoderCLIMCPClient",
4344
"NeovateMCPClient",
4445
"OpenCodeMCPClient",
46+
"ContinueMCPClient",
4547
]

code_assistant_manager/mcp/clients.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .codex import CodexMCPClient
66
from .copilot import CopilotMCPClient
77
from .crush import CrushMCPClient
8+
from .continue_mcp import ContinueMCPClient
89
from .cursor import CursorAgentMCPClient
910
from .droid import DroidMCPClient
1011
from .gemini import GeminiMCPClient
@@ -19,6 +20,7 @@
1920
"ClaudeMCPClient",
2021
"CodeBuddyMCPClient",
2122
"CodexMCPClient",
23+
"ContinueMCPClient",
2224
"CopilotMCPClient",
2325
"CrushMCPClient",
2426
"CursorAgentMCPClient",
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"""Continue MCP client implementation."""
2+
3+
from pathlib import Path
4+
5+
import yaml
6+
7+
from .base import print_squared_frame
8+
from .base_client import MCPClient
9+
10+
11+
class ContinueMCPClient(MCPClient):
12+
"""MCP client for Continue.dev."""
13+
14+
def __init__(self):
15+
super().__init__("continue")
16+
17+
def _get_config_paths(self, scope: str):
18+
home = Path.home()
19+
if scope == "user":
20+
return [home / ".continue" / "config.yaml"]
21+
elif scope == "project":
22+
return [Path.cwd() / ".continue" / "config.yaml"]
23+
else: # all
24+
return [
25+
home / ".continue" / "config.yaml",
26+
Path.cwd() / ".continue" / "config.yaml",
27+
]
28+
29+
def _get_config_locations(self, tool_name: str):
30+
# Used by list_servers()
31+
return self._get_config_paths("all")
32+
33+
def _convert_server_config_to_client_format(self, server_config) -> dict:
34+
# Continue uses the standard MCP "mcpServers" format (stdio/http)
35+
return super()._convert_server_config_to_client_format(server_config)
36+
37+
def _add_server_config_to_file(
38+
self, config_path, server_name: str, client_config: dict
39+
) -> bool:
40+
config_path = Path(config_path)
41+
try:
42+
config = {}
43+
if config_path.exists():
44+
with open(config_path, "r", encoding="utf-8") as f:
45+
loaded = yaml.safe_load(f) or {}
46+
if isinstance(loaded, dict):
47+
config = loaded
48+
49+
if "mcpServers" not in config or not isinstance(
50+
config.get("mcpServers"), dict
51+
):
52+
config["mcpServers"] = {}
53+
54+
config["mcpServers"][server_name] = client_config
55+
56+
config_path.parent.mkdir(parents=True, exist_ok=True)
57+
with open(config_path, "w", encoding="utf-8") as f:
58+
yaml.safe_dump(config, f, sort_keys=False)
59+
return True
60+
except Exception as e:
61+
print(f"Error adding server to Continue config {config_path}: {e}")
62+
return False
63+
64+
def add_server(self, server_name: str, scope: str = "user") -> bool:
65+
server_config = self.get_server_config_from_registry(server_name)
66+
if not server_config:
67+
print_squared_frame(
68+
f"{self.tool_name.upper()} - {server_name.upper()}",
69+
f"Error: Server '{server_name}' not found in registry",
70+
)
71+
return False
72+
73+
client_config = self._convert_server_config_to_client_format(server_config)
74+
config_paths = self._get_config_paths(scope)
75+
for config_path in config_paths:
76+
if self._add_server_config_to_file(config_path, server_name, client_config):
77+
level = (
78+
"user-level" if config_path == config_paths[0] else "project-level"
79+
)
80+
print(
81+
f"✓ Successfully added {server_name} to {level} Continue configuration"
82+
)
83+
return True
84+
85+
print_squared_frame(
86+
f"{self.tool_name.upper()} - {server_name.upper()}",
87+
"Error: Failed to add server to any config file",
88+
)
89+
return False
90+
91+
def remove_server(self, server_name: str, scope: str = "user") -> bool:
92+
config_paths = self._get_config_paths(scope)
93+
removed_any = False
94+
95+
for config_path in config_paths:
96+
config_path = Path(config_path)
97+
if not config_path.exists():
98+
continue
99+
100+
try:
101+
with open(config_path, "r", encoding="utf-8") as f:
102+
config = yaml.safe_load(f) or {}
103+
if not isinstance(config, dict):
104+
continue
105+
106+
if "mcpServers" in config and isinstance(config["mcpServers"], dict):
107+
if server_name in config["mcpServers"]:
108+
del config["mcpServers"][server_name]
109+
with open(config_path, "w", encoding="utf-8") as f:
110+
yaml.safe_dump(config, f, sort_keys=False)
111+
removed_any = True
112+
break
113+
except Exception:
114+
continue
115+
116+
if removed_any:
117+
print(f" Removed {server_name} from {self.tool_name} configuration")
118+
else:
119+
print(f" {server_name} not found in {self.tool_name} configuration")
120+
return removed_any
121+
122+
def list_servers(self, scope: str = "all") -> bool:
123+
tool_configs = self.get_tool_config(self.tool_name)
124+
if not tool_configs:
125+
print(f"No MCP server configurations found for {self.tool_name}")
126+
return False
127+
128+
config_locations = self._get_config_locations(self.tool_name)
129+
user_servers = {}
130+
project_servers = {}
131+
132+
home = Path.home()
133+
cwd = Path.cwd()
134+
135+
for config_path in config_locations:
136+
config_path = Path(config_path)
137+
if not config_path.exists():
138+
continue
139+
try:
140+
with open(config_path, "r", encoding="utf-8") as f:
141+
config = yaml.safe_load(f) or {}
142+
if not isinstance(config, dict):
143+
continue
144+
servers = config.get("mcpServers")
145+
if not isinstance(servers, dict):
146+
continue
147+
148+
if config_path == home / ".continue" / "config.yaml":
149+
user_servers.update(servers)
150+
elif config_path == cwd / ".continue" / "config.yaml":
151+
project_servers.update(servers)
152+
except Exception:
153+
continue
154+
155+
content_lines = []
156+
show_user = scope in ["all", "user"]
157+
show_project = scope in ["all", "project"]
158+
159+
if show_user and user_servers:
160+
content_lines.append("User-level servers:")
161+
content_lines.extend(
162+
[f" {name}: {cfg}" for name, cfg in user_servers.items()]
163+
)
164+
if show_project and project_servers:
165+
content_lines.append("")
166+
167+
if show_project and project_servers:
168+
content_lines.append("Project-level servers:")
169+
content_lines.extend(
170+
[f" {name}: {cfg}" for name, cfg in project_servers.items()]
171+
)
172+
173+
if content_lines:
174+
print_squared_frame(
175+
f"{self.tool_name.upper()} MCP SERVERS", "\n".join(content_lines)
176+
)
177+
else:
178+
print_squared_frame(
179+
f"{self.tool_name.upper()} MCP SERVERS", "No MCP servers configured"
180+
)
181+
return True

code_assistant_manager/mcp/manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def _initialize_clients(
2424
CodexMCPClient,
2525
CopilotMCPClient,
2626
CrushMCPClient,
27+
ContinueMCPClient,
2728
CursorAgentMCPClient,
2829
DroidMCPClient,
2930
GeminiMCPClient,
@@ -50,6 +51,7 @@ def _initialize_clients(
5051
"crush": CrushMCPClient(),
5152
"cursor-agent": CursorAgentMCPClient(),
5253
"opencode": OpenCodeMCPClient(),
54+
"continue": ContinueMCPClient(),
5355
}
5456

5557
def get_client(self, tool_name: str):

0 commit comments

Comments
 (0)