fastmcp 2.11.2__py3-none-any.whl → 2.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. fastmcp/__init__.py +5 -4
  2. fastmcp/cli/claude.py +22 -18
  3. fastmcp/cli/cli.py +472 -136
  4. fastmcp/cli/install/claude_code.py +37 -40
  5. fastmcp/cli/install/claude_desktop.py +37 -42
  6. fastmcp/cli/install/cursor.py +148 -38
  7. fastmcp/cli/install/mcp_json.py +38 -43
  8. fastmcp/cli/install/shared.py +64 -7
  9. fastmcp/cli/run.py +122 -215
  10. fastmcp/client/auth/oauth.py +69 -13
  11. fastmcp/client/client.py +46 -9
  12. fastmcp/client/logging.py +25 -1
  13. fastmcp/client/oauth_callback.py +91 -91
  14. fastmcp/client/sampling.py +12 -4
  15. fastmcp/client/transports.py +143 -67
  16. fastmcp/experimental/sampling/__init__.py +0 -0
  17. fastmcp/experimental/sampling/handlers/__init__.py +3 -0
  18. fastmcp/experimental/sampling/handlers/base.py +21 -0
  19. fastmcp/experimental/sampling/handlers/openai.py +163 -0
  20. fastmcp/experimental/server/openapi/routing.py +1 -3
  21. fastmcp/experimental/server/openapi/server.py +10 -25
  22. fastmcp/experimental/utilities/openapi/__init__.py +2 -2
  23. fastmcp/experimental/utilities/openapi/formatters.py +34 -0
  24. fastmcp/experimental/utilities/openapi/models.py +5 -2
  25. fastmcp/experimental/utilities/openapi/parser.py +252 -70
  26. fastmcp/experimental/utilities/openapi/schemas.py +135 -106
  27. fastmcp/mcp_config.py +40 -20
  28. fastmcp/prompts/prompt_manager.py +4 -2
  29. fastmcp/resources/resource_manager.py +16 -6
  30. fastmcp/server/auth/__init__.py +11 -1
  31. fastmcp/server/auth/auth.py +19 -2
  32. fastmcp/server/auth/oauth_proxy.py +1047 -0
  33. fastmcp/server/auth/providers/azure.py +270 -0
  34. fastmcp/server/auth/providers/github.py +287 -0
  35. fastmcp/server/auth/providers/google.py +305 -0
  36. fastmcp/server/auth/providers/jwt.py +27 -16
  37. fastmcp/server/auth/providers/workos.py +256 -2
  38. fastmcp/server/auth/redirect_validation.py +65 -0
  39. fastmcp/server/auth/registry.py +1 -1
  40. fastmcp/server/context.py +91 -41
  41. fastmcp/server/dependencies.py +32 -2
  42. fastmcp/server/elicitation.py +60 -1
  43. fastmcp/server/http.py +44 -37
  44. fastmcp/server/middleware/logging.py +66 -28
  45. fastmcp/server/proxy.py +2 -0
  46. fastmcp/server/sampling/handler.py +19 -0
  47. fastmcp/server/server.py +85 -20
  48. fastmcp/settings.py +18 -3
  49. fastmcp/tools/tool.py +23 -10
  50. fastmcp/tools/tool_manager.py +5 -1
  51. fastmcp/tools/tool_transform.py +75 -32
  52. fastmcp/utilities/auth.py +34 -0
  53. fastmcp/utilities/cli.py +148 -15
  54. fastmcp/utilities/components.py +21 -5
  55. fastmcp/utilities/inspect.py +166 -37
  56. fastmcp/utilities/json_schema_type.py +4 -2
  57. fastmcp/utilities/logging.py +4 -1
  58. fastmcp/utilities/mcp_config.py +47 -18
  59. fastmcp/utilities/mcp_server_config/__init__.py +25 -0
  60. fastmcp/utilities/mcp_server_config/v1/__init__.py +0 -0
  61. fastmcp/utilities/mcp_server_config/v1/environments/__init__.py +6 -0
  62. fastmcp/utilities/mcp_server_config/v1/environments/base.py +30 -0
  63. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +306 -0
  64. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +446 -0
  65. fastmcp/utilities/mcp_server_config/v1/schema.json +361 -0
  66. fastmcp/utilities/mcp_server_config/v1/sources/__init__.py +0 -0
  67. fastmcp/utilities/mcp_server_config/v1/sources/base.py +30 -0
  68. fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py +216 -0
  69. fastmcp/utilities/openapi.py +4 -4
  70. fastmcp/utilities/tests.py +7 -2
  71. fastmcp/utilities/types.py +15 -2
  72. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/METADATA +3 -2
  73. fastmcp-2.12.0.dist-info/RECORD +129 -0
  74. fastmcp-2.11.2.dist-info/RECORD +0 -108
  75. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/WHEEL +0 -0
  76. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/entry_points.txt +0 -0
  77. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -10,6 +10,7 @@ import cyclopts
