Skip to content

Commit a63b2b0

Browse files
committed
feat(mcp): enhance MCP backend restoration and migration handling
- Introduced functions to restore user and workspace MCP configurations from backend definitions, ensuring that existing configurations are merged and unnecessary keys are removed. - Updated hook uninstall processes to restore MCP configurations alongside removing hooks, improving the overall management of agent settings. - Added checks for migratable MCP backends to prevent unnecessary operations when no valid backends are found. - Enhanced test coverage for MCP restoration and migration scenarios, ensuring robust handling of configurations across different agents. This update aims to streamline the MCP management process and improve the reliability of agent configurations.
1 parent f49558d commit a63b2b0

10 files changed

Lines changed: 682 additions & 83 deletions

File tree

‎scripts/pre-commit-hooks/prek-hook-groups.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
# Integration tests (src/tests/integration) are manual-only:
55
# uv run pytest -m integration --run-integration
66
# prek-loop.sh unsets CYT_RUN_INTEGRATION_TESTS; do not add integration pytest hooks here.
7+
# MCP restore/uninstall unit tests use autouse isolation in src/tests/conftest.py
8+
# (_isolate_agent_hook_paths + _isolate_cyt_mcp_user_config) so pre-commit never
9+
# touches ~/.cursor/mcp.json, ~/.config/cyt/mcp/, or workspace .agents/cyt/config/mcp/.
710
#
811
# Hooks may appear in more than one group. The `suggestion` hook is omitted
912
# intentionally (informational only).

‎src/cyt/hook/setup_wizard.py‎

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,7 +2226,12 @@ def _apply_prevent_hallucinations_config(
22262226
agents: list[HookAgentName],
22272227
) -> dict[str, Any]:
22282228
from cyt.config import save_user_config, sync_config_in_place
2229-
from cyt.tools.cyt_mcp_setup import setup_cyt_mcp_for_agent, write_mcp_aggregator_yaml
2229+
from cyt.hook.install_scope import CytInstallScope
2230+
from cyt.tools.cyt_mcp_setup import (
2231+
has_migratable_mcp_backends,
2232+
setup_cyt_mcp_for_agent,
2233+
write_mcp_aggregator_yaml,
2234+
)
22302235

