Skip to content

claude

Claude app integration utilities.

get_claude_config_path

get_claude_config_path() -> Path | None

Get the Claude config directory based on platform.

Source code in src/mcp/cli/claude.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def get_claude_config_path() -> Path | None:  # pragma: no cover
    """Get the Claude config directory based on platform."""
    if sys.platform == "win32":
        path = Path(Path.home(), "AppData", "Roaming", "Claude")
    elif sys.platform == "darwin":
        path = Path(Path.home(), "Library", "Application Support", "Claude")
    elif sys.platform.startswith("linux"):
        path = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude")
    else:
        return None

    if path.exists():
        return path
    return None

get_uv_path

get_uv_path() -> str

Get the full path to the uv executable.

Source code in src/mcp/cli/claude.py
33
34
35
36
37
38
39
40
41
def get_uv_path() -> str:
    """Get the full path to the uv executable."""
    uv_path = shutil.which("uv")
    if not uv_path:
        logger.error(
            "uv executable not found in PATH, falling back to 'uv'. Please ensure uv is installed and in your PATH"
        )
        return "uv"  # Fall back to just "uv" if not found
    return uv_path

update_claude_config

update_claude_config(
    file_spec: str,
    server_name: str,
    *,
    with_editable: Path | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None
) -> bool

Add or update an MCP server in Claude's configuration.

Parameters:

Name Type Description Default
file_spec str

Path to the server file, optionally with :object suffix

required
server_name str

Name for the server in Claude's config

required
with_editable Path | None

Optional directory to install in editable mode

None
with_packages list[str] | None

Optional list of additional packages to install

None
env_vars dict[str, str] | None

Optional dictionary of environment variables. These are merged with any existing variables, with new values taking precedence.

None

Raises:

Type Description
RuntimeError

If Claude Desktop's config directory is not found, indicating Claude Desktop may not be installed or properly set up.

Source code in src/mcp/cli/claude.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def update_claude_config(
    file_spec: str,
    server_name: str,
    *,
    with_editable: Path | None = None,
    with_packages: list[str] | None = None,
    env_vars: dict[str, str] | None = None,
) -> bool:
    """Add or update an MCP server in Claude's configuration.

    Args:
        file_spec: Path to the server file, optionally with :object suffix
        server_name: Name for the server in Claude's config
        with_editable: Optional directory to install in editable mode
        with_packages: Optional list of additional packages to install
        env_vars: Optional dictionary of environment variables. These are merged with
            any existing variables, with new values taking precedence.

    Raises:
        RuntimeError: If Claude Desktop's config directory is not found, indicating
            Claude Desktop may not be installed or properly set up.
    """
    config_dir = get_claude_config_path()
    uv_path = get_uv_path()
    if not config_dir:
        raise RuntimeError(
            "Claude Desktop config directory not found. Please ensure Claude Desktop"
            " is installed and has been run at least once to initialize its config."
        )

    config_file = config_dir / "claude_desktop_config.json"
    if not config_file.exists():  # pragma: lax no cover
        try:
            config_file.write_text("{}")
        except Exception:
            logger.exception(
                "Failed to create Claude config file",
                extra={
                    "config_file": str(config_file),
                },
            )
            return False

    try:
        config = json.loads(config_file.read_text())
        if "mcpServers" not in config:
            config["mcpServers"] = {}

        # Always preserve existing env vars and merge with new ones
        if server_name in config["mcpServers"] and "env" in config["mcpServers"][server_name]:
            existing_env = config["mcpServers"][server_name]["env"]
            if env_vars:
                # New vars take precedence over existing ones
                env_vars = {**existing_env, **env_vars}
            else:
                env_vars = existing_env

        # Build uv run command
        args = ["run", "--frozen"]

        # Collect all packages in a set to deduplicate
        packages = {MCP_PACKAGE}
        if with_packages:
            packages.update(pkg for pkg in with_packages if pkg)

        # Add all packages with --with
        for pkg in sorted(packages):
            args.extend(["--with", pkg])

        if with_editable:
            args.extend(["--with-editable", str(with_editable)])

        # Convert file path to absolute before adding to command
        # Split off any :object suffix first
        # First check if we have a Windows path (e.g., C:\...)
        has_windows_drive = len(file_spec) > 1 and file_spec[1] == ":"

        # Split on the last colon, but only if it's not part of the Windows drive letter
        if ":" in (file_spec[2:] if has_windows_drive else file_spec):
            file_path, server_object = file_spec.rsplit(":", 1)
            file_spec = f"{Path(file_path).resolve()}:{server_object}"
        else:
            file_spec = str(Path(file_spec).resolve())

        # Add mcp run command
        args.extend(["mcp", "run", file_spec])

        server_config: dict[str, Any] = {"command": uv_path, "args": args}

        # Add environment variables if specified
        if env_vars:
            server_config["env"] = env_vars

        config["mcpServers"][server_name] = server_config

        config_file.write_text(json.dumps(config, indent=2))
        logger.info(
            f"Added server '{server_name}' to Claude config",
            extra={"config_file": str(config_file)},
        )
        return True
    except Exception:  # pragma: no cover
        logger.exception(
            "Failed to update Claude config",
            extra={
                "config_file": str(config_file),
            },
        )
        return False