feat: add --config-path flag to claude-desktop install command (#3380)

* feat: add --config-path flag to claude-desktop install command

* feat: add --config-path flag to claude-desktop install command

* docs: add --config-path option to install-mcp documentation

* fix: show specific error message when provided --config-path does not exist
This commit is contained in:
Sumanshu Nankana 2026-03-04 20:30:59 +00:00 committed by GitHub
commit 34d848b16a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 7 deletions

View file

@ -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

View file

@ -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:

View file

@ -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."""