fastmcp 2.12.2__py3-none-any.whl → 2.12.4__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/claude.py +1 -10
- fastmcp/cli/cli.py +45 -25
- fastmcp/cli/install/__init__.py +2 -0
- fastmcp/cli/install/claude_code.py +1 -10
- fastmcp/cli/install/claude_desktop.py +1 -9
- fastmcp/cli/install/cursor.py +2 -18
- fastmcp/cli/install/gemini_cli.py +241 -0
- fastmcp/cli/install/mcp_json.py +1 -9
- fastmcp/cli/run.py +2 -86
- fastmcp/client/auth/oauth.py +50 -37
- fastmcp/client/client.py +18 -8
- fastmcp/client/elicitation.py +6 -1
- fastmcp/client/transports.py +1 -1
- fastmcp/contrib/component_manager/component_service.py +1 -1
- fastmcp/contrib/mcp_mixin/README.md +3 -3
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +41 -6
- fastmcp/experimental/utilities/openapi/director.py +8 -1
- fastmcp/experimental/utilities/openapi/schemas.py +31 -5
- fastmcp/prompts/prompt.py +10 -8
- fastmcp/resources/resource.py +14 -11
- fastmcp/resources/template.py +12 -10
- fastmcp/server/auth/auth.py +10 -4
- fastmcp/server/auth/oauth_proxy.py +93 -23
- fastmcp/server/auth/oidc_proxy.py +348 -0
- fastmcp/server/auth/providers/auth0.py +174 -0
- fastmcp/server/auth/providers/aws.py +237 -0
- fastmcp/server/auth/providers/azure.py +6 -2
- fastmcp/server/auth/providers/descope.py +172 -0
- fastmcp/server/auth/providers/github.py +6 -2
- fastmcp/server/auth/providers/google.py +6 -2
- fastmcp/server/auth/providers/workos.py +6 -2
- fastmcp/server/context.py +17 -16
- fastmcp/server/dependencies.py +18 -5
- fastmcp/server/http.py +1 -1
- fastmcp/server/middleware/logging.py +147 -116
- fastmcp/server/middleware/middleware.py +3 -2
- fastmcp/server/openapi.py +5 -1
- fastmcp/server/server.py +43 -36
- fastmcp/settings.py +42 -6
- fastmcp/tools/tool.py +105 -87
- fastmcp/tools/tool_transform.py +1 -1
- fastmcp/utilities/json_schema.py +18 -1
- fastmcp/utilities/logging.py +66 -4
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +4 -39
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -2
- fastmcp/utilities/mcp_server_config/v1/schema.json +2 -1
- fastmcp/utilities/storage.py +204 -0
- fastmcp/utilities/tests.py +8 -6
- fastmcp/utilities/types.py +9 -5
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/METADATA +121 -48
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/RECORD +54 -48
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/WHEEL +0 -0
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/claude.py
CHANGED
|
@@ -90,17 +90,8 @@ def update_claude_config(
|
|
|
90
90
|
else:
|
|
91
91
|
env_vars = existing_env
|
|
92
92
|
|
|
93
|
-
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
|
94
|
-
deduplicated_packages = None
|
|
95
|
-
if with_packages:
|
|
96
|
-
deduplicated = list(dict.fromkeys(with_packages))
|
|
97
|
-
deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
|
|
98
|
-
if not deduplicated_packages:
|
|
99
|
-
deduplicated_packages = None
|
|
100
|
-
|
|
101
|
-
# Build uv run command using Environment.build_uv_run_command()
|
|
102
93
|
env_config = UVEnvironment(
|
|
103
|
-
dependencies=
|
|
94
|
+
dependencies=(with_packages or []) + ["fastmcp"],
|
|
104
95
|
editable=[str(p) for p in with_editable] if with_editable else None,
|
|
105
96
|
)
|
|
106
97
|
|
fastmcp/cli/cli.py
CHANGED
|
@@ -20,6 +20,7 @@ import fastmcp
|
|
|
20
20
|
from fastmcp.cli import run as run_module
|
|
21
21
|
from fastmcp.cli.install import install_app
|
|
22
22
|
from fastmcp.server.server import FastMCP
|
|
23
|
+
from fastmcp.utilities.cli import is_already_in_uv_subprocess, load_and_merge_config
|
|
23
24
|
from fastmcp.utilities.inspect import (
|
|
24
25
|
InspectFormat,
|
|
25
26
|
format_info,
|
|
@@ -193,7 +194,6 @@ async def dev(
|
|
|
193
194
|
Args:
|
|
194
195
|
server_spec: Python file to run, optionally with :object suffix, or None to auto-detect fastmcp.json
|
|
195
196
|
"""
|
|
196
|
-
from fastmcp.utilities.cli import load_and_merge_config
|
|
197
197
|
|
|
198
198
|
try:
|
|
199
199
|
# Load config and apply CLI overrides
|
|
@@ -415,7 +415,6 @@ async def run(
|
|
|
415
415
|
Args:
|
|
416
416
|
server_spec: Python file, object specification (file:obj), config file, URL, or None to auto-detect
|
|
417
417
|
"""
|
|
418
|
-
from fastmcp.utilities.cli import is_already_in_uv_subprocess, load_and_merge_config
|
|
419
418
|
|
|
420
419
|
# Check if we were spawned by uv (or user explicitly set --skip-env)
|
|
421
420
|
if skip_env or is_already_in_uv_subprocess():
|
|
@@ -446,6 +445,9 @@ async def run(
|
|
|
446
445
|
final_path = path or config.deployment.path
|
|
447
446
|
final_log_level = log_level or config.deployment.log_level
|
|
448
447
|
final_server_args = server_args or config.deployment.args
|
|
448
|
+
# Use CLI override if provided, otherwise use settings
|
|
449
|
+
# no_banner CLI flag overrides the show_cli_banner setting
|
|
450
|
+
final_no_banner = no_banner if no_banner else not fastmcp.settings.show_cli_banner
|
|
449
451
|
|
|
450
452
|
logger.debug(
|
|
451
453
|
"Running server or client",
|
|
@@ -466,35 +468,53 @@ async def run(
|
|
|
466
468
|
needs_uv = config.environment.build_command(test_cmd) != test_cmd and not skip_env
|
|
467
469
|
|
|
468
470
|
if needs_uv:
|
|
469
|
-
#
|
|
471
|
+
# Build the inner fastmcp command
|
|
472
|
+
inner_cmd = ["fastmcp", "run", server_spec]
|
|
473
|
+
|
|
474
|
+
# Add transport options to the inner command
|
|
475
|
+
if final_transport:
|
|
476
|
+
inner_cmd.extend(["--transport", final_transport])
|
|
477
|
+
# Only add HTTP-specific options for non-stdio transports
|
|
478
|
+
if final_transport != "stdio":
|
|
479
|
+
if final_host:
|
|
480
|
+
inner_cmd.extend(["--host", final_host])
|
|
481
|
+
if final_port:
|
|
482
|
+
inner_cmd.extend(["--port", str(final_port)])
|
|
483
|
+
if final_path:
|
|
484
|
+
inner_cmd.extend(["--path", final_path])
|
|
485
|
+
if final_log_level:
|
|
486
|
+
inner_cmd.extend(["--log-level", final_log_level])
|
|
487
|
+
if final_no_banner:
|
|
488
|
+
inner_cmd.append("--no-banner")
|
|
489
|
+
# Add skip-env flag to prevent infinite recursion
|
|
490
|
+
inner_cmd.append("--skip-env")
|
|
491
|
+
|
|
492
|
+
# Add server args if any
|
|
493
|
+
if final_server_args:
|
|
494
|
+
inner_cmd.append("--")
|
|
495
|
+
inner_cmd.extend(final_server_args)
|
|
496
|
+
|
|
497
|
+
# Build the full uv command using the config's environment
|
|
498
|
+
cmd = config.environment.build_command(inner_cmd)
|
|
499
|
+
|
|
500
|
+
# Set marker to prevent infinite loops when subprocess calls FastMCP again
|
|
501
|
+
env = os.environ | {"FASTMCP_UV_SPAWNED": "1"}
|
|
502
|
+
|
|
503
|
+
# Run the command
|
|
504
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
470
505
|
try:
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
with_packages=config.environment.dependencies,
|
|
475
|
-
with_requirements=Path(config.environment.requirements)
|
|
476
|
-
if config.environment.requirements
|
|
477
|
-
else None,
|
|
478
|
-
project=Path(config.environment.project)
|
|
479
|
-
if config.environment.project
|
|
480
|
-
else None,
|
|
481
|
-
transport=final_transport,
|
|
482
|
-
host=final_host,
|
|
483
|
-
port=final_port,
|
|
484
|
-
path=final_path,
|
|
485
|
-
log_level=final_log_level,
|
|
486
|
-
show_banner=not no_banner,
|
|
487
|
-
editable=config.environment.editable,
|
|
488
|
-
)
|
|
489
|
-
except Exception as e:
|
|
506
|
+
process = subprocess.run(cmd, check=True, env=env)
|
|
507
|
+
sys.exit(process.returncode)
|
|
508
|
+
except subprocess.CalledProcessError as e:
|
|
490
509
|
logger.error(
|
|
491
510
|
f"Failed to run: {e}",
|
|
492
511
|
extra={
|
|
493
512
|
"server_spec": server_spec,
|
|
494
513
|
"error": str(e),
|
|
514
|
+
"returncode": e.returncode,
|
|
495
515
|
},
|
|
496
516
|
)
|
|
497
|
-
sys.exit(
|
|
517
|
+
sys.exit(e.returncode)
|
|
498
518
|
else:
|
|
499
519
|
# Use direct import for backwards compatibility
|
|
500
520
|
try:
|
|
@@ -506,7 +526,7 @@ async def run(
|
|
|
506
526
|
path=final_path,
|
|
507
527
|
log_level=final_log_level,
|
|
508
528
|
server_args=list(final_server_args) if final_server_args else [],
|
|
509
|
-
show_banner=not
|
|
529
|
+
show_banner=not final_no_banner,
|
|
510
530
|
skip_source=skip_source,
|
|
511
531
|
)
|
|
512
532
|
except Exception as e:
|
|
@@ -598,7 +618,6 @@ async def inspect(
|
|
|
598
618
|
Args:
|
|
599
619
|
server_spec: Python file to inspect, optionally with :object suffix, or fastmcp.json
|
|
600
620
|
"""
|
|
601
|
-
from fastmcp.utilities.cli import is_already_in_uv_subprocess, load_and_merge_config
|
|
602
621
|
|
|
603
622
|
# Check if we were spawned by uv (or user explicitly set --skip-env)
|
|
604
623
|
if skip_env or is_already_in_uv_subprocess():
|
|
@@ -641,6 +660,7 @@ async def inspect(
|
|
|
641
660
|
"fastmcp",
|
|
642
661
|
"inspect",
|
|
643
662
|
server_spec,
|
|
663
|
+
"--skip-env", # Prevent infinite recursion
|
|
644
664
|
]
|
|
645
665
|
|
|
646
666
|
# Add format and output flags if specified
|
fastmcp/cli/install/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ import cyclopts
|
|
|
5
5
|
from .claude_code import claude_code_command
|
|
6
6
|
from .claude_desktop import claude_desktop_command
|
|
7
7
|
from .cursor import cursor_command
|
|
8
|
+
from .gemini_cli import gemini_cli_command
|
|
8
9
|
from .mcp_json import mcp_json_command
|
|
9
10
|
|
|
10
11
|
# Create a cyclopts app for install subcommands
|
|
@@ -17,4 +18,5 @@ install_app = cyclopts.App(
|
|
|
17
18
|
install_app.command(claude_code_command, name="claude-code")
|
|
18
19
|
install_app.command(claude_desktop_command, name="claude-desktop")
|
|
19
20
|
install_app.command(cursor_command, name="cursor")
|
|
21
|
+
install_app.command(gemini_cli_command, name="gemini-cli")
|
|
20
22
|
install_app.command(mcp_json_command, name="mcp-json")
|
|
@@ -107,18 +107,9 @@ def install_claude_code(
|
|
|
107
107
|
)
|
|
108
108
|
return False
|
|
109
109
|
|
|
110
|
-
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
|
111
|
-
deduplicated_packages = None
|
|
112
|
-
if with_packages:
|
|
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
110
|
env_config = UVEnvironment(
|
|
120
111
|
python=python_version,
|
|
121
|
-
dependencies=
|
|
112
|
+
dependencies=(with_packages or []) + ["fastmcp"],
|
|
122
113
|
requirements=str(with_requirements) if with_requirements else None,
|
|
123
114
|
project=str(project) if project else None,
|
|
124
115
|
editable=[str(p) for p in with_editable] if with_editable else None,
|
|
@@ -73,17 +73,9 @@ def install_claude_desktop(
|
|
|
73
73
|
|
|
74
74
|
config_file = config_dir / "claude_desktop_config.json"
|
|
75
75
|
|
|
76
|
-
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
|
77
|
-
deduplicated_packages = None
|
|
78
|
-
if with_packages:
|
|
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
76
|
env_config = UVEnvironment(
|
|
85
77
|
python=python_version,
|
|
86
|
-
dependencies=
|
|
78
|
+
dependencies=(with_packages or []) + ["fastmcp"],
|
|
87
79
|
requirements=str(with_requirements) if with_requirements else None,
|
|
88
80
|
project=str(project) if project else None,
|
|
89
81
|
editable=[str(p) for p in with_editable] if with_editable else None,
|
fastmcp/cli/install/cursor.py
CHANGED
|
@@ -107,17 +107,9 @@ def install_cursor_workspace(
|
|
|
107
107
|
|
|
108
108
|
config_file = cursor_dir / "mcp.json"
|
|
109
109
|
|
|
110
|
-
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
|
111
|
-
deduplicated_packages = None
|
|
112
|
-
if with_packages:
|
|
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
110
|
env_config = UVEnvironment(
|
|
119
111
|
python=python_version,
|
|
120
|
-
dependencies=
|
|
112
|
+
dependencies=(with_packages or []) + ["fastmcp"],
|
|
121
113
|
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
|
122
114
|
project=str(project.resolve()) if project else None,
|
|
123
115
|
editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
|
|
@@ -185,17 +177,9 @@ def install_cursor(
|
|
|
185
177
|
True if installation was successful, False otherwise
|
|
186
178
|
"""
|
|
187
179
|
|
|
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
180
|
env_config = UVEnvironment(
|
|
197
181
|
python=python_version,
|
|
198
|
-
dependencies=
|
|
182
|
+
dependencies=(with_packages or []) + ["fastmcp"],
|
|
199
183
|
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
|
200
184
|
project=str(project.resolve()) if project else None,
|
|
201
185
|
editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Gemini CLI integration for FastMCP install using Cyclopts."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
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.utilities.logging import get_logger
|
|
13
|
+
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
|
14
|
+
|
|
15
|
+
from .shared import process_common_args
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def find_gemini_command() -> str | None:
|
|
21
|
+
"""Find the Gemini CLI command."""
|
|
22
|
+
# First try shutil.which() in case it's a real executable in PATH
|
|
23
|
+
gemini_in_path = shutil.which("gemini")
|
|
24
|
+
if gemini_in_path:
|
|
25
|
+
try:
|
|
26
|
+
# If 'gemini --version' fails, it's not the correct path
|
|
27
|
+
subprocess.run(
|
|
28
|
+
[gemini_in_path, "--version"],
|
|
29
|
+
check=True,
|
|
30
|
+
capture_output=True,
|
|
31
|
+
)
|
|
32
|
+
return gemini_in_path
|
|
33
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
# Check common installation locations (aliases don't work with subprocess)
|
|
37
|
+
potential_paths = [
|
|
38
|
+
# Default Gemini CLI installation location (after migration)
|
|
39
|
+
Path.home() / ".gemini" / "local" / "gemini",
|
|
40
|
+
# npm global installation on macOS/Linux (default)
|
|
41
|
+
Path("/usr/local/bin/gemini"),
|
|
42
|
+
# npm global installation with custom prefix
|
|
43
|
+
Path.home() / ".npm-global" / "bin" / "gemini",
|
|
44
|
+
# Homebrew installation on macOS
|
|
45
|
+
Path("/opt/homebrew/bin/gemini"),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
for path in potential_paths:
|
|
49
|
+
if path.exists():
|
|
50
|
+
# If 'gemini --version' fails, it's not the correct path
|
|
51
|
+
try:
|
|
52
|
+
subprocess.run(
|
|
53
|
+
[str(path), "--version"],
|
|
54
|
+
check=True,
|
|
55
|
+
capture_output=True,
|
|
56
|
+
)
|
|
57
|
+
return str(path)
|
|
58
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def check_gemini_cli_available() -> bool:
|
|
65
|
+
"""Check if Gemini CLI is available."""
|
|
66
|
+
return find_gemini_command() is not None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def install_gemini_cli(
|
|
70
|
+
file: Path,
|
|
71
|
+
server_object: str | None,
|
|
72
|
+
name: str,
|
|
73
|
+
*,
|
|
74
|
+
with_editable: list[Path] | None = None,
|
|
75
|
+
with_packages: list[str] | None = None,
|
|
76
|
+
env_vars: dict[str, str] | None = None,
|
|
77
|
+
python_version: str | None = None,
|
|
78
|
+
with_requirements: Path | None = None,
|
|
79
|
+
project: Path | None = None,
|
|
80
|
+
) -> bool:
|
|
81
|
+
"""Install FastMCP server in Gemini CLI.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
file: Path to the server file
|
|
85
|
+
server_object: Optional server object name (for :object suffix)
|
|
86
|
+
name: Name for the server in Gemini CLI
|
|
87
|
+
with_editable: Optional list of directories to install in editable mode
|
|
88
|
+
with_packages: Optional list of additional packages to install
|
|
89
|
+
env_vars: Optional dictionary of environment variables
|
|
90
|
+
python_version: Optional Python version to use
|
|
91
|
+
with_requirements: Optional requirements file to install from
|
|
92
|
+
project: Optional project directory to run within
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True if installation was successful, False otherwise
|
|
96
|
+
"""
|
|
97
|
+
# Check if Gemini CLI is available
|
|
98
|
+
gemini_cmd = find_gemini_command()
|
|
99
|
+
if not gemini_cmd:
|
|
100
|
+
print(
|
|
101
|
+
"[red]Gemini CLI not found.[/red]\n"
|
|
102
|
+
"[blue]Please ensure Gemini CLI is installed. Try running 'gemini --version' to verify.[/blue]\n"
|
|
103
|
+
"[blue]You can install it using 'npm install -g @google/gemini-cli'.[/blue]\n"
|
|
104
|
+
)
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
env_config = UVEnvironment(
|
|
108
|
+
python=python_version,
|
|
109
|
+
dependencies=(with_packages or []) + ["fastmcp"],
|
|
110
|
+
requirements=str(with_requirements) if with_requirements else None,
|
|
111
|
+
project=str(project) if project else None,
|
|
112
|
+
editable=[str(p) for p in with_editable] if with_editable else None,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Build server spec from parsed components
|
|
116
|
+
if server_object:
|
|
117
|
+
server_spec = f"{file.resolve()}:{server_object}"
|
|
118
|
+
else:
|
|
119
|
+
server_spec = str(file.resolve())
|
|
120
|
+
|
|
121
|
+
# Build the full command
|
|
122
|
+
full_command = env_config.build_command(["fastmcp", "run", server_spec])
|
|
123
|
+
|
|
124
|
+
# Build gemini mcp add command
|
|
125
|
+
cmd_parts = [gemini_cmd, "mcp", "add"]
|
|
126
|
+
|
|
127
|
+
# Add environment variables if specified (before the name and command)
|
|
128
|
+
if env_vars:
|
|
129
|
+
for key, value in env_vars.items():
|
|
130
|
+
cmd_parts.extend(["-e", f"{key}={value}"])
|
|
131
|
+
|
|
132
|
+
# Add server name and command
|
|
133
|
+
cmd_parts.extend([name, full_command[0], "--"])
|
|
134
|
+
cmd_parts.extend(full_command[1:])
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
# Run the gemini mcp add command
|
|
138
|
+
subprocess.run(cmd_parts, check=True, capture_output=True, text=True)
|
|
139
|
+
return True
|
|
140
|
+
except subprocess.CalledProcessError as e:
|
|
141
|
+
print(
|
|
142
|
+
f"[red]Failed to install '[bold]{name}[/bold]' in Gemini CLI: {e.stderr.strip() if e.stderr else str(e)}[/red]"
|
|
143
|
+
)
|
|
144
|
+
return False
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print(f"[red]Failed to install '[bold]{name}[/bold]' in Gemini CLI: {e}[/red]")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def gemini_cli_command(
|
|
151
|
+
server_spec: str,
|
|
152
|
+
*,
|
|
153
|
+
server_name: Annotated[
|
|
154
|
+
str | None,
|
|
155
|
+
cyclopts.Parameter(
|
|
156
|
+
name=["--name", "-n"],
|
|
157
|
+
help="Custom name for the server in Gemini CLI",
|
|
158
|
+
),
|
|
159
|
+
] = None,
|
|
160
|
+
with_editable: Annotated[
|
|
161
|
+
list[Path] | None,
|
|
162
|
+
cyclopts.Parameter(
|
|
163
|
+
"--with-editable",
|
|
164
|
+
help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
|
|
165
|
+
negative="",
|
|
166
|
+
),
|
|
167
|
+
] = None,
|
|
168
|
+
with_packages: Annotated[
|
|
169
|
+
list[str] | None,
|
|
170
|
+
cyclopts.Parameter(
|
|
171
|
+
"--with",
|
|
172
|
+
help="Additional packages to install (can be used multiple times)",
|
|
173
|
+
negative="",
|
|
174
|
+
),
|
|
175
|
+
] = None,
|
|
176
|
+
env_vars: Annotated[
|
|
177
|
+
list[str] | None,
|
|
178
|
+
cyclopts.Parameter(
|
|
179
|
+
"--env",
|
|
180
|
+
help="Environment variables in KEY=VALUE format (can be used multiple times)",
|
|
181
|
+
negative="",
|
|
182
|
+
),
|
|
183
|
+
] = None,
|
|
184
|
+
env_file: Annotated[
|
|
185
|
+
Path | None,
|
|
186
|
+
cyclopts.Parameter(
|
|
187
|
+
"--env-file",
|
|
188
|
+
help="Load environment variables from .env file",
|
|
189
|
+
),
|
|
190
|
+
] = None,
|
|
191
|
+
python: Annotated[
|
|
192
|
+
str | None,
|
|
193
|
+
cyclopts.Parameter(
|
|
194
|
+
"--python",
|
|
195
|
+
help="Python version to use (e.g., 3.10, 3.11)",
|
|
196
|
+
),
|
|
197
|
+
] = None,
|
|
198
|
+
with_requirements: Annotated[
|
|
199
|
+
Path | None,
|
|
200
|
+
cyclopts.Parameter(
|
|
201
|
+
"--with-requirements",
|
|
202
|
+
help="Requirements file to install dependencies from",
|
|
203
|
+
),
|
|
204
|
+
] = None,
|
|
205
|
+
project: Annotated[
|
|
206
|
+
Path | None,
|
|
207
|
+
cyclopts.Parameter(
|
|
208
|
+
"--project",
|
|
209
|
+
help="Run the command within the given project directory",
|
|
210
|
+
),
|
|
211
|
+
] = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Install an MCP server in Gemini CLI.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
server_spec: Python file to install, optionally with :object suffix
|
|
217
|
+
"""
|
|
218
|
+
# Convert None to empty lists for list parameters
|
|
219
|
+
with_editable = with_editable or []
|
|
220
|
+
with_packages = with_packages or []
|
|
221
|
+
env_vars = env_vars or []
|
|
222
|
+
file, server_object, name, packages, env_dict = await process_common_args(
|
|
223
|
+
server_spec, server_name, with_packages, env_vars, env_file
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
success = install_gemini_cli(
|
|
227
|
+
file=file,
|
|
228
|
+
server_object=server_object,
|
|
229
|
+
name=name,
|
|
230
|
+
with_editable=with_editable,
|
|
231
|
+
with_packages=packages,
|
|
232
|
+
env_vars=env_dict,
|
|
233
|
+
python_version=python,
|
|
234
|
+
with_requirements=with_requirements,
|
|
235
|
+
project=project,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if success:
|
|
239
|
+
print(f"[green]Successfully installed '{name}' in Gemini CLI")
|
|
240
|
+
else:
|
|
241
|
+
sys.exit(1)
|
fastmcp/cli/install/mcp_json.py
CHANGED
|
@@ -48,17 +48,9 @@ def install_mcp_json(
|
|
|
48
48
|
True if generation was successful, False otherwise
|
|
49
49
|
"""
|
|
50
50
|
try:
|
|
51
|
-
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
|
52
|
-
deduplicated_packages = None
|
|
53
|
-
if with_packages:
|
|
54
|
-
deduplicated = list(dict.fromkeys(with_packages))
|
|
55
|
-
deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
|
|
56
|
-
if not deduplicated_packages:
|
|
57
|
-
deduplicated_packages = None
|
|
58
|
-
|
|
59
51
|
env_config = UVEnvironment(
|
|
60
52
|
python=python_version,
|
|
61
|
-
dependencies=
|
|
53
|
+
dependencies=(with_packages or []) + ["fastmcp"],
|
|
62
54
|
requirements=str(with_requirements) if with_requirements else None,
|
|
63
55
|
project=str(project) if project else None,
|
|
64
56
|
editable=[str(p) for p in with_editable] if with_editable else None,
|
fastmcp/cli/run.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"""FastMCP run command implementation with enhanced type hints."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import os
|
|
5
4
|
import re
|
|
6
|
-
import subprocess
|
|
7
5
|
import sys
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
from typing import Any, Literal
|
|
@@ -15,7 +13,6 @@ from fastmcp.utilities.logging import get_logger
|
|
|
15
13
|
from fastmcp.utilities.mcp_server_config import (
|
|
16
14
|
MCPServerConfig,
|
|
17
15
|
)
|
|
18
|
-
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
|
19
16
|
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
|
|
20
17
|
|
|
21
18
|
logger = get_logger("cli.run")
|
|
@@ -31,87 +28,6 @@ def is_url(path: str) -> bool:
|
|
|
31
28
|
return bool(url_pattern.match(path))
|
|
32
29
|
|
|
33
30
|
|
|
34
|
-
def run_with_uv(
|
|
35
|
-
server_spec: str,
|
|
36
|
-
python_version: str | None = None,
|
|
37
|
-
with_packages: list[str] | None = None,
|
|
38
|
-
with_requirements: Path | None = None,
|
|
39
|
-
project: Path | None = None,
|
|
40
|
-
transport: TransportType | None = None,
|
|
41
|
-
host: str | None = None,
|
|
42
|
-
port: int | None = None,
|
|
43
|
-
path: str | None = None,
|
|
44
|
-
log_level: LogLevelType | None = None,
|
|
45
|
-
show_banner: bool = True,
|
|
46
|
-
editable: str | list[str] | None = None,
|
|
47
|
-
) -> None:
|
|
48
|
-
"""Run a MCP server using uv run subprocess.
|
|
49
|
-
|
|
50
|
-
This function is called when we need to set up a Python environment with specific
|
|
51
|
-
dependencies before running the server. The config parsing and merging should already
|
|
52
|
-
be done by the caller.
|
|
53
|
-
|
|
54
|
-
Args:
|
|
55
|
-
server_spec: Python file, object specification (file:obj), config file, or URL
|
|
56
|
-
python_version: Python version to use (e.g. "3.10")
|
|
57
|
-
with_packages: Additional packages to install
|
|
58
|
-
with_requirements: Requirements file to use
|
|
59
|
-
project: Run the command within the given project directory
|
|
60
|
-
transport: Transport protocol to use
|
|
61
|
-
host: Host to bind to when using http transport
|
|
62
|
-
port: Port to bind to when using http transport
|
|
63
|
-
path: Path to bind to when using http transport
|
|
64
|
-
log_level: Log level
|
|
65
|
-
show_banner: Whether to show the server banner
|
|
66
|
-
editable: Editable package paths
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
# Build uv command using Environment.build_uv_run_command()
|
|
70
|
-
env_config = UVEnvironment(
|
|
71
|
-
python=python_version,
|
|
72
|
-
dependencies=with_packages if with_packages else None,
|
|
73
|
-
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
|
74
|
-
project=str(project.resolve()) if project else None,
|
|
75
|
-
editable=editable
|
|
76
|
-
if isinstance(editable, list)
|
|
77
|
-
else ([editable] if editable else None),
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# Build the inner fastmcp command (environment variable prevents infinite recursion)
|
|
81
|
-
inner_cmd = ["fastmcp", "run", server_spec]
|
|
82
|
-
|
|
83
|
-
# Add transport options to the inner command
|
|
84
|
-
if transport:
|
|
85
|
-
inner_cmd.extend(["--transport", transport])
|
|
86
|
-
# Only add HTTP-specific options for non-stdio transports
|
|
87
|
-
if transport != "stdio":
|
|
88
|
-
if host:
|
|
89
|
-
inner_cmd.extend(["--host", host])
|
|
90
|
-
if port:
|
|
91
|
-
inner_cmd.extend(["--port", str(port)])
|
|
92
|
-
if path:
|
|
93
|
-
inner_cmd.extend(["--path", path])
|
|
94
|
-
if log_level:
|
|
95
|
-
inner_cmd.extend(["--log-level", log_level])
|
|
96
|
-
if not show_banner:
|
|
97
|
-
inner_cmd.append("--no-banner")
|
|
98
|
-
|
|
99
|
-
# Build the full uv command
|
|
100
|
-
cmd = env_config.build_command(inner_cmd)
|
|
101
|
-
|
|
102
|
-
# Set marker to prevent infinite loops when subprocess calls FastMCP again
|
|
103
|
-
env = os.environ | {"FASTMCP_UV_SPAWNED": "1"}
|
|
104
|
-
|
|
105
|
-
# Run the command
|
|
106
|
-
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
107
|
-
try:
|
|
108
|
-
process = subprocess.run(cmd, check=True, env=env)
|
|
109
|
-
sys.exit(process.returncode)
|
|
110
|
-
except subprocess.CalledProcessError as e:
|
|
111
|
-
logger.error(f"Failed to run server: {e}")
|
|
112
|
-
sys.exit(e.returncode)
|
|
113
|
-
|
|
114
|
-
|
|
115
31
|
def create_client_server(url: str) -> Any:
|
|
116
32
|
"""Create a FastMCP server from a client URL.
|
|
117
33
|
|
|
@@ -268,8 +184,8 @@ async def run_command(
|
|
|
268
184
|
kwargs["port"] = port
|
|
269
185
|
if path:
|
|
270
186
|
kwargs["path"] = path
|
|
271
|
-
|
|
272
|
-
|
|
187
|
+
if log_level:
|
|
188
|
+
kwargs["log_level"] = log_level
|
|
273
189
|
|
|
274
190
|
if not show_banner:
|
|
275
191
|
kwargs["show_banner"] = False
|