Skip to content

Commit 1d23445

Browse files
rohita5lclaude
andauthored
Make web_search MCP registration non-blocking on claude setup (#105)
Fixes #100. When `claude mcp add-json` failed, the RuntimeError raised by `add_claude_mcp_server` propagated out of `write_tool_config` and aborted the whole `ucode claude` flow before state could be saved or the tool marked as managed. Catch the error in `_register_web_search_mcp`, warn the user, and return False so setup completes. Re-running `ucode claude` will retry the MCP registration. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 32c7163 commit 1d23445

2 files changed

Lines changed: 63 additions & 3 deletions

File tree

src/ucode/agents/claude.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
)
2323
from ucode.state import mark_tool_managed, save_state
2424
from ucode.telemetry import agent_version, ucode_version
25+
from ucode.ui import print_warning
2526

2627
CLAUDE_CONFIG_DIR = Path.home() / ".claude"
2728
CLAUDE_SETTINGS_PATH = CLAUDE_CONFIG_DIR / "ucode-settings.json"
@@ -128,10 +129,14 @@ def render_overlay(
128129
return overlay, keys
129130

130131

131-
def _register_web_search_mcp(workspace: str, search_model: str, profile: str | None = None) -> None:
132+
def _register_web_search_mcp(workspace: str, search_model: str, profile: str | None = None) -> bool:
132133
"""Register (or replace) the web_search MCP server in Claude Code's user
133134
scope via `claude mcp add-json`. Removes any prior entry first so re-runs
134-
pick up changes to the workspace, model, or ucode binary path."""
135+
pick up changes to the workspace, model, or ucode binary path.
136+
137+
Returns True if registration succeeded. Failures are non-blocking: we warn
138+
and return False so the rest of `ucode claude` setup can complete.
139+
"""
135140
# Imported lazily to avoid a circular import via ucode.mcp -> ucode.agents.
136141
from ucode.mcp import (
137142
MCP_CLEANUP_SCOPES,
@@ -146,7 +151,12 @@ def _register_web_search_mcp(workspace: str, search_model: str, profile: str | N
146151
# Best-effort cleanup of stale entries — keep going.
147152
pass
148153
entry = _web_search_mcp_entry(workspace, search_model, profile)
149-
add_claude_mcp_server(WEB_SEARCH_MCP_NAME, entry)
154+
try:
155+
add_claude_mcp_server(WEB_SEARCH_MCP_NAME, entry)
156+
except RuntimeError as exc:
157+
print_warning(f"{exc} Web search will be unavailable; re-run `ucode claude` to retry.")
158+
return False
159+
return True
150160

151161

152162
def _unregister_web_search_mcp() -> None:

tests/test_agent_claude.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,56 @@ def boom(name, scope):
252252
claude._register_web_search_mcp(WS, "m")
253253
assert added == ["web_search"]
254254

255+
def test_add_failure_is_non_blocking_and_warns(self, monkeypatch, capsys):
256+
# Regression: a failing `claude mcp add-json` used to abort the whole
257+
# `ucode claude` setup. It must now warn and return False instead.
258+
import ucode.mcp as mcp_mod
259+
260+
monkeypatch.setattr(mcp_mod, "remove_claude_mcp_server", lambda name, scope: False)
261+
262+
def boom(name, entry, scope=mcp_mod.MCP_USER_SCOPE):
263+
raise RuntimeError("Failed to add MCP server 'web_search' via claude CLI.")
264+
265+
monkeypatch.setattr(mcp_mod, "add_claude_mcp_server", boom)
266+
result = claude._register_web_search_mcp(WS, "m")
267+
assert result is False
268+
captured = capsys.readouterr()
269+
assert "web_search" in captured.out.lower() or "web search" in captured.out.lower()
270+
271+
def test_add_success_returns_true(self, monkeypatch):
272+
import ucode.mcp as mcp_mod
273+
274+
monkeypatch.setattr(mcp_mod, "remove_claude_mcp_server", lambda name, scope: False)
275+
monkeypatch.setattr(
276+
mcp_mod,
277+
"add_claude_mcp_server",
278+
lambda name, entry, scope=mcp_mod.MCP_USER_SCOPE: None,
279+
)
280+
assert claude._register_web_search_mcp(WS, "m") is True
281+
282+
def test_write_tool_config_completes_when_mcp_registration_fails(self, monkeypatch):
283+
# Regression for issue #100: a `claude mcp add-json` failure must not
284+
# block the rest of `ucode claude` setup (state save, managed-key
285+
# marking, etc.) from completing.
286+
import ucode.mcp as mcp_mod
287+
288+
monkeypatch.setattr(claude, "backup_existing_file", lambda *a, **kw: True)
289+
monkeypatch.setattr(claude, "read_json_safe", lambda path: {})
290+
monkeypatch.setattr(claude, "write_json_file", lambda path, payload: None)
291+
saved: list[dict] = []
292+
monkeypatch.setattr(claude, "save_state", lambda state: saved.append(state))
293+
monkeypatch.setattr(mcp_mod, "remove_claude_mcp_server", lambda name, scope: False)
294+
295+
def boom(name, entry, scope=mcp_mod.MCP_USER_SCOPE):
296+
raise RuntimeError("Failed to add MCP server 'web_search' via claude CLI.")
297+
298+
monkeypatch.setattr(mcp_mod, "add_claude_mcp_server", boom)
299+
300+
state = {"workspace": WS, "codex_models": ["databricks-gpt-5"]}
301+
result = claude.write_tool_config(state, "databricks-claude-sonnet-4")
302+
assert saved, "save_state should still be called when MCP registration fails"
303+
assert result["workspace"] == WS
304+
255305

256306
class TestClaudeLaunch:
257307
def test_sets_oauth_token_before_exec(self, monkeypatch):

0 commit comments

Comments
 (0)