Jeremiah Lowin Jeremiah Lowin marvin-context-protocol[bot] commited on
Commit
d2b87a8
·
unverified ·
1 Parent(s): c516657

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

Browse files

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>

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