diff --git a/docs/cli/install-mcp.mdx b/docs/cli/install-mcp.mdx index bf1b60b36..0171b7854 100644 --- a/docs/cli/install-mcp.mdx +++ b/docs/cli/install-mcp.mdx @@ -65,6 +65,7 @@ See [Server Configuration](/deployment/server-configuration) for the full config | Python | `--python` | Python version (e.g., `3.11`) | | Project | `--project` | Run within a uv project directory | | Requirements | `--with-requirements` | Install from a requirements file | +| Config Path | `--config-path` | Custom path to Claude Desktop config directory (`claude-desktop` only) | ## Examples @@ -92,6 +93,10 @@ fastmcp install cursor server.py --env-file .env fastmcp install claude-desktop server.py \ --python 3.11 \ --with-requirements requirements.txt + +# With custom config path (claude-desktop only) +fastmcp install claude-desktop server.py \ + --config-path "C:\Users\username\AppData\Local\Packages\Claude_xyz\LocalCache\Roaming\Claude" ``` ## Generating MCP JSON diff --git a/src/fastmcp/cli/install/claude_desktop.py b/src/fastmcp/cli/install/claude_desktop.py index 83755c162..780b97c36 100644 --- a/src/fastmcp/cli/install/claude_desktop.py +++ b/src/fastmcp/cli/install/claude_desktop.py @@ -17,8 +17,19 @@ from .shared import process_common_args logger = get_logger(__name__) -def get_claude_config_path() -> Path | None: - """Get the Claude config directory based on platform.""" +def get_claude_config_path(config_path: Path | None = None) -> Path | None: + """Get the Claude config directory based on platform. + + Args: + config_path: Optional custom path to the Claude Desktop config directory + """ + + if config_path: + if not config_path.exists(): + print(f"[red]The specified config path does not exist: {config_path}[/red]") + return None + return config_path + if sys.platform == "win32": path = Path(Path.home(), "AppData", "Roaming", "Claude") elif sys.platform == "darwin": @@ -46,6 +57,7 @@ def install_claude_desktop( python_version: str | None = None, with_requirements: Path | None = None, project: Path | None = None, + config_path: Path | None = None, ) -> bool: """Install FastMCP server in Claude Desktop. @@ -59,16 +71,18 @@ def install_claude_desktop( python_version: Optional Python version to use with_requirements: Optional requirements file to install from project: Optional project directory to run within + config_path: Optional custom path to Claude Desktop config directory Returns: True if installation was successful, False otherwise """ - config_dir = get_claude_config_path() + config_dir = get_claude_config_path(config_path=config_path) if not config_dir: - print( - "[red]Claude Desktop config directory not found.[/red]\n" - "[blue]Please ensure Claude Desktop is installed and has been run at least once to initialize its config.[/blue]" - ) + if not config_path: + print( + "[red]Claude Desktop config directory not found.[/red]\n" + "[blue]Please ensure Claude Desktop is installed and has been run at least once to initialize its config.[/blue]" + ) return False config_file = config_dir / "claude_desktop_config.json" @@ -180,6 +194,13 @@ async def claude_desktop_command( help="Run the command within the given project directory", ), ] = None, + config_path: Annotated[ + Path | None, + cyclopts.Parameter( + "--config-path", + help="Custom path to Claude Desktop config directory", + ), + ] = None, ) -> None: """Install an MCP server in Claude Desktop. @@ -204,6 +225,7 @@ async def claude_desktop_command( python_version=python, with_requirements=with_requirements, project=project, + config_path=config_path, ) if not success: diff --git a/tests/cli/test_install.py b/tests/cli/test_install.py index 81fde3c1c..6b354c9b7 100644 --- a/tests/cli/test_install.py +++ b/tests/cli/test_install.py @@ -142,6 +142,20 @@ class TestClaudeDesktopInstall: assert bound.arguments["project"] == Path("/my/project") assert bound.arguments["with_requirements"] == Path("reqs.txt") + def test_claude_desktop_with_config_path(self): + """Test claude-desktop install with custom config path.""" + command, bound, _ = install_app.parse_args( + ["claude-desktop", "server.py", "--config-path", "/custom/path/Claude"] + ) + + assert bound.arguments["config_path"] == Path("/custom/path/Claude") + + def test_claude_desktop_without_config_path(self): + """Test claude-desktop install without config path defaults to None.""" + command, bound, _ = install_app.parse_args(["claude-desktop", "server.py"]) + + assert bound.arguments.get("config_path") is None + class TestCursorInstall: """Test cursor install command."""