Add --python, --project, and --with-requirements options to CLI commands (#1190)

This commit is contained in:
Jeremiah Lowin 2025-07-19 21:16:09 -04:00 committed by GitHub
commit a65bfd10be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1140 additions and 40 deletions

View file

@ -53,12 +53,48 @@ You can specify transport options and other configuration:
fastmcp run server.py --transport sse --port 9000
```
### Dependency Management with CLI
When using the FastMCP CLI, you can pass additional options to configure how `uv` runs your server:
```bash
# Run with a specific Python version
fastmcp run server.py --python 3.11
# Run with additional packages
fastmcp run server.py --with pandas --with numpy
# Run with dependencies from a requirements file
fastmcp run server.py --with-requirements requirements.txt
# Combine multiple options
fastmcp run server.py --python 3.10 --with httpx --transport http
# Run within a specific project directory
fastmcp run server.py --project /path/to/project
```
<Note>
When using `--python`, `--with`, `--project`, or `--with-requirements`, the server runs via `uv run` subprocess instead of using your local environment. The `uv` command will manage dependencies based on your project configuration.
</Note>
<Tip>
The `--python` option is particularly useful when you need to run a server with a specific Python version that differs from your system's default. This addresses common compatibility issues where servers require a particular Python version to function correctly.
</Tip>
For development and testing, you can use the `dev` command to run your server with the MCP Inspector:
```bash
fastmcp dev server.py
```
The `dev` command also supports the same dependency management options:
```bash
# Dev server with specific Python version and packages
fastmcp dev server.py --python 3.11 --with pandas
```
See the [CLI documentation](/patterns/cli) for detailed information about all available commands and options.
### Passing Arguments to Servers

View file

@ -62,12 +62,26 @@ The command will automatically configure the server with Claude Code's `claude m
#### Dependencies
If your server has dependencies, include them with the `--with` flag:
FastMCP provides flexible dependency management options for your Claude Code servers:
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
```bash
fastmcp install claude-code server.py --with pandas --with requests
```
**Requirements file**: If you maintain a `requirements.txt` file with all your dependencies, use `--with-requirements` to install them:
```bash
fastmcp install claude-code server.py --with-requirements requirements.txt
```
**Editable packages**: For local packages under development, use `--with-editable` to install them in editable mode:
```bash
fastmcp install claude-code server.py --with-editable ./my-local-package
```
Alternatively, you can specify dependencies directly in your server code:
```python server.py
@ -79,14 +93,30 @@ mcp = FastMCP(
)
```
#### Python Version and Project Configuration
Control the Python environment for your server with these options:
**Python version**: Use `--python` to specify which Python version your server requires. This ensures compatibility when your server needs specific Python features:
```bash
fastmcp install claude-code server.py --python 3.11
```
**Project directory**: Use `--project` to run your server within a specific project context. This tells `uv` to use the project's configuration files and virtual environment:
```bash
fastmcp install claude-code server.py --project /path/to/my-project
```
#### Environment Variables
If your server needs environment variables (like API keys), you must include them:
```bash
fastmcp install claude-code server.py --name "Weather Server" \
--env-var API_KEY=your-api-key \
--env-var DEBUG=true
--env API_KEY=your-api-key \
--env DEBUG=true
```
Or load them from a `.env` file:
@ -101,7 +131,7 @@ fastmcp install claude-code server.py --name "Weather Server" --env-file .env
### Manual Configuration
For more control over the configuration, you can manually use Claude Code's built-in MCP management commands:
For more control over the configuration, you can manually use Claude Code's built-in MCP management commands. This gives you direct control over how your server is launched:
```bash
# Add a server with custom configuration
@ -114,6 +144,16 @@ claude mcp add weather-server -e API_KEY=secret -e DEBUG=true -- uv run --with f
claude mcp add my-server --scope user -- uv run --with fastmcp fastmcp run server.py
```
You can also manually specify Python versions and project directories in your Claude Code commands:
```bash
# With specific Python version
claude mcp add ml-server -- uv run --python 3.11 --with fastmcp fastmcp run server.py
# Within a project directory
claude mcp add project-server -- uv run --project /path/to/project --with fastmcp fastmcp run server.py
```
## Using the Server
Once your server is installed, you can start using your FastMCP server with Claude Code.

View file

@ -78,12 +78,26 @@ After installation, restart Claude Desktop completely. You should see a hammer i
#### Dependencies
If your server has dependencies, include them with the `--with` flag:
FastMCP provides several ways to manage your server's dependencies when installing in Claude Desktop:
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
```bash
fastmcp install claude-desktop server.py --with pandas --with requests
```
**Requirements file**: If you have a `requirements.txt` file listing all your dependencies, use `--with-requirements` to install them all at once:
```bash
fastmcp install claude-desktop server.py --with-requirements requirements.txt
```
**Editable packages**: For local packages in development, use `--with-editable` to install them in editable mode:
```bash
fastmcp install claude-desktop server.py --with-editable ./my-local-package
```
Alternatively, you can specify dependencies directly in your server code:
```python server.py
@ -95,6 +109,24 @@ mcp = FastMCP(
)
```
#### Python Version and Project Directory
FastMCP allows you to control the Python environment for your server:
**Python version**: Use `--python` to specify which Python version your server should run with. This is particularly useful when your server requires a specific Python version:
```bash
fastmcp install claude-desktop server.py --python 3.11
```
**Project directory**: Use `--project` to run your server within a specific project directory. This ensures that `uv` will discover all `pyproject.toml`, `uv.toml`, and `.python-version` files from that project:
```bash
fastmcp install claude-desktop server.py --project /path/to/my-project
```
When you specify a project directory, all relative paths in your server will be resolved from that directory, and the project's virtual environment will be used.
#### Environment Variables
<Warning>
@ -105,8 +137,8 @@ If your server needs environment variables (like API keys), you must include the
```bash
fastmcp install claude-desktop server.py --name "Weather Server" \
--env-var API_KEY=your-api-key \
--env-var DEBUG=true
--env API_KEY=your-api-key \
--env DEBUG=true
```
Or load them from a `.env` file:
@ -146,6 +178,8 @@ After updating the configuration file, restart Claude Desktop completely. Look f
If your server has dependencies, you can use `uv` or another package manager to set up the environment.
When manually configuring dependencies, the recommended approach is to use `uv` with FastMCP. The configuration uses `uv run` to create an isolated environment with your specified packages:
```json
{
"mcpServers": {
@ -153,9 +187,11 @@ If your server has dependencies, you can use `uv` or another package manager to
"command": "uv",
"args": [
"run",
"--with", "fastmcp",
"--with", "pandas",
"--with", "requests",
"python",
"fastmcp",
"run",
"path/to/your/server.py"
]
}
@ -163,6 +199,29 @@ If your server has dependencies, you can use `uv` or another package manager to
}
```
You can also manually specify Python versions and project directories in your configuration. Add `--python` to use a specific Python version, or `--project` to run within a project directory:
```json
{
"mcpServers": {
"dice-roller": {
"command": "uv",
"args": [
"run",
"--python", "3.11",
"--project", "/path/to/project",
"--with", "fastmcp",
"fastmcp",
"run",
"path/to/your/server.py"
]
}
}
}
```
The order of arguments matters: Python version and project settings come before package specifications, which come before the actual command to run.
<Warning>
- **`uv` must be installed and available in your system PATH**. Claude Desktop runs in its own isolated environment and needs `uv` to manage dependencies.
- **On macOS, it is recommended to install `uv` globally with Homebrew** so that Claude Desktop will detect it: `brew install uv`. Installing `uv` with other methods may not make it accessible to Claude Desktop.

View file

@ -64,12 +64,26 @@ After running the command, Cursor will open automatically and prompt you to inst
#### Dependencies
If your server has dependencies, include them with the `--with` flag:
FastMCP offers multiple ways to manage dependencies for your Cursor servers:
**Individual packages**: Use the `--with` flag to specify packages your server needs. You can use this flag multiple times:
```bash
fastmcp install cursor server.py --with pandas --with requests
```
**Requirements file**: For projects with a `requirements.txt` file, use `--with-requirements` to install all dependencies at once:
```bash
fastmcp install cursor server.py --with-requirements requirements.txt
```
**Editable packages**: When developing local packages, use `--with-editable` to install them in editable mode:
```bash
fastmcp install cursor server.py --with-editable ./my-local-package
```
Alternatively, you can specify dependencies directly in your server code:
```python server.py
@ -81,6 +95,22 @@ mcp = FastMCP(
)
```
#### Python Version and Project Configuration
Control your server's Python environment with these options:
**Python version**: Use `--python` to specify which Python version your server should use. This is essential when your server requires specific Python features:
```bash
fastmcp install cursor server.py --python 3.11
```
**Project directory**: Use `--project` to run your server within a specific project context. This ensures `uv` discovers all project configuration files and uses the correct virtual environment:
```bash
fastmcp install cursor server.py --project /path/to/my-project
```
#### Environment Variables
<Warning>
@ -91,8 +121,8 @@ If your server needs environment variables (like API keys), you must include the
```bash
fastmcp install cursor server.py --name "Weather Server" \
--env-var API_KEY=your-api-key \
--env-var DEBUG=true
--env API_KEY=your-api-key \
--env DEBUG=true
```
Or load them from a `.env` file:
@ -147,6 +177,8 @@ After updating the configuration file, your server should be available in Cursor
If your server has dependencies, you can use `uv` or another package manager to set up the environment.
When manually configuring dependencies, the recommended approach is to use `uv` with FastMCP. The configuration should use `uv run` to create an isolated environment with your specified packages:
```json
{
"mcpServers": {
@ -154,9 +186,11 @@ If your server has dependencies, you can use `uv` or another package manager to
"command": "uv",
"args": [
"run",
"--with", "fastmcp",
"--with", "pandas",
"--with", "requests",
"python",
"fastmcp",
"run",
"path/to/your/server.py"
]
}
@ -164,6 +198,29 @@ If your server has dependencies, you can use `uv` or another package manager to
}
```
You can also manually specify Python versions and project directories in your configuration:
```json
{
"mcpServers": {
"dice-roller": {
"command": "uv",
"args": [
"run",
"--python", "3.11",
"--project", "/path/to/project",
"--with", "fastmcp",
"fastmcp",
"run",
"path/to/your/server.py"
]
}
}
}
```
Note that the order of arguments is important: Python version and project settings should come before package specifications.
<Warning>
**`uv` must be installed and available in your system PATH**. Cursor runs in its own isolated environment and needs `uv` to manage dependencies.
</Warning>

View file

@ -136,6 +136,10 @@ To use this in a client configuration file, add it to the `mcpServers` object in
}
```
<Note>
When using `--python`, `--project`, or `--with-requirements`, the generated configuration will include these options in the `uv run` command, ensuring your server runs with the correct Python version and dependencies.
</Note>
<Note>
Different MCP clients may have specific configuration requirements or formatting needs. Always consult your client's documentation to ensure proper integration.
</Note>
@ -165,6 +169,9 @@ fastmcp install mcp-json server.py --with pandas --with requests --with httpx
# Editable local package
fastmcp install mcp-json server.py --with-editable ./my-package
# From requirements file
fastmcp install mcp-json server.py --with-requirements requirements.txt
```
You can also specify dependencies directly in your server code:
@ -190,6 +197,18 @@ fastmcp install mcp-json server.py \
fastmcp install mcp-json server.py --env-file .env
```
### Python Version and Project Directory
Specify Python version or run within a specific project:
```bash
# Use specific Python version
fastmcp install mcp-json server.py --python 3.11
# Run within a project directory
fastmcp install mcp-json server.py --project /path/to/project
```
### Server Object Selection
Use the same `file.py:object` notation as other FastMCP commands:
@ -250,6 +269,17 @@ fastmcp install mcp-json api_server.py \
--env TIMEOUT=30
```
### Advanced Configuration
```bash
fastmcp install mcp-json ml_server.py \
--name "ML Analysis Server" \
--python 3.11 \
--with-requirements requirements.txt \
--project /home/user/ml-project \
--env GPU_DEVICE=0
```
Output:
```json
{
@ -275,6 +305,32 @@ Output:
}
```
The advanced configuration example generates:
```json
{
"ML Analysis Server": {
"command": "uv",
"args": [
"run",
"--python",
"3.11",
"--project",
"/home/user/ml-project",
"--with",
"fastmcp",
"--with-requirements",
"requirements.txt",
"fastmcp",
"run",
"/home/user/ml_server.py"
],
"env": {
"GPU_DEVICE": "0"
}
}
}
```
### Pipeline Usage
Save configuration to file:

