├── figma_mcp ├── __init__.py ├── .env.example ├── __pycache__ │ ├── main.cpython-313.pyc │ ├── __init__.cpython-312.pyc │ ├── __init__.cpython-313.pyc │ ├── clean_node.cpython-312.pyc │ └── clean_node.cpython-313.pyc ├── main.py └── clean_node.py ├── figma_mcp.egg-info ├── dependency_links.txt ├── top_level.txt ├── entry_points.txt ├── requires.txt ├── SOURCES.txt └── PKG-INFO ├── .gitattributes ├── MANIFEST.in ├── .gitignore ├── pyproject.toml ├── LICENSE ├── README.md └── uv.lock /figma_mcp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /figma_mcp.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /figma_mcp.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | figma_mcp 2 | -------------------------------------------------------------------------------- /figma_mcp/.env.example: -------------------------------------------------------------------------------- 1 | FIGMA_API_TOKEN=your_figma_api_token -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /figma_mcp.egg-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | figma-mcp = figma_mcp.main:main 3 | -------------------------------------------------------------------------------- /figma_mcp.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | mcp[cli]>=1.3.0 2 | python-dotenv>=1.0.1 3 | requests>=2.32.3 4 | -------------------------------------------------------------------------------- /figma_mcp/__pycache__/main.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayZeeDesign/figma-mcp/HEAD/figma_mcp/__pycache__/main.cpython-313.pyc -------------------------------------------------------------------------------- /figma_mcp/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayZeeDesign/figma-mcp/HEAD/figma_mcp/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /figma_mcp/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayZeeDesign/figma-mcp/HEAD/figma_mcp/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /figma_mcp/__pycache__/clean_node.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayZeeDesign/figma-mcp/HEAD/figma_mcp/__pycache__/clean_node.cpython-312.pyc -------------------------------------------------------------------------------- /figma_mcp/__pycache__/clean_node.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayZeeDesign/figma-mcp/HEAD/figma_mcp/__pycache__/clean_node.cpython-313.pyc -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include .env.example 4 | global-exclude __pycache__ 5 | global-exclude *.py[cod] 6 | global-exclude *.so 7 | global-exclude .DS_Store -------------------------------------------------------------------------------- /figma_mcp.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | MANIFEST.in 3 | README.md 4 | pyproject.toml 5 | figma_mcp/__init__.py 6 | figma_mcp/clean_node.py 7 | figma_mcp/main.py 8 | figma_mcp.egg-info/PKG-INFO 9 | figma_mcp.egg-info/SOURCES.txt 10 | figma_mcp.egg-info/dependency_links.txt 11 | figma_mcp.egg-info/entry_points.txt 12 | figma_mcp.egg-info/requires.txt 13 | figma_mcp.egg-info/top_level.txt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /dist 3 | dist/figma_mcp-0.1.0-py3-none-any.whl 4 | dist/figma_mcp-0.1.0.tar.gz 5 | dist/figma_mcp-0.1.1-py3-none-any.whl 6 | dist/figma_mcp-0.1.1.tar.gz 7 | dist/figma_mcp-0.1.2-py3-none-any.whl 8 | dist/figma_mcp-0.1.2.tar.gz 9 | figma_mcp.egg-info/PKG-INFO 10 | figma_mcp.egg-info/SOURCES.txt 11 | dist/figma_mcp-0.1.0-py3-none-any.whl 12 | dist/figma_mcp-0.1.0.tar.gz 13 | dist/figma_mcp-0.1.1-py3-none-any.whl 14 | dist/figma_mcp-0.1.1.tar.gz 15 | dist/figma_mcp-0.1.2-py3-none-any.whl 16 | dist/figma_mcp-0.1.2.tar.gz 17 | figma_mcp.egg-info/PKG-INFO 18 | figma_mcp.egg-info/SOURCES.txt 19 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "figma-mcp" 3 | version = "0.1.3" 4 | description = "Allow your AI coding agents to access figma files & prototypes directly" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "mcp[cli]>=1.3.0", 9 | "python-dotenv>=1.0.1", 10 | "requests>=2.32.3", 11 | ] 12 | authors = [ 13 | {name = "Jason Zhou", email = "jason.zhou.design@gmail.com"} 14 | ] 15 | license = {text = "MIT"} 16 | classifiers = [ 17 | "Programming Language :: Python :: 3", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | ] 21 | 22 | [project.urls] 23 | "Homepage" = "https://github.com/JayZeeDesign/figma-mcp" 24 | "Bug Tracker" = "https://github.com/JayZeeDesign/figma-mcp" 25 | 26 | [project.scripts] 27 | figma-mcp = "figma_mcp.main:main" 28 | 29 | [build-system] 30 | requires = ["setuptools>=42", "wheel"] 31 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Figma MCP Python 2 | 3 | [![PyPI version](https://badge.fury.io/py/figma-mcp.svg)](https://badge.fury.io/py/figma-mcp) 4 | 5 | Allow your AI coding agents to access Figma files & prototypes directly. 6 | You can DM me for any issues / improvements: https://x.com/jasonzhou1993 7 | 8 | 9 | figma-mcp MCP server 10 | 11 | 12 | ## Quick Installation with pipx 13 | 14 | ```bash 15 | pipx install figma-mcp 16 | ``` 17 | 18 | ### For Cursor: 19 | 20 | 1. In settings, add an MCP server using the command: 21 | ```shell 22 | figma-mcp --figma-api-key=your_figma_key 23 | ``` 24 | 25 | 2. OR Add a `.cursor/mcp.json` file in your project: 26 | 27 | ```json 28 | { 29 | "mcpServers": { 30 | "figma-python": { 31 | "command": "figma-mcp", 32 | "args": [ 33 | "--figma-api-key=your_figma_key" 34 | ] 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | 41 | ### For other IDEs like Windsurf, use an MCP configuration file (e.g., `mcp_config.json`): 42 | 43 | ```json 44 | { 45 | "mcpServers": { 46 | "figma-python": { 47 | "command": "figma-mcp", 48 | "args": [ 49 | "--figma-api-key=your_figma_key" 50 | ] 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | 57 | ## Install uv and set up the environment 58 | ```bash 59 | curl -LsSf https://astral.sh/uv/install.sh | sh 60 | uv venv 61 | source .venv/bin/activate 62 | uv sync 63 | ``` 64 | 65 | ## Test locally 66 | ```bash 67 | python -m figma_mcp.main 68 | ``` -------------------------------------------------------------------------------- /figma_mcp.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.2 2 | Name: figma-mcp 3 | Version: 0.1.3 4 | Summary: Allow your AI coding agents to access figma files & prototypes directly 5 | Author-email: Jason Zhou 6 | License: MIT 7 | Project-URL: Homepage, https://github.com/JayZeeDesign/figma-mcp 8 | Project-URL: Bug Tracker, https://github.com/JayZeeDesign/figma-mcp 9 | Classifier: Programming Language :: Python :: 3 10 | Classifier: License :: OSI Approved :: MIT License 11 | Classifier: Operating System :: OS Independent 12 | Requires-Python: >=3.12 13 | Description-Content-Type: text/markdown 14 | License-File: LICENSE 15 | Requires-Dist: mcp[cli]>=1.3.0 16 | Requires-Dist: python-dotenv>=1.0.1 17 | Requires-Dist: requests>=2.32.3 18 | 19 | # Figma MCP Python 20 | 21 | [![PyPI version](https://badge.fury.io/py/figma-mcp.svg)](https://badge.fury.io/py/figma-mcp) 22 | 23 | Allow your AI coding agents to access Figma files & prototypes directly. 24 | You can DM me for any issues / improvements: https://x.com/jasonzhou1993 25 | 26 | ## Quick Installation with pipx 27 | 28 | ```bash 29 | pipx install figma-mcp 30 | ``` 31 | 32 | ### For Cursor: 33 | 34 | 1. In settings, add an MCP server using the command: 35 | ```shell 36 | figma-mcp --figma-api-key=your_figma_key 37 | ``` 38 | 39 | 2. OR Add a `.cursor/mcp.json` file in your project: 40 | 41 | ```json 42 | { 43 | "mcpServers": { 44 | "figma-python": { 45 | "command": "figma-mcp", 46 | "args": [ 47 | "--figma-api-key=your_figma_key" 48 | ] 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | 55 | ### For other IDEs like Windsurf, use an MCP configuration file (e.g., `mcp_config.json`): 56 | 57 | ```json 58 | { 59 | "mcpServers": { 60 | "figma-python": { 61 | "command": "figma-mcp", 62 | "args": [ 63 | "--figma-api-key=your_figma_key" 64 | ] 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | 71 | ## Install uv and set up the environment 72 | ```bash 73 | curl -LsSf https://astral.sh/uv/install.sh | sh 74 | uv venv 75 | source .venv/bin/activate 76 | uv sync 77 | ``` 78 | 79 | ## Test locally 80 | ```bash 81 | python -m figma_mcp.main 82 | ``` 83 | 84 | 85 | -------------------------------------------------------------------------------- /figma_mcp/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import os 4 | import argparse 5 | import json 6 | from pathlib import Path 7 | 8 | # Import the clean_node module from the package 9 | from figma_mcp.clean_node import transform_figma_json 10 | 11 | import requests 12 | from dotenv import load_dotenv 13 | from mcp.server.fastmcp import FastMCP 14 | 15 | 16 | def main(): 17 | """Entry point for the figma-mcp CLI.""" 18 | # Load environment variables 19 | load_dotenv() 20 | 21 | # Create an MCP server 22 | mcp = FastMCP("figma") 23 | 24 | # Parse command-line arguments 25 | parser = argparse.ArgumentParser(description="Figma MCP Python Server") 26 | parser.add_argument( 27 | "--figma-api-key", 28 | type=str, 29 | help="Figma API token to use instead of environment variable", 30 | ) 31 | args = parser.parse_args() 32 | 33 | # Use command-line argument if provided, otherwise use environment variable 34 | FIGMA_API_TOKEN = args.figma_api_key or os.getenv("FIGMA_API_TOKEN") 35 | if not FIGMA_API_TOKEN: 36 | print("Error: Figma API token not provided. Please set FIGMA_API_TOKEN environment variable or use --figma-api-key.") 37 | sys.exit(1) 38 | 39 | # Define helper functions 40 | def fetch_figma_file(file_key: str, download_file: bool = False): 41 | headers = {"X-Figma-Token": FIGMA_API_TOKEN} 42 | url = f"https://api.figma.com/v1/files/{file_key}" 43 | response = requests.get(url, headers=headers) 44 | 45 | if response.status_code != 200: 46 | return {"error": f"Failed to fetch Figma file: {response.status_code}"} 47 | 48 | figma_data = response.json() 49 | 50 | if download_file: 51 | with open(f"{file_key}.json", "w") as f: 52 | json.dump(figma_data, f, indent=2) 53 | 54 | return figma_data 55 | 56 | def extract_prototype_connections(figma_data): 57 | connections = [] 58 | 59 | def traverse_nodes(node): 60 | if "children" in node: 61 | for child in node.get("children", []): 62 | if "transitionNodeID" in child and child.get("transitionNodeID"): 63 | connections.append({ 64 | "sourceNodeID": child.get("id"), 65 | "sourceNodeName": child.get("name", "Unnamed"), 66 | "targetNodeID": child.get("transitionNodeID"), 67 | "interaction": child.get("transitionDuration", 0), 68 | }) 69 | traverse_nodes(child) 70 | 71 | # Start traversal from the document 72 | if "document" in figma_data: 73 | traverse_nodes(figma_data["document"]) 74 | 75 | return connections 76 | 77 | def fetch_figma_nodes(file_key: str, node_ids: str): 78 | headers = {"X-Figma-Token": FIGMA_API_TOKEN} 79 | url = f"https://api.figma.com/v1/files/{file_key}/nodes?ids={node_ids}" 80 | response = requests.get(url, headers=headers) 81 | 82 | if response.status_code != 200: 83 | return {"error": f"Failed to fetch Figma nodes: {response.status_code}"} 84 | 85 | return response.json() 86 | 87 | # Register MCP tools 88 | @mcp.tool() 89 | def get_components(file_key: str) -> list[dict]: 90 | """Get components available in a Figma file 91 | 92 | Args: 93 | file_key (str): The file key found in the shared Figma URL 94 | 95 | Returns: 96 | list[dict]: List of components found in the Figma file 97 | """ 98 | figma_data = fetch_figma_file(file_key) 99 | if "error" in figma_data: 100 | return [{"error": figma_data["error"]}] 101 | 102 | components = [] 103 | for component_id, component_data in figma_data.get("components", {}).items(): 104 | components.append({ 105 | "id": component_id, 106 | "name": component_data.get("name", "Unnamed Component"), 107 | "description": component_data.get("description", ""), 108 | }) 109 | 110 | return components 111 | 112 | @mcp.tool() 113 | def get_node(file_key: str, node_id: str) -> dict: 114 | """Get a specific node from a Figma file 115 | 116 | Args: 117 | file_key (str): The file key found in the shared Figma URL, e.g. if url is https://www.figma.com/proto/do4pJqHwNwH1nBrrscu6Ld/Untitled?page-id=0%3A1&node-id=0-3&viewport=361%2C361%2C0.08&t=9SVttILbgMlPWuL0-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=0%3A3, then the file key is do4pJqHwNwH1nBrrscu6Ld 118 | node_id (str): The ID of the node to retrieve, has to be in format x:x, e.g. in url it will be like 0-3, but it should be 0:3 119 | 120 | Returns: 121 | dict: The node data if found, empty dict if not found 122 | """ 123 | # Convert node_id format if needed (from 0-3 to 0:3) 124 | if "-" in node_id and ":" not in node_id: 125 | node_id = node_id.replace("-", ":") 126 | 127 | response = fetch_figma_nodes(file_key, node_id) 128 | if "error" in response: 129 | return {"error": response["error"]} 130 | 131 | # Find the node in the response 132 | def find_node_by_id(data, target_id): 133 | if isinstance(data, dict): 134 | if data.get("id") == target_id: 135 | return transform_figma_json(data) 136 | 137 | for key, value in data.items(): 138 | result = find_node_by_id(value, target_id) 139 | if result: 140 | return result 141 | 142 | elif isinstance(data, list): 143 | for item in data: 144 | result = find_node_by_id(item, target_id) 145 | if result: 146 | return result 147 | 148 | return None 149 | 150 | # Look for the node in the nodes data 151 | for node_key, node_data in response.get("nodes", {}).items(): 152 | if node_data.get("document"): 153 | node = find_node_by_id(node_data["document"], node_id) 154 | if node: 155 | return node 156 | 157 | return {} 158 | 159 | @mcp.tool() 160 | def get_workflow(file_key: str) -> list[dict]: 161 | """Get workflows available in a Figma file 162 | 163 | Args: 164 | file_key (str): The file key found in the shared Figma URL, e.g. if url is https://www.figma.com/proto/do4pJqHwNwH1nBrrscu6Ld/Untitled?page-id=0%3A1&node-id=0-3&viewport=361%2C361%2C0.08&t=9SVttILbgMlPWuL0-1&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=0%3A3, then the file key is do4pJqHwNwH1nBrrscu6Ld 165 | 166 | Returns: 167 | list[dict]: List of workflow connections found in the Figma file 168 | """ 169 | figma_data = fetch_figma_file(file_key) 170 | if "error" in figma_data: 171 | return [{"error": figma_data["error"]}] 172 | 173 | connections = extract_prototype_connections(figma_data) 174 | return connections 175 | 176 | # Start the MCP server 177 | print(f"Starting Figma MCP server...") 178 | mcp.run() 179 | 180 | 181 | if __name__ == "__main__": 182 | main() -------------------------------------------------------------------------------- /figma_mcp/clean_node.py: -------------------------------------------------------------------------------- 1 | import json 2 | import hashlib 3 | 4 | def rgba_to_hex(rgba): 5 | """ 6 | Convert an RGBA dict into a #RRGGBB hex string (ignoring alpha in this demo). 7 | """ 8 | r = int(rgba["r"] * 255) 9 | g = int(rgba["g"] * 255) 10 | b = int(rgba["b"] * 255) 11 | return f"#{r:02x}{g:02x}{b:02x}" 12 | 13 | def style_hash(style_data): 14 | """ 15 | Create a hash from style data to use as a unique identifier. 16 | """ 17 | style_str = json.dumps(style_data, sort_keys=True) 18 | return hashlib.md5(style_str.encode()).hexdigest() 19 | 20 | def figma_align_to_flex(figma_val): 21 | """ 22 | Convert Figma alignment values to CSS flex values. 23 | """ 24 | align_map = { 25 | "MIN": "flex-start", 26 | "CENTER": "center", 27 | "MAX": "flex-end", 28 | "SPACE_BETWEEN": "space-between" 29 | } 30 | return align_map.get(figma_val, "flex-start") 31 | 32 | def get_fill_style_id(fills, styles): 33 | """ 34 | Process fill styles and add them to the styles dictionary. 35 | """ 36 | if not fills or len(fills) == 0 or fills[0].get("visible", True) == False: 37 | return None 38 | 39 | fill = fills[0] 40 | 41 | if fill["type"] == "SOLID": 42 | color = fill["color"] 43 | opacity = fill.get("opacity", 1) 44 | 45 | # Create a style object 46 | style_data = { 47 | "backgroundColor": rgba_to_hex(color), 48 | "opacity": opacity 49 | } 50 | 51 | # Generate a unique ID for this style 52 | style_id = style_hash(style_data) 53 | 54 | # Add to styles dictionary if not already present 55 | if style_id not in styles: 56 | styles[style_id] = style_data 57 | 58 | return style_id 59 | 60 | return None 61 | 62 | def get_layout_style_id(raw_node, styles): 63 | """ 64 | Process layout styles and add them to the styles dictionary. 65 | """ 66 | layout_style = {} 67 | 68 | # Extract layout properties 69 | if "layoutMode" in raw_node: 70 | layout_mode = raw_node["layoutMode"] 71 | 72 | if layout_mode == "HORIZONTAL": 73 | layout_style["display"] = "flex" 74 | layout_style["flexDirection"] = "row" 75 | elif layout_mode == "VERTICAL": 76 | layout_style["display"] = "flex" 77 | layout_style["flexDirection"] = "column" 78 | 79 | # Process padding 80 | if "paddingLeft" in raw_node: 81 | layout_style["paddingLeft"] = f"{raw_node['paddingLeft']}px" 82 | if "paddingRight" in raw_node: 83 | layout_style["paddingRight"] = f"{raw_node['paddingRight']}px" 84 | if "paddingTop" in raw_node: 85 | layout_style["paddingTop"] = f"{raw_node['paddingTop']}px" 86 | if "paddingBottom" in raw_node: 87 | layout_style["paddingBottom"] = f"{raw_node['paddingBottom']}px" 88 | 89 | # Process spacing 90 | if "itemSpacing" in raw_node: 91 | layout_style["gap"] = f"{raw_node['itemSpacing']}px" 92 | 93 | # Process alignment 94 | if "primaryAxisAlignItems" in raw_node: 95 | primary_align = raw_node["primaryAxisAlignItems"] 96 | counter_align = raw_node.get("counterAxisAlignItems", "MIN") 97 | 98 | def align_map_primary(val): 99 | if layout_mode == "HORIZONTAL": 100 | return figma_align_to_flex(val) 101 | return figma_align_to_flex(val) 102 | 103 | def align_map_counter(val): 104 | if layout_mode == "HORIZONTAL": 105 | return figma_align_to_flex(val) 106 | return figma_align_to_flex(val) 107 | 108 | layout_style["justifyContent"] = align_map_primary(primary_align) 109 | layout_style["alignItems"] = align_map_counter(counter_align) 110 | 111 | # Process size constraints 112 | if "absoluteBoundingBox" in raw_node: 113 | bbox = raw_node["absoluteBoundingBox"] 114 | layout_style["width"] = f"{bbox['width']}px" 115 | layout_style["height"] = f"{bbox['height']}px" 116 | 117 | # Process border radius 118 | border_radius = get_border_radius(raw_node) 119 | if border_radius: 120 | layout_style["borderRadius"] = border_radius 121 | 122 | # Process corner smoothing 123 | corner_smoothing = get_corner_smoothing(raw_node) 124 | if corner_smoothing: 125 | layout_style["borderRadius"] = corner_smoothing 126 | 127 | # Generate a unique ID for this style if it's not empty 128 | if layout_style: 129 | style_id = style_hash(layout_style) 130 | 131 | # Add to styles dictionary if not already present 132 | if style_id not in styles: 133 | styles[style_id] = layout_style 134 | 135 | return style_id 136 | 137 | return None 138 | 139 | def get_border_radius(raw_node): 140 | """ 141 | Extract border radius from a node. 142 | """ 143 | if "cornerRadius" in raw_node: 144 | return f"{raw_node['cornerRadius']}px" 145 | return None 146 | 147 | def get_corner_smoothing(raw_node): 148 | """ 149 | Extract corner smoothing from a node. 150 | """ 151 | if "cornerSmoothing" in raw_node and raw_node["cornerSmoothing"] > 0: 152 | return f"{raw_node['cornerSmoothing']}px" 153 | return None 154 | 155 | def get_text_style_id(raw_node, styles): 156 | """ 157 | Process text styles and add them to the styles dictionary. 158 | """ 159 | if raw_node["type"] != "TEXT" or "style" not in raw_node: 160 | return None 161 | 162 | text_style = {} 163 | node_style = raw_node["style"] 164 | 165 | # Extract text properties 166 | if "fontFamily" in node_style: 167 | text_style["fontFamily"] = node_style["fontFamily"] 168 | if "fontWeight" in node_style: 169 | text_style["fontWeight"] = node_style["fontWeight"] 170 | if "fontSize" in node_style: 171 | text_style["fontSize"] = f"{node_style['fontSize']}px" 172 | if "lineHeightPx" in node_style: 173 | text_style["lineHeight"] = f"{node_style['lineHeightPx']}px" 174 | if "letterSpacing" in node_style: 175 | text_style["letterSpacing"] = f"{node_style['letterSpacing']}px" 176 | if "textAlignHorizontal" in node_style: 177 | align_map = {"LEFT": "left", "CENTER": "center", "RIGHT": "right", "JUSTIFIED": "justify"} 178 | text_style["textAlign"] = align_map.get(node_style["textAlignHorizontal"], "left") 179 | 180 | # Process fill color for text 181 | if "fills" in raw_node and raw_node["fills"]: 182 | fill = raw_node["fills"][0] 183 | if fill["type"] == "SOLID": 184 | text_style["color"] = rgba_to_hex(fill["color"]) 185 | 186 | # Generate a unique ID for this style if it's not empty 187 | if text_style: 188 | style_id = style_hash(text_style) 189 | 190 | # Add to styles dictionary if not already present 191 | if style_id not in styles: 192 | styles[style_id] = text_style 193 | 194 | return style_id 195 | 196 | return None 197 | 198 | def transform_node(raw_node, styles): 199 | """ 200 | Transform a Figma node into a simplified structure. 201 | """ 202 | node_type = raw_node["type"] 203 | 204 | # Basic node properties 205 | node = { 206 | "id": raw_node["id"], 207 | "name": raw_node["name"], 208 | "type": node_type, 209 | "visible": raw_node.get("visible", True) 210 | } 211 | 212 | # Process styles 213 | fill_style_id = get_fill_style_id(raw_node.get("fills", []), styles) 214 | if fill_style_id: 215 | node["fillStyleId"] = fill_style_id 216 | 217 | layout_style_id = get_layout_style_id(raw_node, styles) 218 | if layout_style_id: 219 | node["layoutStyleId"] = layout_style_id 220 | 221 | text_style_id = get_text_style_id(raw_node, styles) 222 | if text_style_id: 223 | node["textStyleId"] = text_style_id 224 | 225 | # Process text content 226 | if node_type == "TEXT" and "characters" in raw_node: 227 | node["characters"] = raw_node["characters"] 228 | 229 | # Process image content 230 | if node_type == "RECTANGLE" and "fills" in raw_node: 231 | for fill in raw_node.get("fills", []): 232 | if fill["type"] == "IMAGE" and "imageRef" in fill: 233 | node["imageRef"] = fill["imageRef"] 234 | 235 | # Process children 236 | if "children" in raw_node: 237 | node["children"] = [] 238 | for child in raw_node["children"]: 239 | # Skip invisible nodes 240 | if child.get("visible", True) == False: 241 | continue 242 | 243 | transformed_child = transform_node(child, styles) 244 | node["children"].append(transformed_child) 245 | 246 | # Process component properties 247 | if "componentPropertyReferences" in raw_node: 248 | node["componentPropertyReferences"] = raw_node["componentPropertyReferences"] 249 | 250 | # Process component properties 251 | if "componentProperties" in raw_node: 252 | node["componentProperties"] = raw_node["componentProperties"] 253 | 254 | # Process component property definitions 255 | if "componentPropertyDefinitions" in raw_node: 256 | node["componentPropertyDefinitions"] = raw_node["componentPropertyDefinitions"] 257 | 258 | # Process variants 259 | if "variantProperties" in raw_node: 260 | node["variantProperties"] = raw_node["variantProperties"] 261 | 262 | # Process component set 263 | if "componentSetId" in raw_node: 264 | node["componentSetId"] = raw_node["componentSetId"] 265 | 266 | # Process component 267 | if "componentId" in raw_node: 268 | node["componentId"] = raw_node["componentId"] 269 | 270 | return node 271 | 272 | def transform_figma_json(raw_data): 273 | """ 274 | Transform Figma JSON data into a simplified structure. 275 | """ 276 | # Initialize styles dictionary 277 | styles = {} 278 | 279 | # Transform the document 280 | if isinstance(raw_data, dict) and "document" in raw_data: 281 | document = transform_node(raw_data["document"], styles) 282 | else: 283 | document = transform_node(raw_data, styles) 284 | 285 | # Return the transformed data 286 | return { 287 | "document": document, 288 | "styles": styles 289 | } -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.12" 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.8.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "idna" }, 20 | { name = "sniffio" }, 21 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 22 | ] 23 | sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } 24 | wheels = [ 25 | { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, 26 | ] 27 | 28 | [[package]] 29 | name = "certifi" 30 | version = "2025.1.31" 31 | source = { registry = "https://pypi.org/simple" } 32 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } 33 | wheels = [ 34 | { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, 35 | ] 36 | 37 | [[package]] 38 | name = "charset-normalizer" 39 | version = "3.4.1" 40 | source = { registry = "https://pypi.org/simple" } 41 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } 42 | wheels = [ 43 | { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, 44 | { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, 45 | { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, 46 | { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, 47 | { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, 48 | { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, 49 | { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, 50 | { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, 51 | { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, 52 | { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, 53 | { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, 54 | { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, 55 | { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, 56 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, 57 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, 58 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, 59 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, 60 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, 61 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, 62 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, 63 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, 64 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, 65 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, 66 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, 67 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, 68 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, 69 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, 70 | ] 71 | 72 | [[package]] 73 | name = "click" 74 | version = "8.1.8" 75 | source = { registry = "https://pypi.org/simple" } 76 | dependencies = [ 77 | { name = "colorama", marker = "sys_platform == 'win32'" }, 78 | ] 79 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 80 | wheels = [ 81 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 82 | ] 83 | 84 | [[package]] 85 | name = "colorama" 86 | version = "0.4.6" 87 | source = { registry = "https://pypi.org/simple" } 88 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 89 | wheels = [ 90 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 91 | ] 92 | 93 | [[package]] 94 | name = "figma-mcp" 95 | version = "0.1.2" 96 | source = { editable = "." } 97 | dependencies = [ 98 | { name = "mcp", extra = ["cli"] }, 99 | { name = "python-dotenv" }, 100 | { name = "requests" }, 101 | ] 102 | 103 | [package.metadata] 104 | requires-dist = [ 105 | { name = "mcp", extras = ["cli"], specifier = ">=1.3.0" }, 106 | { name = "python-dotenv", specifier = ">=1.0.1" }, 107 | { name = "requests", specifier = ">=2.32.3" }, 108 | ] 109 | 110 | [[package]] 111 | name = "h11" 112 | version = "0.14.0" 113 | source = { registry = "https://pypi.org/simple" } 114 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 115 | wheels = [ 116 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 117 | ] 118 | 119 | [[package]] 120 | name = "httpcore" 121 | version = "1.0.7" 122 | source = { registry = "https://pypi.org/simple" } 123 | dependencies = [ 124 | { name = "certifi" }, 125 | { name = "h11" }, 126 | ] 127 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } 128 | wheels = [ 129 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, 130 | ] 131 | 132 | [[package]] 133 | name = "httpx" 134 | version = "0.28.1" 135 | source = { registry = "https://pypi.org/simple" } 136 | dependencies = [ 137 | { name = "anyio" }, 138 | { name = "certifi" }, 139 | { name = "httpcore" }, 140 | { name = "idna" }, 141 | ] 142 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 143 | wheels = [ 144 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 145 | ] 146 | 147 | [[package]] 148 | name = "httpx-sse" 149 | version = "0.4.0" 150 | source = { registry = "https://pypi.org/simple" } 151 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 152 | wheels = [ 153 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 154 | ] 155 | 156 | [[package]] 157 | name = "idna" 158 | version = "3.10" 159 | source = { registry = "https://pypi.org/simple" } 160 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 161 | wheels = [ 162 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 163 | ] 164 | 165 | [[package]] 166 | name = "markdown-it-py" 167 | version = "3.0.0" 168 | source = { registry = "https://pypi.org/simple" } 169 | dependencies = [ 170 | { name = "mdurl" }, 171 | ] 172 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 173 | wheels = [ 174 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 175 | ] 176 | 177 | [[package]] 178 | name = "mcp" 179 | version = "1.3.0" 180 | source = { registry = "https://pypi.org/simple" } 181 | dependencies = [ 182 | { name = "anyio" }, 183 | { name = "httpx" }, 184 | { name = "httpx-sse" }, 185 | { name = "pydantic" }, 186 | { name = "pydantic-settings" }, 187 | { name = "sse-starlette" }, 188 | { name = "starlette" }, 189 | { name = "uvicorn" }, 190 | ] 191 | sdist = { url = "https://files.pythonhosted.org/packages/6b/b6/81e5f2490290351fc97bf46c24ff935128cb7d34d68e3987b522f26f7ada/mcp-1.3.0.tar.gz", hash = "sha256:f409ae4482ce9d53e7ac03f3f7808bcab735bdfc0fba937453782efb43882d45", size = 150235 } 192 | wheels = [ 193 | { url = "https://files.pythonhosted.org/packages/d0/d2/a9e87b506b2094f5aa9becc1af5178842701b27217fa43877353da2577e3/mcp-1.3.0-py3-none-any.whl", hash = "sha256:2829d67ce339a249f803f22eba5e90385eafcac45c94b00cab6cef7e8f217211", size = 70672 }, 194 | ] 195 | 196 | [package.optional-dependencies] 197 | cli = [ 198 | { name = "python-dotenv" }, 199 | { name = "typer" }, 200 | ] 201 | 202 | [[package]] 203 | name = "mdurl" 204 | version = "0.1.2" 205 | source = { registry = "https://pypi.org/simple" } 206 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 207 | wheels = [ 208 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 209 | ] 210 | 211 | [[package]] 212 | name = "pydantic" 213 | version = "2.10.6" 214 | source = { registry = "https://pypi.org/simple" } 215 | dependencies = [ 216 | { name = "annotated-types" }, 217 | { name = "pydantic-core" }, 218 | { name = "typing-extensions" }, 219 | ] 220 | sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } 221 | wheels = [ 222 | { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, 223 | ] 224 | 225 | [[package]] 226 | name = "pydantic-core" 227 | version = "2.27.2" 228 | source = { registry = "https://pypi.org/simple" } 229 | dependencies = [ 230 | { name = "typing-extensions" }, 231 | ] 232 | sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } 233 | wheels = [ 234 | { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, 235 | { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, 236 | { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, 237 | { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, 238 | { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, 239 | { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, 240 | { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, 241 | { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, 242 | { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, 243 | { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, 244 | { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, 245 | { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, 246 | { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, 247 | { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, 248 | { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, 249 | { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, 250 | { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, 251 | { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, 252 | { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, 253 | { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, 254 | { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, 255 | { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, 256 | { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, 257 | { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, 258 | { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, 259 | { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, 260 | { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, 261 | { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, 262 | ] 263 | 264 | [[package]] 265 | name = "pydantic-settings" 266 | version = "2.8.1" 267 | source = { registry = "https://pypi.org/simple" } 268 | dependencies = [ 269 | { name = "pydantic" }, 270 | { name = "python-dotenv" }, 271 | ] 272 | sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } 273 | wheels = [ 274 | { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, 275 | ] 276 | 277 | [[package]] 278 | name = "pygments" 279 | version = "2.19.1" 280 | source = { registry = "https://pypi.org/simple" } 281 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 282 | wheels = [ 283 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 284 | ] 285 | 286 | [[package]] 287 | name = "python-dotenv" 288 | version = "1.0.1" 289 | source = { registry = "https://pypi.org/simple" } 290 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } 291 | wheels = [ 292 | { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, 293 | ] 294 | 295 | [[package]] 296 | name = "requests" 297 | version = "2.32.3" 298 | source = { registry = "https://pypi.org/simple" } 299 | dependencies = [ 300 | { name = "certifi" }, 301 | { name = "charset-normalizer" }, 302 | { name = "idna" }, 303 | { name = "urllib3" }, 304 | ] 305 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 306 | wheels = [ 307 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 308 | ] 309 | 310 | [[package]] 311 | name = "rich" 312 | version = "13.9.4" 313 | source = { registry = "https://pypi.org/simple" } 314 | dependencies = [ 315 | { name = "markdown-it-py" }, 316 | { name = "pygments" }, 317 | ] 318 | sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } 319 | wheels = [ 320 | { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, 321 | ] 322 | 323 | [[package]] 324 | name = "shellingham" 325 | version = "1.5.4" 326 | source = { registry = "https://pypi.org/simple" } 327 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 328 | wheels = [ 329 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, 330 | ] 331 | 332 | [[package]] 333 | name = "sniffio" 334 | version = "1.3.1" 335 | source = { registry = "https://pypi.org/simple" } 336 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 337 | wheels = [ 338 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 339 | ] 340 | 341 | [[package]] 342 | name = "sse-starlette" 343 | version = "2.2.1" 344 | source = { registry = "https://pypi.org/simple" } 345 | dependencies = [ 346 | { name = "anyio" }, 347 | { name = "starlette" }, 348 | ] 349 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } 350 | wheels = [ 351 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, 352 | ] 353 | 354 | [[package]] 355 | name = "starlette" 356 | version = "0.46.0" 357 | source = { registry = "https://pypi.org/simple" } 358 | dependencies = [ 359 | { name = "anyio" }, 360 | ] 361 | sdist = { url = "https://files.pythonhosted.org/packages/44/b6/fb9a32e3c5d59b1e383c357534c63c2d3caa6f25bf3c59dd89d296ecbaec/starlette-0.46.0.tar.gz", hash = "sha256:b359e4567456b28d473d0193f34c0de0ed49710d75ef183a74a5ce0499324f50", size = 2575568 } 362 | wheels = [ 363 | { url = "https://files.pythonhosted.org/packages/41/94/8af675a62e3c91c2dee47cf92e602cfac86e8767b1a1ac3caf1b327c2ab0/starlette-0.46.0-py3-none-any.whl", hash = "sha256:913f0798bd90ba90a9156383bcf1350a17d6259451d0d8ee27fc0cf2db609038", size = 71991 }, 364 | ] 365 | 366 | [[package]] 367 | name = "typer" 368 | version = "0.15.2" 369 | source = { registry = "https://pypi.org/simple" } 370 | dependencies = [ 371 | { name = "click" }, 372 | { name = "rich" }, 373 | { name = "shellingham" }, 374 | { name = "typing-extensions" }, 375 | ] 376 | sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } 377 | wheels = [ 378 | { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, 379 | ] 380 | 381 | [[package]] 382 | name = "typing-extensions" 383 | version = "4.12.2" 384 | source = { registry = "https://pypi.org/simple" } 385 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 386 | wheels = [ 387 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 388 | ] 389 | 390 | [[package]] 391 | name = "urllib3" 392 | version = "2.3.0" 393 | source = { registry = "https://pypi.org/simple" } 394 | sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } 395 | wheels = [ 396 | { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, 397 | ] 398 | 399 | [[package]] 400 | name = "uvicorn" 401 | version = "0.34.0" 402 | source = { registry = "https://pypi.org/simple" } 403 | dependencies = [ 404 | { name = "click" }, 405 | { name = "h11" }, 406 | ] 407 | sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } 408 | wheels = [ 409 | { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, 410 | ] 411 | --------------------------------------------------------------------------------