From e424b26ef0c3550892c0933c6ed25e829b7840b5 Mon Sep 17 00:00:00 2001 From: HeaTTap Date: Wed, 16 Sep 2026 08:06:54 +0000 Subject: [PATCH] feat(discovery): add cline support to home discovery (#2) --- src/mcplint/discovery.py | 30 ++++++++++++++ src/mcplint/parse.py | 2 + tests/test_cli.py | 37 +++++++++++++++++ tests/test_parse.py | 89 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+) diff --git a/src/mcplint/discovery.py b/src/mcplint/discovery.py index 2610e0b..cc6e784 100644 --- a/src/mcplint/discovery.py +++ b/src/mcplint/discovery.py @@ -22,6 +22,36 @@ ("~/.codex/config.toml", "codex"), ("~/.gemini/settings.json", "gemini"), ("~/.config/opencode/opencode.json", "opencode"), + ( + "~/.config/Code/User/globalStorage/" + "saoudrizwan.claude-dev/settings/cline_mcp_settings.json", + "cline", + ), + ( + "~/.config/Cursor/User/globalStorage/" + "saoudrizwan.claude-dev/settings/cline_mcp_settings.json", + "cline", + ), + ( + "~/Library/Application Support/Code/User/globalStorage/" + "saoudrizwan.claude-dev/settings/cline_mcp_settings.json", + "cline", + ), + ( + "~/Library/Application Support/Cursor/User/globalStorage/" + "saoudrizwan.claude-dev/settings/cline_mcp_settings.json", + "cline", + ), + ( + "~/AppData/Roaming/Code/User/globalStorage/" + "saoudrizwan.claude-dev/settings/cline_mcp_settings.json", + "cline", + ), + ( + "~/AppData/Roaming/Cursor/User/globalStorage/" + "saoudrizwan.claude-dev/settings/cline_mcp_settings.json", + "cline", + ), ] INSTRUCTION_FILES = [ diff --git a/src/mcplint/parse.py b/src/mcplint/parse.py index a3dd0fc..5b62723 100644 --- a/src/mcplint/parse.py +++ b/src/mcplint/parse.py @@ -55,6 +55,8 @@ def strip_jsonc(text: str) -> str: def guess_client(path: Path) -> str: posix = path.as_posix() name = path.name.lower() + if "cline" in name or "saoudrizwan.claude-dev" in posix: + return "cline" if ".cursor/" in posix: return "cursor" if "windsurf" in posix: diff --git a/tests/test_cli.py b/tests/test_cli.py index 712ce03..acc2c6d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -53,3 +53,40 @@ def test_version_flag() -> None: result = runner.invoke(app, ["--version"]) assert result.exit_code == 0 assert "mcplint" in result.output + + +def test_scan_home_finds_cline(tmp_path: Path, monkeypatch) -> None: + home = tmp_path / "home" + empty = tmp_path / "empty" + empty.mkdir() + cline_dir = ( + home + / ".config" + / "Code" + / "User" + / "globalStorage" + / "saoudrizwan.claude-dev" + / "settings" + ) + cline_dir.mkdir(parents=True) + settings = cline_dir / "cline_mcp_settings.json" + settings.write_text( + """ + { + "mcpServers": { + "demo": { + "command": "npx", + "args": ["-y", "safe-tool@1.0.0"] + } + } + } + """, + encoding="utf-8", + ) + monkeypatch.setenv("HOME", str(home)) + result = runner.invoke(app, ["scan", str(empty), "--home", "--json", "--fail-on", "none"]) + assert result.exit_code == 0, result.output + payload = json.loads(result.output) + assert any("cline_mcp_settings.json" in c for c in payload["scanned"]["configs"]) + + diff --git a/tests/test_parse.py b/tests/test_parse.py index d295819..dc10f17 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -84,3 +84,92 @@ def test_nested_configs_are_discovered(tmp_path: Path) -> None: assert ".mcp.json" in names assert "mcp.json" in names assert all("node_modules" not in str(path) for path in configs) + + +def test_cline_linux_discovery_and_parse(tmp_path: Path, monkeypatch) -> None: + from mcplint.discovery import discover + + home = tmp_path / "home" + cline_dir = ( + home + / ".config" + / "Code" + / "User" + / "globalStorage" + / "saoudrizwan.claude-dev" + / "settings" + ) + cline_dir.mkdir(parents=True) + settings = cline_dir / "cline_mcp_settings.json" + settings.write_text( + """ + { + "mcpServers": { + "weather": { + "command": "node", + "args": ["build/index.js"], + "env": { + "API_KEY": "secret" + } + } + } + } + """, + encoding="utf-8", + ) + + monkeypatch.setenv("HOME", str(home)) + configs, _ = discover([], include_home=True) + assert any(c.name == "cline_mcp_settings.json" for c in configs) + + parsed = parse_config_file(settings) + assert parsed is not None + assert parsed.client == "cline" + assert len(parsed.servers) == 1 + assert parsed.servers[0].name == "weather" + assert parsed.servers[0].command == ["node", "build/index.js"] + assert parsed.servers[0].env["API_KEY"] == "secret" + + +def test_cline_macos_discovery_with_spaces(tmp_path: Path, monkeypatch) -> None: + from mcplint.discovery import discover + + home = tmp_path / "home" + cline_dir = ( + home + / "Library" + / "Application Support" + / "Code" + / "User" + / "globalStorage" + / "saoudrizwan.claude-dev" + / "settings" + ) + cline_dir.mkdir(parents=True) + settings = cline_dir / "cline_mcp_settings.json" + settings.write_text( + """ + { + "mcpServers": { + "fetch": { + "command": "uvx", + "args": ["mcp-server-fetch"] + } + } + } + """, + encoding="utf-8", + ) + + monkeypatch.setenv("HOME", str(home)) + configs, _ = discover([], include_home=True) + assert any( + "Application Support" in str(c) and c.name == "cline_mcp_settings.json" + for c in configs + ) + + parsed = parse_config_file(settings) + assert parsed is not None + assert parsed.client == "cline" + assert parsed.servers[0].name == "fetch" +