10
10
  from rich import print
11
11
 
12
12
  from fastmcp.utilities.logging import get_logger
13
+ from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
13
14
 
14
15
  from .shared import process_common_args
15
16
 
@@ -74,7 +75,7 @@ def install_claude_code(
74
75
  server_object: str | None,
75
76
  name: str,
76
77
  *,
77
- with_editable: Path | None = None,
78
+ with_editable: list[Path] | None = None,
78
79
  with_packages: list[str] | None = None,
79
80
  env_vars: dict[str, str] | None = None,
80
81
  python_version: str | None = None,
@@ -87,7 +88,7 @@ def install_claude_code(
87
88
  file: Path to the server file
88
89
  server_object: Optional server object name (for :object suffix)
89
90
  name: Name for the server in Claude Code
90
- with_editable: Optional directory to install in editable mode
91
+ with_editable: Optional list of directories to install in editable mode
91
92
  with_packages: Optional list of additional packages to install
92
93
  env_vars: Optional dictionary of environment variables
93
94
  python_version: Optional Python version to use
@@ -106,31 +107,22 @@ def install_claude_code(
106
107
  )
107
108
  return False
108
109
 
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"}
110
+ # Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
111
+ deduplicated_packages = None
122
112
  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)])
113
+ deduplicated = list(dict.fromkeys(with_packages))
114
+ deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
115
+ if not deduplicated_packages:
116
+ deduplicated_packages = None
117
+
118
+ # Build uv run command using Environment.build_uv_run_command()
119
+ env_config = UVEnvironment(
120
+ python=python_version,
121
+ dependencies=deduplicated_packages,
122
+ requirements=str(with_requirements) if with_requirements else None,
123
+ project=str(project) if project else None,
124
+ editable=[str(p) for p in with_editable] if with_editable else None,
125
+ )
134
126
 
135
127
  # Build server spec from parsed components
136
128
  if server_object:
