mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 21:14:17 +02:00
Merge branch 'main' of https://github.com/jlowin/fastmcp
This commit is contained in:
commit
007af5d9b3
15 changed files with 391 additions and 392 deletions
|
|
@ -25,7 +25,7 @@ repos:
|
|||
- repo: local
|
||||
hooks:
|
||||
- id: ty
|
||||
name: type check
|
||||
name: ty check
|
||||
entry: uv run ty check
|
||||
language: system
|
||||
types: [python]
|
||||
|
|
@ -37,4 +37,5 @@ repos:
|
|||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: no-commit-to-branch
|
||||
name: prevent commits to main
|
||||
args: [--branch, main]
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ from fastmcp.settings import Settings
|
|||
from fastmcp.utilities.logging import configure_logging as _configure_logging
|
||||
|
||||
settings = Settings()
|
||||
# if False:
|
||||
_configure_logging(
|
||||
level=settings.log_level,
|
||||
enable_rich_tracebacks=settings.enable_rich_tracebacks,
|
||||
)
|
||||
if settings.log_enabled:
|
||||
_configure_logging(
|
||||
level=settings.log_level,
|
||||
enable_rich_tracebacks=settings.enable_rich_tracebacks,
|
||||
)
|
||||
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.server.context import Context
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import sys
|
|||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastmcp.utilities.cli import build_uv_run_args
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -89,20 +90,11 @@ def update_claude_config(
|
|||
else:
|
||||
env_vars = existing_env
|
||||
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
# Build uv run command using centralized function
|
||||
args = build_uv_run_args(
|
||||
with_editable=with_editable,
|
||||
with_packages=with_packages,
|
||||
)
|
||||
|
||||
# Convert file path to absolute before adding to command
|
||||
# Split off any :object suffix first
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import fastmcp
|
|||
from fastmcp.cli import run as run_module
|
||||
from fastmcp.cli.install import install_app
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.utilities.cli import build_uv_command
|
||||
from fastmcp.utilities.inspect import FastMCPInfo, inspect_fastmcp
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
|
|
@ -57,46 +58,8 @@ def _parse_env_var(env_var: str) -> tuple[str, str]:
|
|||
return key.strip(), value.strip()
|
||||
|
||||
|
||||
def _build_uv_command(
|
||||
server_spec: str,
|
||||
with_editable: Path | None = None,
|
||||
with_packages: list[str] | None = None,
|
||||
no_banner: bool = False,
|
||||
python_version: str | None = None,
|
||||
with_requirements: Path | None = None,
|
||||
project: Path | None = None,
|
||||
) -> list[str]:
|
||||
"""Build the uv run command that runs a MCP server through mcp run."""
|
||||
cmd = ["uv", "run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
cmd.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
cmd.extend(["--project", str(project)])
|
||||
|
||||
cmd.extend(["--with", "fastmcp"])
|
||||
|
||||
if with_editable:
|
||||
cmd.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_packages:
|
||||
for pkg in with_packages:
|
||||
if pkg:
|
||||
cmd.extend(["--with", pkg])
|
||||
|
||||
if with_requirements:
|
||||
cmd.extend(["--with-requirements", str(with_requirements)])
|
||||
|
||||
# Add mcp run command
|
||||
cmd.extend(["fastmcp", "run", server_spec])
|
||||
|
||||
if no_banner:
|
||||
cmd.append("--no-banner")
|
||||
|
||||
return cmd
|
||||
# The _build_uv_command function has been moved to cli/utils.py
|
||||
# and is now imported as build_uv_command
|
||||
|
||||
|
||||
@app.command
|
||||
|
|
@ -302,16 +265,18 @@ async def dev(
|
|||
if inspector_version:
|
||||
inspector_cmd += f"@{inspector_version}"
|
||||
|
||||
uv_cmd = _build_uv_command(
|
||||
uv_cmd = build_uv_command(
|
||||
server_spec,
|
||||
with_editable,
|
||||
with_packages,
|
||||
no_banner=True,
|
||||
with_editable=with_editable,
|
||||
with_packages=with_packages,
|
||||
python_version=python,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
|
||||
# Add --no-banner flag for dev command
|
||||
uv_cmd.append("--no-banner")
|
||||
|
||||
# Run the MCP Inspector command with shell=True on Windows
|
||||
shell = sys.platform == "win32"
|
||||
process = subprocess.run(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from typing import Annotated
|
|||
import cyclopts
|
||||
from rich import print
|
||||
|
||||
from fastmcp.utilities.cli import build_uv_run_args
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
from .shared import process_common_args
|
||||
|
|
@ -106,31 +107,14 @@ def install_claude_code(
|
|||
)
|
||||
return False
|
||||
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
# Build uv run command using centralized function
|
||||
args = build_uv_run_args(
|
||||
with_editable=with_editable,
|
||||
with_packages=with_packages,
|
||||
python_version=python_version,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import cyclopts
|
|||
from rich import print
|
||||
|
||||
from fastmcp.mcp_config import StdioMCPServer, update_config_file
|
||||
from fastmcp.utilities.cli import build_uv_run_args
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
from .shared import process_common_args
|
||||
|
|
@ -72,31 +73,14 @@ def install_claude_desktop(
|
|||
|
||||
config_file = config_dir / "claude_desktop_config.json"
|
||||
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
# Build uv run command using centralized function
|
||||
args = build_uv_run_args(
|
||||
with_editable=with_editable,
|
||||
with_packages=with_packages,
|
||||
python_version=python_version,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import cyclopts
|
|||
from rich import print
|
||||
|
||||
from fastmcp.mcp_config import StdioMCPServer, update_config_file
|
||||
from fastmcp.utilities.cli import build_uv_run_args
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
from .shared import process_common_args
|
||||
|
|
@ -106,31 +107,14 @@ def install_cursor_workspace(
|
|||
|
||||
config_file = cursor_dir / "mcp.json"
|
||||
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
# Build uv run command using centralized function
|
||||
args = build_uv_run_args(
|
||||
with_editable=with_editable,
|
||||
with_packages=with_packages,
|
||||
python_version=python_version,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
@ -194,31 +178,14 @@ def install_cursor(
|
|||
Returns:
|
||||
True if installation was successful, False otherwise
|
||||
"""
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
# Build uv run command using centralized function
|
||||
args = build_uv_run_args(
|
||||
with_editable=with_editable,
|
||||
with_packages=with_packages,
|
||||
python_version=python_version,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import cyclopts
|
|||
import pyperclip
|
||||
from rich import print
|
||||
|
||||
from fastmcp.utilities.cli import build_uv_run_args
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
from .shared import process_common_args
|
||||
|
|
@ -47,31 +48,14 @@ def install_mcp_json(
|
|||
True if generation was successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
# Build uv run command using centralized function
|
||||
args = build_uv_run_args(
|
||||
with_editable=with_editable,
|
||||
with_packages=with_packages,
|
||||
python_version=python_version,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from typing import Any, Literal
|
|||
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
||||
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.utilities.cli import build_uv_command
|
||||
from fastmcp.utilities.fastmcp_config import (
|
||||
DeploymentConfig,
|
||||
EntrypointConfig,
|
||||
|
|
@ -255,31 +256,14 @@ def run_with_uv(
|
|||
port = merged_deploy["port"]
|
||||
path = merged_deploy["path"]
|
||||
log_level = merged_deploy["log_level"]
|
||||
cmd = ["uv", "run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
cmd.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
cmd.extend(["--project", str(project)])
|
||||
|
||||
# Add fastmcp package
|
||||
cmd.extend(["--with", "fastmcp"])
|
||||
|
||||
# Add additional packages
|
||||
if with_packages:
|
||||
for pkg in with_packages:
|
||||
if pkg:
|
||||
cmd.extend(["--with", pkg])
|
||||
|
||||
# Add requirements file
|
||||
if with_requirements:
|
||||
cmd.extend(["--with-requirements", str(with_requirements)])
|
||||
|
||||
# Add fastmcp run command
|
||||
cmd.extend(["fastmcp", "run", server_spec])
|
||||
# Build uv command using centralized function
|
||||
cmd = build_uv_command(
|
||||
server_spec,
|
||||
with_packages=with_packages,
|
||||
python_version=python_version,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
|
||||
# Add transport options
|
||||
if transport:
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ class Settings(BaseSettings):
|
|||
|
||||
test_mode: bool = False
|
||||
|
||||
log_enabled: bool = True
|
||||
log_level: LOG_LEVEL = "INFO"
|
||||
|
||||
@field_validator("log_level", mode="before")
|
||||
|
|
@ -314,7 +315,7 @@ class Settings(BaseSettings):
|
|||
Whether to include FastMCP meta in the server's MCP responses.
|
||||
If True, a `_fastmcp` key will be added to the `meta` field of
|
||||
all MCP component responses. This key will contain a dict of
|
||||
various FastMCP-specific metadata, such as tags.
|
||||
various FastMCP-specific metadata, such as tags.
|
||||
"""
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from importlib.metadata import version
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from rich.align import Align
|
||||
|
|
@ -109,3 +110,119 @@ def log_server_banner(
|
|||
|
||||
console = Console(stderr=True)
|
||||
console.print(Group("\n", panel, "\n"))
|
||||
|
||||
|
||||
def build_uv_command(
|
||||
server_spec: str,
|
||||
*,
|
||||
with_editable: Path | None = None,
|
||||
with_packages: list[str] | None = None,
|
||||
python_version: str | None = None,
|
||||
with_requirements: Path | None = None,
|
||||
project: Path | None = None,
|
||||
) -> list[str]:
|
||||
"""Build a uv run command for running a FastMCP server.
|
||||
|
||||
This centralized function ensures consistent path resolution and command building
|
||||
across all CLI commands.
|
||||
|
||||
Args:
|
||||
server_spec: Server specification (file path, optionally with :object)
|
||||
with_editable: Directory to install in editable mode
|
||||
with_packages: Additional packages to install
|
||||
python_version: Python version to use (e.g., "3.10", "3.11")
|
||||
with_requirements: Requirements file to install from
|
||||
project: Project directory to run within
|
||||
|
||||
Returns:
|
||||
List of command arguments for subprocess execution
|
||||
"""
|
||||
cmd = ["uv", "run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
cmd.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified - resolve to absolute path
|
||||
if project:
|
||||
cmd.extend(["--project", str(project.expanduser().resolve())])
|
||||
|
||||
# Always include fastmcp
|
||||
cmd.extend(["--with", "fastmcp"])
|
||||
|
||||
# Add additional packages
|
||||
if with_packages:
|
||||
# Deduplicate and sort packages for consistency
|
||||
packages = set(pkg for pkg in with_packages if pkg)
|
||||
for pkg in sorted(packages):
|
||||
cmd.extend(["--with", pkg])
|
||||
|
||||
# Add editable directory - resolve to absolute path
|
||||
if with_editable:
|
||||
cmd.extend(["--with-editable", str(with_editable.expanduser().resolve())])
|
||||
|
||||
# Add requirements file - resolve to absolute path
|
||||
if with_requirements:
|
||||
cmd.extend(
|
||||
["--with-requirements", str(with_requirements.expanduser().resolve())]
|
||||
)
|
||||
|
||||
# Add fastmcp run command
|
||||
cmd.extend(["fastmcp", "run", server_spec])
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def build_uv_run_args(
|
||||
*,
|
||||
with_editable: Path | None = None,
|
||||
with_packages: list[str] | None = None,
|
||||
python_version: str | None = None,
|
||||
with_requirements: Path | None = None,
|
||||
project: Path | None = None,
|
||||
) -> list[str]:
|
||||
"""Build just the uv run arguments without the server spec.
|
||||
|
||||
This is useful for install commands that need to build the args array
|
||||
without the full command structure.
|
||||
|
||||
Args:
|
||||
with_editable: Directory to install in editable mode
|
||||
with_packages: Additional packages to install (fastmcp will be added automatically)
|
||||
python_version: Python version to use
|
||||
with_requirements: Requirements file to install from
|
||||
project: Project directory to run within
|
||||
|
||||
Returns:
|
||||
List of arguments starting with "run"
|
||||
"""
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified - resolve to absolute path
|
||||
if project:
|
||||
args.extend(["--project", str(project.expanduser().resolve())])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
# Add editable directory - resolve to absolute path
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable.expanduser().resolve())])
|
||||
|
||||
# Add requirements file - resolve to absolute path
|
||||
if with_requirements:
|
||||
args.extend(
|
||||
["--with-requirements", str(with_requirements.expanduser().resolve())]
|
||||
)
|
||||
|
||||
return args
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from unittest.mock import Mock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from fastmcp.cli.cli import _build_uv_command, _parse_env_var, app
|
||||
from fastmcp.cli.cli import _parse_env_var, app
|
||||
|
||||
|
||||
class TestMainCLI:
|
||||
|
|
@ -34,150 +34,6 @@ class TestMainCLI:
|
|||
_parse_env_var("INVALID_FORMAT")
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_build_uv_command_basic(self):
|
||||
"""Test building basic uv command."""
|
||||
cmd = _build_uv_command("server.py")
|
||||
expected = ["uv", "run", "--with", "fastmcp", "fastmcp", "run", "server.py"]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_editable(self):
|
||||
"""Test building uv command with editable package."""
|
||||
editable_path = Path("/path/to/package")
|
||||
cmd = _build_uv_command("server.py", with_editable=editable_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-editable",
|
||||
str(editable_path),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_packages(self):
|
||||
"""Test building uv command with additional packages."""
|
||||
cmd = _build_uv_command("server.py", with_packages=["pkg1", "pkg2"])
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with",
|
||||
"pkg1",
|
||||
"--with",
|
||||
"pkg2",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_no_banner(self):
|
||||
"""Test building uv command with no banner flag."""
|
||||
cmd = _build_uv_command("server.py", no_banner=True)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
"--no-banner",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_python_version(self):
|
||||
"""Test building uv command with Python version."""
|
||||
cmd = _build_uv_command("server.py", python_version="3.11")
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--python",
|
||||
"3.11",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_project(self):
|
||||
"""Test building uv command with project directory."""
|
||||
project_path = Path("/path/to/project")
|
||||
cmd = _build_uv_command("server.py", project=project_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--project",
|
||||
str(project_path),
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_requirements(self):
|
||||
"""Test building uv command with requirements file."""
|
||||
req_path = Path("requirements.txt")
|
||||
cmd = _build_uv_command("server.py", with_requirements=req_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-requirements",
|
||||
"requirements.txt",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_all_options(self):
|
||||
"""Test building uv command with all options."""
|
||||
project_path = Path("/my/project")
|
||||
editable_path = Path("/local/pkg")
|
||||
requirements_path = Path("reqs.txt")
|
||||
cmd = _build_uv_command(
|
||||
"server.py",
|
||||
python_version="3.10",
|
||||
project=project_path,
|
||||
with_packages=["pandas", "numpy"],
|
||||
with_requirements=requirements_path,
|
||||
with_editable=editable_path,
|
||||
no_banner=True,
|
||||
)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--python",
|
||||
"3.10",
|
||||
"--project",
|
||||
str(project_path),
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-editable",
|
||||
str(editable_path),
|
||||
"--with",
|
||||
"pandas",
|
||||
"--with",
|
||||
"numpy",
|
||||
"--with-requirements",
|
||||
str(requirements_path),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
"--no-banner",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
|
||||
class TestVersionCommand:
|
||||
"""Test the version command."""
|
||||
|
|
|
|||
|
|
@ -255,9 +255,10 @@ class TestInstallCursor:
|
|||
config_data = json.loads(decoded)
|
||||
|
||||
assert "--with-editable" in config_data["args"]
|
||||
# Check for the editable path in a platform-agnostic way
|
||||
editable_path_str = str(Path("/local/package"))
|
||||
assert editable_path_str in config_data["args"]
|
||||
# Check that the path was resolved (should be absolute)
|
||||
editable_idx = config_data["args"].index("--with-editable") + 1
|
||||
resolved_path = config_data["args"][editable_idx]
|
||||
assert Path(resolved_path).is_absolute()
|
||||
assert "server.py:custom_app" in " ".join(config_data["args"])
|
||||
|
||||
@patch("fastmcp.cli.install.cursor.open_deeplink")
|
||||
|
|
|
|||
|
|
@ -65,18 +65,12 @@ class TestRunWithUv:
|
|||
assert exc_info.value.code == 0
|
||||
|
||||
cmd = mock_run.call_args[0][0]
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--project",
|
||||
str(Path("/my/project")),
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
# Check the basic structure
|
||||
assert cmd[:3] == ["uv", "run", "--project"]
|
||||
# Check that the project path is absolute
|
||||
assert Path(cmd[3]).is_absolute()
|
||||
# Check the rest of the command
|
||||
assert cmd[4:] == ["--with", "fastmcp", "fastmcp", "run", "server.py"]
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_run_with_uv_with_packages(self, mock_run):
|
||||
|
|
@ -95,9 +89,9 @@ class TestRunWithUv:
|
|||
"--with",
|
||||
"fastmcp",
|
||||
"--with",
|
||||
"pandas",
|
||||
"numpy", # sorted alphabetically
|
||||
"--with",
|
||||
"numpy",
|
||||
"pandas", # sorted alphabetically
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -122,7 +116,7 @@ class TestRunWithUv:
|
|||
"--with",
|
||||
"fastmcp",
|
||||
"--with-requirements",
|
||||
"requirements.txt",
|
||||
str(req_path.expanduser().resolve()), # resolved to absolute path
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -190,19 +184,16 @@ class TestRunWithUv:
|
|||
assert exc_info.value.code == 0
|
||||
|
||||
cmd = mock_run.call_args[0][0]
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--python",
|
||||
"3.10",
|
||||
"--project",
|
||||
str(Path("/workspace")),
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with",
|
||||
"pandas",
|
||||
"--with-requirements",
|
||||
"reqs.txt",
|
||||
|
||||
# Check the structure piece by piece to be platform-agnostic
|
||||
assert cmd[:5] == ["uv", "run", "--python", "3.10", "--project"]
|
||||
# Check project path is absolute
|
||||
assert Path(cmd[5]).is_absolute()
|
||||
assert cmd[6:10] == ["--with", "fastmcp", "--with", "pandas"]
|
||||
assert cmd[10] == "--with-requirements"
|
||||
# Check requirements path is absolute
|
||||
assert Path(cmd[11]).is_absolute()
|
||||
assert cmd[12:] == [
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -212,7 +203,6 @@ class TestRunWithUv:
|
|||
"9000",
|
||||
"--no-banner",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_run_with_uv_error_handling(self, mock_run):
|
||||
|
|
|
|||
173
tests/utilities/test_cli.py
Normal file
173
tests/utilities/test_cli.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
from pathlib import Path
|
||||
|
||||
from fastmcp.utilities.cli import build_uv_command
|
||||
|
||||
|
||||
class TestBuildUVCommand:
|
||||
"""Test the build_uv_command function."""
|
||||
|
||||
def test_build_uv_command_basic(self):
|
||||
"""Test building basic uv command."""
|
||||
cmd = build_uv_command("server.py")
|
||||
expected = ["uv", "run", "--with", "fastmcp", "fastmcp", "run", "server.py"]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_editable(self):
|
||||
"""Test building uv command with editable package."""
|
||||
editable_path = Path("/path/to/package")
|
||||
cmd = build_uv_command("server.py", with_editable=editable_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-editable",
|
||||
str(editable_path.expanduser().resolve()),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_packages(self):
|
||||
"""Test building uv command with additional packages."""
|
||||
cmd = build_uv_command("server.py", with_packages=["pkg1", "pkg2"])
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with",
|
||||
"pkg1",
|
||||
"--with",
|
||||
"pkg2",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_python_version(self):
|
||||
"""Test building uv command with Python version."""
|
||||
cmd = build_uv_command("server.py", python_version="3.11")
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--python",
|
||||
"3.11",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_project(self):
|
||||
"""Test building uv command with project directory."""
|
||||
project_path = Path("/path/to/project")
|
||||
cmd = build_uv_command("server.py", project=project_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--project",
|
||||
str(project_path.expanduser().resolve()),
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_requirements(self):
|
||||
"""Test building uv command with requirements file."""
|
||||
req_path = Path("requirements.txt")
|
||||
cmd = build_uv_command("server.py", with_requirements=req_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-requirements",
|
||||
str(req_path.expanduser().resolve()),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_all_options(self):
|
||||
"""Test building uv command with all options."""
|
||||
project_path = Path("/my/project")
|
||||
editable_path = Path("/local/pkg")
|
||||
requirements_path = Path("reqs.txt")
|
||||
cmd = build_uv_command(
|
||||
"server.py",
|
||||
python_version="3.10",
|
||||
project=project_path,
|
||||
with_packages=["pandas", "numpy"],
|
||||
with_requirements=requirements_path,
|
||||
with_editable=editable_path,
|
||||
)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--python",
|
||||
"3.10",
|
||||
"--project",
|
||||
str(project_path.expanduser().resolve()),
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with",
|
||||
"numpy",
|
||||
"--with",
|
||||
"pandas",
|
||||
"--with-editable",
|
||||
str(editable_path.expanduser().resolve()),
|
||||
"--with-requirements",
|
||||
str(requirements_path.expanduser().resolve()),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_with_editable_resolves_dot(self):
|
||||
"""Test that '.' in with_editable becomes absolute."""
|
||||
cmd = build_uv_command("server.py", with_editable=Path("."))
|
||||
idx = cmd.index("--with-editable") + 1
|
||||
assert Path(cmd[idx]).is_absolute()
|
||||
|
||||
def test_with_editable_resolves_tilde(self):
|
||||
"""Test that '~' in with_editable is expanded."""
|
||||
cmd = build_uv_command("server.py", with_editable=Path("~/project"))
|
||||
idx = cmd.index("--with-editable") + 1
|
||||
assert Path(cmd[idx]).is_absolute()
|
||||
assert "~" not in cmd[idx]
|
||||
|
||||
def test_with_requirements_resolves_dot(self):
|
||||
"""Test that '.' in with_requirements becomes absolute."""
|
||||
cmd = build_uv_command("server.py", with_requirements=Path("./reqs.txt"))
|
||||
idx = cmd.index("--with-requirements") + 1
|
||||
assert Path(cmd[idx]).is_absolute()
|
||||
|
||||
def test_with_requirements_resolves_tilde(self):
|
||||
"""Test that '~' in with_requirements is expanded."""
|
||||
cmd = build_uv_command("server.py", with_requirements=Path("~/reqs.txt"))
|
||||
idx = cmd.index("--with-requirements") + 1
|
||||
assert Path(cmd[idx]).is_absolute()
|
||||
assert "~" not in cmd[idx]
|
||||
|
||||
def test_project_resolves_relative(self):
|
||||
"""Test that relative path in project becomes absolute."""
|
||||
cmd = build_uv_command("server.py", project=Path("../project"))
|
||||
idx = cmd.index("--project") + 1
|
||||
assert Path(cmd[idx]).is_absolute()
|
||||
|
||||
def test_project_resolves_tilde(self):
|
||||
"""Test that '~' in project is expanded."""
|
||||
cmd = build_uv_command("server.py", project=Path("~/work"))
|
||||
idx = cmd.index("--project") + 1
|
||||
assert Path(cmd[idx]).is_absolute()
|
||||
assert "~" not in cmd[idx]
|
||||
Loading…
Add table
Add a link
Reference in a new issue