├── LICENSE ├── .python-version ├── .gitignore ├── mcp_python_interpreter ├── main.py ├── __init__.py └── server.py ├── pyproject.toml ├── README.md └── uv.lock /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.10 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | test_dir/ 8 | *.egg-info 9 | .pyirc 10 | 11 | # Virtual environments 12 | .venv 13 | -------------------------------------------------------------------------------- /mcp_python_interpreter/main.py: -------------------------------------------------------------------------------- 1 | """Main module for mcp-python-interpreter.""" 2 | 3 | from mcp_python_interpreter.server import mcp 4 | 5 | 6 | def main(): 7 | """Run the MCP Python Interpreter server.""" 8 | mcp.run(transport='stdio') 9 | 10 | 11 | if __name__ == "__main__": 12 | main() -------------------------------------------------------------------------------- /mcp_python_interpreter/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MCP Python Interpreter 3 | 4 | A Model Context Protocol server for Python code execution and environment management. 5 | """ 6 | 7 | __version__ = "1.2.3" 8 | __author__ = "YZFly" 9 | __email__ = "ethereal_ai@hotmail.com" 10 | 11 | from mcp_python_interpreter.server import mcp 12 | 13 | __all__ = ["mcp"] -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "mcp-python-interpreter" 7 | version = "1.2.3" 8 | description = "MCP server for Python code execution and environment management" 9 | authors = [ 10 | {name = "YZFly", email = "ethereal_ai@hotmail.com"}, 11 | ] 12 | readme = "README.md" 13 | requires-python = ">=3.10" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "License :: OSI Approved :: MIT License", 17 | "Operating System :: OS Independent", 18 | ] 19 | dependencies = [ 20 | "fastmcp>=2.0.0", 21 | ] 22 | 23 | [project.scripts] 24 | mcp-python-interpreter = "mcp_python_interpreter.main:main" 25 | 26 | [project.urls] 27 | "Homepage" = "https://github.com/yzfly/mcp-python-interpreter" 28 | "Bug Tracker" = "https://github.com/yzfly/mcp-python-interpreter/issues" 29 | 30 | [tool.setuptools] 31 | packages = ["mcp_python_interpreter"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/yzfly-mcp-python-interpreter-badge.png)](https://mseep.ai/app/yzfly-mcp-python-interpreter) 2 | 3 | # MCP Python Interpreter 4 | 5 | A Model Context Protocol (MCP) server that allows LLMs to interact with Python environments, read and write files, execute Python code, and manage development workflows. 6 | 7 | ## Features 8 | 9 | - **Environment Management**: List and use different Python environments (system and conda) 10 | - **Code Execution**: Run Python code or scripts in any available environment 11 | - **Package Management**: List installed packages and install new ones 12 | - **File Operations**: 13 | - Read files of any type (text, source code, binary) 14 | - Write text and binary files 15 | - **Python Prompts**: Templates for common Python tasks like function creation and debugging 16 | 17 | ## Installation 18 | 19 | You can install the MCP Python Interpreter using pip: 20 | 21 | ```bash 22 | pip install mcp-python-interpreter 23 | ``` 24 | 25 | Or with uv: 26 | 27 | ```bash 28 | uv install mcp-python-interpreter 29 | ``` 30 | 31 | ## Usage with Claude Desktop 32 | 33 | 1. Install [Claude Desktop](https://claude.ai/download) 34 | 2. Open Claude Desktop, click on menu, then Settings 35 | 3. Go to Developer tab and click "Edit Config" 36 | 4. Add the following to your `claude_desktop_config.json`: 37 | 38 | ```json 39 | { 40 | "mcpServers": { 41 | "mcp-python-interpreter": { 42 | "command": "uvx", 43 | "args": [ 44 | "mcp-python-interpreter", 45 | "--dir", 46 | "/path/to/your/work/dir", 47 | "--python-path", 48 | "/path/to/your/python" 49 | ], 50 | "env": { 51 | "MCP_ALLOW_SYSTEM_ACCESS": 0 52 | }, 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | For Windows: 59 | 60 | ```json 61 | { 62 | "mcpServers": { 63 | "python-interpreter": { 64 | "command": "uvx", 65 | "args": [ 66 | "mcp-python-interpreter", 67 | "--dir", 68 | "C:\\path\\to\\your\\working\\directory", 69 | "--python-path", 70 | "/path/to/your/python" 71 | ], 72 | "env": { 73 | "MCP_ALLOW_SYSTEM_ACCESS": "0" 74 | }, 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | 5. Restart Claude Desktop 81 | 6. You should now see the MCP tools icon in the chat interface 82 | 83 | The `--dir` parameter is **required** and specifies where all files will be saved and executed. This helps maintain security by isolating the MCP server to a specific directory. 84 | 85 | ### Prerequisites 86 | 87 | - Make sure you have `uv` installed. If not, install it using: 88 | ```bash 89 | curl -LsSf https://astral.sh/uv/install.sh | sh 90 | ``` 91 | - For Windows: 92 | ```powershell 93 | powershell -ExecutionPolicy Bypass -Command "iwr -useb https://astral.sh/uv/install.ps1 | iex" 94 | ``` 95 | 96 | ## Available Tools 97 | 98 | The Python Interpreter provides the following tools: 99 | 100 | ### Environment and Package Management 101 | - **list_python_environments**: List all available Python environments (system and conda) 102 | - **list_installed_packages**: List packages installed in a specific environment 103 | - **install_package**: Install a Python package in a specific environment 104 | 105 | ### Code Execution 106 | - **run_python_code**: Execute Python code in a specific environment 107 | - **run_python_file**: Execute a Python file in a specific environment 108 | 109 | ### File Operations 110 | - **read_file**: Read contents of any file type, with size and safety limits 111 | - Supports text files with syntax highlighting 112 | - Displays hex representation for binary files 113 | - **write_file**: Create or overwrite files with text or binary content 114 | - **write_python_file**: Create or overwrite a Python file specifically 115 | - **list_directory**: List Python files in a directory 116 | 117 | ## Available Resources 118 | 119 | - **python://environments**: List all available Python environments 120 | - **python://packages/{env_name}**: List installed packages for a specific environment 121 | - **python://file/{file_path}**: Get the content of a Python file 122 | - **python://directory/{directory_path}**: List all Python files in a directory 123 | 124 | ## Prompts 125 | 126 | - **python_function_template**: Generate a template for a Python function 127 | - **refactor_python_code**: Help refactor Python code 128 | - **debug_python_error**: Help debug a Python error 129 | 130 | ## Example Usage 131 | 132 | Here are some examples of what you can ask Claude to do with this MCP server: 133 | 134 | - "Show me all available Python environments on my system" 135 | - "Run this Python code in my conda-base environment: print('Hello, world!')" 136 | - "Create a new Python file called 'hello.py' with a function that says hello" 137 | - "Read the contents of my 'data.json' file" 138 | - "Write a new configuration file with these settings..." 139 | - "List all packages installed in my system Python environment" 140 | - "Install the requests package in my system Python environment" 141 | - "Run data_analysis.py with these arguments: --input=data.csv --output=results.csv" 142 | 143 | ## File Handling Capabilities 144 | 145 | The MCP Python Interpreter now supports comprehensive file operations: 146 | - Read text and binary files up to 1MB 147 | - Write text and binary files 148 | - Syntax highlighting for source code files 149 | - Hex representation for binary files 150 | - Strict file path security (only within the working directory) 151 | 152 | ## Security Considerations 153 | 154 | This MCP server has access to your Python environments and file system. Key security features include: 155 | - Isolated working directory 156 | - File size limits 157 | - Prevented writes outside the working directory 158 | - Explicit overwrite protection 159 | 160 | Always be cautious about running code or file operations that you don't fully understand. 161 | 162 | ## License 163 | 164 | MIT 165 | -------------------------------------------------------------------------------- /mcp_python_interpreter/server.py: -------------------------------------------------------------------------------- 1 | """ 2 | MCP Python Interpreter 3 | 4 | A Model Context Protocol server for interacting with Python environments 5 | and executing Python code. Supports both in-process execution (default, fast) 6 | and subprocess execution (for environment isolation). 7 | """ 8 | 9 | import os 10 | import sys 11 | import json 12 | import subprocess 13 | import tempfile 14 | import argparse 15 | import traceback 16 | import builtins 17 | from pathlib import Path 18 | from io import StringIO 19 | from typing import Dict, List, Optional, Any 20 | import asyncio 21 | import concurrent.futures 22 | 23 | # Import MCP SDK 24 | from mcp.server.fastmcp import FastMCP 25 | 26 | # Parse command line arguments 27 | parser = argparse.ArgumentParser(description='MCP Python Interpreter') 28 | parser.add_argument('--dir', type=str, default=os.getcwd(), 29 | help='Working directory for code execution and file operations') 30 | parser.add_argument('--python-path', type=str, default=None, 31 | help='Custom Python interpreter path to use as default') 32 | args, unknown = parser.parse_known_args() 33 | 34 | # Configuration 35 | ALLOW_SYSTEM_ACCESS = os.environ.get('MCP_ALLOW_SYSTEM_ACCESS', 'false').lower() in ('true', '1', 'yes') 36 | WORKING_DIR = Path(args.dir).absolute() 37 | WORKING_DIR.mkdir(parents=True, exist_ok=True) 38 | DEFAULT_PYTHON_PATH = args.python_path if args.python_path else sys.executable 39 | 40 | # Startup message 41 | print(f"MCP Python Interpreter starting in directory: {WORKING_DIR}", file=sys.stderr) 42 | print(f"Using default Python interpreter: {DEFAULT_PYTHON_PATH}", file=sys.stderr) 43 | print(f"System-wide file access: {'ENABLED' if ALLOW_SYSTEM_ACCESS else 'DISABLED'}", file=sys.stderr) 44 | print(f"Platform: {sys.platform}", file=sys.stderr) 45 | 46 | # Create MCP server 47 | mcp = FastMCP("python-interpreter") 48 | 49 | # Thread pool for subprocess fallback 50 | _executor = concurrent.futures.ThreadPoolExecutor(max_workers=4) 51 | 52 | # ============================================================================ 53 | # REPL Session Management (for in-process execution) 54 | # ============================================================================ 55 | 56 | class ReplSession: 57 | """Manages a Python REPL session with persistent state.""" 58 | 59 | def __init__(self): 60 | self.locals = { 61 | "__builtins__": builtins, 62 | "__name__": "__main__", 63 | "__doc__": None, 64 | "__package__": None, 65 | } 66 | self.history = [] 67 | 68 | def execute(self, code: str, timeout: Optional[int] = None) -> Dict[str, Any]: 69 | """ 70 | Execute Python code in this session. 71 | 72 | Args: 73 | code: Python code to execute 74 | timeout: Optional timeout (not enforced for inline execution) 75 | 76 | Returns: 77 | Dict with stdout, stderr, result, and status 78 | """ 79 | stdout_capture = StringIO() 80 | stderr_capture = StringIO() 81 | 82 | # Save original streams 83 | old_stdout, old_stderr = sys.stdout, sys.stderr 84 | sys.stdout, sys.stderr = stdout_capture, stderr_capture 85 | 86 | result_value = None 87 | status = 0 88 | 89 | try: 90 | # Change to working directory for execution 91 | old_cwd = os.getcwd() 92 | os.chdir(WORKING_DIR) 93 | 94 | try: 95 | # Try to evaluate as expression first 96 | try: 97 | result_value = eval(code, self.locals) 98 | if result_value is not None: 99 | print(repr(result_value)) 100 | except SyntaxError: 101 | # If not an expression, execute as statement 102 | exec(code, self.locals) 103 | 104 | except Exception: 105 | traceback.print_exc() 106 | status = 1 107 | finally: 108 | os.chdir(old_cwd) 109 | 110 | finally: 111 | # Restore original streams 112 | sys.stdout, sys.stderr = old_stdout, old_stderr 113 | 114 | return { 115 | "stdout": stdout_capture.getvalue(), 116 | "stderr": stderr_capture.getvalue(), 117 | "result": result_value, 118 | "status": status 119 | } 120 | 121 | # Global sessions storage 122 | _sessions: Dict[str, ReplSession] = {} 123 | 124 | def get_session(session_id: str = "default") -> ReplSession: 125 | """Get or create a REPL session.""" 126 | if session_id not in _sessions: 127 | _sessions[session_id] = ReplSession() 128 | return _sessions[session_id] 129 | 130 | # ============================================================================ 131 | # Helper functions 132 | # ============================================================================ 133 | 134 | def is_path_allowed(path: Path) -> bool: 135 | """Check if a path is allowed based on security settings.""" 136 | if ALLOW_SYSTEM_ACCESS: 137 | return True 138 | 139 | try: 140 | path.resolve().relative_to(WORKING_DIR.resolve()) 141 | return True 142 | except ValueError: 143 | return False 144 | 145 | 146 | def _run_subprocess_sync( 147 | cmd: List[str], 148 | cwd: Optional[str] = None, 149 | timeout: int = 300 150 | ) -> Dict[str, Any]: 151 | """Synchronous subprocess execution for Windows compatibility.""" 152 | try: 153 | creation_flags = 0 154 | if sys.platform == "win32": 155 | creation_flags = subprocess.CREATE_NO_WINDOW 156 | try: 157 | creation_flags |= subprocess.CREATE_NEW_PROCESS_GROUP 158 | except AttributeError: 159 | pass 160 | 161 | result = subprocess.run( 162 | cmd, 163 | cwd=cwd, 164 | capture_output=True, 165 | text=True, 166 | timeout=timeout, 167 | creationflags=creation_flags if sys.platform == "win32" else 0, 168 | encoding='utf-8', 169 | errors='replace', 170 | stdin=subprocess.DEVNULL 171 | ) 172 | 173 | return { 174 | "stdout": result.stdout, 175 | "stderr": result.stderr, 176 | "status": result.returncode 177 | } 178 | 179 | except subprocess.TimeoutExpired as e: 180 | stdout = e.stdout.decode('utf-8', errors='replace') if e.stdout else "" 181 | stderr = e.stderr.decode('utf-8', errors='replace') if e.stderr else "" 182 | 183 | return { 184 | "stdout": stdout, 185 | "stderr": stderr + f"\nExecution timed out after {timeout} seconds", 186 | "status": -1 187 | } 188 | 189 | except Exception as e: 190 | return { 191 | "stdout": "", 192 | "stderr": f"Error executing command: {str(e)}", 193 | "status": -1 194 | } 195 | 196 | 197 | async def run_subprocess_async( 198 | cmd: List[str], 199 | cwd: Optional[str] = None, 200 | timeout: int = 300, 201 | input_data: Optional[str] = None 202 | ) -> Dict[str, Any]: 203 | """Run subprocess with Windows compatibility.""" 204 | 205 | # On Windows, use thread pool for reliability 206 | if sys.platform == "win32": 207 | if input_data: 208 | print("Warning: input_data not supported on Windows sync mode", file=sys.stderr) 209 | 210 | loop = asyncio.get_event_loop() 211 | from functools import partial 212 | func = partial(_run_subprocess_sync, cmd, cwd, timeout) 213 | result = await loop.run_in_executor(_executor, func) 214 | return result 215 | 216 | # On Unix, use asyncio 217 | try: 218 | process = await asyncio.create_subprocess_exec( 219 | *cmd, 220 | stdout=asyncio.subprocess.PIPE, 221 | stderr=asyncio.subprocess.PIPE, 222 | stdin=asyncio.subprocess.PIPE if input_data else asyncio.subprocess.DEVNULL, 223 | cwd=cwd 224 | ) 225 | 226 | try: 227 | stdout, stderr = await asyncio.wait_for( 228 | process.communicate(input=input_data.encode('utf-8') if input_data else None), 229 | timeout=timeout 230 | ) 231 | 232 | return { 233 | "stdout": stdout.decode('utf-8', errors='replace'), 234 | "stderr": stderr.decode('utf-8', errors='replace'), 235 | "status": process.returncode 236 | } 237 | 238 | except asyncio.TimeoutError: 239 | try: 240 | process.kill() 241 | await process.wait() 242 | except: 243 | pass 244 | 245 | return { 246 | "stdout": "", 247 | "stderr": f"Execution timed out after {timeout} seconds", 248 | "status": -1 249 | } 250 | 251 | except Exception as e: 252 | return { 253 | "stdout": "", 254 | "stderr": f"Error executing command: {str(e)}", 255 | "status": -1 256 | } 257 | 258 | 259 | async def execute_python_code_subprocess( 260 | code: str, 261 | python_path: Optional[str] = None, 262 | working_dir: Optional[str] = None, 263 | timeout: int = 300 264 | ) -> Dict[str, Any]: 265 | """Execute Python code via subprocess (for environment isolation).""" 266 | if python_path is None: 267 | python_path = DEFAULT_PYTHON_PATH 268 | 269 | temp_file = None 270 | try: 271 | fd, temp_file = tempfile.mkstemp(suffix='.py', text=True) 272 | 273 | try: 274 | with os.fdopen(fd, 'w', encoding='utf-8') as f: 275 | f.write(code) 276 | f.flush() 277 | os.fsync(f.fileno()) 278 | except Exception as e: 279 | os.close(fd) 280 | raise e 281 | 282 | if sys.platform == "win32": 283 | await asyncio.sleep(0.05) 284 | temp_file = os.path.abspath(temp_file) 285 | if working_dir: 286 | working_dir = os.path.abspath(working_dir) 287 | 288 | result = await run_subprocess_async( 289 | [python_path, temp_file], 290 | cwd=working_dir, 291 | timeout=timeout 292 | ) 293 | 294 | return result 295 | 296 | finally: 297 | if temp_file: 298 | try: 299 | if sys.platform == "win32": 300 | await asyncio.sleep(0.05) 301 | 302 | if os.path.exists(temp_file): 303 | os.unlink(temp_file) 304 | except Exception as e: 305 | print(f"Warning: Could not delete temp file {temp_file}: {e}", file=sys.stderr) 306 | 307 | 308 | def get_python_environments() -> List[Dict[str, str]]: 309 | """Get all available Python environments.""" 310 | environments = [] 311 | 312 | if DEFAULT_PYTHON_PATH != sys.executable: 313 | try: 314 | result = subprocess.run( 315 | [DEFAULT_PYTHON_PATH, "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')"], 316 | capture_output=True, text=True, check=True, timeout=10, 317 | stdin=subprocess.DEVNULL, 318 | creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 319 | ) 320 | version = result.stdout.strip() 321 | 322 | environments.append({ 323 | "name": "default", 324 | "path": DEFAULT_PYTHON_PATH, 325 | "version": version 326 | }) 327 | except Exception as e: 328 | print(f"Error getting version for custom Python path: {e}", file=sys.stderr) 329 | 330 | environments.append({ 331 | "name": "system", 332 | "path": sys.executable, 333 | "version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" 334 | }) 335 | 336 | # Try conda environments 337 | try: 338 | result = subprocess.run( 339 | ["conda", "info", "--envs", "--json"], 340 | capture_output=True, text=True, check=False, timeout=10, 341 | stdin=subprocess.DEVNULL, 342 | creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 343 | ) 344 | 345 | if result.returncode == 0: 346 | conda_info = json.loads(result.stdout) 347 | for env in conda_info.get("envs", []): 348 | env_name = os.path.basename(env) 349 | if env_name == "base": 350 | env_name = "conda-base" 351 | 352 | python_path = os.path.join(env, "bin", "python") 353 | if not os.path.exists(python_path): 354 | python_path = os.path.join(env, "python.exe") 355 | 356 | if os.path.exists(python_path): 357 | try: 358 | version_result = subprocess.run( 359 | [python_path, "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')"], 360 | capture_output=True, text=True, check=True, timeout=10, 361 | stdin=subprocess.DEVNULL, 362 | creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 363 | ) 364 | version = version_result.stdout.strip() 365 | 366 | environments.append({ 367 | "name": env_name, 368 | "path": python_path, 369 | "version": version 370 | }) 371 | except Exception: 372 | pass 373 | except Exception as e: 374 | print(f"Error getting conda environments: {e}", file=sys.stderr) 375 | 376 | return environments 377 | 378 | 379 | def get_installed_packages(python_path: str) -> List[Dict[str, str]]: 380 | """Get installed packages for a specific Python environment.""" 381 | try: 382 | result = subprocess.run( 383 | [python_path, "-m", "pip", "list", "--format=json"], 384 | capture_output=True, text=True, check=True, timeout=30, 385 | stdin=subprocess.DEVNULL, 386 | creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 387 | ) 388 | return json.loads(result.stdout) 389 | except Exception as e: 390 | print(f"Error getting installed packages: {e}", file=sys.stderr) 391 | return [] 392 | 393 | 394 | def find_python_files(directory: Path) -> List[Dict[str, str]]: 395 | """Find all Python files in a directory.""" 396 | files = [] 397 | 398 | if not directory.exists(): 399 | return files 400 | 401 | for path in directory.rglob("*.py"): 402 | if path.is_file(): 403 | files.append({ 404 | "path": str(path), 405 | "name": path.name, 406 | "size": path.stat().st_size, 407 | "modified": path.stat().st_mtime 408 | }) 409 | 410 | return files 411 | 412 | 413 | # ============================================================================ 414 | # Resources 415 | # ============================================================================ 416 | 417 | @mcp.resource("python://environments") 418 | def get_environments_resource() -> str: 419 | """List all available Python environments as a resource.""" 420 | environments = get_python_environments() 421 | return json.dumps(environments, indent=2) 422 | 423 | 424 | @mcp.resource("python://packages/{env_name}") 425 | def get_packages_resource(env_name: str) -> str: 426 | """List installed packages for a specific environment as a resource.""" 427 | environments = get_python_environments() 428 | 429 | env = next((e for e in environments if e["name"] == env_name), None) 430 | if not env: 431 | return json.dumps({"error": f"Environment '{env_name}' not found"}) 432 | 433 | packages = get_installed_packages(env["path"]) 434 | return json.dumps(packages, indent=2) 435 | 436 | 437 | @mcp.resource("python://directory") 438 | def get_working_directory_listing() -> str: 439 | """List all Python files in the working directory as a resource.""" 440 | try: 441 | files = find_python_files(WORKING_DIR) 442 | return json.dumps({ 443 | "working_directory": str(WORKING_DIR), 444 | "files": files 445 | }, indent=2) 446 | except Exception as e: 447 | return json.dumps({"error": f"Error listing directory: {str(e)}"}) 448 | 449 | 450 | @mcp.resource("python://session/{session_id}/history") 451 | def get_session_history(session_id: str) -> str: 452 | """Get execution history for a REPL session.""" 453 | if session_id not in _sessions: 454 | return json.dumps({"error": f"Session '{session_id}' not found"}) 455 | 456 | session = _sessions[session_id] 457 | return json.dumps({ 458 | "session_id": session_id, 459 | "history": session.history 460 | }, indent=2) 461 | 462 | 463 | # ============================================================================ 464 | # Tools 465 | # ============================================================================ 466 | 467 | @mcp.tool() 468 | def list_python_environments() -> str: 469 | """List all available Python environments (system Python and conda environments).""" 470 | environments = get_python_environments() 471 | 472 | if not environments: 473 | return "No Python environments found." 474 | 475 | result = "Available Python Environments:\n\n" 476 | for env in environments: 477 | result += f"- Name: {env['name']}\n" 478 | result += f" Path: {env['path']}\n" 479 | result += f" Version: Python {env['version']}\n\n" 480 | 481 | return result 482 | 483 | 484 | @mcp.tool() 485 | def list_installed_packages(environment: str = "default") -> str: 486 | """ 487 | List installed packages for a specific Python environment. 488 | 489 | Args: 490 | environment: Name of the Python environment 491 | """ 492 | environments = get_python_environments() 493 | 494 | if environment == "default" and not any(e["name"] == "default" for e in environments): 495 | environment = "system" 496 | 497 | env = next((e for e in environments if e["name"] == environment), None) 498 | if not env: 499 | return f"Environment '{environment}' not found. Available: {', '.join(e['name'] for e in environments)}" 500 | 501 | packages = get_installed_packages(env["path"]) 502 | 503 | if not packages: 504 | return f"No packages found in environment '{environment}'." 505 | 506 | result = f"Installed Packages in '{environment}':\n\n" 507 | for pkg in packages: 508 | result += f"- {pkg['name']} {pkg['version']}\n" 509 | 510 | return result 511 | 512 | 513 | @mcp.tool() 514 | async def run_python_code( 515 | code: str, 516 | execution_mode: str = "inline", 517 | session_id: str = "default", 518 | environment: str = "system", 519 | save_as: Optional[str] = None, 520 | timeout: int = 300 521 | ) -> str: 522 | """ 523 | Execute Python code with flexible execution modes. 524 | 525 | Args: 526 | code: Python code to execute 527 | execution_mode: Execution mode - "inline" (default, fast, in-process) or "subprocess" (isolated) 528 | session_id: Session ID for inline mode to maintain state across executions 529 | environment: Python environment name (only for subprocess mode) 530 | save_as: Optional filename to save the code before execution 531 | timeout: Maximum execution time in seconds (only enforced for subprocess mode) 532 | 533 | Returns: 534 | Execution result with output 535 | 536 | Execution modes: 537 | - "inline" (default): Executes code in the current process. Fast and reliable, 538 | maintains session state. Use for most code execution tasks. 539 | - "subprocess": Executes code in a separate Python process. Use when you need 540 | environment isolation or a different Python environment. 541 | """ 542 | 543 | # Save code if requested 544 | if save_as: 545 | save_path = WORKING_DIR / save_as 546 | if not save_path.suffix == '.py': 547 | save_path = save_path.with_suffix('.py') 548 | 549 | try: 550 | save_path.parent.mkdir(parents=True, exist_ok=True) 551 | with open(save_path, 'w', encoding='utf-8') as f: 552 | f.write(code) 553 | except Exception as e: 554 | return f"Error saving code to file: {str(e)}" 555 | 556 | # Execute based on mode 557 | if execution_mode == "inline": 558 | # In-process execution (default, fast, no subprocess issues) 559 | try: 560 | session = get_session(session_id) 561 | result = session.execute(code, timeout) 562 | 563 | # Store in history 564 | session.history.append({ 565 | "code": code, 566 | "stdout": result["stdout"], 567 | "stderr": result["stderr"], 568 | "status": result["status"] 569 | }) 570 | 571 | output = f"Execution in session '{session_id}' (inline mode)" 572 | if save_as: 573 | output += f" (saved to {save_as})" 574 | output += ":\n\n" 575 | 576 | if result["status"] == 0: 577 | output += "--- Output ---\n" 578 | output += result["stdout"] if result["stdout"] else "(No output)\n" 579 | else: 580 | output += "--- Error ---\n" 581 | output += result["stderr"] if result["stderr"] else "(No error message)\n" 582 | 583 | if result["stdout"]: 584 | output += "\n--- Output ---\n" 585 | output += result["stdout"] 586 | 587 | return output 588 | 589 | except Exception as e: 590 | return f"Error in inline execution: {str(e)}\n{traceback.format_exc()}" 591 | 592 | elif execution_mode == "subprocess": 593 | # Subprocess execution (for environment isolation) 594 | environments = get_python_environments() 595 | 596 | if environment == "default" and not any(e["name"] == "default" for e in environments): 597 | environment = "system" 598 | 599 | env = next((e for e in environments if e["name"] == environment), None) 600 | if not env: 601 | return f"Environment '{environment}' not found. Available: {', '.join(e['name'] for e in environments)}" 602 | 603 | result = await execute_python_code_subprocess(code, env["path"], str(WORKING_DIR), timeout) 604 | 605 | output = f"Execution in '{environment}' environment (subprocess mode)" 606 | if save_as: 607 | output += f" (saved to {save_as})" 608 | output += ":\n\n" 609 | 610 | if result["status"] == 0: 611 | output += "--- Output ---\n" 612 | output += result["stdout"] if result["stdout"] else "(No output)\n" 613 | else: 614 | output += f"--- Error (status code: {result['status']}) ---\n" 615 | output += result["stderr"] if result["stderr"] else "(No error message)\n" 616 | 617 | if result["stdout"]: 618 | output += "\n--- Output ---\n" 619 | output += result["stdout"] 620 | 621 | return output 622 | 623 | else: 624 | return f"Unknown execution mode: {execution_mode}. Use 'inline' or 'subprocess'." 625 | 626 | 627 | @mcp.tool() 628 | async def run_python_file( 629 | file_path: str, 630 | environment: str = "default", 631 | arguments: Optional[List[str]] = None, 632 | timeout: int = 300 633 | ) -> str: 634 | """ 635 | Execute a Python file (always uses subprocess for file execution). 636 | 637 | Args: 638 | file_path: Path to the Python file to execute 639 | environment: Name of the Python environment to use 640 | arguments: List of command-line arguments to pass to the script 641 | timeout: Maximum execution time in seconds (default: 300) 642 | """ 643 | path = Path(file_path) 644 | if path.is_absolute(): 645 | if not is_path_allowed(path): 646 | return f"Access denied: Can only run files in working directory: {WORKING_DIR}" 647 | else: 648 | path = WORKING_DIR / path 649 | 650 | if not path.exists(): 651 | return f"File '{path}' not found." 652 | 653 | environments = get_python_environments() 654 | 655 | if environment == "default" and not any(e["name"] == "default" for e in environments): 656 | environment = "system" 657 | 658 | env = next((e for e in environments if e["name"] == environment), None) 659 | if not env: 660 | return f"Environment '{environment}' not found. Available: {', '.join(e['name'] for e in environments)}" 661 | 662 | cmd = [env["path"], str(path)] 663 | if arguments: 664 | cmd.extend(arguments) 665 | 666 | result = await run_subprocess_async(cmd, cwd=str(WORKING_DIR), timeout=timeout) 667 | 668 | output = f"Execution of '{path}' in '{environment}' environment:\n\n" 669 | 670 | if result["status"] == 0: 671 | output += "--- Output ---\n" 672 | output += result["stdout"] if result["stdout"] else "(No output)\n" 673 | else: 674 | output += f"--- Error (status code: {result['status']}) ---\n" 675 | output += result["stderr"] if result["stderr"] else "(No error message)\n" 676 | 677 | if result["stdout"]: 678 | output += "\n--- Output ---\n" 679 | output += result["stdout"] 680 | 681 | return output 682 | 683 | 684 | @mcp.tool() 685 | async def install_package( 686 | package_name: str, 687 | environment: str = "default", 688 | upgrade: bool = False, 689 | timeout: int = 300 690 | ) -> str: 691 | """ 692 | Install a Python package in the specified environment. 693 | 694 | Args: 695 | package_name: Name of the package to install 696 | environment: Name of the Python environment 697 | upgrade: Whether to upgrade if already installed 698 | timeout: Maximum execution time in seconds 699 | """ 700 | environments = get_python_environments() 701 | 702 | if environment == "default" and not any(e["name"] == "default" for e in environments): 703 | environment = "system" 704 | 705 | env = next((e for e in environments if e["name"] == environment), None) 706 | if not env: 707 | return f"Environment '{environment}' not found. Available: {', '.join(e['name'] for e in environments)}" 708 | 709 | cmd = [env["path"], "-m", "pip", "install"] 710 | if upgrade: 711 | cmd.append("--upgrade") 712 | cmd.append(package_name) 713 | 714 | result = await run_subprocess_async(cmd, timeout=timeout) 715 | 716 | if result["status"] == 0: 717 | return f"Successfully {'upgraded' if upgrade else 'installed'} {package_name} in {environment}." 718 | else: 719 | return f"Error installing {package_name}:\n{result['stderr']}" 720 | 721 | 722 | @mcp.tool() 723 | def read_file(file_path: str, max_size_kb: int = 1024) -> str: 724 | """ 725 | Read the content of any file, with size limits for safety. 726 | 727 | Args: 728 | file_path: Path to the file 729 | max_size_kb: Maximum file size to read in KB 730 | """ 731 | path = Path(file_path) 732 | if path.is_absolute(): 733 | if not is_path_allowed(path): 734 | return f"Access denied: Can only read files in working directory: {WORKING_DIR}" 735 | else: 736 | path = WORKING_DIR / path 737 | 738 | try: 739 | if not path.exists(): 740 | return f"Error: File '{file_path}' not found" 741 | 742 | file_size_kb = path.stat().st_size / 1024 743 | if file_size_kb > max_size_kb: 744 | return f"Error: File size ({file_size_kb:.2f} KB) exceeds maximum ({max_size_kb} KB)" 745 | 746 | try: 747 | with open(path, 'r', encoding='utf-8') as f: 748 | content = f.read() 749 | 750 | source_extensions = ['.py', '.js', '.html', '.css', '.json', '.xml', '.md', '.txt', '.sh', '.bat', '.ps1'] 751 | if path.suffix.lower() in source_extensions: 752 | file_type = path.suffix[1:] if path.suffix else 'plain' 753 | return f"File: {file_path}\n\n```{file_type}\n{content}\n```" 754 | 755 | return f"File: {file_path}\n\n{content}" 756 | 757 | except UnicodeDecodeError: 758 | with open(path, 'rb') as f: 759 | content = f.read() 760 | hex_content = content.hex() 761 | return f"Binary file: {file_path}\nSize: {len(content)} bytes\nHex (first 1024 chars):\n{hex_content[:1024]}" 762 | 763 | except Exception as e: 764 | return f"Error reading file: {str(e)}" 765 | 766 | 767 | @mcp.tool() 768 | def write_file( 769 | file_path: str, 770 | content: str, 771 | overwrite: bool = False 772 | ) -> str: 773 | """ 774 | Write content to a file. 775 | 776 | Args: 777 | file_path: Path to the file to write 778 | content: Content to write 779 | overwrite: Whether to overwrite if exists 780 | """ 781 | path = Path(file_path) 782 | if path.is_absolute(): 783 | if not is_path_allowed(path): 784 | return f"Access denied: Can only write files in working directory: {WORKING_DIR}" 785 | else: 786 | path = WORKING_DIR / path 787 | 788 | try: 789 | if path.exists() and not overwrite: 790 | return f"File '{path}' exists. Use overwrite=True to replace." 791 | 792 | path.parent.mkdir(parents=True, exist_ok=True) 793 | 794 | with open(path, 'w', encoding='utf-8') as f: 795 | f.write(content) 796 | f.flush() 797 | os.fsync(f.fileno()) 798 | 799 | file_size_kb = path.stat().st_size / 1024 800 | return f"Successfully wrote to {path}. Size: {file_size_kb:.2f} KB" 801 | 802 | except Exception as e: 803 | return f"Error writing file: {str(e)}" 804 | 805 | 806 | @mcp.tool() 807 | def list_directory(directory_path: str = "") -> str: 808 | """ 809 | List all Python files in a directory. 810 | 811 | Args: 812 | directory_path: Path to directory (empty for working directory) 813 | """ 814 | try: 815 | if not directory_path: 816 | path = WORKING_DIR 817 | else: 818 | path = Path(directory_path) 819 | if path.is_absolute(): 820 | if not is_path_allowed(path): 821 | return f"Access denied: Can only list files in working directory: {WORKING_DIR}" 822 | else: 823 | path = WORKING_DIR / directory_path 824 | 825 | if not path.exists(): 826 | return f"Error: Directory '{directory_path}' not found" 827 | 828 | if not path.is_dir(): 829 | return f"Error: '{directory_path}' is not a directory" 830 | 831 | files = find_python_files(path) 832 | 833 | if not files: 834 | return f"No Python files found in {directory_path or 'working directory'}" 835 | 836 | result = f"Python files in: {directory_path or str(WORKING_DIR)}\n\n" 837 | 838 | files_by_dir = {} 839 | base_dir = path if ALLOW_SYSTEM_ACCESS else WORKING_DIR 840 | 841 | for file in files: 842 | file_path = Path(file["path"]) 843 | try: 844 | relative_path = file_path.relative_to(base_dir) 845 | parent = str(relative_path.parent) 846 | if parent == ".": 847 | parent = "(root)" 848 | except ValueError: 849 | parent = str(file_path.parent) 850 | 851 | if parent not in files_by_dir: 852 | files_by_dir[parent] = [] 853 | 854 | files_by_dir[parent].append({ 855 | "name": file["name"], 856 | "size": file["size"] 857 | }) 858 | 859 | for dir_name, dir_files in sorted(files_by_dir.items()): 860 | result += f"📁 {dir_name}:\n" 861 | for file in sorted(dir_files, key=lambda x: x["name"]): 862 | size_kb = round(file["size"] / 1024, 1) 863 | result += f" 📄 {file['name']} ({size_kb} KB)\n" 864 | result += "\n" 865 | 866 | return result 867 | except Exception as e: 868 | return f"Error listing directory: {str(e)}" 869 | 870 | 871 | @mcp.tool() 872 | def clear_session(session_id: str = "default") -> str: 873 | """ 874 | Clear a REPL session's state and history. 875 | 876 | Args: 877 | session_id: Session ID to clear 878 | """ 879 | if session_id in _sessions: 880 | del _sessions[session_id] 881 | return f"Session '{session_id}' cleared." 882 | else: 883 | return f"Session '{session_id}' not found." 884 | 885 | 886 | @mcp.tool() 887 | def list_sessions() -> str: 888 | """List all active REPL sessions.""" 889 | if not _sessions: 890 | return "No active sessions." 891 | 892 | result = "Active REPL Sessions:\n\n" 893 | for session_id, session in _sessions.items(): 894 | result += f"- Session: {session_id}\n" 895 | result += f" History entries: {len(session.history)}\n" 896 | result += f" Variables: {len([k for k in session.locals.keys() if not k.startswith('__')])}\n\n" 897 | 898 | return result 899 | 900 | 901 | # ============================================================================ 902 | # Prompts 903 | # ============================================================================ 904 | 905 | @mcp.prompt() 906 | def python_function_template(description: str) -> str: 907 | """Generate a template for a Python function with docstring.""" 908 | return f"""Please create a Python function based on this description: 909 | 910 | {description} 911 | 912 | Include: 913 | - Type hints 914 | - Docstring with parameters, return value, and examples 915 | - Error handling where appropriate 916 | - Comments for complex logic""" 917 | 918 | 919 | @mcp.prompt() 920 | def refactor_python_code(code: str) -> str: 921 | """Help refactor Python code for better readability and performance.""" 922 | return f"""Please refactor this Python code to improve readability, performance, error handling, and structure: 923 | 924 | ```python 925 | {code} 926 | ``` 927 | 928 | Explain the changes you made and why they improve the code.""" 929 | 930 | 931 | @mcp.prompt() 932 | def debug_python_error(code: str, error_message: str) -> str: 933 | """Help debug a Python error.""" 934 | return f"""I'm getting this error: 935 | 936 | ```python 937 | {code} 938 | ``` 939 | 940 | Error message: 941 | ``` 942 | {error_message} 943 | ``` 944 | 945 | Please help by: 946 | 1. Explaining what the error means 947 | 2. Identifying the cause 948 | 3. Suggesting fixes""" 949 | 950 | 951 | # Run the server 952 | if __name__ == "__main__": 953 | mcp.run(transport='stdio') -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.10" 4 | 5 | [[package]] 6 | name = "annotated-types" 7 | version = "0.7.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.9.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 20 | { name = "idna" }, 21 | { name = "sniffio" }, 22 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 23 | ] 24 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } 25 | wheels = [ 26 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, 27 | ] 28 | 29 | [[package]] 30 | name = "certifi" 31 | version = "2025.1.31" 32 | source = { registry = "https://pypi.org/simple" } 33 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } 34 | wheels = [ 35 | { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, 36 | ] 37 | 38 | [[package]] 39 | name = "click" 40 | version = "8.1.8" 41 | source = { registry = "https://pypi.org/simple" } 42 | dependencies = [ 43 | { name = "colorama", marker = "sys_platform == 'win32'" }, 44 | ] 45 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 46 | wheels = [ 47 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 48 | ] 49 | 50 | [[package]] 51 | name = "colorama" 52 | version = "0.4.6" 53 | source = { registry = "https://pypi.org/simple" } 54 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 55 | wheels = [ 56 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 57 | ] 58 | 59 | [[package]] 60 | name = "exceptiongroup" 61 | version = "1.2.2" 62 | source = { registry = "https://pypi.org/simple" } 63 | sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } 64 | wheels = [ 65 | { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, 66 | ] 67 | 68 | [[package]] 69 | name = "h11" 70 | version = "0.14.0" 71 | source = { registry = "https://pypi.org/simple" } 72 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 73 | wheels = [ 74 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 75 | ] 76 | 77 | [[package]] 78 | name = "httpcore" 79 | version = "1.0.7" 80 | source = { registry = "https://pypi.org/simple" } 81 | dependencies = [ 82 | { name = "certifi" }, 83 | { name = "h11" }, 84 | ] 85 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } 86 | wheels = [ 87 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, 88 | ] 89 | 90 | [[package]] 91 | name = "httpx" 92 | version = "0.28.1" 93 | source = { registry = "https://pypi.org/simple" } 94 | dependencies = [ 95 | { name = "anyio" }, 96 | { name = "certifi" }, 97 | { name = "httpcore" }, 98 | { name = "idna" }, 99 | ] 100 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 101 | wheels = [ 102 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 103 | ] 104 | 105 | [[package]] 106 | name = "httpx-sse" 107 | version = "0.4.0" 108 | source = { registry = "https://pypi.org/simple" } 109 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 110 | wheels = [ 111 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 112 | ] 113 | 114 | [[package]] 115 | name = "idna" 116 | version = "3.10" 117 | source = { registry = "https://pypi.org/simple" } 118 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 119 | wheels = [ 120 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 121 | ] 122 | 123 | [[package]] 124 | name = "markdown-it-py" 125 | version = "3.0.0" 126 | source = { registry = "https://pypi.org/simple" } 127 | dependencies = [ 128 | { name = "mdurl" }, 129 | ] 130 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 131 | wheels = [ 132 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 133 | ] 134 | 135 | [[package]] 136 | name = "mcp" 137 | version = "1.6.0" 138 | source = { registry = "https://pypi.org/simple" } 139 | dependencies = [ 140 | { name = "anyio" }, 141 | { name = "httpx" }, 142 | { name = "httpx-sse" }, 143 | { name = "pydantic" }, 144 | { name = "pydantic-settings" }, 145 | { name = "sse-starlette" }, 146 | { name = "starlette" }, 147 | { name = "uvicorn" }, 148 | ] 149 | sdist = { url = "https://files.pythonhosted.org/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723", size = 200031 } 150 | wheels = [ 151 | { url = "https://files.pythonhosted.org/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0", size = 76077 }, 152 | ] 153 | 154 | [package.optional-dependencies] 155 | cli = [ 156 | { name = "python-dotenv" }, 157 | { name = "typer" }, 158 | ] 159 | 160 | [[package]] 161 | name = "mcp-python-interpreter" 162 | version = "1.0" 163 | source = { editable = "." } 164 | dependencies = [ 165 | { name = "mcp", extra = ["cli"] }, 166 | ] 167 | 168 | [package.metadata] 169 | requires-dist = [{ name = "mcp", extras = ["cli"], specifier = ">=1.6.0" }] 170 | 171 | [[package]] 172 | name = "mdurl" 173 | version = "0.1.2" 174 | source = { registry = "https://pypi.org/simple" } 175 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 176 | wheels = [ 177 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 178 | ] 179 | 180 | [[package]] 181 | name = "pydantic" 182 | version = "2.11.2" 183 | source = { registry = "https://pypi.org/simple" } 184 | dependencies = [ 185 | { name = "annotated-types" }, 186 | { name = "pydantic-core" }, 187 | { name = "typing-extensions" }, 188 | { name = "typing-inspection" }, 189 | ] 190 | sdist = { url = "https://files.pythonhosted.org/packages/b0/41/832125a41fe098b58d1fdd04ae819b4dc6b34d6b09ed78304fd93d4bc051/pydantic-2.11.2.tar.gz", hash = "sha256:2138628e050bd7a1e70b91d4bf4a91167f4ad76fdb83209b107c8d84b854917e", size = 784742 } 191 | wheels = [ 192 | { url = "https://files.pythonhosted.org/packages/bf/c2/0f3baea344d0b15e35cb3e04ad5b953fa05106b76efbf4c782a3f47f22f5/pydantic-2.11.2-py3-none-any.whl", hash = "sha256:7f17d25846bcdf89b670a86cdfe7b29a9f1c9ca23dee154221c9aa81845cfca7", size = 443295 }, 193 | ] 194 | 195 | [[package]] 196 | name = "pydantic-core" 197 | version = "2.33.1" 198 | source = { registry = "https://pypi.org/simple" } 199 | dependencies = [ 200 | { name = "typing-extensions" }, 201 | ] 202 | sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } 203 | wheels = [ 204 | { url = "https://files.pythonhosted.org/packages/38/ea/5f572806ab4d4223d11551af814d243b0e3e02cc6913def4d1fe4a5ca41c/pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26", size = 2044021 }, 205 | { url = "https://files.pythonhosted.org/packages/8c/d1/f86cc96d2aa80e3881140d16d12ef2b491223f90b28b9a911346c04ac359/pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927", size = 1861742 }, 206 | { url = "https://files.pythonhosted.org/packages/37/08/fbd2cd1e9fc735a0df0142fac41c114ad9602d1c004aea340169ae90973b/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db", size = 1910414 }, 207 | { url = "https://files.pythonhosted.org/packages/7f/73/3ac217751decbf8d6cb9443cec9b9eb0130eeada6ae56403e11b486e277e/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48", size = 1996848 }, 208 | { url = "https://files.pythonhosted.org/packages/9a/f5/5c26b265cdcff2661e2520d2d1e9db72d117ea00eb41e00a76efe68cb009/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969", size = 2141055 }, 209 | { url = "https://files.pythonhosted.org/packages/5d/14/a9c3cee817ef2f8347c5ce0713e91867a0dceceefcb2973942855c917379/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e", size = 2753806 }, 210 | { url = "https://files.pythonhosted.org/packages/f2/68/866ce83a51dd37e7c604ce0050ff6ad26de65a7799df89f4db87dd93d1d6/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89", size = 2007777 }, 211 | { url = "https://files.pythonhosted.org/packages/b6/a8/36771f4404bb3e49bd6d4344da4dede0bf89cc1e01f3b723c47248a3761c/pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde", size = 2122803 }, 212 | { url = "https://files.pythonhosted.org/packages/18/9c/730a09b2694aa89360d20756369822d98dc2f31b717c21df33b64ffd1f50/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65", size = 2086755 }, 213 | { url = "https://files.pythonhosted.org/packages/54/8e/2dccd89602b5ec31d1c58138d02340ecb2ebb8c2cac3cc66b65ce3edb6ce/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc", size = 2257358 }, 214 | { url = "https://files.pythonhosted.org/packages/d1/9c/126e4ac1bfad8a95a9837acdd0963695d69264179ba4ede8b8c40d741702/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091", size = 2257916 }, 215 | { url = "https://files.pythonhosted.org/packages/7d/ba/91eea2047e681a6853c81c20aeca9dcdaa5402ccb7404a2097c2adf9d038/pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383", size = 1923823 }, 216 | { url = "https://files.pythonhosted.org/packages/94/c0/fcdf739bf60d836a38811476f6ecd50374880b01e3014318b6e809ddfd52/pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504", size = 1952494 }, 217 | { url = "https://files.pythonhosted.org/packages/d6/7f/c6298830cb780c46b4f46bb24298d01019ffa4d21769f39b908cd14bbd50/pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24", size = 2044224 }, 218 | { url = "https://files.pythonhosted.org/packages/a8/65/6ab3a536776cad5343f625245bd38165d6663256ad43f3a200e5936afd6c/pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30", size = 1858845 }, 219 | { url = "https://files.pythonhosted.org/packages/e9/15/9a22fd26ba5ee8c669d4b8c9c244238e940cd5d818649603ca81d1c69861/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595", size = 1910029 }, 220 | { url = "https://files.pythonhosted.org/packages/d5/33/8cb1a62818974045086f55f604044bf35b9342900318f9a2a029a1bec460/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e", size = 1997784 }, 221 | { url = "https://files.pythonhosted.org/packages/c0/ca/49958e4df7715c71773e1ea5be1c74544923d10319173264e6db122543f9/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a", size = 2141075 }, 222 | { url = "https://files.pythonhosted.org/packages/7b/a6/0b3a167a9773c79ba834b959b4e18c3ae9216b8319bd8422792abc8a41b1/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505", size = 2745849 }, 223 | { url = "https://files.pythonhosted.org/packages/0b/60/516484135173aa9e5861d7a0663dce82e4746d2e7f803627d8c25dfa5578/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f", size = 2005794 }, 224 | { url = "https://files.pythonhosted.org/packages/86/70/05b1eb77459ad47de00cf78ee003016da0cedf8b9170260488d7c21e9181/pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77", size = 2123237 }, 225 | { url = "https://files.pythonhosted.org/packages/c7/57/12667a1409c04ae7dc95d3b43158948eb0368e9c790be8b095cb60611459/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961", size = 2086351 }, 226 | { url = "https://files.pythonhosted.org/packages/57/61/cc6d1d1c1664b58fdd6ecc64c84366c34ec9b606aeb66cafab6f4088974c/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1", size = 2258914 }, 227 | { url = "https://files.pythonhosted.org/packages/d1/0a/edb137176a1f5419b2ddee8bde6a0a548cfa3c74f657f63e56232df8de88/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c", size = 2257385 }, 228 | { url = "https://files.pythonhosted.org/packages/26/3c/48ca982d50e4b0e1d9954919c887bdc1c2b462801bf408613ccc641b3daa/pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896", size = 1923765 }, 229 | { url = "https://files.pythonhosted.org/packages/33/cd/7ab70b99e5e21559f5de38a0928ea84e6f23fdef2b0d16a6feaf942b003c/pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83", size = 1950688 }, 230 | { url = "https://files.pythonhosted.org/packages/4b/ae/db1fc237b82e2cacd379f63e3335748ab88b5adde98bf7544a1b1bd10a84/pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89", size = 1908185 }, 231 | { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 }, 232 | { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 }, 233 | { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 }, 234 | { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 }, 235 | { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 }, 236 | { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 }, 237 | { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 }, 238 | { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 }, 239 | { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 }, 240 | { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 }, 241 | { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 }, 242 | { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 }, 243 | { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 }, 244 | { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 }, 245 | { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, 246 | { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, 247 | { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, 248 | { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, 249 | { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, 250 | { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, 251 | { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, 252 | { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, 253 | { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, 254 | { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, 255 | { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, 256 | { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, 257 | { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, 258 | { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, 259 | { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, 260 | { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, 261 | { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, 262 | { url = "https://files.pythonhosted.org/packages/9c/c7/8b311d5adb0fe00a93ee9b4e92a02b0ec08510e9838885ef781ccbb20604/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02", size = 2041659 }, 263 | { url = "https://files.pythonhosted.org/packages/8a/d6/4f58d32066a9e26530daaf9adc6664b01875ae0691570094968aaa7b8fcc/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068", size = 1873294 }, 264 | { url = "https://files.pythonhosted.org/packages/f7/3f/53cc9c45d9229da427909c751f8ed2bf422414f7664ea4dde2d004f596ba/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e", size = 1903771 }, 265 | { url = "https://files.pythonhosted.org/packages/f0/49/bf0783279ce674eb9903fb9ae43f6c614cb2f1c4951370258823f795368b/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe", size = 2083558 }, 266 | { url = "https://files.pythonhosted.org/packages/9c/5b/0d998367687f986c7d8484a2c476d30f07bf5b8b1477649a6092bd4c540e/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1", size = 2118038 }, 267 | { url = "https://files.pythonhosted.org/packages/b3/33/039287d410230ee125daee57373ac01940d3030d18dba1c29cd3089dc3ca/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7", size = 2079315 }, 268 | { url = "https://files.pythonhosted.org/packages/1f/85/6d8b2646d99c062d7da2d0ab2faeb0d6ca9cca4c02da6076376042a20da3/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde", size = 2249063 }, 269 | { url = "https://files.pythonhosted.org/packages/17/d7/c37d208d5738f7b9ad8f22ae8a727d88ebf9c16c04ed2475122cc3f7224a/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add", size = 2254631 }, 270 | { url = "https://files.pythonhosted.org/packages/13/e0/bafa46476d328e4553b85ab9b2f7409e7aaef0ce4c937c894821c542d347/pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c", size = 2080877 }, 271 | { url = "https://files.pythonhosted.org/packages/0b/76/1794e440c1801ed35415238d2c728f26cd12695df9057154ad768b7b991c/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a", size = 2042858 }, 272 | { url = "https://files.pythonhosted.org/packages/73/b4/9cd7b081fb0b1b4f8150507cd59d27b275c3e22ad60b35cb19ea0977d9b9/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc", size = 1873745 }, 273 | { url = "https://files.pythonhosted.org/packages/e1/d7/9ddb7575d4321e40d0363903c2576c8c0c3280ebea137777e5ab58d723e3/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b", size = 1904188 }, 274 | { url = "https://files.pythonhosted.org/packages/d1/a8/3194ccfe461bb08da19377ebec8cb4f13c9bd82e13baebc53c5c7c39a029/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe", size = 2083479 }, 275 | { url = "https://files.pythonhosted.org/packages/42/c7/84cb569555d7179ca0b3f838cef08f66f7089b54432f5b8599aac6e9533e/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5", size = 2118415 }, 276 | { url = "https://files.pythonhosted.org/packages/3b/67/72abb8c73e0837716afbb58a59cc9e3ae43d1aa8677f3b4bc72c16142716/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761", size = 2079623 }, 277 | { url = "https://files.pythonhosted.org/packages/0b/cd/c59707e35a47ba4cbbf153c3f7c56420c58653b5801b055dc52cccc8e2dc/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850", size = 2250175 }, 278 | { url = "https://files.pythonhosted.org/packages/84/32/e4325a6676b0bed32d5b084566ec86ed7fd1e9bcbfc49c578b1755bde920/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544", size = 2254674 }, 279 | { url = "https://files.pythonhosted.org/packages/12/6f/5596dc418f2e292ffc661d21931ab34591952e2843e7168ea5a52591f6ff/pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5", size = 2080951 }, 280 | ] 281 | 282 | [[package]] 283 | name = "pydantic-settings" 284 | version = "2.8.1" 285 | source = { registry = "https://pypi.org/simple" } 286 | dependencies = [ 287 | { name = "pydantic" }, 288 | { name = "python-dotenv" }, 289 | ] 290 | sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } 291 | wheels = [ 292 | { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, 293 | ] 294 | 295 | [[package]] 296 | name = "pygments" 297 | version = "2.19.1" 298 | source = { registry = "https://pypi.org/simple" } 299 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 300 | wheels = [ 301 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 302 | ] 303 | 304 | [[package]] 305 | name = "python-dotenv" 306 | version = "1.1.0" 307 | source = { registry = "https://pypi.org/simple" } 308 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } 309 | wheels = [ 310 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, 311 | ] 312 | 313 | [[package]] 314 | name = "rich" 315 | version = "14.0.0" 316 | source = { registry = "https://pypi.org/simple" } 317 | dependencies = [ 318 | { name = "markdown-it-py" }, 319 | { name = "pygments" }, 320 | { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 321 | ] 322 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } 323 | wheels = [ 324 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, 325 | ] 326 | 327 | [[package]] 328 | name = "shellingham" 329 | version = "1.5.4" 330 | source = { registry = "https://pypi.org/simple" } 331 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 332 | wheels = [ 333 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, 334 | ] 335 | 336 | [[package]] 337 | name = "sniffio" 338 | version = "1.3.1" 339 | source = { registry = "https://pypi.org/simple" } 340 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 341 | wheels = [ 342 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 343 | ] 344 | 345 | [[package]] 346 | name = "sse-starlette" 347 | version = "2.2.1" 348 | source = { registry = "https://pypi.org/simple" } 349 | dependencies = [ 350 | { name = "anyio" }, 351 | { name = "starlette" }, 352 | ] 353 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } 354 | wheels = [ 355 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, 356 | ] 357 | 358 | [[package]] 359 | name = "starlette" 360 | version = "0.46.1" 361 | source = { registry = "https://pypi.org/simple" } 362 | dependencies = [ 363 | { name = "anyio" }, 364 | ] 365 | sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } 366 | wheels = [ 367 | { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, 368 | ] 369 | 370 | [[package]] 371 | name = "typer" 372 | version = "0.15.2" 373 | source = { registry = "https://pypi.org/simple" } 374 | dependencies = [ 375 | { name = "click" }, 376 | { name = "rich" }, 377 | { name = "shellingham" }, 378 | { name = "typing-extensions" }, 379 | ] 380 | sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } 381 | wheels = [ 382 | { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, 383 | ] 384 | 385 | [[package]] 386 | name = "typing-extensions" 387 | version = "4.13.1" 388 | source = { registry = "https://pypi.org/simple" } 389 | sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633 } 390 | wheels = [ 391 | { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739 }, 392 | ] 393 | 394 | [[package]] 395 | name = "typing-inspection" 396 | version = "0.4.0" 397 | source = { registry = "https://pypi.org/simple" } 398 | dependencies = [ 399 | { name = "typing-extensions" }, 400 | ] 401 | sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } 402 | wheels = [ 403 | { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, 404 | ] 405 | 406 | [[package]] 407 | name = "uvicorn" 408 | version = "0.34.0" 409 | source = { registry = "https://pypi.org/simple" } 410 | dependencies = [ 411 | { name = "click" }, 412 | { name = "h11" }, 413 | { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 414 | ] 415 | sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } 416 | wheels = [ 417 | { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, 418 | ] 419 | --------------------------------------------------------------------------------