22312236
inject_map: dict[str, str] = {
22322237
"cursor": "hook",
@@ -2251,6 +2256,7 @@ def _apply_prevent_hallucinations_config(
22512256
sync_config_in_place(config, config_path)
22522257

22532258
invocation = detect_hook_cli_invocation()
2259+
scope = CytInstallScope.from_cwd()
22542260
if invocation.is_dev and invocation.repo_root is not None:
22552261
print(
22562262
f"\nInstalling development cyt-mcp via uv run --directory {invocation.repo_root}",
@@ -2261,6 +2267,12 @@ def _apply_prevent_hallucinations_config(
22612267
if inject_map.get(agent) != "hook":
22622268
write_mcp_aggregator_yaml(agent, transport="stdio", verify_only=True)
22632269
continue
2270+
if not invocation.is_dev and not has_migratable_mcp_backends(agent, scope):
2271+
print(
2272+
f"No MCP backend servers found; skipping cyt-mcp injection for {agent}.",
2273+
file=sys.stderr,
2274+
)
2275+
continue
22642276
migrate_backends = _prompt_prevent_hallucinations_mcp_migration(agent)
22652277
setup_cyt_mcp_for_agent(
22662278
agent,
@@ -2603,9 +2615,9 @@ def run_hook_uninstall_workspace(
26032615
agents: list[HookAgentName] | None = None,
26042616
workspace_root: Path | None = None,
26052617
) -> None:
2606-
"""Remove workspace-scoped cyt-mcp artifacts from the current or given project."""
2618+
"""Restore workspace-scoped agent MCP configs from cyt backend defs."""
26072619
from cyt.hook.install_scope import CytInstallScope
2608-
from cyt.tools.cyt_mcp_setup import remove_workspace_cyt_mcp_for_agent
2620+
from cyt.tools.cyt_mcp_setup import restore_workspace_cyt_mcp_for_agent
26092621

26102622
selected_agents = _resolve_hook_setup_agents(agents)
26112623
scope = CytInstallScope(
@@ -2618,7 +2630,7 @@ def run_hook_uninstall_workspace(
26182630
print("CYT workspace MCP uninstall\n")
26192631
any_changed = False
26202632
for agent in selected_agents:
2621-
if remove_workspace_cyt_mcp_for_agent(agent, scope):
2633+
if restore_workspace_cyt_mcp_for_agent(agent, scope):
26222634
any_changed = True
26232635

26242636
if any_changed:
@@ -2628,8 +2640,15 @@ def run_hook_uninstall_workspace(
26282640

26292641

26302642
def run_hook_uninstall(*, agents: list[HookAgentName] | None = None) -> None:
2631-
"""Remove CYT agent hooks from Claude, Codex, and/or Cursor config files."""
2643+
"""Remove CYT agent hooks and restore migrated MCP configs."""
2644+
from cyt.hook.install_scope import CytInstallScope
2645+
from cyt.tools.cyt_mcp_setup import (
2646+
restore_user_cyt_mcp_for_agent,
2647+
restore_workspace_cyt_mcp_for_agent,
2648+
)
2649+
26322650
selected_agents = _resolve_hook_setup_agents(agents)
2651+
scope = CytInstallScope.from_cwd()
26332652

26342653
if len(selected_agents) == 1:
26352654
print(f"CYT hook uninstall ({selected_agents[0]})\n")
@@ -2642,23 +2661,31 @@ def run_hook_uninstall(*, agents: list[HookAgentName] | None = None) -> None:
26422661
path = _agent_hook_path(agent)
26432662
if not path.is_file():
26442663
print(f"{label}: skipped ({path} not found)")
2645-
continue
2646-
preserve_empty_hooks_object = agent == "cursor"
2647-
if uninstall_hooks_from_file(
2648-
path,
2649-
preserve_empty_hooks_object=preserve_empty_hooks_object,
2650-
):
2651-
print(f"{label}: removed CYT hook from {path}")
2652-
any_changed = True
26532664
else:
2654-
print(f"{label}: no CYT hook in {path}")
2665+
preserve_empty_hooks_object = agent == "cursor"
2666+
if uninstall_hooks_from_file(
2667+
path,
2668+
preserve_empty_hooks_object=preserve_empty_hooks_object,
2669+
):
2670+
print(f"{label}: removed CYT hook from {path}")
2671+
any_changed = True
2672+
else:
2673+
print(f"{label}: no CYT hook in {path}")
2674+
2675+
if restore_user_cyt_mcp_for_agent(agent, scope):
2676+
print(f"{label}: restored user MCP config")
2677+
any_changed = True
2678+
2679+
if scope.has_workspace and restore_workspace_cyt_mcp_for_agent(agent, scope):
2680+
print(f"{label}: restored workspace MCP config")
2681+
any_changed = True
26552682

26562683
if "cursor" in selected_agents:
26572684
removed = remove_windows_hook_wrappers()
26582685
if removed:
26592686
print(f"Cursor: removed {len(removed)} Windows hook wrapper(s)")
26602687

26612688
if any_changed:
2662-
print("\nRestart your agent so hook changes take effect.")
2689+
print("\nRestart your agent so hook and MCP changes take effect.")
26632690
else:
2664-
print("\nNo hook files were modified.")
2691+
print("\nNo hook or MCP files were modified.")

‎src/cyt/proxy/cli_impl.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,12 @@ def _add_hook_handler_args(parser: argparse.ArgumentParser) -> None:
391391
hook_uninstall_parent.add_argument(
392392
"--uninstall",
393393
action="store_true",
394-
help="Remove CYT hooks from agent config file(s)",
394+
help="Remove CYT hooks and restore migrated MCP configs (user and workspace)",
395395
)
396396
hook_uninstall_parent.add_argument(
397397
"--uninstall-workspace",
398398
action="store_true",
399-
help="Remove workspace-scoped cyt-mcp from the current project only",
399+
help="Restore workspace-scoped agent MCP configs from cyt backend defs only",
400400
)
401401

402402
hook_parser = subparsers.add_parser(

0 commit comments

Comments
 (0)