fastmcp 2.11.3__py3-none-any.whl → 2.12.0__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/__init__.py +5 -4
- fastmcp/cli/claude.py +22 -18
- fastmcp/cli/cli.py +472 -136
- fastmcp/cli/install/claude_code.py +37 -40
- fastmcp/cli/install/claude_desktop.py +37 -42
- fastmcp/cli/install/cursor.py +148 -38
- fastmcp/cli/install/mcp_json.py +38 -43
- fastmcp/cli/install/shared.py +64 -7
- fastmcp/cli/run.py +122 -215
- fastmcp/client/auth/oauth.py +69 -13
- fastmcp/client/client.py +46 -9
- fastmcp/client/oauth_callback.py +91 -91
- fastmcp/client/sampling.py +12 -4
- fastmcp/client/transports.py +139 -64
- fastmcp/experimental/sampling/__init__.py +0 -0
- fastmcp/experimental/sampling/handlers/__init__.py +3 -0
- fastmcp/experimental/sampling/handlers/base.py +21 -0
- fastmcp/experimental/sampling/handlers/openai.py +163 -0
- fastmcp/experimental/server/openapi/routing.py +0 -2
- fastmcp/experimental/server/openapi/server.py +0 -2
- fastmcp/experimental/utilities/openapi/parser.py +5 -1
- fastmcp/mcp_config.py +40 -20
- fastmcp/prompts/prompt_manager.py +2 -0
- fastmcp/resources/resource_manager.py +4 -0
- fastmcp/server/auth/__init__.py +2 -0
- fastmcp/server/auth/auth.py +2 -1
- fastmcp/server/auth/oauth_proxy.py +1047 -0
- fastmcp/server/auth/providers/azure.py +270 -0
- fastmcp/server/auth/providers/github.py +287 -0
- fastmcp/server/auth/providers/google.py +305 -0
- fastmcp/server/auth/providers/jwt.py +24 -12
- fastmcp/server/auth/providers/workos.py +256 -2
- fastmcp/server/auth/redirect_validation.py +65 -0
- fastmcp/server/context.py +91 -41
- fastmcp/server/elicitation.py +60 -1
- fastmcp/server/http.py +3 -3
- fastmcp/server/middleware/logging.py +66 -28
- fastmcp/server/proxy.py +2 -0
- fastmcp/server/sampling/handler.py +19 -0
- fastmcp/server/server.py +76 -15
- fastmcp/settings.py +16 -1
- fastmcp/tools/tool.py +22 -9
- fastmcp/tools/tool_manager.py +2 -0
- fastmcp/tools/tool_transform.py +39 -10
- fastmcp/utilities/auth.py +34 -0
- fastmcp/utilities/cli.py +148 -15
- fastmcp/utilities/components.py +2 -1
- fastmcp/utilities/inspect.py +166 -37
- fastmcp/utilities/json_schema_type.py +4 -2
- fastmcp/utilities/logging.py +4 -1
- fastmcp/utilities/mcp_config.py +47 -18
- fastmcp/utilities/mcp_server_config/__init__.py +25 -0
- fastmcp/utilities/mcp_server_config/v1/__init__.py +0 -0
- fastmcp/utilities/mcp_server_config/v1/environments/__init__.py +6 -0
- fastmcp/utilities/mcp_server_config/v1/environments/base.py +30 -0
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +306 -0
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +446 -0
- fastmcp/utilities/mcp_server_config/v1/schema.json +361 -0
- fastmcp/utilities/mcp_server_config/v1/sources/__init__.py +0 -0
- fastmcp/utilities/mcp_server_config/v1/sources/base.py +30 -0
- fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py +216 -0
- fastmcp/utilities/tests.py +7 -2
- fastmcp/utilities/types.py +15 -2
- {fastmcp-2.11.3.dist-info → fastmcp-2.12.0.dist-info}/METADATA +2 -1
- fastmcp-2.12.0.dist-info/RECORD +129 -0
- fastmcp-2.11.3.dist-info/RECORD +0 -108
- {fastmcp-2.11.3.dist-info → fastmcp-2.12.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.11.3.dist-info → fastmcp-2.12.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.11.3.dist-info → fastmcp-2.12.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/utilities/mcp_config.py
CHANGED
|
@@ -1,28 +1,57 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from fastmcp.
|
|
3
|
+
from fastmcp.client.transports import (
|
|
4
|
+
ClientTransport,
|
|
5
|
+
SSETransport,
|
|
6
|
+
StdioTransport,
|
|
7
|
+
StreamableHttpTransport,
|
|
8
|
+
)
|
|
9
|
+
from fastmcp.mcp_config import (
|
|
10
|
+
MCPConfig,
|
|
11
|
+
MCPServerTypes,
|
|
12
|
+
)
|
|
13
|
+
from fastmcp.server.proxy import FastMCPProxy, ProxyClient
|
|
4
14
|
from fastmcp.server.server import FastMCP
|
|
5
15
|
|
|
6
16
|
|
|
7
|
-
def
|
|
8
|
-
config: MCPConfig,
|
|
9
|
-
) -> FastMCP[
|
|
10
|
-
"""A utility function to
|
|
11
|
-
|
|
17
|
+
def mcp_config_to_servers_and_transports(
|
|
18
|
+
config: MCPConfig,
|
|
19
|
+
) -> list[tuple[str, FastMCP[Any], ClientTransport]]:
|
|
20
|
+
"""A utility function to convert each entry of an MCP Config into a transport and server."""
|
|
21
|
+
return [
|
|
22
|
+
mcp_server_type_to_servers_and_transports(name, mcp_server)
|
|
23
|
+
for name, mcp_server in config.mcpServers.items()
|
|
24
|
+
]
|
|
12
25
|
|
|
13
|
-
mount_mcp_config_into_server(config, composite_server, name_as_prefix)
|
|
14
26
|
|
|
15
|
-
|
|
27
|
+
def mcp_server_type_to_servers_and_transports(
|
|
28
|
+
name: str,
|
|
29
|
+
mcp_server: MCPServerTypes,
|
|
30
|
+
) -> tuple[str, FastMCP[Any], ClientTransport]:
|
|
31
|
+
"""A utility function to convert each entry of an MCP Config into a transport and server."""
|
|
16
32
|
|
|
33
|
+
from fastmcp.mcp_config import (
|
|
34
|
+
TransformingRemoteMCPServer,
|
|
35
|
+
TransformingStdioMCPServer,
|
|
36
|
+
)
|
|
17
37
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
server.
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
server: FastMCP[Any]
|
|
39
|
+
transport: ClientTransport
|
|
40
|
+
|
|
41
|
+
client_name = ProxyClient.generate_name(f"MCP_{name}")
|
|
42
|
+
server_name = FastMCPProxy.generate_name(f"MCP_{name}")
|
|
43
|
+
|
|
44
|
+
if isinstance(mcp_server, TransformingRemoteMCPServer | TransformingStdioMCPServer):
|
|
45
|
+
server, transport = mcp_server._to_server_and_underlying_transport(
|
|
46
|
+
server_name=server_name,
|
|
47
|
+
client_name=client_name,
|
|
28
48
|
)
|
|
49
|
+
else:
|
|
50
|
+
transport = mcp_server.to_transport()
|
|
51
|
+
client: ProxyClient[StreamableHttpTransport | SSETransport | StdioTransport] = (
|
|
52
|
+
ProxyClient(transport=transport, name=client_name)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
server = FastMCP.as_proxy(name=server_name, backend=client)
|
|
56
|
+
|
|
57
|
+
return name, server, transport
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""FastMCP Configuration module.
|
|
2
|
+
|
|
3
|
+
This module provides versioned configuration support for FastMCP servers.
|
|
4
|
+
The current version is v1, which is re-exported here for convenience.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastmcp.utilities.mcp_server_config.v1.environments.base import Environment
|
|
8
|
+
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
|
9
|
+
from fastmcp.utilities.mcp_server_config.v1.mcp_server_config import (
|
|
10
|
+
Deployment,
|
|
11
|
+
MCPServerConfig,
|
|
12
|
+
generate_schema,
|
|
13
|
+
)
|
|
14
|
+
from fastmcp.utilities.mcp_server_config.v1.sources.base import Source
|
|
15
|
+
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Source",
|
|
19
|
+
"Deployment",
|
|
20
|
+
"Environment",
|
|
21
|
+
"UVEnvironment",
|
|
22
|
+
"MCPServerConfig",
|
|
23
|
+
"FileSystemSource",
|
|
24
|
+
"generate_schema",
|
|
25
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Environment(BaseModel, ABC):
|
|
8
|
+
"""Base class for environment configuration."""
|
|
9
|
+
|
|
10
|
+
type: str = Field(description="Environment type identifier")
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def build_command(self, command: list[str]) -> list[str]:
|
|
14
|
+
"""Build the full command with environment setup.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
command: Base command to wrap with environment setup
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Full command ready for subprocess execution
|
|
21
|
+
"""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
async def prepare(self, output_dir: Path | None = None) -> None:
|
|
25
|
+
"""Prepare the environment (optional, can be no-op).
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
output_dir: Directory for persistent environment setup
|
|
29
|
+
"""
|
|
30
|
+
pass # Default no-op implementation
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
|
|
10
|
+
from fastmcp.utilities.logging import get_logger
|
|
11
|
+
from fastmcp.utilities.mcp_server_config.v1.environments.base import Environment
|
|
12
|
+
|
|
13
|
+
logger = get_logger("cli.config")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UVEnvironment(Environment):
|
|
17
|
+
"""Configuration for Python environment setup."""
|
|
18
|
+
|
|
19
|
+
type: Literal["uv"] = "uv"
|
|
20
|
+
|
|
21
|
+
python: str | None = Field(
|
|
22
|
+
default=None,
|
|
23
|
+
description="Python version constraint",
|
|
24
|
+
examples=["3.10", "3.11", "3.12"],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
dependencies: list[str] | None = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Python packages to install with PEP 508 specifiers",
|
|
30
|
+
examples=[["fastmcp>=2.0,<3", "httpx", "pandas>=2.0"]],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
requirements: str | None = Field(
|
|
34
|
+
default=None,
|
|
35
|
+
description="Path to requirements.txt file",
|
|
36
|
+
examples=["requirements.txt", "../requirements/prod.txt"],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
project: str | None = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="Path to project directory containing pyproject.toml",
|
|
42
|
+
examples=[".", "../my-project"],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
editable: list[str] | None = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
description="Directories to install in editable mode",
|
|
48
|
+
examples=[[".", "../my-package"], ["/path/to/package"]],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def build_command(self, command: list[str]) -> list[str]:
|
|
52
|
+
"""Build complete uv run command with environment args and command to execute.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
command: Command to execute (e.g., ["fastmcp", "run", "server.py"])
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Complete command ready for subprocess.run, including "uv" prefix if needed.
|
|
59
|
+
If no environment configuration is set, returns the command unchanged.
|
|
60
|
+
"""
|
|
61
|
+
# If no environment setup is needed, return command as-is
|
|
62
|
+
if not self._needs_setup():
|
|
63
|
+
return command
|
|
64
|
+
|
|
65
|
+
args = ["uv", "run"]
|
|
66
|
+
|
|
67
|
+
# Add project if specified
|
|
68
|
+
if self.project:
|
|
69
|
+
args.extend(["--project", str(self.project)])
|
|
70
|
+
|
|
71
|
+
# Add Python version if specified (only if no project, as project has its own Python)
|
|
72
|
+
if self.python and not self.project:
|
|
73
|
+
args.extend(["--python", self.python])
|
|
74
|
+
|
|
75
|
+
# Always add dependencies, requirements, and editable packages
|
|
76
|
+
# These work with --project to add additional packages on top of the project env
|
|
77
|
+
if self.dependencies:
|
|
78
|
+
for dep in self.dependencies:
|
|
79
|
+
args.extend(["--with", dep])
|
|
80
|
+
|
|
81
|
+
# Add requirements file
|
|
82
|
+
if self.requirements:
|
|
83
|
+
args.extend(["--with-requirements", str(self.requirements)])
|
|
84
|
+
|
|
85
|
+
# Add editable packages
|
|
86
|
+
if self.editable:
|
|
87
|
+
for editable_path in self.editable:
|
|
88
|
+
args.extend(["--with-editable", str(editable_path)])
|
|
89
|
+
|
|
90
|
+
# Add the command
|
|
91
|
+
args.extend(command)
|
|
92
|
+
|
|
93
|
+
return args
|
|
94
|
+
|
|
95
|
+
def run_with_uv(self, command: list[str]) -> None:
|
|
96
|
+
"""Execute a command using uv run with this environment configuration.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
command: Command and arguments to execute (e.g., ["fastmcp", "run", "server.py"])
|
|
100
|
+
"""
|
|
101
|
+
import subprocess
|
|
102
|
+
|
|
103
|
+
# Build the full uv command
|
|
104
|
+
cmd = self.build_command(command)
|
|
105
|
+
|
|
106
|
+
# Set marker to prevent infinite loops when subprocess calls FastMCP again
|
|
107
|
+
env = os.environ | {"FASTMCP_UV_SPAWNED": "1"}
|
|
108
|
+
|
|
109
|
+
logger.debug(f"Running command: {' '.join(cmd)}")
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Run without capturing output so it flows through naturally
|
|
113
|
+
process = subprocess.run(cmd, check=True, env=env)
|
|
114
|
+
sys.exit(process.returncode)
|
|
115
|
+
except subprocess.CalledProcessError as e:
|
|
116
|
+
logger.error(f"Command failed: {e}")
|
|
117
|
+
sys.exit(e.returncode)
|
|
118
|
+
|
|
119
|
+
def _needs_setup(self) -> bool:
|
|
120
|
+
"""Check if this environment config requires uv to set up.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if any environment settings require uv run
|
|
124
|
+
"""
|
|
125
|
+
return any(
|
|
126
|
+
[
|
|
127
|
+
self.python is not None,
|
|
128
|
+
self.dependencies is not None,
|
|
129
|
+
self.requirements is not None,
|
|
130
|
+
self.project is not None,
|
|
131
|
+
self.editable is not None,
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Backward compatibility aliases
|
|
136
|
+
def needs_uv(self) -> bool:
|
|
137
|
+
"""Deprecated: Use _needs_setup() internally or check if build_command modifies the command."""
|
|
138
|
+
return self._needs_setup()
|
|
139
|
+
|
|
140
|
+
def build_uv_run_command(self, command: list[str]) -> list[str]:
|
|
141
|
+
"""Deprecated: Use build_command() instead."""
|
|
142
|
+
return self.build_command(command)
|
|
143
|
+
|
|
144
|
+
async def prepare(self, output_dir: Path | None = None) -> None:
|
|
145
|
+
"""Prepare the Python environment using uv.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
output_dir: Directory where the persistent uv project will be created.
|
|
149
|
+
If None, creates a temporary directory for ephemeral use.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
# Check if uv is available
|
|
153
|
+
if not shutil.which("uv"):
|
|
154
|
+
raise RuntimeError(
|
|
155
|
+
"uv is not installed. Please install it with: "
|
|
156
|
+
"curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Only prepare environment if there are actual settings to apply
|
|
160
|
+
if not self._needs_setup():
|
|
161
|
+
logger.debug("No environment settings configured, skipping preparation")
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
# Handle None case for ephemeral use
|
|
165
|
+
if output_dir is None:
|
|
166
|
+
import tempfile
|
|
167
|
+
|
|
168
|
+
output_dir = Path(tempfile.mkdtemp(prefix="fastmcp-env-"))
|
|
169
|
+
logger.info(f"Creating ephemeral environment in {output_dir}")
|
|
170
|
+
else:
|
|
171
|
+
logger.info(f"Creating persistent environment in {output_dir}")
|
|
172
|
+
output_dir = Path(output_dir).resolve()
|
|
173
|
+
|
|
174
|
+
# Initialize the project
|
|
175
|
+
logger.debug(f"Initializing uv project in {output_dir}")
|
|
176
|
+
try:
|
|
177
|
+
subprocess.run(
|
|
178
|
+
[
|
|
179
|
+
"uv",
|
|
180
|
+
"init",
|
|
181
|
+
"--project",
|
|
182
|
+
str(output_dir),
|
|
183
|
+
"--name",
|
|
184
|
+
"fastmcp-env",
|
|
185
|
+
],
|
|
186
|
+
check=True,
|
|
187
|
+
capture_output=True,
|
|
188
|
+
text=True,
|
|
189
|
+
)
|
|
190
|
+
except subprocess.CalledProcessError as e:
|
|
191
|
+
# If project already exists, that's fine - continue
|
|
192
|
+
if "already initialized" in e.stderr.lower():
|
|
193
|
+
logger.debug(
|
|
194
|
+
f"Project already initialized at {output_dir}, continuing..."
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
logger.error(f"Failed to initialize project: {e.stderr}")
|
|
198
|
+
raise RuntimeError(f"Failed to initialize project: {e.stderr}") from e
|
|
199
|
+
|
|
200
|
+
# Pin Python version if specified
|
|
201
|
+
if self.python:
|
|
202
|
+
logger.debug(f"Pinning Python version to {self.python}")
|
|
203
|
+
try:
|
|
204
|
+
subprocess.run(
|
|
205
|
+
[
|
|
206
|
+
"uv",
|
|
207
|
+
"python",
|
|
208
|
+
"pin",
|
|
209
|
+
self.python,
|
|
210
|
+
"--project",
|
|
211
|
+
str(output_dir),
|
|
212
|
+
],
|
|
213
|
+
check=True,
|
|
214
|
+
capture_output=True,
|
|
215
|
+
text=True,
|
|
216
|
+
)
|
|
217
|
+
except subprocess.CalledProcessError as e:
|
|
218
|
+
logger.error(f"Failed to pin Python version: {e.stderr}")
|
|
219
|
+
raise RuntimeError(f"Failed to pin Python version: {e.stderr}") from e
|
|
220
|
+
|
|
221
|
+
# Add dependencies with --no-sync to defer installation
|
|
222
|
+
# dependencies ALWAYS include fastmcp; this is compatible with
|
|
223
|
+
# specific fastmcp versions that might be in the dependencies list
|
|
224
|
+
dependencies = (self.dependencies or []) + ["fastmcp"]
|
|
225
|
+
logger.debug(f"Adding dependencies: {', '.join(dependencies)}")
|
|
226
|
+
try:
|
|
227
|
+
subprocess.run(
|
|
228
|
+
[
|
|
229
|
+
"uv",
|
|
230
|
+
"add",
|
|
231
|
+
*dependencies,
|
|
232
|
+
"--no-sync",
|
|
233
|
+
"--project",
|
|
234
|
+
str(output_dir),
|
|
235
|
+
],
|
|
236
|
+
check=True,
|
|
237
|
+
capture_output=True,
|
|
238
|
+
text=True,
|
|
239
|
+
)
|
|
240
|
+
except subprocess.CalledProcessError as e:
|
|
241
|
+
logger.error(f"Failed to add dependencies: {e.stderr}")
|
|
242
|
+
raise RuntimeError(f"Failed to add dependencies: {e.stderr}") from e
|
|
243
|
+
|
|
244
|
+
# Add requirements file if specified
|
|
245
|
+
if self.requirements:
|
|
246
|
+
logger.debug(f"Adding requirements from {self.requirements}")
|
|
247
|
+
# Resolve requirements path relative to current directory
|
|
248
|
+
req_path = Path(self.requirements).resolve()
|
|
249
|
+
try:
|
|
250
|
+
subprocess.run(
|
|
251
|
+
[
|
|
252
|
+
"uv",
|
|
253
|
+
"add",
|
|
254
|
+
"-r",
|
|
255
|
+
str(req_path),
|
|
256
|
+
"--no-sync",
|
|
257
|
+
"--project",
|
|
258
|
+
str(output_dir),
|
|
259
|
+
],
|
|
260
|
+
check=True,
|
|
261
|
+
capture_output=True,
|
|
262
|
+
text=True,
|
|
263
|
+
)
|
|
264
|
+
except subprocess.CalledProcessError as e:
|
|
265
|
+
logger.error(f"Failed to add requirements: {e.stderr}")
|
|
266
|
+
raise RuntimeError(f"Failed to add requirements: {e.stderr}") from e
|
|
267
|
+
|
|
268
|
+
# Add editable packages if specified
|
|
269
|
+
if self.editable:
|
|
270
|
+
editable_paths = [str(Path(e).resolve()) for e in self.editable]
|
|
271
|
+
logger.debug(f"Adding editable packages: {', '.join(editable_paths)}")
|
|
272
|
+
try:
|
|
273
|
+
subprocess.run(
|
|
274
|
+
[
|
|
275
|
+
"uv",
|
|
276
|
+
"add",
|
|
277
|
+
"--editable",
|
|
278
|
+
*editable_paths,
|
|
279
|
+
"--no-sync",
|
|
280
|
+
"--project",
|
|
281
|
+
str(output_dir),
|
|
282
|
+
],
|
|
283
|
+
check=True,
|
|
284
|
+
capture_output=True,
|
|
285
|
+
text=True,
|
|
286
|
+
)
|
|
287
|
+
except subprocess.CalledProcessError as e:
|
|
288
|
+
logger.error(f"Failed to add editable packages: {e.stderr}")
|
|
289
|
+
raise RuntimeError(
|
|
290
|
+
f"Failed to add editable packages: {e.stderr}"
|
|
291
|
+
) from e
|
|
292
|
+
|
|
293
|
+
# Final sync to install everything
|
|
294
|
+
logger.info("Installing dependencies...")
|
|
295
|
+
try:
|
|
296
|
+
subprocess.run(
|
|
297
|
+
["uv", "sync", "--project", str(output_dir)],
|
|
298
|
+
check=True,
|
|
299
|
+
capture_output=True,
|
|
300
|
+
text=True,
|
|
301
|
+
)
|
|
302
|
+
except subprocess.CalledProcessError as e:
|
|
303
|
+
logger.error(f"Failed to sync dependencies: {e.stderr}")
|
|
304
|
+
raise RuntimeError(f"Failed to sync dependencies: {e.stderr}") from e
|
|
305
|
+
|
|
306
|
+
logger.info(f"Environment prepared successfully in {output_dir}")
|