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,165 @@
1
+ """MCP configuration JSON generation for FastMCP install using Cyclopts."""
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Annotated
7
+
8
+ import cyclopts
9
+ import pyperclip
10
+ from rich import print
11
+
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 install_mcp_config(
20
+ file: Path,
21
+ server_object: str | None,
22
+ name: str,
23
+ *,
24
+ with_editable: Path | None = None,
25
+ with_packages: list[str] | None = None,
26
+ env_vars: dict[str, str] | None = None,
27
+ copy: bool = False,
28
+ ) -> bool:
29
+ """Generate MCP configuration JSON for manual installation.
30
+
31
+ Args:
32
+ file: Path to the server file
33
+ server_object: Optional server object name (for :object suffix)
34
+ name: Name for the server in MCP config
35
+ with_editable: Optional directory to install in editable mode
36
+ with_packages: Optional list of additional packages to install
37
+ env_vars: Optional dictionary of environment variables
38
+ copy: If True, copy to clipboard instead of printing to stdout
39
+
40
+ Returns:
41
+ True if generation was successful, False otherwise
42
+ """
43
+ try:
44
+ # Build uv run command
45
+ args = ["run"]
46
+
47
+ # Collect all packages in a set to deduplicate
48
+ packages = {"fastmcp"}
49
+ if with_packages:
50
+ packages.update(pkg for pkg in with_packages if pkg)
51
+
52
+ # Add all packages with --with
53
+ for pkg in sorted(packages):
54
+ args.extend(["--with", pkg])
55
+
56
+ if with_editable:
57
+ args.extend(["--with-editable", str(with_editable)])
58
+
59
+ # Build server spec from parsed components
60
+ if server_object:
61
+ server_spec = f"{file.resolve()}:{server_object}"
62
+ else:
63
+ server_spec = str(file.resolve())
64
+
65
+ # Add fastmcp run command
66
+ args.extend(["fastmcp", "run", server_spec])
67
+
68
+ # Build MCP server configuration (just the server object, not the wrapper)
69
+ config = {
70
+ "command": "uv",
71
+ "args": args,
72
+ }
73
+
74
+ # Add environment variables if provided
75
+ if env_vars:
76
+ config["env"] = env_vars
77
+
78
+ # Convert to JSON
79
+ json_output = json.dumps(config, indent=2)
80
+
81
+ # Handle output
82
+ if copy:
83
+ pyperclip.copy(json_output)
84
+ print(f"[green]MCP configuration for '{name}' copied to clipboard[/green]")
85
+ else:
86
+ # Print to stdout (for piping)
87
+ print(json_output)
88
+
89
+ return True
90
+
91
+ except Exception as e:
92
+ print(f"[red]Failed to generate MCP configuration: {e}[/red]")
93
+ return False
94
+
95
+
96
+ def mcp_config_command(
97
+ server_spec: str,
98
+ *,
99
+ server_name: Annotated[
100
+ str | None,
101
+ cyclopts.Parameter(
102
+ name=["--server-name", "-n"],
103
+ help="Custom name for the server in MCP config",
104
+ ),
105
+ ] = None,
106
+ with_editable: Annotated[
107
+ Path | None,
108
+ cyclopts.Parameter(
109
+ name=["--with-editable", "-e"],
110
+ help="Directory with pyproject.toml to install in editable mode",
111
+ ),
112
+ ] = None,
113
+ with_packages: Annotated[
114
+ list[str],
115
+ cyclopts.Parameter(
116
+ "--with",
117
+ help="Additional packages to install",
118
+ negative=False,
119
+ ),
120
+ ] = [],
121
+ env_vars: Annotated[
122
+ list[str],
123
+ cyclopts.Parameter(
124
+ "--env",
125
+ help="Environment variables in KEY=VALUE format",
126
+ negative=False,
127
+ ),
128
+ ] = [],
129
+ env_file: Annotated[
130
+ Path | None,
131
+ cyclopts.Parameter(
132
+ "--env-file",
133
+ help="Load environment variables from .env file",
134
+ ),
135
+ ] = None,
136
+ copy: Annotated[
137
+ bool,
138
+ cyclopts.Parameter(
139
+ "--copy",
140
+ help="Copy configuration to clipboard instead of printing to stdout",
141
+ negative=False,
142
+ ),
143
+ ] = False,
144
+ ) -> None:
145
+ """Generate MCP configuration JSON for manual installation.
146
+
147
+ Args:
148
+ server_spec: Python file to install, optionally with :object suffix
149
+ """
150
+ file, server_object, name, packages, env_dict = process_common_args(
151
+ server_spec, server_name, with_packages, env_vars, env_file
152
+ )
153
+
154
+ success = install_mcp_config(
155
+ file=file,
156
+ server_object=server_object,
157
+ name=name,
158
+ with_editable=with_editable,
159
+ with_packages=packages,
160
+ env_vars=env_dict,
161
+ copy=copy,
162
+ )
163
+
164
+ if not success:
165
+ sys.exit(1)
@@ -0,0 +1,85 @@
1
+ """Shared utilities for install commands."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from dotenv import dotenv_values
7
+ from rich import print
8
+
9
+ from fastmcp.cli.run import import_server, parse_file_path
10
+ from fastmcp.utilities.logging import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ def parse_env_var(env_var: str) -> tuple[str, str]:
16
+ """Parse environment variable string in format KEY=VALUE."""
17
+ if "=" not in env_var:
18
+ print(
19
+ f"[red]Invalid environment variable format: '[bold]{env_var}[/bold]'. Must be KEY=VALUE[/red]"
20
+ )
21
+ sys.exit(1)
22
+ key, value = env_var.split("=", 1)
23
+ return key.strip(), value.strip()
24
+
25
+
26
+ def process_common_args(
27
+ server_spec: str,
28
+ server_name: str | None,
29
+ with_packages: list[str],
30
+ env_vars: list[str],
31
+ env_file: Path | None,
32
+ ) -> tuple[Path, str | None, str, list[str], dict[str, str] | None]:
33
+ """Process common arguments shared by all install commands."""
34
+ # Parse server spec
35
+ file, server_object = parse_file_path(server_spec)
36
+
37
+ logger.debug(
38
+ "Installing server",
39
+ extra={
40
+ "file": str(file),
41
+ "server_name": server_name,
42
+ "server_object": server_object,
43
+ "with_packages": with_packages,
44
+ },
45
+ )
46
+
47
+ # Try to import server to get its name and dependencies
48
+ name = server_name
49
+ server = None
50
+ if not name:
51
+ try:
52
+ server = import_server(file, server_object)
53
+ name = server.name
54
+ except (ImportError, ModuleNotFoundError) as e:
55
+ logger.debug(
56
+ "Could not import server (likely missing dependencies), using file name",
57
+ extra={"error": str(e)},
58
+ )
59
+ name = file.stem
60
+
61
+ # Get server dependencies if available
62
+ server_dependencies = getattr(server, "dependencies", []) if server else []
63
+ if server_dependencies:
64
+ with_packages = list(set(with_packages + server_dependencies))
65
+
66
+ # Process environment variables if provided
67
+ env_dict: dict[str, str] | None = None
68
+ if env_file or env_vars:
69
+ env_dict = {}
70
+ # Load from .env file if specified
71
+ if env_file:
72
+ try:
73
+ env_dict |= {
74
+ k: v for k, v in dotenv_values(env_file).items() if v is not None
75
+ }
76
+ except Exception as e:
77
+ print(f"[red]Failed to load .env file: {e}[/red]")
78
+ sys.exit(1)
79
+
80
+ # Add command line environment variables
81
+ for env_var in env_vars:
82
+ key, value = parse_env_var(env_var)
83
+ env_dict[key] = value
84
+
85
+ return file, server_object, name, with_packages, env_dict
fastmcp/cli/run.py CHANGED
@@ -1,15 +1,19 @@
1
- """FastMCP run command implementation."""
1
+ """FastMCP run command implementation with enhanced type hints."""
2
2
 
3
3
  import importlib.util
4
4
  import re
5
5
  import sys
6
6
  from pathlib import Path
7
- from typing import Any
7
+ from typing import Any, Literal
8
8
 
9
9
  from fastmcp.utilities.logging import get_logger
10
10
 
11
11
  logger = get_logger("cli.run")
12
12
 
13
+ # Type aliases for better type safety
14
+ TransportType = Literal["stdio", "http", "sse"]
15
+ LogLevelType = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
16
+
13
17
 
14
18
  def is_url(path: str) -> bool:
15
19
  """Check if a string is a URL."""
@@ -164,11 +168,13 @@ def import_server_with_args(
164
168
 
165
169
  def run_command(
166
170
  server_spec: str,
167
- transport: str | None = None,
171
+ transport: TransportType | None = None,
168
172
  host: str | None = None,
169
173
  port: int | None = None,
170
- log_level: str | None = None,
174
+ path: str | None = None,
175
+ log_level: LogLevelType | None = None,
171
176
  server_args: list[str] | None = None,
177
+ show_banner: bool = True,
172
178
  ) -> None:
173
179
  """Run a MCP server or connect to a remote one.
174
180
 
@@ -177,8 +183,10 @@ def run_command(
177
183
  transport: Transport protocol to use
178
184
  host: Host to bind to when using http transport
179
185
  port: Port to bind to when using http transport
186
+ path: Path to bind to when using http transport
180
187
  log_level: Log level
181
188
  server_args: Additional arguments to pass to the server
189
+ show_banner: Whether to show the server banner
182
190
  """
183
191
  if is_url(server_spec):
184
192
  # Handle URL case
@@ -198,9 +206,14 @@ def run_command(
198
206
  kwargs["host"] = host
199
207
  if port:
200
208
  kwargs["port"] = port
209
+ if path:
210
+ kwargs["path"] = path
201
211
  if log_level:
202
212
  kwargs["log_level"] = log_level
203
213
 
214
+ if not show_banner:
215
+ kwargs["show_banner"] = False
216
+
204
217
  try:
205
218
  server.run(**kwargs)
206
219
  except Exception as e: