Spaces:
Running
Running
Jeremiah Lowin Jeremiah Lowin marvin-context-protocol[bot] commited on
feat: Add --workspace flag to fastmcp install cursor (#1522)
Browse filesCo-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>
- docs/integrations/cursor.mdx +15 -0
- src/fastmcp/cli/install/cursor.py +126 -1
- tests/cli/test_cursor.py +1 -0
docs/integrations/cursor.mdx
CHANGED
|
@@ -47,6 +47,21 @@ The easiest way to install a FastMCP server in Cursor is using the `fastmcp inst
|
|
| 47 |
fastmcp install cursor server.py
|
| 48 |
```
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
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:
|
| 51 |
|
| 52 |
```bash
|
|
|
|
| 47 |
fastmcp install cursor server.py
|
| 48 |
```
|
| 49 |
|
| 50 |
+
#### Workspace Installation
|
| 51 |
+
<VersionBadge version="2.12.0" />
|
| 52 |
+
|
| 53 |
+
By default, FastMCP installs servers globally for Cursor. You can also install servers to project-specific workspaces using the `--workspace` flag:
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
# Install to current directory's .cursor/ folder
|
| 57 |
+
fastmcp install cursor server.py --workspace .
|
| 58 |
+
|
| 59 |
+
# Install to specific workspace
|
| 60 |
+
fastmcp install cursor server.py --workspace /path/to/project
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
This creates a `.cursor/mcp.json` configuration file in the specified workspace directory, allowing different projects to have their own MCP server configurations.
|
| 64 |
+
|
| 65 |
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:
|
| 66 |
|
| 67 |
```bash
|
src/fastmcp/cli/install/cursor.py
CHANGED
|
@@ -9,7 +9,7 @@ from typing import Annotated
|
|
| 9 |
import cyclopts
|
| 10 |
from rich import print
|
| 11 |
|
| 12 |
-
from fastmcp.mcp_config import StdioMCPServer
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
| 14 |
|
| 15 |
from .shared import process_common_args
|
|
@@ -64,6 +64,106 @@ def open_deeplink(deeplink: str) -> bool:
|
|
| 64 |
return False
|
| 65 |
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
def install_cursor(
|
| 68 |
file: Path,
|
| 69 |
server_object: str | None,
|
|
@@ -75,6 +175,7 @@ def install_cursor(
|
|
| 75 |
python_version: str | None = None,
|
| 76 |
with_requirements: Path | None = None,
|
| 77 |
project: Path | None = None,
|
|
|
|
| 78 |
) -> bool:
|
| 79 |
"""Install FastMCP server in Cursor.
|
| 80 |
|
|
@@ -88,6 +189,7 @@ def install_cursor(
|
|
| 88 |
python_version: Optional Python version to use
|
| 89 |
with_requirements: Optional requirements file to install from
|
| 90 |
project: Optional project directory to run within
|
|
|
|
| 91 |
|
| 92 |
Returns:
|
| 93 |
True if installation was successful, False otherwise
|
|
@@ -127,6 +229,21 @@ def install_cursor(
|
|
| 127 |
# Add fastmcp run command
|
| 128 |
args.extend(["fastmcp", "run", server_spec])
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
# Create server configuration
|
| 131 |
server_config = StdioMCPServer(
|
| 132 |
command="uv",
|
|
@@ -211,6 +328,13 @@ async def cursor_command(
|
|
| 211 |
help="Run the command within the given project directory",
|
| 212 |
),
|
| 213 |
] = None,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
) -> None:
|
| 215 |
"""Install an MCP server in Cursor.
|
| 216 |
|
|
@@ -231,6 +355,7 @@ async def cursor_command(
|
|
| 231 |
python_version=python,
|
| 232 |
with_requirements=with_requirements,
|
| 233 |
project=project,
|
|
|
|
| 234 |
)
|
| 235 |
|
| 236 |
if not success:
|
|
|
|
| 9 |
import cyclopts
|
| 10 |
from rich import print
|
| 11 |
|
| 12 |
+
from fastmcp.mcp_config import StdioMCPServer, update_config_file
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
| 14 |
|
| 15 |
from .shared import process_common_args
|
|
|
|
| 64 |
return False
|
| 65 |
|
| 66 |
|
| 67 |
+
def install_cursor_workspace(
|
| 68 |
+
file: Path,
|
| 69 |
+
server_object: str | None,
|
| 70 |
+
name: str,
|
| 71 |
+
workspace_path: Path,
|
| 72 |
+
*,
|
| 73 |
+
with_editable: Path | None = None,
|
| 74 |
+
with_packages: list[str] | None = None,
|
| 75 |
+
env_vars: dict[str, str] | None = None,
|
| 76 |
+
python_version: str | None = None,
|
| 77 |
+
with_requirements: Path | None = None,
|
| 78 |
+
project: Path | None = None,
|
| 79 |
+
) -> bool:
|
| 80 |
+
"""Install FastMCP server to workspace-specific Cursor configuration.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
file: Path to the server file
|
| 84 |
+
server_object: Optional server object name (for :object suffix)
|
| 85 |
+
name: Name for the server in Cursor
|
| 86 |
+
workspace_path: Path to the workspace directory
|
| 87 |
+
with_editable: Optional directory to install in editable mode
|
| 88 |
+
with_packages: Optional list of additional packages to install
|
| 89 |
+
env_vars: Optional dictionary of environment variables
|
| 90 |
+
python_version: Optional Python version to use
|
| 91 |
+
with_requirements: Optional requirements file to install from
|
| 92 |
+
project: Optional project directory to run within
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
True if installation was successful, False otherwise
|
| 96 |
+
"""
|
| 97 |
+
# Ensure workspace path is absolute and exists
|
| 98 |
+
workspace_path = workspace_path.resolve()
|
| 99 |
+
if not workspace_path.exists():
|
| 100 |
+
print(f"[red]Workspace directory does not exist: {workspace_path}[/red]")
|
| 101 |
+
return False
|
| 102 |
+
|
| 103 |
+
# Create .cursor directory in workspace
|
| 104 |
+
cursor_dir = workspace_path / ".cursor"
|
| 105 |
+
cursor_dir.mkdir(exist_ok=True)
|
| 106 |
+
|
| 107 |
+
config_file = cursor_dir / "mcp.json"
|
| 108 |
+
|
| 109 |
+
# Build uv run command
|
| 110 |
+
args = ["run"]
|
| 111 |
+
|
| 112 |
+
# Add Python version if specified
|
| 113 |
+
if python_version:
|
| 114 |
+
args.extend(["--python", python_version])
|
| 115 |
+
|
| 116 |
+
# Add project if specified
|
| 117 |
+
if project:
|
| 118 |
+
args.extend(["--project", str(project)])
|
| 119 |
+
|
| 120 |
+
# Collect all packages in a set to deduplicate
|
| 121 |
+
packages = {"fastmcp"}
|
| 122 |
+
if with_packages:
|
| 123 |
+
packages.update(pkg for pkg in with_packages if pkg)
|
| 124 |
+
|
| 125 |
+
# Add all packages with --with
|
| 126 |
+
for pkg in sorted(packages):
|
| 127 |
+
args.extend(["--with", pkg])
|
| 128 |
+
|
| 129 |
+
if with_editable:
|
| 130 |
+
args.extend(["--with-editable", str(with_editable)])
|
| 131 |
+
|
| 132 |
+
if with_requirements:
|
| 133 |
+
args.extend(["--with-requirements", str(with_requirements)])
|
| 134 |
+
|
| 135 |
+
# Build server spec from parsed components
|
| 136 |
+
if server_object:
|
| 137 |
+
server_spec = f"{file.resolve()}:{server_object}"
|
| 138 |
+
else:
|
| 139 |
+
server_spec = str(file.resolve())
|
| 140 |
+
|
| 141 |
+
# Add fastmcp run command
|
| 142 |
+
args.extend(["fastmcp", "run", server_spec])
|
| 143 |
+
|
| 144 |
+
# Create server configuration
|
| 145 |
+
server_config = StdioMCPServer(
|
| 146 |
+
command="uv",
|
| 147 |
+
args=args,
|
| 148 |
+
env=env_vars or {},
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
# Create the config file if it doesn't exist
|
| 153 |
+
if not config_file.exists():
|
| 154 |
+
config_file.write_text('{"mcpServers": {}}')
|
| 155 |
+
|
| 156 |
+
# Update configuration with the new server
|
| 157 |
+
update_config_file(config_file, name, server_config)
|
| 158 |
+
print(
|
| 159 |
+
f"[green]Successfully installed '{name}' to workspace at {workspace_path}[/green]"
|
| 160 |
+
)
|
| 161 |
+
return True
|
| 162 |
+
except Exception as e:
|
| 163 |
+
print(f"[red]Failed to install server to workspace: {e}[/red]")
|
| 164 |
+
return False
|
| 165 |
+
|
| 166 |
+
|
| 167 |
def install_cursor(
|
| 168 |
file: Path,
|
| 169 |
server_object: str | None,
|
|
|
|
| 175 |
python_version: str | None = None,
|
| 176 |
with_requirements: Path | None = None,
|
| 177 |
project: Path | None = None,
|
| 178 |
+
workspace: Path | None = None,
|
| 179 |
) -> bool:
|
| 180 |
"""Install FastMCP server in Cursor.
|
| 181 |
|
|
|
|
| 189 |
python_version: Optional Python version to use
|
| 190 |
with_requirements: Optional requirements file to install from
|
| 191 |
project: Optional project directory to run within
|
| 192 |
+
workspace: Optional workspace directory for project-specific installation
|
| 193 |
|
| 194 |
Returns:
|
| 195 |
True if installation was successful, False otherwise
|
|
|
|
| 229 |
# Add fastmcp run command
|
| 230 |
args.extend(["fastmcp", "run", server_spec])
|
| 231 |
|
| 232 |
+
# If workspace is specified, install to workspace-specific config
|
| 233 |
+
if workspace:
|
| 234 |
+
return install_cursor_workspace(
|
| 235 |
+
file=file,
|
| 236 |
+
server_object=server_object,
|
| 237 |
+
name=name,
|
| 238 |
+
workspace_path=workspace,
|
| 239 |
+
with_editable=with_editable,
|
| 240 |
+
with_packages=with_packages,
|
| 241 |
+
env_vars=env_vars,
|
| 242 |
+
python_version=python_version,
|
| 243 |
+
with_requirements=with_requirements,
|
| 244 |
+
project=project,
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
# Create server configuration
|
| 248 |
server_config = StdioMCPServer(
|
| 249 |
command="uv",
|
|
|
|
| 328 |
help="Run the command within the given project directory",
|
| 329 |
),
|
| 330 |
] = None,
|
| 331 |
+
workspace: Annotated[
|
| 332 |
+
Path | None,
|
| 333 |
+
cyclopts.Parameter(
|
| 334 |
+
"--workspace",
|
| 335 |
+
help="Install to workspace directory (will create .cursor/ inside it) instead of using deeplink",
|
| 336 |
+
),
|
| 337 |
+
] = None,
|
| 338 |
) -> None:
|
| 339 |
"""Install an MCP server in Cursor.
|
| 340 |
|
|
|
|
| 355 |
python_version=python,
|
| 356 |
with_requirements=with_requirements,
|
| 357 |
project=project,
|
| 358 |
+
workspace=workspace,
|
| 359 |
)
|
| 360 |
|
| 361 |
if not success:
|
tests/cli/test_cursor.py
CHANGED
|
@@ -330,6 +330,7 @@ class TestCursorCommand:
|
|
| 330 |
python_version=None,
|
| 331 |
with_requirements=None,
|
| 332 |
project=None,
|
|
|
|
| 333 |
)
|
| 334 |
mock_exit.assert_not_called()
|
| 335 |
|
|
|
|
| 330 |
python_version=None,
|
| 331 |
with_requirements=None,
|
| 332 |
project=None,
|
| 333 |
+
workspace=None,
|
| 334 |
)
|
| 335 |
mock_exit.assert_not_called()
|
| 336 |
|