sandboxy 0.0.1__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.
- sandboxy/__init__.py +3 -0
- sandboxy/agents/__init__.py +21 -0
- sandboxy/agents/base.py +66 -0
- sandboxy/agents/llm_prompt.py +308 -0
- sandboxy/agents/loader.py +222 -0
- sandboxy/api/__init__.py +5 -0
- sandboxy/api/app.py +76 -0
- sandboxy/api/routes/__init__.py +1 -0
- sandboxy/api/routes/agents.py +92 -0
- sandboxy/api/routes/local.py +1388 -0
- sandboxy/api/routes/tools.py +106 -0
- sandboxy/cli/__init__.py +1 -0
- sandboxy/cli/main.py +1196 -0
- sandboxy/cli/type_detector.py +48 -0
- sandboxy/config.py +49 -0
- sandboxy/core/__init__.py +1 -0
- sandboxy/core/async_runner.py +824 -0
- sandboxy/core/mdl_parser.py +441 -0
- sandboxy/core/runner.py +599 -0
- sandboxy/core/safe_eval.py +165 -0
- sandboxy/core/state.py +234 -0
- sandboxy/datasets/__init__.py +20 -0
- sandboxy/datasets/loader.py +193 -0
- sandboxy/datasets/runner.py +442 -0
- sandboxy/errors.py +166 -0
- sandboxy/local/context.py +235 -0
- sandboxy/local/results.py +173 -0
- sandboxy/logging.py +31 -0
- sandboxy/mcp/__init__.py +25 -0
- sandboxy/mcp/client.py +360 -0
- sandboxy/mcp/wrapper.py +99 -0
- sandboxy/providers/__init__.py +34 -0
- sandboxy/providers/anthropic_provider.py +271 -0
- sandboxy/providers/base.py +123 -0
- sandboxy/providers/http_client.py +101 -0
- sandboxy/providers/openai_provider.py +282 -0
- sandboxy/providers/openrouter.py +958 -0
- sandboxy/providers/registry.py +199 -0
- sandboxy/scenarios/__init__.py +11 -0
- sandboxy/scenarios/comparison.py +491 -0
- sandboxy/scenarios/loader.py +262 -0
- sandboxy/scenarios/runner.py +468 -0
- sandboxy/scenarios/unified.py +1434 -0
- sandboxy/session/__init__.py +21 -0
- sandboxy/session/manager.py +278 -0
- sandboxy/tools/__init__.py +34 -0
- sandboxy/tools/base.py +127 -0
- sandboxy/tools/loader.py +270 -0
- sandboxy/tools/yaml_tools.py +708 -0
- sandboxy/ui/__init__.py +27 -0
- sandboxy/ui/dist/assets/index-CgAkYWrJ.css +1 -0
- sandboxy/ui/dist/assets/index-D4zoGFcr.js +347 -0
- sandboxy/ui/dist/index.html +14 -0
- sandboxy/utils/__init__.py +3 -0
- sandboxy/utils/time.py +20 -0
- sandboxy-0.0.1.dist-info/METADATA +241 -0
- sandboxy-0.0.1.dist-info/RECORD +60 -0
- sandboxy-0.0.1.dist-info/WHEEL +4 -0
- sandboxy-0.0.1.dist-info/entry_points.txt +3 -0
- sandboxy-0.0.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Local development context manager.
|
|
2
|
+
|
|
3
|
+
This module provides the LocalContext class for managing local development
|
|
4
|
+
configurations in Sandboxy. It handles path resolution, file discovery,
|
|
5
|
+
and environment variable overrides for containerized deployments.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import yaml
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# Global context - set when running in local mode
|
|
23
|
+
_local_context: LocalContext | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class LocalContext:
|
|
28
|
+
"""Configuration for local development mode.
|
|
29
|
+
|
|
30
|
+
Manages paths and discovery for a local Sandboxy project.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
root_dir: Path
|
|
34
|
+
scenarios_dir: Path = field(init=False)
|
|
35
|
+
tools_dir: Path = field(init=False)
|
|
36
|
+
agents_dir: Path = field(init=False)
|
|
37
|
+
runs_dir: Path = field(init=False)
|
|
38
|
+
datasets_dir: Path = field(init=False)
|
|
39
|
+
|
|
40
|
+
def __post_init__(self) -> None:
|
|
41
|
+
"""Initialize derived paths from root directory."""
|
|
42
|
+
# Support env var overrides for containerization
|
|
43
|
+
self.scenarios_dir = Path(
|
|
44
|
+
os.environ.get("SANDBOXY_SCENARIOS_DIR", self.root_dir / "scenarios")
|
|
45
|
+
)
|
|
46
|
+
self.tools_dir = Path(os.environ.get("SANDBOXY_TOOLS_DIR", self.root_dir / "tools"))
|
|
47
|
+
self.agents_dir = Path(os.environ.get("SANDBOXY_AGENTS_DIR", self.root_dir / "agents"))
|
|
48
|
+
self.runs_dir = Path(os.environ.get("SANDBOXY_RUNS_DIR", self.root_dir / "runs"))
|
|
49
|
+
self.datasets_dir = Path(
|
|
50
|
+
os.environ.get("SANDBOXY_DATASETS_DIR", self.root_dir / "datasets")
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def discover(self) -> dict[str, list[dict[str, Any]]]:
|
|
54
|
+
"""Discover all local files (YAML and Python tools).
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Dictionary with scenarios, tools, and agents lists.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
# Tools include both YAML and Python files
|
|
61
|
+
tools = self._list_yaml_files(self.tools_dir)
|
|
62
|
+
tools.extend(self._list_python_tools(self.tools_dir))
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"scenarios": self._list_yaml_files(self.scenarios_dir),
|
|
66
|
+
"tools": tools,
|
|
67
|
+
"agents": self._list_yaml_files(self.agents_dir),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def _list_yaml_files(self, directory: Path) -> list[dict[str, Any]]:
|
|
71
|
+
"""List YAML files with basic metadata.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
directory: Directory to scan for YAML files.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of file metadata dictionaries.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
files = []
|
|
81
|
+
if not directory.exists():
|
|
82
|
+
return files
|
|
83
|
+
|
|
84
|
+
for path in directory.glob("**/*.yaml"):
|
|
85
|
+
files.append(self._parse_yaml_metadata(path))
|
|
86
|
+
for path in directory.glob("**/*.yml"):
|
|
87
|
+
files.append(self._parse_yaml_metadata(path))
|
|
88
|
+
|
|
89
|
+
# Sort by name
|
|
90
|
+
files.sort(key=lambda x: x.get("name", x.get("id", "")))
|
|
91
|
+
return files
|
|
92
|
+
|
|
93
|
+
def _parse_yaml_metadata(self, path: Path) -> dict[str, Any]:
|
|
94
|
+
"""Parse basic metadata from a YAML file.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
path: Path to the YAML file.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dictionary with file metadata.
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
content = yaml.safe_load(path.read_text())
|
|
105
|
+
if not isinstance(content, dict):
|
|
106
|
+
content = {}
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.warning("Failed to parse %s: %s", path, e)
|
|
109
|
+
content = {}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"id": content.get("id", path.stem),
|
|
113
|
+
"name": content.get("name", path.stem),
|
|
114
|
+
"description": content.get("description", ""),
|
|
115
|
+
"type": content.get("type"),
|
|
116
|
+
"path": str(path),
|
|
117
|
+
"relative_path": str(path.relative_to(self.root_dir)),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def _list_python_tools(self, directory: Path) -> list[dict[str, Any]]:
|
|
121
|
+
"""List Python tool files with metadata.
|
|
122
|
+
|
|
123
|
+
Scans for .py files that contain BaseTool subclasses.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
directory: Directory to scan for Python files.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List of file metadata dictionaries.
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
files = []
|
|
133
|
+
if not directory.exists():
|
|
134
|
+
return files
|
|
135
|
+
|
|
136
|
+
for path in directory.glob("*.py"):
|
|
137
|
+
# Skip dunder files
|
|
138
|
+
if path.name.startswith("_"):
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
source = path.read_text()
|
|
143
|
+
tree = ast.parse(source)
|
|
144
|
+
|
|
145
|
+
# Find classes that might be BaseTool subclasses
|
|
146
|
+
for node in ast.walk(tree):
|
|
147
|
+
if isinstance(node, ast.ClassDef):
|
|
148
|
+
# Check if it inherits from BaseTool
|
|
149
|
+
for base in node.bases:
|
|
150
|
+
base_name = ""
|
|
151
|
+
if isinstance(base, ast.Name):
|
|
152
|
+
base_name = base.id
|
|
153
|
+
elif isinstance(base, ast.Attribute):
|
|
154
|
+
base_name = base.attr
|
|
155
|
+
|
|
156
|
+
if base_name == "BaseTool":
|
|
157
|
+
# Convert class name to snake_case for tool type
|
|
158
|
+
class_name = node.name
|
|
159
|
+
s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", class_name)
|
|
160
|
+
tool_type = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
|
161
|
+
|
|
162
|
+
# Extract docstring
|
|
163
|
+
docstring = ast.get_docstring(node) or ""
|
|
164
|
+
description = docstring.split("\n")[0] if docstring else ""
|
|
165
|
+
|
|
166
|
+
files.append(
|
|
167
|
+
{
|
|
168
|
+
"id": tool_type,
|
|
169
|
+
"name": class_name,
|
|
170
|
+
"description": description,
|
|
171
|
+
"type": "python",
|
|
172
|
+
"path": str(path),
|
|
173
|
+
"relative_path": str(path.relative_to(self.root_dir)),
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
break # Only add once per file
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.warning("Failed to parse Python tool %s: %s", path, e)
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
files.sort(key=lambda x: x.get("name", x.get("id", "")))
|
|
183
|
+
return files
|
|
184
|
+
|
|
185
|
+
def ensure_directories(self) -> list[Path]:
|
|
186
|
+
"""Ensure all standard directories exist.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
List of created directories.
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
created = []
|
|
193
|
+
for directory in [
|
|
194
|
+
self.scenarios_dir,
|
|
195
|
+
self.tools_dir,
|
|
196
|
+
self.agents_dir,
|
|
197
|
+
self.runs_dir,
|
|
198
|
+
self.datasets_dir,
|
|
199
|
+
]:
|
|
200
|
+
if not directory.exists():
|
|
201
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
202
|
+
created.append(directory)
|
|
203
|
+
logger.info("Created directory: %s", directory)
|
|
204
|
+
return created
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_local_context() -> LocalContext | None:
|
|
208
|
+
"""Get the current local context.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
The active LocalContext or None if not in local mode.
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
return _local_context
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def set_local_context(ctx: LocalContext | None) -> None:
|
|
218
|
+
"""Set the local context.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
ctx: LocalContext to set, or None to clear.
|
|
222
|
+
|
|
223
|
+
"""
|
|
224
|
+
global _local_context
|
|
225
|
+
_local_context = ctx
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def is_local_mode() -> bool:
|
|
229
|
+
"""Check if running in local mode.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
True if a local context is active.
|
|
233
|
+
|
|
234
|
+
"""
|
|
235
|
+
return _local_context is not None
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Local results storage for Sandboxy runs.
|
|
2
|
+
|
|
3
|
+
This module provides functions for persisting, retrieving, and managing
|
|
4
|
+
scenario run results in the local runs/ directory. Results are stored
|
|
5
|
+
as JSON files with timestamps for easy tracking and analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from sandboxy.local.context import get_local_context
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def save_run_result(
|
|
22
|
+
scenario_id: str,
|
|
23
|
+
result: dict[str, Any] | Any,
|
|
24
|
+
metadata: dict[str, Any] | None = None,
|
|
25
|
+
) -> Path:
|
|
26
|
+
"""Save a run result to the local runs/ directory.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
scenario_id: Identifier for the scenario that was run.
|
|
30
|
+
result: The result data to save. If it has a to_dict() method, it will be called.
|
|
31
|
+
metadata: Optional additional metadata to include.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Path to the saved result file.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
RuntimeError: If not in local mode.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
ctx = get_local_context()
|
|
41
|
+
if not ctx:
|
|
42
|
+
raise RuntimeError("Not in local mode - cannot save results")
|
|
43
|
+
|
|
44
|
+
# Ensure runs directory exists
|
|
45
|
+
ctx.runs_dir.mkdir(exist_ok=True)
|
|
46
|
+
|
|
47
|
+
# Generate filename with timestamp
|
|
48
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
49
|
+
filename = f"{scenario_id}_{timestamp}.json"
|
|
50
|
+
filepath = ctx.runs_dir / filename
|
|
51
|
+
|
|
52
|
+
# Convert result to dict if needed
|
|
53
|
+
if hasattr(result, "to_dict"):
|
|
54
|
+
result_data = result.to_dict()
|
|
55
|
+
elif hasattr(result, "__dict__"):
|
|
56
|
+
result_data = result.__dict__
|
|
57
|
+
else:
|
|
58
|
+
result_data = result
|
|
59
|
+
|
|
60
|
+
# Build output structure
|
|
61
|
+
output = {
|
|
62
|
+
"scenario_id": scenario_id,
|
|
63
|
+
"timestamp": datetime.now().isoformat(),
|
|
64
|
+
"result": result_data,
|
|
65
|
+
"metadata": metadata or {},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Write to file
|
|
69
|
+
filepath.write_text(json.dumps(output, indent=2, default=str))
|
|
70
|
+
logger.info("Saved run result to %s", filepath)
|
|
71
|
+
|
|
72
|
+
return filepath
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def list_run_results(
|
|
76
|
+
limit: int = 100,
|
|
77
|
+
scenario_id: str | None = None,
|
|
78
|
+
) -> list[dict[str, Any]]:
|
|
79
|
+
"""List run results from the local runs/ directory.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
limit: Maximum number of results to return.
|
|
83
|
+
scenario_id: Optional filter by scenario ID.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List of run result summaries, most recent first.
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
ctx = get_local_context()
|
|
90
|
+
if not ctx:
|
|
91
|
+
return []
|
|
92
|
+
|
|
93
|
+
if not ctx.runs_dir.exists():
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
results = []
|
|
97
|
+
for path in sorted(ctx.runs_dir.glob("*.json"), reverse=True):
|
|
98
|
+
if len(results) >= limit:
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
data = json.loads(path.read_text())
|
|
103
|
+
|
|
104
|
+
# Filter by scenario_id if specified
|
|
105
|
+
if scenario_id and data.get("scenario_id") != scenario_id:
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
results.append(
|
|
109
|
+
{
|
|
110
|
+
"filename": path.name,
|
|
111
|
+
"path": str(path),
|
|
112
|
+
"scenario_id": data.get("scenario_id"),
|
|
113
|
+
"timestamp": data.get("timestamp"),
|
|
114
|
+
"metadata": data.get("metadata", {}),
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.warning("Failed to read run result %s: %s", path, e)
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
return results
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_run_result(filename: str) -> dict[str, Any] | None:
|
|
125
|
+
"""Get a specific run result by filename.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
filename: The filename of the run result.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The full run result data, or None if not found.
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
ctx = get_local_context()
|
|
135
|
+
if not ctx:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
filepath = ctx.runs_dir / filename
|
|
139
|
+
if not filepath.exists():
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
return json.loads(filepath.read_text())
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.warning("Failed to read run result %s: %s", filepath, e)
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def delete_run_result(filename: str) -> bool:
|
|
150
|
+
"""Delete a run result by filename.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
filename: The filename of the run result to delete.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True if deleted, False if not found or error.
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
ctx = get_local_context()
|
|
160
|
+
if not ctx:
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
filepath = ctx.runs_dir / filename
|
|
164
|
+
if not filepath.exists():
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
filepath.unlink()
|
|
169
|
+
logger.info("Deleted run result %s", filepath)
|
|
170
|
+
return True
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.warning("Failed to delete run result %s: %s", filepath, e)
|
|
173
|
+
return False
|
sandboxy/logging.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Logging configuration for Sandboxy."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def setup_logging(level: int = logging.INFO, verbose: bool = False) -> None:
|
|
8
|
+
"""Configure logging for Sandboxy.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
level: Base logging level.
|
|
12
|
+
verbose: If True, use DEBUG level and show module names.
|
|
13
|
+
"""
|
|
14
|
+
if verbose:
|
|
15
|
+
level = logging.DEBUG
|
|
16
|
+
fmt = "%(asctime)s %(levelname)s [%(name)s] %(message)s"
|
|
17
|
+
else:
|
|
18
|
+
fmt = "%(levelname)s: %(message)s"
|
|
19
|
+
|
|
20
|
+
logging.basicConfig(
|
|
21
|
+
level=level,
|
|
22
|
+
format=fmt,
|
|
23
|
+
stream=sys.stderr,
|
|
24
|
+
force=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Quiet noisy libraries
|
|
28
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
29
|
+
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
30
|
+
logging.getLogger("openai").setLevel(logging.WARNING)
|
|
31
|
+
logging.getLogger("anthropic").setLevel(logging.WARNING)
|
sandboxy/mcp/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) integration for Sandboxy.
|
|
2
|
+
|
|
3
|
+
This module provides support for connecting to real MCP servers and using
|
|
4
|
+
their tools alongside YAML mock tools in scenarios.
|
|
5
|
+
|
|
6
|
+
Supports two transport types:
|
|
7
|
+
- stdio: Local servers run as subprocesses (e.g., npx, python scripts)
|
|
8
|
+
- HTTP: Remote servers accessed via SSE or Streamable HTTP
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from sandboxy.mcp.client import (
|
|
12
|
+
McpManager,
|
|
13
|
+
McpServerConfig,
|
|
14
|
+
inspect_mcp_server,
|
|
15
|
+
inspect_mcp_server_http,
|
|
16
|
+
)
|
|
17
|
+
from sandboxy.mcp.wrapper import McpToolWrapper
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"McpManager",
|
|
21
|
+
"McpServerConfig",
|
|
22
|
+
"McpToolWrapper",
|
|
23
|
+
"inspect_mcp_server",
|
|
24
|
+
"inspect_mcp_server_http",
|
|
25
|
+
]
|