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,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:
|
|
171
|
+
transport: TransportType | None = None,
|
|
168
172
|
host: str | None = None,
|
|
169
173
|
port: int | None = None,
|
|
170
|
-
|
|
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:
|