View file

@ -18,8 +18,8 @@ fastmcp --help
| Command | Purpose | Dependency Management |
| ------- | ------- | --------------------- |
| `run` | Run a FastMCP server directly | Uses your current environment; you are responsible for ensuring all dependencies are available |
| `dev` | Run a server with the MCP Inspector for testing | Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
| `run` | Run a FastMCP server directly | Default: Uses your local environment directly. With `--python`, `--with`, `--project`, or `--with-requirements`: Runs via `uv run` subprocess |
| `dev` | Run a server with the MCP Inspector for testing | Always runs via `uv run` subprocess (never uses your local environment); dependencies must be specified or available in a uv-managed project |
| `install` | Install a server in MCP client applications | Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
| `inspect` | Generate a JSON report about a FastMCP server | Uses your current environment; you are responsible for ensuring all dependencies are available |
| `version` | Display version information | N/A |
@ -35,7 +35,7 @@ fastmcp run server.py
```
<Tip>
This command runs the server directly in your current Python environment. You are responsible for ensuring all dependencies are available.
By default, this command runs the server directly in your current Python environment. You are responsible for ensuring all dependencies are available. When using `--python`, `--with`, `--project`, or `--with-requirements` options, it runs the server via `uv run` subprocess instead.
</Tip>
#### Options
@ -48,6 +48,10 @@ This command runs the server directly in your current Python environment. You ar
| Path | `--path` | Path to bind to when using http transport (default: `/mcp/` or `/sse/` for SSE) |
| Log Level | `--log-level`, `-l` | Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
| No Banner | `--no-banner` | Disable the startup banner display |
| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) |
| Additional Packages | `--with` | Additional packages to install (can be used multiple times) |
| Project Directory | `--project` | Run the command within the given project directory |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
#### Server Specification
@ -96,6 +100,18 @@ fastmcp run https://example.com/mcp-server
# Connect to a remote server with specified log level
fastmcp run https://example.com/mcp-server --log-level DEBUG
# Run with a specific Python version
fastmcp run server.py --python 3.11
# Run with additional packages
fastmcp run server.py --with pandas --with numpy
# Run within a specific project directory
fastmcp run server.py --project /path/to/project
# Run with dependencies from a requirements file
fastmcp run server.py --with-requirements requirements.txt
```
### `dev`
@ -107,7 +123,7 @@ fastmcp dev server.py
```
<Tip>
This command runs your server in an isolated environment. All dependencies must be explicitly specified using the `--with` and/or `--with-editable` options.
This command always runs your server via `uv run` subprocess (never your local environment) to work with the MCP Inspector. All dependencies must be explicitly specified using the `--with` and/or `--with-editable` options, or be available in a uv-managed project.
</Tip>
<Warning>
@ -136,12 +152,24 @@ This command does not support HTTP testing. To test a server over Streamable HTT
| Inspector Version | `--inspector-version` | Version of the MCP Inspector to use |
| UI Port | `--ui-port` | Port for the MCP Inspector UI |
| Server Port | `--server-port` | Port for the MCP Inspector Proxy server |
| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) |
| Project Directory | `--project` | Run the command within the given project directory |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
**Example**
**Examples**
```bash
# Run dev server with editable mode and additional packages
fastmcp dev server.py -e . --with pandas --with matplotlib
# Run dev server with specific Python version
fastmcp dev server.py --python 3.11
# Run dev server with requirements file
fastmcp dev server.py --with-requirements requirements.txt
# Run dev server within a specific project directory
fastmcp dev server.py --project /path/to/project
```
### `install`
@ -167,6 +195,10 @@ Note that for security reasons, MCP clients usually run every server in a comple
**`uv` must be installed and available in your system PATH**. Both Claude Desktop and Cursor run in isolated environments and need `uv` to manage dependencies. On macOS, install `uv` globally with Homebrew for Claude Desktop compatibility: `brew install uv`.
</Warning>
<Note>
**Python Version Considerations**: The install commands now support the `--python` option to specify a Python version directly. You can also use `--project` to run within a specific project directory or `--with-requirements` to install dependencies from a requirements file.
</Note>
<Tip>
**FastMCP `install` commands focus on local server files with STDIO transport.** For remote servers running with HTTP or SSE transport, use your client's native configuration - FastMCP's value is simplifying the complex local setup with dependencies and `uv` commands.
</Tip>
@ -187,6 +219,9 @@ The `install` command supports the same `file.py:object` notation as the `run` c
| Additional Packages | `--with` | Additional packages to install (can be used multiple times) |
| Environment Variables | `--env` | Environment variables in KEY=VALUE format (can be used multiple times) |
| Environment File | `--env-file`, `-f` | Load environment variables from a .env file |
| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) |
| Project Directory | `--project` | Run the command within the given project directory |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
**Examples**
@ -209,6 +244,15 @@ fastmcp install cursor server.py --env API_KEY=secret --env DEBUG=true
# Install with environment file
fastmcp install cursor server.py --env-file .env
# Install with specific Python version
fastmcp install claude-desktop server.py --python 3.11
# Install with requirements file
fastmcp install claude-code server.py --with-requirements requirements.txt
# Install within a project directory
fastmcp install cursor server.py --project /path/to/project
# Generate MCP JSON configuration
fastmcp install mcp-json server.py --name "My Server" --with pandas

View file

@ -62,11 +62,22 @@ def _build_uv_command(
with_editable: Path | None = None,
with_packages: list[str] | None = None,
no_banner: bool = False,
python_version: str | None = None,
with_requirements: Path | None = None,
project: Path | None = None,
) -> list[str]:
"""Build the uv run command that runs a MCP server through mcp run."""
cmd = ["uv"]
cmd = ["uv", "run"]
cmd.extend(["run", "--with", "fastmcp"])
# Add Python version if specified
if python_version:
cmd.extend(["--python", python_version])
# Add project if specified
if project:
cmd.extend(["--project", str(project)])
cmd.extend(["--with", "fastmcp"])
if with_editable:
cmd.extend(["--with-editable", str(with_editable)])
@ -76,6 +87,9 @@ def _build_uv_command(
if pkg:
cmd.extend(["--with", pkg])
if with_requirements:
cmd.extend(["--with-requirements", str(with_requirements)])
# Add mcp run command
cmd.extend(["fastmcp", "run", server_spec])
@ -163,6 +177,27 @@ def dev(
help="Port for the MCP Inspector Proxy server",
),
] = None,
python: Annotated[
str | None,
cyclopts.Parameter(
"--python",
help="Python version to use (e.g., 3.10, 3.11)",
),
] = None,
with_requirements: Annotated[
Path | None,
cyclopts.Parameter(
"--with-requirements",
help="Requirements file to install dependencies from",
),
] = None,
project: Annotated[
Path | None,
cyclopts.Parameter(
"--project",
help="Run the command within the given project directory",
),
] = None,
) -> None:
"""Run an MCP server with the MCP Inspector for development.
@ -209,7 +244,13 @@ def dev(
inspector_cmd += f"@{inspector_version}"
uv_cmd = _build_uv_command(
server_spec, with_editable, with_packages, no_banner=True
server_spec,
with_editable,
with_packages,
no_banner=True,
python_version=python,
with_requirements=with_requirements,
project=project,
)
# Run the MCP Inspector command with shell=True on Windows
@ -288,6 +329,35 @@ def run(
negative=False,
),
] = False,
python: Annotated[
str | None,
cyclopts.Parameter(
"--python",
help="Python version to use (e.g., 3.10, 3.11)",
),
] = None,
with_packages: Annotated[
list[str],
cyclopts.Parameter(
"--with",
help="Additional packages to install (can be used multiple times)",
negative=False,
),
] = [],
project: Annotated[
Path | None,
cyclopts.Parameter(
"--project",
help="Run the command within the given project directory",
),
] = None,
with_requirements: Annotated[
Path | None,
cyclopts.Parameter(
"--with-requirements",
help="Requirements file to install dependencies from",
),
] = None,
) -> None:
"""Run an MCP server or connect to a remote one.
@ -318,26 +388,53 @@ def run(
},
)
try:
run_module.run_command(
server_spec=server_spec,
transport=transport,
host=host,
port=port,
path=path,
log_level=log_level,
server_args=server_args,
show_banner=not no_banner,
)
except Exception as e:
logger.error(
f"Failed to run: {e}",
extra={
"server_spec": server_spec,
"error": str(e),
},
)
sys.exit(1)
# If any uv-specific options are provided, use uv run
if python or with_packages or with_requirements or project:
try:
run_module.run_with_uv(
server_spec=server_spec,
python_version=python,
with_packages=with_packages,
with_requirements=with_requirements,
project=project,
transport=transport,
host=host,
port=port,
path=path,
log_level=log_level,
show_banner=not no_banner,
)
except Exception as e:
logger.error(
f"Failed to run: {e}",
extra={
"server_spec": server_spec,
"error": str(e),
},
)
sys.exit(1)
else:
# Use direct import for backwards compatibility
try:
run_module.run_command(
server_spec=server_spec,
transport=transport,
host=host,
port=port,
path=path,
log_level=log_level,
server_args=server_args,
show_banner=not no_banner,
)
except Exception as e:
logger.error(
f"Failed to run: {e}",
extra={
"server_spec": server_spec,
"error": str(e),
},
)
sys.exit(1)
@app.command

View file

@ -77,6 +77,9 @@ def install_claude_code(
with_editable: Path | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
python_version: str | None = None,
with_requirements: Path | None = None,
project: Path | None = None,
) -> bool:
"""Install FastMCP server in Claude Code.
@ -87,6 +90,9 @@ def install_claude_code(
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
python_version: Optional Python version to use
with_requirements: Optional requirements file to install from
project: Optional project directory to run within
Returns:
True if installation was successful, False otherwise
@ -103,6 +109,14 @@ def install_claude_code(
# Build uv run command
args = ["run"]
# Add Python version if specified
if python_version:
args.extend(["--python", python_version])
# Add project if specified
if project:
args.extend(["--project", str(project)])
# Collect all packages in a set to deduplicate
packages = {"fastmcp"}
if with_packages:
@ -115,6 +129,9 @@ def install_claude_code(
if with_editable:
args.extend(["--with-editable", str(with_editable)])
if with_requirements:
args.extend(["--with-requirements", str(with_requirements)])
# Build server spec from parsed components
if server_object:
server_spec = f"{file.resolve()}:{server_object}"
@ -190,6 +207,27 @@ def claude_code_command(
help="Load environment variables from .env file",
),
] = None,
python: Annotated[
str | None,
cyclopts.Parameter(
"--python",
help="Python version to use (e.g., 3.10, 3.11)",
),
] = None,
with_requirements: Annotated[
Path | None,
cyclopts.Parameter(
"--with-requirements",
help="Requirements file to install dependencies from",
),
] = None,
project: Annotated[
Path | None,
cyclopts.Parameter(
"--project",
help="Run the command within the given project directory",
),
] = None,
) -> None:
"""Install an MCP server in Claude Code.
@ -207,6 +245,9 @@ def claude_code_command(
with_editable=with_editable,
with_packages=packages,
env_vars=env_dict,
python_version=python,
with_requirements=with_requirements,
project=project,
)
if success:

View file

@ -42,6 +42,9 @@ def install_claude_desktop(
with_editable: Path | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
python_version: str | None = None,
with_requirements: Path | None = None,
project: Path | None = None,
) -> bool:
"""Install FastMCP server in Claude Desktop.
@ -52,6 +55,9 @@ def install_claude_desktop(
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
python_version: Optional Python version to use
with_requirements: Optional requirements file to install from
project: Optional project directory to run within
Returns:
True if installation was successful, False otherwise
@ -69,6 +75,14 @@ def install_claude_desktop(
# Build uv run command
args = ["run"]
# Add Python version if specified
if python_version:
args.extend(["--python", python_version])
# Add project if specified
if project:
args.extend(["--project", str(project)])
# Collect all packages in a set to deduplicate
packages = {"fastmcp"}
if with_packages:
@ -81,6 +95,9 @@ def install_claude_desktop(
if with_editable:
args.extend(["--with-editable", str(with_editable)])
if with_requirements:
args.extend(["--with-requirements", str(with_requirements)])
# Build server spec from parsed components
if server_object:
server_spec = f"{file.resolve()}:{server_object}"
@ -163,6 +180,27 @@ def claude_desktop_command(
help="Load environment variables from .env file",
),
] = None,
python: Annotated[
str | None,
cyclopts.Parameter(
"--python",
help="Python version to use (e.g., 3.10, 3.11)",
),
] = None,
with_requirements: Annotated[
Path | None,
cyclopts.Parameter(
"--with-requirements",
help="Requirements file to install dependencies from",
),
] = None,
project: Annotated[
Path | None,
cyclopts.Parameter(
"--project",
help="Run the command within the given project directory",
),
] = None,
) -> None:
"""Install an MCP server in Claude Desktop.
@ -180,6 +218,9 @@ def claude_desktop_command(
with_editable=with_editable,
with_packages=with_packages,
env_vars=env_dict,
python_version=python,
with_requirements=with_requirements,
project=project,
)
if not success:

View file

@ -72,6 +72,9 @@ def install_cursor(
with_editable: Path | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
python_version: str | None = None,
with_requirements: Path | None = None,
project: Path | None = None,
) -> bool:
"""Install FastMCP server in Cursor.
@ -82,6 +85,9 @@ def install_cursor(
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
python_version: Optional Python version to use
with_requirements: Optional requirements file to install from
project: Optional project directory to run within
Returns:
True if installation was successful, False otherwise
@ -89,6 +95,14 @@ def install_cursor(
# Build uv run command
args = ["run"]
# Add Python version if specified
if python_version:
args.extend(["--python", python_version])
# Add project if specified
if project:
args.extend(["--project", str(project)])
# Collect all packages in a set to deduplicate
packages = {"fastmcp"}
if with_packages:
@ -101,6 +115,9 @@ def install_cursor(
if with_editable:
args.extend(["--with-editable", str(with_editable)])
if with_requirements:
args.extend(["--with-requirements", str(with_requirements)])
# Build server spec from parsed components
if server_object:
server_spec = f"{file.resolve()}:{server_object}"
@ -173,6 +190,27 @@ def cursor_command(
help="Load environment variables from .env file",
),
] = None,
python: Annotated[
str | None,
cyclopts.Parameter(
"--python",
help="Python version to use (e.g., 3.10, 3.11)",
),
] = None,
with_requirements: Annotated[
Path | None,
cyclopts.Parameter(
"--with-requirements",
help="Requirements file to install dependencies from",
),
] = None,
project: Annotated[
Path | None,
cyclopts.Parameter(
"--project",
help="Run the command within the given project directory",
),
] = None,
) -> None:
"""Install an MCP server in Cursor.
@ -190,6 +228,9 @@ def cursor_command(
with_editable=with_editable,
with_packages=with_packages,
env_vars=env_dict,
python_version=python,
with_requirements=with_requirements,
project=project,
)
if not success:

View file

@ -25,6 +25,9 @@ def install_mcp_json(
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
copy: bool = False,
python_version: str | None = None,
with_requirements: Path | None = None,
project: Path | None = None,
) -> bool:
"""Generate MCP configuration JSON for manual installation.
@ -36,6 +39,9 @@ def install_mcp_json(
with_packages: Optional list of additional packages to install
env_vars: Optional dictionary of environment variables
copy: If True, copy to clipboard instead of printing to stdout
python_version: Optional Python version to use
with_requirements: Optional requirements file to install from
project: Optional project directory to run within
Returns:
True if generation was successful, False otherwise
@ -44,6 +50,14 @@ def install_mcp_json(
# Build uv run command
args = ["run"]
# Add Python version if specified
if python_version:
args.extend(["--python", python_version])
# Add project if specified
if project:
args.extend(["--project", str(project)])
# Collect all packages in a set to deduplicate
packages = {"fastmcp"}
if with_packages:
@ -56,6 +70,9 @@ def install_mcp_json(
if with_editable:
args.extend(["--with-editable", str(with_editable)])
if with_requirements:
args.extend(["--with-requirements", str(with_requirements)])
# Build server spec from parsed components
if server_object:
server_spec = f"{file.resolve()}:{server_object}"
@ -144,6 +161,27 @@ def mcp_json_command(
negative=False,
),
] = False,
python: Annotated[
str | None,
cyclopts.Parameter(
"--python",
help="Python version to use (e.g., 3.10, 3.11)",
),
] = None,
with_requirements: Annotated[
Path | None,
cyclopts.Parameter(
"--with-requirements",
help="Requirements file to install dependencies from",
),
] = None,
project: Annotated[
Path | None,
cyclopts.Parameter(
"--project",
help="Run the command within the given project directory",
),
] = None,
) -> None:
"""Generate MCP configuration JSON for manual installation.
@ -162,6 +200,9 @@ def mcp_json_command(
with_packages=packages,
env_vars=env_dict,
copy=copy,
python_version=python,
with_requirements=with_requirements,
project=project,
)
if not success:

View file

@ -2,6 +2,7 @@
import importlib.util
import re
import subprocess
import sys
from pathlib import Path
from typing import Any, Literal
@ -122,6 +123,84 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
return server
def run_with_uv(
server_spec: str,
python_version: str | None = None,
with_packages: list[str] | None = None,
with_requirements: Path | None = None,
project: Path | None = None,
transport: TransportType | None = None,
host: str | None = None,
port: int | None = None,
path: str | None = None,
log_level: LogLevelType | None = None,
show_banner: bool = True,
) -> None:
"""Run a MCP server using uv run subprocess.
Args:
server_spec: Python file, object specification (file:obj), or URL
python_version: Python version to use (e.g. "3.10")
with_packages: Additional packages to install
with_requirements: Requirements file to use
project: Run the command within the given project directory
transport: Transport protocol to use
host: Host to bind to when using http transport
port: Port to bind to when using http transport
path: Path to bind to when using http transport
log_level: Log level
show_banner: Whether to show the server banner
"""
cmd = ["uv", "run"]
# Add Python version if specified
if python_version:
cmd.extend(["--python", python_version])
# Add project if specified
if project:
cmd.extend(["--project", str(project)])
# Add fastmcp package
cmd.extend(["--with", "fastmcp"])
# Add additional packages
if with_packages:
for pkg in with_packages:
if pkg:
cmd.extend(["--with", pkg])
# Add requirements file
if with_requirements:
cmd.extend(["--with-requirements", str(with_requirements)])
# Add fastmcp run command
cmd.extend(["fastmcp", "run", server_spec])
# Add transport options
if transport:
cmd.extend(["--transport", transport])
if host:
cmd.extend(["--host", host])
if port:
cmd.extend(["--port", str(port)])
if path:
cmd.extend(["--path", path])
if log_level:
cmd.extend(["--log-level", log_level])
if not show_banner:
cmd.append("--no-banner")
# Run the command
logger.debug(f"Running command: {' '.join(cmd)}")
try:
process = subprocess.run(cmd, check=True)
sys.exit(process.returncode)
except subprocess.CalledProcessError as e:
logger.error(f"Failed to run server: {e}")
sys.exit(e.returncode)
def create_client_server(url: str) -> Any:
"""Create a FastMCP server from a client URL.
@ -175,6 +254,7 @@ def run_command(
log_level: LogLevelType | None = None,
server_args: list[str] | None = None,
show_banner: bool = True,
use_direct_import: bool = False,
) -> None:
"""Run a MCP server or connect to a remote one.
@ -187,6 +267,7 @@ def run_command(
log_level: Log level
server_args: Additional arguments to pass to the server
show_banner: Whether to show the server banner
use_direct_import: Whether to use direct import instead of subprocess
"""
if is_url(server_spec):
# Handle URL case

View file

@ -90,6 +90,94 @@ class TestMainCLI:
]
assert cmd == expected
def test_build_uv_command_with_python_version(self):
"""Test building uv command with Python version."""
cmd = _build_uv_command("server.py", python_version="3.11")
expected = [
"uv",
"run",
"--python",
"3.11",
"--with",
"fastmcp",
"fastmcp",
"run",
"server.py",
]
assert cmd == expected
def test_build_uv_command_with_project(self):
"""Test building uv command with project directory."""
project_path = Path("/path/to/project")
cmd = _build_uv_command("server.py", project=project_path)
expected = [
"uv",
"run",
"--project",
str(project_path),
"--with",
"fastmcp",
"fastmcp",
"run",
"server.py",
]
assert cmd == expected
def test_build_uv_command_with_requirements(self):
"""Test building uv command with requirements file."""
req_path = Path("requirements.txt")
cmd = _build_uv_command("server.py", with_requirements=req_path)
expected = [
"uv",
"run",
"--with",
"fastmcp",
"--with-requirements",
"requirements.txt",
"fastmcp",
"run",
"server.py",
]
assert cmd == expected
def test_build_uv_command_with_all_options(self):
"""Test building uv command with all options."""
project_path = Path("/my/project")
editable_path = Path("/local/pkg")
requirements_path = Path("reqs.txt")
cmd = _build_uv_command(
"server.py",
python_version="3.10",
project=project_path,
with_packages=["pandas", "numpy"],
with_requirements=requirements_path,
with_editable=editable_path,
no_banner=True,
)
expected = [
"uv",
"run",
"--python",
"3.10",
"--project",
str(project_path),
"--with",
"fastmcp",
"--with-editable",
str(editable_path),
"--with",
"pandas",
"--with",
"numpy",
"--with-requirements",
str(requirements_path),
"fastmcp",
"run",
"server.py",
"--no-banner",
]
assert cmd == expected
class TestVersionCommand:
"""Test the version command."""
@ -167,6 +255,29 @@ class TestDevCommand:
assert bound.arguments["inspector_version"] == "1.0.0"
assert bound.arguments["ui_port"] == 3000
def test_dev_command_parsing_with_new_options(self):
"""Test dev command parsing with new uv options."""
command, bound, _ = app.parse_args(
[
"dev",
"server.py",
"--python",
"3.10",
"--project",
"/workspace",
"--with-requirements",
"dev-requirements.txt",
"--with",
"pytest",
]
)
assert command is not None
assert bound.arguments["server_spec"] == "server.py"
assert bound.arguments["python"] == "3.10"
assert bound.arguments["project"] == Path("/workspace")
assert bound.arguments["with_requirements"] == Path("dev-requirements.txt")
assert bound.arguments["with_packages"] == ["pytest"]
class TestRunCommand:
"""Test the run command."""
@ -236,6 +347,32 @@ class TestRunCommand:
assert "log_level" not in bound.arguments
assert "path" not in bound.arguments
def test_run_command_parsing_with_new_options(self):
"""Test run command parsing with new uv options."""
command, bound, _ = app.parse_args(
[
"run",
"server.py",
"--python",
"3.11",
"--with",
"pandas",
"--with",
"numpy",
"--project",
"/path/to/project",
"--with-requirements",
"requirements.txt",
]
)
assert command is not None
assert bound.arguments["server_spec"] == "server.py"
assert bound.arguments["python"] == "3.11"
assert bound.arguments["with_packages"] == ["pandas", "numpy"]
assert bound.arguments["project"] == Path("/path/to/project")
assert bound.arguments["with_requirements"] == Path("requirements.txt")
def test_run_command_transport_aliases(self):
"""Test that both 'http' and 'streamable-http' are accepted as valid transport options."""
# Test with 'http' transport

View file

@ -327,6 +327,9 @@ class TestCursorCommand:
with_editable=None,
with_packages=[],
env_vars={},
python_version=None,
with_requirements=None,
project=None,
)
mock_exit.assert_not_called()

View file

@ -1,3 +1,5 @@
from pathlib import Path
from fastmcp.cli.install import install_app
@ -63,6 +65,27 @@ class TestClaudeCodeInstall:
assert bound.arguments["with_packages"] == ["package1", "package2"]
assert bound.arguments["env_vars"] == ["VAR1=value1"]
def test_claude_code_with_new_options(self):
"""Test claude-code install with new uv options."""
from pathlib import Path
command, bound, _ = install_app.parse_args(
[
"claude-code",
"server.py",
"--python",
"3.11",
"--project",
"/workspace",
"--with-requirements",
"requirements.txt",
]
)
assert bound.arguments["python"] == "3.11"
assert bound.arguments["project"] == Path("/workspace")
assert bound.arguments["with_requirements"] == Path("requirements.txt")
class TestClaudeDesktopInstall:
"""Test claude-desktop install command."""
@ -94,6 +117,27 @@ class TestClaudeDesktopInstall:
assert bound.arguments["env_vars"] == ["VAR1=value1", "VAR2=value2"]
def test_claude_desktop_with_new_options(self):
"""Test claude-desktop install with new uv options."""
from pathlib import Path
command, bound, _ = install_app.parse_args(
[
"claude-desktop",
"server.py",
"--python",
"3.10",
"--project",
"/my/project",
"--with-requirements",
"reqs.txt",
]
)
assert bound.arguments["python"] == "3.10"
assert bound.arguments["project"] == Path("/my/project")
assert bound.arguments["with_requirements"] == Path("reqs.txt")
class TestCursorInstall:
"""Test cursor install command."""
@ -163,3 +207,45 @@ class TestInstallCommandParsing:
command, bound, _ = install_app.parse_args(["mcp-json", "server.py"])
assert command is not None
assert bound.arguments["server_spec"] == "server.py"
def test_python_option(self):
"""Test --python option for all install commands."""
commands_to_test = [
["claude-code", "server.py", "--python", "3.11"],
["claude-desktop", "server.py", "--python", "3.11"],
["cursor", "server.py", "--python", "3.11"],
["mcp-json", "server.py", "--python", "3.11"],
]
for cmd_args in commands_to_test:
command, bound, _ = install_app.parse_args(cmd_args)
assert command is not None
assert bound.arguments["python"] == "3.11"
def test_with_requirements_option(self):
"""Test --with-requirements option for all install commands."""
commands_to_test = [
["claude-code", "server.py", "--with-requirements", "requirements.txt"],
["claude-desktop", "server.py", "--with-requirements", "requirements.txt"],
["cursor", "server.py", "--with-requirements", "requirements.txt"],
["mcp-json", "server.py", "--with-requirements", "requirements.txt"],
]
for cmd_args in commands_to_test:
command, bound, _ = install_app.parse_args(cmd_args)
assert command is not None
assert str(bound.arguments["with_requirements"]) == "requirements.txt"
def test_project_option(self):
"""Test --project option for all install commands."""
commands_to_test = [
["claude-code", "server.py", "--project", "/path/to/project"],
["claude-desktop", "server.py", "--project", "/path/to/project"],
["cursor", "server.py", "--project", "/path/to/project"],
["mcp-json", "server.py", "--project", "/path/to/project"],
]
for cmd_args in commands_to_test:
command, bound, _ = install_app.parse_args(cmd_args)
assert command is not None
assert str(bound.arguments["project"]) == str(Path("/path/to/project"))

View file

@ -0,0 +1,240 @@
"""Tests for the run_with_uv function and related functionality."""
import subprocess
from pathlib import Path
from unittest.mock import Mock, patch
import pytest
from fastmcp.cli.run import run_with_uv
class TestRunWithUv:
"""Test the run_with_uv function."""
@patch("subprocess.run")
def test_run_with_uv_basic(self, mock_run):
"""Test basic run_with_uv execution."""
mock_run.return_value = Mock(returncode=0)
with pytest.raises(SystemExit) as exc_info:
run_with_uv("server.py")
assert exc_info.value.code == 0
# Check the command that was called
mock_run.assert_called_once()
cmd = mock_run.call_args[0][0]
expected = ["uv", "run", "--with", "fastmcp", "fastmcp", "run", "server.py"]
assert cmd == expected
@patch("subprocess.run")
def test_run_with_uv_python_version(self, mock_run):
"""Test run_with_uv with Python version."""
mock_run.return_value = Mock(returncode=0)
with pytest.raises(SystemExit) as exc_info:
run_with_uv("server.py", python_version="3.11")
assert exc_info.value.code == 0
cmd = mock_run.call_args[0][0]
expected = [
"uv",
"run",
"--python",
"3.11",
"--with",
"fastmcp",
"fastmcp",
"run",
"server.py",
]
assert cmd == expected
@patch("subprocess.run")
def test_run_with_uv_project(self, mock_run):
"""Test run_with_uv with project directory."""
mock_run.return_value = Mock(returncode=0)
project_path = Path("/my/project")
with pytest.raises(SystemExit) as exc_info:
run_with_uv("server.py", project=project_path)
assert exc_info.value.code == 0
cmd = mock_run.call_args[0][0]
expected = [
"uv",
"run",
"--project",
str(Path("/my/project")),
"--with",
"fastmcp",
"fastmcp",
"run",
"server.py",
]
assert cmd == expected
@patch("subprocess.run")
def test_run_with_uv_with_packages(self, mock_run):
"""Test run_with_uv with additional packages."""
mock_run.return_value = Mock(returncode=0)
with pytest.raises(SystemExit) as exc_info:
run_with_uv("server.py", with_packages=["pandas", "numpy"])
assert exc_info.value.code == 0
cmd = mock_run.call_args[0][0]
expected = [
"uv",
"run",
"--with",
"fastmcp",
"--with",
"pandas",
"--with",
"numpy",
"fastmcp",
"run",
"server.py",
]
assert cmd == expected
@patch("subprocess.run")
def test_run_with_uv_with_requirements(self, mock_run):
"""Test run_with_uv with requirements file."""
mock_run.return_value = Mock(returncode=0)
req_path = Path("requirements.txt")
with pytest.raises(SystemExit) as exc_info:
run_with_uv("server.py", with_requirements=req_path)
assert exc_info.value.code == 0
cmd = mock_run.call_args[0][0]
expected = [
"uv",
"run",
"--with",
"fastmcp",
"--with-requirements",
"requirements.txt",
"fastmcp",
"run",
"server.py",
]
assert cmd == expected
@patch("subprocess.run")
def test_run_with_uv_transport_options(self, mock_run):
"""Test run_with_uv with transport-related options."""
mock_run.return_value = Mock(returncode=0)
with pytest.raises(SystemExit) as exc_info:
run_with_uv(
"server.py",
transport="http",
host="localhost",
port=8080,
path="/api",
log_level="DEBUG",
show_banner=False,
)
assert exc_info.value.code == 0
cmd = mock_run.call_args[0][0]
expected = [
"uv",
"run",
"--with",
"fastmcp",
"fastmcp",
"run",
"server.py",
"--transport",
"http",
"--host",
"localhost",
"--port",
"8080",
"--path",
"/api",
"--log-level",
"DEBUG",
"--no-banner",
]
assert cmd == expected
@patch("subprocess.run")
def test_run_with_uv_all_options(self, mock_run):
"""Test run_with_uv with all options combined."""
mock_run.return_value = Mock(returncode=0)
with pytest.raises(SystemExit) as exc_info:
run_with_uv(
"server.py",
python_version="3.10",
project=Path("/workspace"),
with_packages=["pandas"],
with_requirements=Path("reqs.txt"),
transport="http",
port=9000,
show_banner=False,
)
assert exc_info.value.code == 0
cmd = mock_run.call_args[0][0]
expected = [
"uv",
"run",
"--python",
"3.10",
"--project",
str(Path("/workspace")),
"--with",
"fastmcp",
"--with",
"pandas",
"--with-requirements",
"reqs.txt",
"fastmcp",
"run",
"server.py",
"--transport",
"http",
"--port",
"9000",
"--no-banner",
]
assert cmd == expected
@patch("subprocess.run")
def test_run_with_uv_error_handling(self, mock_run):
"""Test run_with_uv error handling."""
mock_run.side_effect = subprocess.CalledProcessError(1, ["uv", "run"])
with pytest.raises(SystemExit) as exc_info:
run_with_uv("server.py")
assert exc_info.value.code == 1
@patch("fastmcp.cli.run.logger")
@patch("subprocess.run")
def test_run_with_uv_logging(self, mock_run, mock_logger):
"""Test that run_with_uv logs the command."""
mock_run.return_value = Mock(returncode=0)
with pytest.raises(SystemExit):
run_with_uv("server.py", python_version="3.11")
# Check that debug logging was called with the command
mock_logger.debug.assert_called()
call_args = mock_logger.debug.call_args[0][0]
assert "Running command:" in call_args
assert "uv run --python 3.11" in call_args