fastmcp 2.10.1__py3-none-any.whl → 2.10.3__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.
- fastmcp/cli/cli.py +117 -225
- fastmcp/cli/install/__init__.py +20 -0
- fastmcp/cli/install/claude_code.py +186 -0
- fastmcp/cli/install/claude_desktop.py +186 -0
- fastmcp/cli/install/cursor.py +196 -0
- fastmcp/cli/install/mcp_config.py +165 -0
- fastmcp/cli/install/shared.py +85 -0
- fastmcp/cli/run.py +17 -4
- fastmcp/client/client.py +230 -124
- fastmcp/client/elicitation.py +5 -0
- fastmcp/client/transports.py +3 -5
- fastmcp/mcp_config.py +282 -0
- fastmcp/prompts/prompt.py +2 -4
- fastmcp/resources/resource.py +2 -2
- fastmcp/resources/template.py +1 -1
- fastmcp/server/auth/providers/bearer.py +15 -6
- fastmcp/server/openapi.py +40 -9
- fastmcp/server/proxy.py +206 -37
- fastmcp/server/server.py +102 -20
- fastmcp/settings.py +19 -1
- fastmcp/tools/tool.py +3 -2
- fastmcp/tools/tool_transform.py +5 -6
- fastmcp/utilities/cli.py +102 -0
- fastmcp/utilities/json_schema.py +14 -3
- fastmcp/utilities/logging.py +3 -0
- fastmcp/utilities/openapi.py +92 -0
- fastmcp/utilities/tests.py +13 -0
- fastmcp/utilities/types.py +4 -3
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/METADATA +4 -3
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/RECORD +33 -26
- fastmcp/utilities/mcp_config.py +0 -93
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/WHEEL +0 -0
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.10.1.dist-info → fastmcp-2.10.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Claude Code integration for FastMCP install using Cyclopts."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import cyclopts
|
|
9
|
+
from rich import print
|
|
10
|
+
|
|
11
|
+
from fastmcp.utilities.logging import get_logger
|
|
12
|
+
|
|
13
|
+
from .shared import process_common_args
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def find_claude_command() -> str | None:
|
|
19
|
+
"""Find the Claude Code CLI command."""
|
|
20
|
+
# Check the default installation location
|
|
21
|
+
default_path = Path.home() / ".claude" / "local" / "claude"
|
|
22
|
+
if default_path.exists():
|
|
23
|
+
try:
|
|
24
|
+
result = subprocess.run(
|
|
25
|
+
[str(default_path), "--version"],
|
|
26
|
+
check=True,
|
|
27
|
+
capture_output=True,
|
|
28
|
+
text=True,
|
|
29
|
+
)
|
|
30
|
+
if "Claude Code" in result.stdout:
|
|
31
|
+
return str(default_path)
|
|
32
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check_claude_code_available() -> bool:
|
|
39
|
+
"""Check if Claude Code CLI is available."""
|
|
40
|
+
return find_claude_command() is not None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def install_claude_code(
|
|
44
|
+
file: Path,
|
|
45
|
+
server_object: str | None,
|
|
46
|
+
name: str,
|
|
47
|
+
*,
|
|
48
|
+
with_editable: Path | None = None,
|
|
49
|
+
with_packages: list[str] | None = None,
|
|
50
|
+
env_vars: dict[str, str] | None = None,
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Install FastMCP server in Claude Code.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
file: Path to the server file
|
|
56
|
+
server_object: Optional server object name (for :object suffix)
|
|
57
|
+
name: Name for the server in Claude Code
|
|
58
|
+
with_editable: Optional directory to install in editable mode
|
|
59
|
+
with_packages: Optional list of additional packages to install
|
|
60
|
+
env_vars: Optional dictionary of environment variables
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
True if installation was successful, False otherwise
|
|
64
|
+
"""
|
|
65
|
+
# Check if Claude Code CLI is available
|
|
66
|
+
claude_cmd = find_claude_command()
|
|
67
|
+
if not claude_cmd:
|
|
68
|
+
print(
|
|
69
|
+
"[red]Claude Code CLI not found.[/red]\n"
|
|
70
|
+
"[blue]Please ensure Claude Code is installed. Try running 'claude --version' to verify.[/blue]"
|
|
71
|
+
)
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
# Build uv run command
|
|
75
|
+
args = ["run"]
|
|
76
|
+
|
|
77
|
+
# Collect all packages in a set to deduplicate
|
|
78
|
+
packages = {"fastmcp"}
|
|
79
|
+
if with_packages:
|
|
80
|
+
packages.update(pkg for pkg in with_packages if pkg)
|
|
81
|
+
|
|
82
|
+
# Add all packages with --with
|
|
83
|
+
for pkg in sorted(packages):
|
|
84
|
+
args.extend(["--with", pkg])
|
|
85
|
+
|
|
86
|
+
if with_editable:
|
|
87
|
+
args.extend(["--with-editable", str(with_editable)])
|
|
88
|
+
|
|
89
|
+
# Build server spec from parsed components
|
|
90
|
+
if server_object:
|
|
91
|
+
server_spec = f"{file.resolve()}:{server_object}"
|
|
92
|
+
else:
|
|
93
|
+
server_spec = str(file.resolve())
|
|
94
|
+
|
|
95
|
+
# Add fastmcp run command
|
|
96
|
+
args.extend(["fastmcp", "run", server_spec])
|
|
97
|
+
|
|
98
|
+
# Build claude mcp add command
|
|
99
|
+
cmd_parts = [claude_cmd, "mcp", "add"]
|
|
100
|
+
|
|
101
|
+
# Add environment variables if specified (before the name and command)
|
|
102
|
+
if env_vars:
|
|
103
|
+
for key, value in env_vars.items():
|
|
104
|
+
cmd_parts.extend(["-e", f"{key}={value}"])
|
|
105
|
+
|
|
106
|
+
# Add server name and command
|
|
107
|
+
cmd_parts.extend([name, "--"])
|
|
108
|
+
cmd_parts.extend(["uv"] + args)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
# Run the claude mcp add command
|
|
112
|
+
subprocess.run(cmd_parts, check=True, capture_output=True, text=True)
|
|
113
|
+
return True
|
|
114
|
+
except subprocess.CalledProcessError as e:
|
|
115
|
+
print(
|
|
116
|
+
f"[red]Failed to install '[bold]{name}[/bold]' in Claude Code: {e.stderr.strip() if e.stderr else str(e)}[/red]"
|
|
117
|
+
)
|
|
118
|
+
return False
|
|
119
|
+
except Exception as e:
|
|
120
|
+
print(f"[red]Failed to install '[bold]{name}[/bold]' in Claude Code: {e}[/red]")
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def claude_code_command(
|
|
125
|
+
server_spec: str,
|
|
126
|
+
*,
|
|
127
|
+
server_name: Annotated[
|
|
128
|
+
str | None,
|
|
129
|
+
cyclopts.Parameter(
|
|
130
|
+
name=["--server-name", "-n"],
|
|
131
|
+
help="Custom name for the server in Claude Code",
|
|
132
|
+
),
|
|
133
|
+
] = None,
|
|
134
|
+
with_editable: Annotated[
|
|
135
|
+
Path | None,
|
|
136
|
+
cyclopts.Parameter(
|
|
137
|
+
name=["--with-editable", "-e"],
|
|
138
|
+
help="Directory with pyproject.toml to install in editable mode",
|
|
139
|
+
),
|
|
140
|
+
] = None,
|
|
141
|
+
with_packages: Annotated[
|
|
142
|
+
list[str],
|
|
143
|
+
cyclopts.Parameter(
|
|
144
|
+
"--with",
|
|
145
|
+
help="Additional packages to install",
|
|
146
|
+
negative=False,
|
|
147
|
+
),
|
|
148
|
+
] = [],
|
|
149
|
+
env_vars: Annotated[
|
|
150
|
+
list[str],
|
|
151
|
+
cyclopts.Parameter(
|
|
152
|
+
"--env",
|
|
153
|
+
help="Environment variables in KEY=VALUE format",
|
|
154
|
+
negative=False,
|
|
155
|
+
),
|
|
156
|
+
] = [],
|
|
157
|
+
env_file: Annotated[
|
|
158
|
+
Path | None,
|
|
159
|
+
cyclopts.Parameter(
|
|
160
|
+
"--env-file",
|
|
161
|
+
help="Load environment variables from .env file",
|
|
162
|
+
),
|
|
163
|
+
] = None,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Install an MCP server in Claude Code.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
server_spec: Python file to install, optionally with :object suffix
|
|
169
|
+
"""
|
|
170
|
+
file, server_object, name, packages, env_dict = process_common_args(
|
|
171
|
+
server_spec, server_name, with_packages, env_vars, env_file
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
success = install_claude_code(
|
|
175
|
+
file=file,
|
|
176
|
+
server_object=server_object,
|
|
177
|
+
name=name,
|
|
178
|
+
with_editable=with_editable,
|
|
179
|
+
with_packages=packages,
|
|
180
|
+
env_vars=env_dict,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if success:
|
|
184
|
+
print(f"[green]Successfully installed '{name}' in Claude Code[/green]")
|
|
185
|
+
else:
|
|
186
|
+
sys.exit(1)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Claude Desktop integration for FastMCP install using Cyclopts."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import cyclopts
|
|
9
|
+
from rich import print
|
|
10
|
+
|
|
11
|
+
from fastmcp.mcp_config import StdioMCPServer, update_config_file
|
|
12
|
+
from fastmcp.utilities.logging import get_logger
|
|
13
|
+
|
|
14
|
+
from .shared import process_common_args
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_claude_config_path() -> Path | None:
|
|
20
|
+
"""Get the Claude config directory based on platform."""
|
|
21
|
+
if sys.platform == "win32":
|
|
22
|
+
path = Path(Path.home(), "AppData", "Roaming", "Claude")
|
|
23
|
+
elif sys.platform == "darwin":
|
|
24
|
+
path = Path(Path.home(), "Library", "Application Support", "Claude")
|
|
25
|
+
elif sys.platform.startswith("linux"):
|
|
26
|
+
path = Path(
|
|
27
|
+
os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude"
|
|
28
|
+
)
|
|
29
|
+
else:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
if path.exists():
|
|
33
|
+
return path
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def install_claude_desktop(
|
|
38
|
+
file: Path,
|
|
39
|
+
server_object: str | None,
|
|
40
|
+
name: str,
|
|
41
|
+
*,
|
|
42
|
+
with_editable: Path | None = None,
|
|
43
|
+
with_packages: list[str] | None = None,
|
|
44
|
+
env_vars: dict[str, str] | None = None,
|
|
45
|
+
) -> bool:
|
|
46
|
+
"""Install FastMCP server in Claude Desktop.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
file: Path to the server file
|
|
50
|
+
server_object: Optional server object name (for :object suffix)
|
|
51
|
+
name: Name for the server in Claude's config
|
|
52
|
+
with_editable: Optional directory to install in editable mode
|
|
53
|
+
with_packages: Optional list of additional packages to install
|
|
54
|
+
env_vars: Optional dictionary of environment variables
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
True if installation was successful, False otherwise
|
|
58
|
+
"""
|
|
59
|
+
config_dir = get_claude_config_path()
|
|
60
|
+
if not config_dir:
|
|
61
|
+
print(
|
|
62
|
+
"[red]Claude Desktop config directory not found.[/red]\n"
|
|
63
|
+
"[blue]Please ensure Claude Desktop is installed and has been run at least once to initialize its config.[/blue]"
|
|
64
|
+
)
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
config_file = config_dir / "claude_desktop_config.json"
|
|
68
|
+
|
|
69
|
+
# Build uv run command
|
|
70
|
+
args = ["run"]
|
|
71
|
+
|
|
72
|
+
# Collect all packages in a set to deduplicate
|
|
73
|
+
packages = {"fastmcp"}
|
|
74
|
+
if with_packages:
|
|
75
|
+
packages.update(pkg for pkg in with_packages if pkg)
|
|
76
|
+
|
|
77
|
+
# Add all packages with --with
|
|
78
|
+
for pkg in sorted(packages):
|
|
79
|
+
args.extend(["--with", pkg])
|
|
80
|
+
|
|
81
|
+
if with_editable:
|
|
82
|
+
args.extend(["--with-editable", str(with_editable)])
|
|
83
|
+
|
|
84
|
+
# Build server spec from parsed components
|
|
85
|
+
if server_object:
|
|
86
|
+
server_spec = f"{file.resolve()}:{server_object}"
|
|
87
|
+
else:
|
|
88
|
+
server_spec = str(file.resolve())
|
|
89
|
+
|
|
90
|
+
# Add fastmcp run command
|
|
91
|
+
args.extend(["fastmcp", "run", server_spec])
|
|
92
|
+
|
|
93
|
+
# Create server configuration
|
|
94
|
+
server_config = StdioMCPServer(
|
|
95
|
+
command="uv",
|
|
96
|
+
args=args,
|
|
97
|
+
env=env_vars or {},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Handle environment variable merging manually since we need to preserve existing config
|
|
102
|
+
if config_file.exists():
|
|
103
|
+
import json
|
|
104
|
+
|
|
105
|
+
content = config_file.read_text().strip()
|
|
106
|
+
if content:
|
|
107
|
+
config = json.loads(content)
|
|
108
|
+
if "mcpServers" in config and name in config["mcpServers"]:
|
|
109
|
+
existing_env = config["mcpServers"][name].get("env", {})
|
|
110
|
+
if env_vars:
|
|
111
|
+
# New vars take precedence over existing ones
|
|
112
|
+
merged_env = {**existing_env, **env_vars}
|
|
113
|
+
else:
|
|
114
|
+
merged_env = existing_env
|
|
115
|
+
server_config.env = merged_env
|
|
116
|
+
|
|
117
|
+
# Update configuration with correct function signature
|
|
118
|
+
update_config_file(config_file, name, server_config)
|
|
119
|
+
print(f"[green]Successfully installed '{name}' in Claude Desktop[/green]")
|
|
120
|
+
return True
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print(f"[red]Failed to install server: {e}[/red]")
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def claude_desktop_command(
|
|
127
|
+
server_spec: str,
|
|
128
|
+
*,
|
|
129
|
+
server_name: Annotated[
|
|
130
|
+
str | None,
|
|
131
|
+
cyclopts.Parameter(
|
|
132
|
+
name=["--server-name", "-n"],
|
|
133
|
+
help="Custom name for the server in Claude Desktop's config",
|
|
134
|
+
),
|
|
135
|
+
] = None,
|
|
136
|
+
with_editable: Annotated[
|
|
137
|
+
Path | None,
|
|
138
|
+
cyclopts.Parameter(
|
|
139
|
+
name=["--with-editable", "-e"],
|
|
140
|
+
help="Directory with pyproject.toml to install in editable mode",
|
|
141
|
+
),
|
|
142
|
+
] = None,
|
|
143
|
+
with_packages: Annotated[
|
|
144
|
+
list[str],
|
|
145
|
+
cyclopts.Parameter(
|
|
146
|
+
"--with",
|
|
147
|
+
help="Additional packages to install",
|
|
148
|
+
negative=False,
|
|
149
|
+
),
|
|
150
|
+
] = [],
|
|
151
|
+
env_vars: Annotated[
|
|
152
|
+
list[str],
|
|
153
|
+
cyclopts.Parameter(
|
|
154
|
+
"--env",
|
|
155
|
+
help="Environment variables in KEY=VALUE format",
|
|
156
|
+
negative=False,
|
|
157
|
+
),
|
|
158
|
+
] = [],
|
|
159
|
+
env_file: Annotated[
|
|
160
|
+
Path | None,
|
|
161
|
+
cyclopts.Parameter(
|
|
162
|
+
"--env-file",
|
|
163
|
+
help="Load environment variables from .env file",
|
|
164
|
+
),
|
|
165
|
+
] = None,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Install an MCP server in Claude Desktop.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
server_spec: Python file to install, optionally with :object suffix
|
|
171
|
+
"""
|
|
172
|
+
file, server_object, name, with_packages, env_dict = process_common_args(
|
|
173
|
+
server_spec, server_name, with_packages, env_vars, env_file
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
success = install_claude_desktop(
|
|
177
|
+
file=file,
|
|
178
|
+
server_object=server_object,
|
|
179
|
+
name=name,
|
|
180
|
+
with_editable=with_editable,
|
|
181
|
+
with_packages=with_packages,
|
|
182
|
+
env_vars=env_dict,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if not success:
|
|
186
|
+
sys.exit(1)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Cursor integration for FastMCP install using Cyclopts."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
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
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def generate_cursor_deeplink(
|
|
21
|
+
server_name: str,
|
|
22
|
+
server_config: StdioMCPServer,
|
|
23
|
+
) -> str:
|
|
24
|
+
"""Generate a Cursor deeplink for installing the MCP server.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
server_name: Name of the server
|
|
28
|
+
server_config: Server configuration
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Deeplink URL that can be clicked to install the server
|
|
32
|
+
"""
|
|
33
|
+
# Create the configuration structure expected by Cursor
|
|
34
|
+
# Base64 encode the configuration (URL-safe for query parameter)
|
|
35
|
+
config_json = server_config.model_dump_json(exclude_none=True)
|
|
36
|
+
config_b64 = base64.urlsafe_b64encode(config_json.encode()).decode()
|
|
37
|
+
|
|
38
|
+
# Generate the deeplink URL
|
|
39
|
+
deeplink = f"cursor://anysphere.cursor-deeplink/mcp/install?name={server_name}&config={config_b64}"
|
|
40
|
+
|
|
41
|
+
return deeplink
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def open_deeplink(deeplink: str) -> bool:
|
|
45
|
+
"""Attempt to open a deeplink URL using the system's default handler.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
deeplink: The deeplink URL to open
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
True if the command succeeded, False otherwise
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
if sys.platform == "darwin": # macOS
|
|
55
|
+
subprocess.run(["open", deeplink], check=True, capture_output=True)
|
|
56
|
+
elif sys.platform == "win32": # Windows
|
|
57
|
+
subprocess.run(
|
|
58
|
+
["start", deeplink], shell=True, check=True, capture_output=True
|
|
59
|
+
)
|
|
60
|
+
else: # Linux and others
|
|
61
|
+
subprocess.run(["xdg-open", deeplink], check=True, capture_output=True)
|
|
62
|
+
return True
|
|
63
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def install_cursor(
|
|
68
|
+
file: Path,
|
|
69
|
+
server_object: str | None,
|
|
70
|
+
name: str,
|
|
71
|
+
*,
|
|
72
|
+
with_editable: Path | None = None,
|
|
73
|
+
with_packages: list[str] | None = None,
|
|
74
|
+
env_vars: dict[str, str] | None = None,
|
|
75
|
+
) -> bool:
|
|
76
|
+
"""Install FastMCP server in Cursor.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
file: Path to the server file
|
|
80
|
+
server_object: Optional server object name (for :object suffix)
|
|
81
|
+
name: Name for the server in Cursor
|
|
82
|
+
with_editable: Optional directory to install in editable mode
|
|
83
|
+
with_packages: Optional list of additional packages to install
|
|
84
|
+
env_vars: Optional dictionary of environment variables
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
True if installation was successful, False otherwise
|
|
88
|
+
"""
|
|
89
|
+
# Build uv run command
|
|
90
|
+
args = ["run"]
|
|
91
|
+
|
|
92
|
+
# Collect all packages in a set to deduplicate
|
|
93
|
+
packages = {"fastmcp"}
|
|
94
|
+
if with_packages:
|
|
95
|
+
packages.update(pkg for pkg in with_packages if pkg)
|
|
96
|
+
|
|
97
|
+
# Add all packages with --with
|
|
98
|
+
for pkg in sorted(packages):
|
|
99
|
+
args.extend(["--with", pkg])
|
|
100
|
+
|
|
101
|
+
if with_editable:
|
|
102
|
+
args.extend(["--with-editable", str(with_editable)])
|
|
103
|
+
|
|
104
|
+
# Build server spec from parsed components
|
|
105
|
+
if server_object:
|
|
106
|
+
server_spec = f"{file.resolve()}:{server_object}"
|
|
107
|
+
else:
|
|
108
|
+
server_spec = str(file.resolve())
|
|
109
|
+
|
|
110
|
+
# Add fastmcp run command
|
|
111
|
+
args.extend(["fastmcp", "run", server_spec])
|
|
112
|
+
|
|
113
|
+
# Create server configuration
|
|
114
|
+
server_config = StdioMCPServer(
|
|
115
|
+
command="uv",
|
|
116
|
+
args=args,
|
|
117
|
+
env=env_vars or {},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Generate deeplink
|
|
121
|
+
deeplink = generate_cursor_deeplink(name, server_config)
|
|
122
|
+
|
|
123
|
+
print(f"[blue]Opening Cursor to install '{name}'[/blue]")
|
|
124
|
+
|
|
125
|
+
if open_deeplink(deeplink):
|
|
126
|
+
print("[green]Cursor should now open with the installation dialog[/green]")
|
|
127
|
+
return True
|
|
128
|
+
else:
|
|
129
|
+
print(
|
|
130
|
+
"[red]Could not open Cursor automatically.[/red]\n"
|
|
131
|
+
f"[blue]Please copy this link and open it in Cursor: {deeplink}[/blue]"
|
|
132
|
+
)
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def cursor_command(
|
|
137
|
+
server_spec: str,
|
|
138
|
+
*,
|
|
139
|
+
server_name: Annotated[
|
|
140
|
+
str | None,
|
|
141
|
+
cyclopts.Parameter(
|
|
142
|
+
name=["--server-name", "-n"],
|
|
143
|
+
help="Custom name for the server in Cursor",
|
|
144
|
+
),
|
|
145
|
+
] = None,
|
|
146
|
+
with_editable: Annotated[
|
|
147
|
+
Path | None,
|
|
148
|
+
cyclopts.Parameter(
|
|
149
|
+
name=["--with-editable", "-e"],
|
|
150
|
+
help="Directory with pyproject.toml to install in editable mode",
|
|
151
|
+
),
|
|
152
|
+
] = None,
|
|
153
|
+
with_packages: Annotated[
|
|
154
|
+
list[str],
|
|
155
|
+
cyclopts.Parameter(
|
|
156
|
+
"--with",
|
|
157
|
+
help="Additional packages to install",
|
|
158
|
+
negative=False,
|
|
159
|
+
),
|
|
160
|
+
] = [],
|
|
161
|
+
env_vars: Annotated[
|
|
162
|
+
list[str],
|
|
163
|
+
cyclopts.Parameter(
|
|
164
|
+
"--env",
|
|
165
|
+
help="Environment variables in KEY=VALUE format",
|
|
166
|
+
negative=False,
|
|
167
|
+
),
|
|
168
|
+
] = [],
|
|
169
|
+
env_file: Annotated[
|
|
170
|
+
Path | None,
|
|
171
|
+
cyclopts.Parameter(
|
|
172
|
+
"--env-file",
|
|
173
|
+
help="Load environment variables from .env file",
|
|
174
|
+
),
|
|
175
|
+
] = None,
|
|
176
|
+
) -> None:
|
|
177
|
+
"""Install an MCP server in Cursor.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
server_spec: Python file to install, optionally with :object suffix
|
|
181
|
+
"""
|
|
182
|
+
file, server_object, name, with_packages, env_dict = process_common_args(
|
|
183
|
+
server_spec, server_name, with_packages, env_vars, env_file
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
success = install_cursor(
|
|
187
|
+
file=file,
|
|
188
|
+
server_object=server_object,
|
|
189
|
+
name=name,
|
|
190
|
+
with_editable=with_editable,
|
|
191
|
+
with_packages=with_packages,
|
|
192
|
+
env_vars=env_dict,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if not success:
|
|
196
|
+
sys.exit(1)
|