@@ -138,8 +130,8 @@ def install_claude_code(
138
130
  else:
139
131
  server_spec = str(file.resolve())
140
132
 
141
- # Add fastmcp run command
142
- args.extend(["fastmcp", "run", server_spec])
133
+ # Build the full command
134
+ full_command = env_config.build_command(["fastmcp", "run", server_spec])
143
135
 
144
136
  # Build claude mcp add command
145
137
  cmd_parts = [claude_cmd, "mcp", "add"]
@@ -151,7 +143,7 @@ def install_claude_code(
151
143
 
152
144
  # Add server name and command
153
145
  cmd_parts.extend([name, "--"])
154
- cmd_parts.extend(["uv"] + args)
146
+ cmd_parts.extend(full_command)
155
147
 
156
148
  try:
157
149
  # Run the claude mcp add command
@@ -178,28 +170,29 @@ async def claude_code_command(
178
170
  ),
179
171
  ] = None,
180
172
  with_editable: Annotated[
181
- Path | None,
173
+ list[Path] | None,
182
174
  cyclopts.Parameter(
183
- name=["--with-editable", "-e"],
184
- help="Directory with pyproject.toml to install in editable mode",
175
+ "--with-editable",
176
+ help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
177
+ negative="",
185
178
  ),
186
179
  ] = None,
187
180
  with_packages: Annotated[
188
- list[str],
181
+ list[str] | None,
189
182
  cyclopts.Parameter(
190
183
  "--with",
191
- help="Additional packages to install",
192
- negative=False,
184
+ help="Additional packages to install (can be used multiple times)",
185
+ negative="",
193
186
  ),
194
- ] = [],
187
+ ] = None,
195
188
  env_vars: Annotated[
196
- list[str],
189
+ list[str] | None,
197
190
  cyclopts.Parameter(
198
191
  "--env",
199
- help="Environment variables in KEY=VALUE format",
200
- negative=False,
192
+ help="Environment variables in KEY=VALUE format (can be used multiple times)",
193
+ negative="",
201
194
  ),
202
- ] = [],
195
+ ] = None,
203
196
  env_file: Annotated[
204
197
  Path | None,
205
198
  cyclopts.Parameter(
@@ -234,6 +227,10 @@ async def claude_code_command(
234
227
  Args:
235
228
  server_spec: Python file to install, optionally with :object suffix
236
229
  """
230
+ # Convert None to empty lists for list parameters
231
+ with_editable = with_editable or []
232
+ with_packages = with_packages or []
233
+ env_vars = env_vars or []
237
234
  file, server_object, name, packages, env_dict = await process_common_args(
238
235
  server_spec, server_name, with_packages, env_vars, env_file
239
236
  )
@@ -10,6 +10,7 @@ from rich import print
10
10
 
11
11
  from fastmcp.mcp_config import StdioMCPServer, update_config_file
12
12
  from fastmcp.utilities.logging import get_logger
13
+ from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
13
14
 
14
15
  from .shared import process_common_args
15
16
 
@@ -39,7 +40,7 @@ def install_claude_desktop(
39
40
  server_object: str | None,
40
41
  name: str,
41
42
  *,
42
- with_editable: Path | None = None,
43
+ with_editable: list[Path] | None = None,
43
44
  with_packages: list[str] | None = None,
44
45
  env_vars: dict[str, str] | None = None,
45
46
  python_version: str | None = None,
@@ -52,7 +53,7 @@ def install_claude_desktop(
52
53
  file: Path to the server file
53
54
  server_object: Optional server object name (for :object suffix)
54
55
  name: Name for the server in Claude's config
55
- with_editable: Optional directory to install in editable mode
56
+ with_editable: Optional list of directories to install in editable mode
56
57
  with_packages: Optional list of additional packages to install
57
58
  env_vars: Optional dictionary of environment variables
58
59
  python_version: Optional Python version to use
@@ -72,45 +73,34 @@ def install_claude_desktop(
72
73
 
73
74
  config_file = config_dir / "claude_desktop_config.json"
74
75
 
75
- # Build uv run command
76
- args = ["run"]
77
-
78
- # Add Python version if specified
79
- if python_version:
80
- args.extend(["--python", python_version])
81
-
82
- # Add project if specified
83
- if project:
84
- args.extend(["--project", str(project)])
85
-
86
- # Collect all packages in a set to deduplicate
87
- packages = {"fastmcp"}
76
+ # Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
77
+ deduplicated_packages = None
88
78
  if with_packages:
89
- packages.update(pkg for pkg in with_packages if pkg)
90
-
91
- # Add all packages with --with
92
- for pkg in sorted(packages):
93
- args.extend(["--with", pkg])
94
-
95
- if with_editable:
96
- args.extend(["--with-editable", str(with_editable)])
97
-
98
- if with_requirements:
99
- args.extend(["--with-requirements", str(with_requirements)])
100
-
79
+ deduplicated = list(dict.fromkeys(with_packages))
80
+ deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
81
+ if not deduplicated_packages:
82
+ deduplicated_packages = None
83
+
84
+ env_config = UVEnvironment(
85
+ python=python_version,
86
+ dependencies=deduplicated_packages,
87
+ requirements=str(with_requirements) if with_requirements else None,
88
+ project=str(project) if project else None,
89
+ editable=[str(p) for p in with_editable] if with_editable else None,
90
+ )
101
91
  # Build server spec from parsed components
102
92
  if server_object:
103
93
  server_spec = f"{file.resolve()}:{server_object}"
104
94
  else:
105
95
  server_spec = str(file.resolve())
106
96
 
107
- # Add fastmcp run command
108
- args.extend(["fastmcp", "run", server_spec])
97
+ # Build the full command
98
+ full_command = env_config.build_command(["fastmcp", "run", server_spec])
109
99
 
110
100
  # Create server configuration
111
101
  server_config = StdioMCPServer(
112
- command="uv",
113
- args=args,
102
+ command=full_command[0],
103
+ args=full_command[1:],
114
104
  env=env_vars or {},
115
105
  )
116
106
 
@@ -151,28 +141,29 @@ async def claude_desktop_command(
151
141
  ),
152
142
  ] = None,
153
143
  with_editable: Annotated[
154
- Path | None,
144
+ list[Path] | None,
155
145
  cyclopts.Parameter(
156
- name=["--with-editable", "-e"],
157
- help="Directory with pyproject.toml to install in editable mode",
146
+ "--with-editable",
147
+ help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
148
+ negative="",
158
149
  ),
159
150
  ] = None,
160
151
  with_packages: Annotated[
161
- list[str],
152
+ list[str] | None,
162
153
  cyclopts.Parameter(
163
154
  "--with",
164
- help="Additional packages to install",
165
- negative=False,
155
+ help="Additional packages to install (can be used multiple times)",
156
+ negative="",
166
157
  ),
167
- ] = [],
158
+ ] = None,
168
159
  env_vars: Annotated[
169
- list[str],
160
+ list[str] | None,
170
161
  cyclopts.Parameter(
171
162
  "--env",
172
- help="Environment variables in KEY=VALUE format",
173
- negative=False,
163
+ help="Environment variables in KEY=VALUE format (can be used multiple times)",
164
+ negative="",
174
165
  ),
175
- ] = [],
166
+ ] = None,
176
167
  env_file: Annotated[
177
168
  Path | None,
178
169
  cyclopts.Parameter(
@@ -207,6 +198,10 @@ async def claude_desktop_command(
207
198
  Args:
208
199
  server_spec: Python file to install, optionally with :object suffix
209
200
  """
201
+ # Convert None to empty lists for list parameters
202
+ with_editable = with_editable or []
203
+ with_packages = with_packages or []
204
+ env_vars = env_vars or []
210
205
  file, server_object, name, with_packages, env_dict = await process_common_args(
211
206
  server_spec, server_name, with_packages, env_vars, env_file
212
207
  )
@@ -9,8 +9,9 @@ from typing import Annotated
9
9
  import cyclopts
10
10
  from rich import print
11
11
 
12
- from fastmcp.mcp_config import StdioMCPServer
12
+ from fastmcp.mcp_config import StdioMCPServer, update_config_file
13
13
  from fastmcp.utilities.logging import get_logger
14
+ from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
14
15
 
15
16
  from .shared import process_common_args
16
17
 
@@ -64,25 +65,27 @@ def open_deeplink(deeplink: str) -> bool:
64
65
  return False
65
66
 
66
67
 
67
- def install_cursor(
68
+ def install_cursor_workspace(
68
69
  file: Path,
69
70
  server_object: str | None,
70
71
  name: str,
72
+ workspace_path: Path,
71
73
  *,
72
- with_editable: Path | None = None,
74
+ with_editable: list[Path] | None = None,
73
75
  with_packages: list[str] | None = None,
74
76
  env_vars: dict[str, str] | None = None,
75
77
  python_version: str | None = None,
76
78
  with_requirements: Path | None = None,
77
79
  project: Path | None = None,
78
80
  ) -> bool:
79
- """Install FastMCP server in Cursor.
81
+ """Install FastMCP server to workspace-specific Cursor configuration.
80
82
 
81
83
  Args:
82
84
  file: Path to the server file
83
85
  server_object: Optional server object name (for :object suffix)
84
86
  name: Name for the server in Cursor
85
- with_editable: Optional directory to install in editable mode
87
+ workspace_path: Path to the workspace directory
88
+ with_editable: Optional list of directories to install in editable mode
86
89
  with_packages: Optional list of additional packages to install
87
90
  env_vars: Optional dictionary of environment variables
88
91
  python_version: Optional Python version to use
@@ -92,45 +95,139 @@ def install_cursor(
92
95
  Returns:
93
96
  True if installation was successful, False otherwise
94
97
  """
95
- # Build uv run command
96
- args = ["run"]
98
+ # Ensure workspace path is absolute and exists
99
+ workspace_path = workspace_path.resolve()
100
+ if not workspace_path.exists():
101
+ print(f"[red]Workspace directory does not exist: {workspace_path}[/red]")
102
+ return False
97
103
 
98
- # Add Python version if specified
99
- if python_version:
100
- args.extend(["--python", python_version])
104
+ # Create .cursor directory in workspace
105
+ cursor_dir = workspace_path / ".cursor"
106
+ cursor_dir.mkdir(exist_ok=True)
101
107
 
102
- # Add project if specified
103
- if project:
104
- args.extend(["--project", str(project)])
108
+ config_file = cursor_dir / "mcp.json"
105
109
 
106
- # Collect all packages in a set to deduplicate
107
- packages = {"fastmcp"}
110
+ # Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
111
+ deduplicated_packages = None
108
112
  if with_packages:
109
- packages.update(pkg for pkg in with_packages if pkg)
113
+ deduplicated = list(dict.fromkeys(with_packages))
114
+ deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
115
+ if not deduplicated_packages:
116
+ deduplicated_packages = None
117
+
118
+ env_config = UVEnvironment(
119
+ python=python_version,
120
+ dependencies=deduplicated_packages,
121
+ requirements=str(with_requirements.resolve()) if with_requirements else None,
122
+ project=str(project.resolve()) if project else None,
123
+ editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
124
+ )
125
+ # Build server spec from parsed components
126
+ if server_object:
127
+ server_spec = f"{file.resolve()}:{server_object}"
128
+ else:
129
+ server_spec = str(file.resolve())
110
130
 
111
- # Add all packages with --with
112
- for pkg in sorted(packages):
113
- args.extend(["--with", pkg])
131
+ # Build the full command
132
+ full_command = env_config.build_command(["fastmcp", "run", server_spec])
114
133
 
115
- if with_editable:
116
- args.extend(["--with-editable", str(with_editable)])
134
+ # Create server configuration
135
+ server_config = StdioMCPServer(
136
+ command=full_command[0],
137
+ args=full_command[1:],
138
+ env=env_vars or {},
139
+ )
117
140
 
118
- if with_requirements:
119
- args.extend(["--with-requirements", str(with_requirements)])
141
+ try:
142
+ # Create the config file if it doesn't exist
143
+ if not config_file.exists():
144
+ config_file.write_text('{"mcpServers": {}}')
120
145
 
146
+ # Update configuration with the new server
147
+ update_config_file(config_file, name, server_config)
148
+ print(
149
+ f"[green]Successfully installed '{name}' to workspace at {workspace_path}[/green]"
150
+ )
151
+ return True
152
+ except Exception as e:
153
+ print(f"[red]Failed to install server to workspace: {e}[/red]")
154
+ return False
155
+
156
+
157
+ def install_cursor(
158
+ file: Path,
159
+ server_object: str | None,
160
+ name: str,
161
+ *,
162
+ with_editable: list[Path] | None = None,
163
+ with_packages: list[str] | None = None,
164
+ env_vars: dict[str, str] | None = None,
165
+ python_version: str | None = None,
166
+ with_requirements: Path | None = None,
167
+ project: Path | None = None,
168
+ workspace: Path | None = None,
169
+ ) -> bool:
170
+ """Install FastMCP server in Cursor.
171
+
172
+ Args:
173
+ file: Path to the server file
174
+ server_object: Optional server object name (for :object suffix)
175
+ name: Name for the server in Cursor
176
+ with_editable: Optional list of directories to install in editable mode
177
+ with_packages: Optional list of additional packages to install
178
+ env_vars: Optional dictionary of environment variables
179
+ python_version: Optional Python version to use
180
+ with_requirements: Optional requirements file to install from
181
+ project: Optional project directory to run within
182
+ workspace: Optional workspace directory for project-specific installation
183
+
184
+ Returns:
185
+ True if installation was successful, False otherwise
186
+ """
187
+
188
+ # Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
189
+ deduplicated_packages = None
190
+ if with_packages:
191
+ deduplicated = list(dict.fromkeys(with_packages))
192
+ deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
193
+ if not deduplicated_packages:
194
+ deduplicated_packages = None
195
+
196
+ env_config = UVEnvironment(
197
+ python=python_version,
198
+ dependencies=deduplicated_packages,
199
+ requirements=str(with_requirements.resolve()) if with_requirements else None,
200
+ project=str(project.resolve()) if project else None,
201
+ editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
202
+ )
121
203
  # Build server spec from parsed components
122
204
  if server_object:
123
205
  server_spec = f"{file.resolve()}:{server_object}"
124
206
  else:
125
207
  server_spec = str(file.resolve())
126
208
 
127
- # Add fastmcp run command
128
- args.extend(["fastmcp", "run", server_spec])
209
+ # Build the full command
210
+ full_command = env_config.build_command(["fastmcp", "run", server_spec])
211
+
212
+ # If workspace is specified, install to workspace-specific config
213
+ if workspace:
214
+ return install_cursor_workspace(
215
+ file=file,
216
+ server_object=server_object,
217
+ name=name,
218
+ workspace_path=workspace,
219
+ with_editable=with_editable,
220
+ with_packages=with_packages,
221
+ env_vars=env_vars,
222
+ python_version=python_version,
223
+ with_requirements=with_requirements,
224
+ project=project,
225
+ )
129
226
 
130
227
  # Create server configuration
131
228
  server_config = StdioMCPServer(
132
- command="uv",
133
- args=args,
229
+ command=full_command[0],
230
+ args=full_command[1:],
134
231
  env=env_vars or {},
135
232
  )
136
233
 
@@ -161,28 +258,29 @@ async def cursor_command(
161
258
  ),
162
259
  ] = None,
163
260
  with_editable: Annotated[
164
- Path | None,
261
+ list[Path] | None,
165
262
  cyclopts.Parameter(
166
- name=["--with-editable", "-e"],
167
- help="Directory with pyproject.toml to install in editable mode",
263
+ "--with-editable",
264
+ help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
265
+ negative="",
168
266
  ),
169
267
  ] = None,
170
268
  with_packages: Annotated[
171
- list[str],
269
+ list[str] | None,
172
270
  cyclopts.Parameter(
173
271
  "--with",
174
- help="Additional packages to install",
175
- negative=False,
272
+ help="Additional packages to install (can be used multiple times)",
273
+ negative="",
176
274
  ),
177
- ] = [],
275
+ ] = None,
178
276
  env_vars: Annotated[
179
- list[str],
277
+ list[str] | None,
180
278
  cyclopts.Parameter(
181
279
  "--env",
182
- help="Environment variables in KEY=VALUE format",
183
- negative=False,
280
+ help="Environment variables in KEY=VALUE format (can be used multiple times)",
281
+ negative="",
184
282
  ),
185
- ] = [],
283
+ ] = None,
186
284
  env_file: Annotated[
187
285
  Path | None,
188
286
  cyclopts.Parameter(
@@ -211,12 +309,23 @@ async def cursor_command(
211
309
  help="Run the command within the given project directory",
212
310
  ),
213
311
  ] = None,
312
+ workspace: Annotated[
313
+ Path | None,
314
+ cyclopts.Parameter(
315
+ "--workspace",
316
+ help="Install to workspace directory (will create .cursor/ inside it) instead of using deeplink",
317
+ ),
318
+ ] = None,
214
319
  ) -> None:
215
320
  """Install an MCP server in Cursor.
216
321
 
217
322
  Args:
218
323
  server_spec: Python file to install, optionally with :object suffix
219
324
  """
325
+ # Convert None to empty lists for list parameters
326
+ with_editable = with_editable or []
327
+ with_packages = with_packages or []
328
+ env_vars = env_vars or []
220
329
  file, server_object, name, with_packages, env_dict = await process_common_args(
221
330
  server_spec, server_name, with_packages, env_vars, env_file
222
331
  )
@@ -231,6 +340,7 @@ async def cursor_command(
231
340
  python_version=python,
232
341
  with_requirements=with_requirements,
233
342
  project=project,
343
+ workspace=workspace,
234
344
  )
235
345
 
236
346
  if not success: