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.
@@ -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)