feat: Add --workspace flag to fastmcp install cursor (#1522)

Co-authored-by: Jeremiah Lowin <jlowin@users.noreply.github.com>
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
This commit is contained in:
Jeremiah Lowin 2025-08-19 15:38:55 -04:00 committed by GitHub
commit 108ad4b70d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 142 additions and 1 deletions

View file

@ -47,6 +47,21 @@ The easiest way to install a FastMCP server in Cursor is using the `fastmcp inst
fastmcp install cursor server.py
```
#### Workspace Installation
<VersionBadge version="2.12.0" />
By default, FastMCP installs servers globally for Cursor. You can also install servers to project-specific workspaces using the `--workspace` flag:
```bash
# Install to current directory's .cursor/ folder
fastmcp install cursor server.py --workspace .
# Install to specific workspace
fastmcp install cursor server.py --workspace /path/to/project
```
This creates a `.cursor/mcp.json` configuration file in the specified workspace directory, allowing different projects to have their own MCP server configurations.
The install command supports the same `file.py:object` notation as the `run` command. If no object is specified, it will automatically look for a FastMCP server object named `mcp`, `server`, or `app` in your file:
```bash

View file

@ -9,7 +9,7 @@ from typing import Annotated
import cyclopts
from rich import print
from fastmcp.mcp_config import StdioMCPServer
from fastmcp.mcp_config import StdioMCPServer, update_config_file
from fastmcp.utilities.logging import get_logger
from .shared import process_common_args
@ -64,6 +64,106 @@ def open_deeplink(deeplink: str) -> bool:
return False
def install_cursor_workspace(
file: Path,
server_object: str | None,
name: str,
workspace_path: Path,
*,
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 to workspace-specific Cursor configuration.
Args:
file: Path to the server file
server_object: Optional server object name (for :object suffix)
name: Name for the server in Cursor
workspace_path: Path to the workspace directory
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
"""
# Ensure workspace path is absolute and exists
workspace_path = workspace_path.resolve()
if not workspace_path.exists():
print(f"[red]Workspace directory does not exist: {workspace_path}[/red]")
return False
# Create .cursor directory in workspace
cursor_dir = workspace_path / ".cursor"
cursor_dir.mkdir(exist_ok=True)
config_file = cursor_dir / "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:
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)])
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}"
else:
server_spec = str(file.resolve())
# Add fastmcp run command
args.extend(["fastmcp", "run", server_spec])
# Create server configuration
server_config = StdioMCPServer(
command="uv",
args=args,
env=env_vars or {},
)
try:
# Create the config file if it doesn't exist
if not config_file.exists():
config_file.write_text('{"mcpServers": {}}')
# Update configuration with the new server
update_config_file(config_file, name, server_config)
print(
f"[green]Successfully installed '{name}' to workspace at {workspace_path}[/green]"
)
return True
except Exception as e:
print(f"[red]Failed to install server to workspace: {e}[/red]")
return False
def install_cursor(
file: Path,
server_object: str | None,
@ -75,6 +175,7 @@ def install_cursor(
python_version: str | None = None,
with_requirements: Path | None = None,
project: Path | None = None,
workspace: Path | None = None,
) -> bool:
"""Install FastMCP server in Cursor.
@ -88,6 +189,7 @@ def install_cursor(
python_version: Optional Python version to use
with_requirements: Optional requirements file to install from
project: Optional project directory to run within
workspace: Optional workspace directory for project-specific installation
Returns:
True if installation was successful, False otherwise
@ -127,6 +229,21 @@ def install_cursor(
# Add fastmcp run command
args.extend(["fastmcp", "run", server_spec])
# If workspace is specified, install to workspace-specific config
if workspace:
return install_cursor_workspace(
file=file,
server_object=server_object,
name=name,
workspace_path=workspace,
with_editable=with_editable,
with_packages=with_packages,
env_vars=env_vars,
python_version=python_version,
with_requirements=with_requirements,
project=project,
)
# Create server configuration
server_config = StdioMCPServer(
command="uv",
@ -211,6 +328,13 @@ async def cursor_command(
help="Run the command within the given project directory",
),
] = None,
workspace: Annotated[
Path | None,
cyclopts.Parameter(
"--workspace",
help="Install to workspace directory (will create .cursor/ inside it) instead of using deeplink",
),
] = None,
) -> None:
"""Install an MCP server in Cursor.
@ -231,6 +355,7 @@ async def cursor_command(
python_version=python,
with_requirements=with_requirements,
project=project,
workspace=workspace,
)
if not success:

View file

@ -330,6 +330,7 @@ class TestCursorCommand:
python_version=None,
with_requirements=None,
project=None,
workspace=None,
)
mock_exit.assert_not_called()