mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
Fix path resolution in install commands (#1951)
This commit is contained in:
parent
7c10feaeb1
commit
3a21e7b9b2
12 changed files with 64 additions and 55 deletions
|
|
@ -110,9 +110,9 @@ def install_claude_code(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=[str(p) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@ def install_claude_desktop(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=[str(p) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ def install_cursor_workspace(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
||||
project=str(project.resolve()) if project else None,
|
||||
editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
@ -180,9 +180,9 @@ def install_cursor(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
||||
project=str(project.resolve()) if project else None,
|
||||
editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -107,9 +107,9 @@ def install_gemini_cli(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=[str(p) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ def install_mcp_json(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=[str(p) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -583,15 +583,15 @@ class UvStdioTransport(StdioTransport):
|
|||
command: str,
|
||||
args: list[str] | None = None,
|
||||
module: bool = False,
|
||||
project_directory: str | None = None,
|
||||
project_directory: Path | None = None,
|
||||
python_version: str | None = None,
|
||||
with_packages: list[str] | None = None,
|
||||
with_requirements: str | None = None,
|
||||
with_requirements: Path | None = None,
|
||||
env_vars: dict[str, str] | None = None,
|
||||
keep_alive: bool | None = None,
|
||||
):
|
||||
# Basic validation
|
||||
if project_directory and not Path(project_directory).exists():
|
||||
if project_directory and not project_directory.exists():
|
||||
raise NotADirectoryError(
|
||||
f"Project directory not found: {project_directory}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,19 +28,19 @@ class UVEnvironment(Environment):
|
|||
examples=[["fastmcp>=2.0,<3", "httpx", "pandas>=2.0"]],
|
||||
)
|
||||
|
||||
requirements: str | None = Field(
|
||||
requirements: Path | None = Field(
|
||||
default=None,
|
||||
description="Path to requirements.txt file",
|
||||
examples=["requirements.txt", "../requirements/prod.txt"],
|
||||
)
|
||||
|
||||
project: str | None = Field(
|
||||
project: Path | None = Field(
|
||||
default=None,
|
||||
description="Path to project directory containing pyproject.toml",
|
||||
examples=[".", "../my-project"],
|
||||
)
|
||||
|
||||
editable: list[str] | None = Field(
|
||||
editable: list[Path] | None = Field(
|
||||
default=None,
|
||||
description="Directories to install in editable mode",
|
||||
examples=[[".", "../my-package"], ["/path/to/package"]],
|
||||
|
|
@ -64,7 +64,7 @@ class UVEnvironment(Environment):
|
|||
|
||||
# Add project if specified
|
||||
if self.project:
|
||||
args.extend(["--project", str(self.project)])
|
||||
args.extend(["--project", str(self.project.resolve())])
|
||||
|
||||
# Add Python version if specified (only if no project, as project has its own Python)
|
||||
if self.python and not self.project:
|
||||
|
|
@ -78,12 +78,12 @@ class UVEnvironment(Environment):
|
|||
|
||||
# Add requirements file
|
||||
if self.requirements:
|
||||
args.extend(["--with-requirements", str(self.requirements)])
|
||||
args.extend(["--with-requirements", str(self.requirements.resolve())])
|
||||
|
||||
# Add editable packages
|
||||
if self.editable:
|
||||
for editable_path in self.editable:
|
||||
args.extend(["--with-editable", str(editable_path)])
|
||||
args.extend(["--with-editable", str(editable_path.resolve())])
|
||||
|
||||
# Add the command
|
||||
args.extend(command)
|
||||
|
|
|
|||
|
|
@ -291,9 +291,9 @@ class MCPServerConfig(BaseModel):
|
|||
environment = UVEnvironment(
|
||||
python=python,
|
||||
dependencies=dependencies,
|
||||
requirements=requirements,
|
||||
project=project,
|
||||
editable=[editable] if editable else None,
|
||||
requirements=Path(requirements) if requirements else None,
|
||||
project=Path(project) if project else None,
|
||||
editable=[Path(editable)] if editable else None,
|
||||
)
|
||||
|
||||
# Build deployment config if any deployment args provided
|
||||
|
|
|
|||
|
|
@ -66,9 +66,10 @@ class TestEnvironment:
|
|||
env = config.environment
|
||||
assert env.python == "3.12"
|
||||
assert env.dependencies == ["requests", "numpy>=2.0"]
|
||||
assert env.requirements == "requirements.txt"
|
||||
assert env.project == "."
|
||||
assert env.editable == ["../my-package"]
|
||||
# Paths are stored as Path objects
|
||||
assert env.requirements == Path("requirements.txt")
|
||||
assert env.project == Path(".")
|
||||
assert env.editable == [Path("../my-package")]
|
||||
|
||||
def test_needs_uv(self):
|
||||
"""Test needs_uv() method."""
|
||||
|
|
@ -112,12 +113,16 @@ class TestEnvironment:
|
|||
assert "--python" not in cmd
|
||||
assert "3.12" not in cmd
|
||||
assert "--project" in cmd
|
||||
assert "." in cmd
|
||||
# Project path should be resolved to absolute path
|
||||
project_idx = cmd.index("--project")
|
||||
assert Path(cmd[project_idx + 1]).is_absolute()
|
||||
assert "--with" in cmd
|
||||
assert "requests" in cmd
|
||||
assert "numpy" in cmd
|
||||
assert "--with-requirements" in cmd
|
||||
assert "requirements.txt" in cmd
|
||||
# Requirements path should be resolved to absolute path
|
||||
req_idx = cmd.index("--with-requirements")
|
||||
assert Path(cmd[req_idx + 1]).is_absolute()
|
||||
# Command args should be at the end
|
||||
assert "fastmcp" in cmd[-3:]
|
||||
assert "run" in cmd[-2:]
|
||||
|
|
|
|||
|
|
@ -234,10 +234,11 @@ class TestPathResolution:
|
|||
assert config.environment is not None
|
||||
uv_cmd = config.environment.build_command(["fastmcp", "run"])
|
||||
|
||||
# Should include requirements file
|
||||
# Should include requirements file with absolute path
|
||||
assert "--with-requirements" in uv_cmd
|
||||
req_idx = uv_cmd.index("--with-requirements") + 1
|
||||
assert uv_cmd[req_idx] == "requirements.txt"
|
||||
assert Path(uv_cmd[req_idx]).is_absolute()
|
||||
assert Path(uv_cmd[req_idx]).name == "requirements.txt"
|
||||
|
||||
|
||||
class TestConfigValidation:
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ async def test_uv_transport_module():
|
|||
with_packages=["fastmcp"],
|
||||
command="my_module",
|
||||
module=True,
|
||||
project_directory=tmpdir,
|
||||
project_directory=Path(tmpdir),
|
||||
keep_alive=False,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"""Tests for CLI utility functions."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
||||
|
||||
|
||||
|
|
@ -16,14 +18,14 @@ class TestEnvironmentBuildUVRunCommand:
|
|||
|
||||
def test_build_uv_run_command_with_editable(self):
|
||||
"""Test building uv command with editable package."""
|
||||
editable_path = "/path/to/package"
|
||||
editable_path = Path("/path/to/package")
|
||||
env = UVEnvironment(editable=[editable_path])
|
||||
cmd = env.build_command(["fastmcp", "run", "server.py"])
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with-editable",
|
||||
editable_path,
|
||||
str(editable_path.resolve()),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -64,14 +66,14 @@ class TestEnvironmentBuildUVRunCommand:
|
|||
|
||||
def test_build_uv_run_command_with_requirements(self):
|
||||
"""Test building uv command with requirements file."""
|
||||
requirements_path = "/path/to/requirements.txt"
|
||||
requirements_path = Path("/path/to/requirements.txt")
|
||||
env = UVEnvironment(requirements=requirements_path)
|
||||
cmd = env.build_command(["fastmcp", "run", "server.py"])
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with-requirements",
|
||||
requirements_path,
|
||||
str(requirements_path.resolve()),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -80,14 +82,14 @@ class TestEnvironmentBuildUVRunCommand:
|
|||
|
||||
def test_build_uv_run_command_with_project(self):
|
||||
"""Test building uv command with project directory."""
|
||||
project_path = "/path/to/project"
|
||||
project_path = Path("/path/to/project")
|
||||
env = UVEnvironment(project=project_path)
|
||||
cmd = env.build_command(["fastmcp", "run", "server.py"])
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--project",
|
||||
project_path,
|
||||
str(project_path.resolve()),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -96,8 +98,8 @@ class TestEnvironmentBuildUVRunCommand:
|
|||
|
||||
def test_build_uv_run_command_with_everything(self):
|
||||
"""Test building uv command with all options."""
|
||||
requirements_path = "/path/to/requirements.txt"
|
||||
editable_path = "/local/pkg"
|
||||
requirements_path = Path("/path/to/requirements.txt")
|
||||
editable_path = Path("/local/pkg")
|
||||
env = UVEnvironment(
|
||||
python="3.10",
|
||||
dependencies=["pandas", "numpy"],
|
||||
|
|
@ -115,9 +117,9 @@ class TestEnvironmentBuildUVRunCommand:
|
|||
"--with",
|
||||
"pandas",
|
||||
"--with-requirements",
|
||||
requirements_path,
|
||||
str(requirements_path.resolve()),
|
||||
"--with-editable",
|
||||
editable_path,
|
||||
str(editable_path.resolve()),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -129,23 +131,24 @@ class TestEnvironmentBuildUVRunCommand:
|
|||
|
||||
def test_build_uv_run_command_project_with_extras(self):
|
||||
"""Test that project flag works with additional dependencies."""
|
||||
project_path = "/path/to/project"
|
||||
project_path = Path("/path/to/project")
|
||||
editable_path = Path("/pkg")
|
||||
env = UVEnvironment(
|
||||
project=project_path,
|
||||
python="3.10", # Should be ignored with project
|
||||
dependencies=["pandas"], # Should be added on top of project
|
||||
editable=["/pkg"], # Should be added on top of project
|
||||
editable=[editable_path], # Should be added on top of project
|
||||
)
|
||||
cmd = env.build_command(["fastmcp", "run", "server.py"])
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--project",
|
||||
project_path,
|
||||
str(project_path.resolve()),
|
||||
"--with",
|
||||
"pandas",
|
||||
"--with-editable",
|
||||
"/pkg",
|
||||
str(editable_path.resolve()),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
|
|
@ -168,17 +171,17 @@ class TestEnvironmentNeedsUV:
|
|||
|
||||
def test_needs_uv_with_requirements(self):
|
||||
"""Test that needs_uv returns True with requirements."""
|
||||
env = UVEnvironment(requirements="/path/to/requirements.txt")
|
||||
env = UVEnvironment(requirements=Path("/path/to/requirements.txt"))
|
||||
assert env._must_run_with_uv() is True
|
||||
|
||||
def test_needs_uv_with_project(self):
|
||||
"""Test that needs_uv returns True with project."""
|
||||
env = UVEnvironment(project="/path/to/project")
|
||||
env = UVEnvironment(project=Path("/path/to/project"))
|
||||
assert env._must_run_with_uv() is True
|
||||
|
||||
def test_needs_uv_with_editable(self):
|
||||
"""Test that needs_uv returns True with editable."""
|
||||
env = UVEnvironment(editable=["/pkg"])
|
||||
env = UVEnvironment(editable=[Path("/pkg")])
|
||||
assert env._must_run_with_uv() is True
|
||||
|
||||
def test_needs_uv_empty(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue