├── .dockerignore ├── .gitignore ├── .python-version ├── Dockerfile ├── README.md ├── api_key_auth.py ├── blog.md ├── main.py ├── pyproject.toml ├── uv.lock └── weather.py /.dockerignore: -------------------------------------------------------------------------------- 1 | # Python bytecode 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | 8 | # Distribution / packaging 9 | build/ 10 | develop-eggs/ 11 | dist/ 12 | downloads/ 13 | eggs/ 14 | .eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | wheels/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Virtual environments 26 | venv/ 27 | env/ 28 | ENV/ 29 | .env/ 30 | .venv/ 31 | virtualenv/ 32 | 33 | # Unit test / coverage reports 34 | htmlcov/ 35 | .tox/ 36 | .coverage 37 | .coverage.* 38 | .cache 39 | nosetests.xml 40 | coverage.xml 41 | *.cover 42 | .hypothesis/ 43 | 44 | # Development tools 45 | .idea/ 46 | .vscode/ 47 | *.swp 48 | *.swo 49 | .DS_Store 50 | .git 51 | .gitignore 52 | 53 | # Local development settings 54 | .env 55 | .env.* 56 | local_settings.py 57 | 58 | # Logs 59 | logs/ 60 | *.log 61 | 62 | # Documentation 63 | docs/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | .vscode/mcp.json 12 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13-slim 2 | 3 | # Install uv. 4 | COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ 5 | 6 | # Copy the application into the container. 7 | COPY . /app 8 | 9 | # Install the application dependencies. 10 | WORKDIR /app 11 | RUN uv sync --frozen --no-cache 12 | 13 | # Run the application. 14 | CMD ["/app/.venv/bin/fastapi", "run", "/app/main.py", "--port", "8000", "--host", "0.0.0.0"] 15 | 16 | EXPOSE 8000 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Container Apps remote MCP server example 2 | 3 | This MCP server uses SSE transport and is authenticated with an API key. 4 | 5 | ## Running locally 6 | 7 | Prerequisites: 8 | * Python 3.11 or later 9 | * [uv](https://docs.astral.sh/uv/getting-started/installation/) 10 | 11 | Run the server locally: 12 | 13 | ```bash 14 | uv venv 15 | uv sync 16 | 17 | # linux/macOS 18 | export API_KEYS= 19 | # windows 20 | set API_KEYS= 21 | 22 | uv run fastapi dev main.py 23 | ``` 24 | 25 | VS Code MCP configuration (mcp.json): 26 | 27 | ```json 28 | { 29 | "inputs": [ 30 | { 31 | "type": "promptString", 32 | "id": "weather-api-key", 33 | "description": "Weather API Key", 34 | "password": true 35 | } 36 | ], 37 | "servers": { 38 | "weather-sse": { 39 | "type": "sse", 40 | "url": "http://localhost:8000/sse", 41 | "headers": { 42 | "x-api-key": "${input:weather-api-key}" 43 | } 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ## Deploy to Azure Container Apps 50 | 51 | ```bash 52 | az containerapp up -g -n weather-mcp --environment mcp -l westus --env-vars API_KEYS= --source . 53 | ``` 54 | 55 | If the deployment is successful, the Azure CLI returns the URL of the app. You can use this URL to connect to the server from Visual Studio Code. 56 | 57 | If the deployment fails, try again after updating the CLI and the Azure Container Apps extension: 58 | 59 | ```bash 60 | az upgrade 61 | az extension add -n containerapp --upgrade 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /api_key_auth.py: -------------------------------------------------------------------------------- 1 | from fastapi import Security, HTTPException, status 2 | from fastapi.security import APIKeyHeader 3 | import os 4 | 5 | api_key_header = APIKeyHeader(name="x-api-key") 6 | 7 | def ensure_valid_api_key(api_key_header: str = Security(api_key_header)): 8 | def check_api_key(key: str) -> bool: 9 | valid_keys = os.environ.get("API_KEYS", "").split(",") 10 | return key in valid_keys and key != "" 11 | 12 | if not check_api_key(api_key_header): 13 | raise HTTPException( 14 | status_code=status.HTTP_403_FORBIDDEN, 15 | detail="Invalid API key", 16 | ) 17 | -------------------------------------------------------------------------------- /blog.md: -------------------------------------------------------------------------------- 1 | # Host remote MCP servers in Azure Container Apps 2 | 3 | Whether you're building AI agents or using LLM powered tools like GitHub Copilot in Visual Studio Code, you're probably hearing a lot about [MCP (Model Context Protocol)](https://modelcontextprotocol.io/introduction) lately; maybe you're already using it. It's quickly becoming the standard interoperability layer between different components of the AI stack. 4 | 5 | In this article, we'll explore how to run remote MCP servers as serverless containers in [Azure Container Apps](https://learn.microsoft.com/azure/container-apps/overview) and use them in GitHub Copilot in Visual Studio Code. 6 | 7 | ## MCP servers today 8 | 9 | MCP follows a client-server architecture. It all starts with a host, such as GitHub Copilot in VS Code or Claude Desktop. The host acts as a client and connects to one or more MCP servers. 10 | 11 | Servers are the main extensibility points in MCP. Each server provides new tools, skills, and capabilities to the host. For example, a server can expose a search engine that allows an agent running in the host to search the web. Or the agent can use a GitHub MCP server to work with GitHub repositories. The possibilities are endless. 12 | 13 | By default, most MCP servers run locally. The host starts each server as a subprocess and communicates with it using standard input and output. This works well for many scenarios. It's also safer from a security perspective because the server isn't exposed on a port and any secrets that the server needs remain local to the host. 14 | 15 | ## Remote MCP servers 16 | 17 | As MCP matures, we're starting to see the community explore the idea of remote MCP servers. This is a natural evolution of the protocol. Instead of running a local MCP server that calls a search engine, imagine connecting to a large-scale, multi-tenant, remote MCP server in the cloud that's managed by the search engine provider. 18 | 19 | MCP is still relatively in its early stages, and the community is still evolving the spec to support remote servers. 20 | 21 | First, MCP needs to support a transport layer that allows the host to connect to a remote server. An HTTP with SSE (Server-Sent Events) transport layer was initially added, but the most recent spec is already deprecating it in favor of a [streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http). 22 | 23 | And second, now that there are transports that allow remote servers, there now needs to be ways for clients to authenticate with remote servers. The spec in this area is still evolving as well. 24 | 25 | ## Building a remote MCP server 26 | 27 | As of the time of this writing, clients are still catching up to the evolving spec. A client like VS Code already supports remote servers, but the current version only supports SSE transport and the only authentication method it works well with is API key headers. 28 | 29 | We'll now deploy a remote MCP server using SSE transport and API key auth. 30 | 31 | To follow along, clone the repo: 32 | 33 | ```bash 34 | git clone https://github.com/anthonychu/azure-container-apps-mcp-sample.git 35 | ``` 36 | 37 | We begin with the weather MCP server from the official quickstart (see `weather.py`). It exposes a simple tool for getting the weather in a given location in the United States. It runs locally and communicates with the host using standard input/output. 38 | 39 | To add SSE transport, we add a FastAPI server (`main.py`). 40 | 41 | ```python 42 | from fastapi import FastAPI, Request, Depends 43 | from mcp.server.sse import SseServerTransport 44 | from starlette.routing import Mount 45 | from weather import mcp 46 | 47 | 48 | app = FastAPI(docs_url=None, redoc_url=None) 49 | 50 | sse = SseServerTransport("/messages/") 51 | app.router.routes.append(Mount("/messages", app=sse.handle_post_message)) 52 | 53 | @app.get("/sse", tags=["MCP"]) 54 | async def handle_sse(request: Request): 55 | 56 | async with sse.connect_sse(request.scope, request.receive, request._send) as ( 57 | read_stream, 58 | write_stream, 59 | ): 60 | init_options = mcp._mcp_server.create_initialization_options() 61 | 62 | await mcp._mcp_server.run( 63 | read_stream, 64 | write_stream, 65 | init_options, 66 | ) 67 | ``` 68 | 69 | The MCP SDK already has a built-in SSE transport implementation. We just needed to add a FastAPI server that exposes the `/sse` endpoint and handles the `/messages` endpoint using the MCP SDK. 70 | 71 | And to add API key authentication, we create a function that checks the `x-api-key` header in the request and compares it to a list of valid API keys. In more advanced scenarios, we could use a database or a secret management service to store the API keys and map them to individual users. 72 | 73 | ```python 74 | from fastapi import Security, HTTPException, status 75 | from fastapi.security import APIKeyHeader 76 | import os 77 | 78 | api_key_header = APIKeyHeader(name="x-api-key") 79 | 80 | def ensure_valid_api_key(api_key_header: str = Security(api_key_header)): 81 | def check_api_key(key: str) -> bool: 82 | valid_keys = os.environ.get("API_KEYS", "").split(",") 83 | return key in valid_keys and key != "" 84 | 85 | if not check_api_key(api_key_header): 86 | raise HTTPException( 87 | status_code=status.HTTP_403_FORBIDDEN, 88 | detail="Invalid API key", 89 | ) 90 | ``` 91 | 92 | We can then add the `ensure_valid_api_key` function as a dependency so that it's required to call any route on the server. 93 | 94 | ```python 95 | app = FastAPI(docs_url=None, redoc_url=None, dependencies=[Depends(ensure_valid_api_key)]) 96 | ``` 97 | 98 | ## Deploying the server to Azure Container Apps 99 | 100 | Now let's deploy the server to Azure Container Apps. 101 | 102 | Making sure we're in the root of the repo, we can run the following command to deploy the app along with all other necessary resources: 103 | 104 | ```bash 105 | az containerapp up -g -n weather-mcp --environment mcp -l westus --env-vars API_KEYS= --source . 106 | ``` 107 | 108 | It uses the Dockerfile in the repo to build the container image and deploy it to Azure Container Apps. The `--env-vars` flag sets the `API_KEYS` environment variable in the container. Provide a secure value. You can add multiple API keys by separating them with commas. 109 | 110 | After the deployment is complete, the Azure CLI returns the URL of the app. 111 | 112 | ## Connecting to the server from Visual Studio Code 113 | 114 | In VS Code, run the `MCP: Add server` command. Choose `HTTP (Server-sent events)` as the transport and enter the URL of the `/sse` endpoint on the server (for example, `https://weather-mcp.sleepypanda.westus.azurecontainerapps.io/sse`). 115 | 116 | VS Code's `mcp.json` configuration file opens. Because the server requires an API key, make a couple of changes: 117 | 118 | * Add a new `promptString` input for the API key. This will prompt you for the API key when you start the server. 119 | * Add the `x-api-key` header to the server configuration. This will send the API key to the server when connecting. 120 | 121 | ```json 122 | { 123 | "inputs": [ 124 | { 125 | "type": "promptString", 126 | "id": "weather-api-key", 127 | "description": "Weather API Key", 128 | "password": true 129 | } 130 | ], 131 | "servers": { 132 | "weather-sse": { 133 | "type": "sse", 134 | "url": "https://weather-mcp.sleepypanda.westus.azurecontainerapps.io/sse", 135 | "headers": { 136 | "x-api-key": "${input:weather-api-key}" 137 | } 138 | } 139 | } 140 | } 141 | ``` 142 | 143 | Next to the server in the JSON file, click the "Start" button. This initiates VS Code's connection to the server. You'll be prompted for the API key. Enter an API key you set in the `API_KEYS` environment variable when deploying the app. 144 | 145 | Open the Copilot chat interface by typing `Ctrl+Shift+I` (`Cmd+Shift+I` on macOS). Change the mode to "Agent". Type "What's the weather in Seattle?" and press enter. The agent should use the MCP server and respond with the current weather in Seattle. 146 | 147 | For more information on using MCP servers in VS Code, check out the [VS Code docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers). 148 | 149 | ## Conclusion 150 | 151 | In this article, we explored how to run remote MCP servers in Azure Container Apps. We started with a simple weather MCP server and added SSE transport and API key authentication. We then deployed the server to Azure Container Apps and used it in GitHub Copilot in Visual Studio Code. 152 | 153 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request, Depends 2 | from mcp.server.sse import SseServerTransport 3 | from starlette.routing import Mount 4 | from weather import mcp 5 | from api_key_auth import ensure_valid_api_key 6 | import uvicorn 7 | 8 | 9 | app = FastAPI(docs_url=None, redoc_url=None, dependencies=[Depends(ensure_valid_api_key)]) 10 | 11 | sse = SseServerTransport("/messages/") 12 | app.router.routes.append(Mount("/messages", app=sse.handle_post_message)) 13 | 14 | @app.get("/sse", tags=["MCP"]) 15 | async def handle_sse(request: Request): 16 | 17 | async with sse.connect_sse(request.scope, request.receive, request._send) as ( 18 | read_stream, 19 | write_stream, 20 | ): 21 | init_options = mcp._mcp_server.create_initialization_options() 22 | 23 | await mcp._mcp_server.run( 24 | read_stream, 25 | write_stream, 26 | init_options, 27 | ) 28 | 29 | 30 | if __name__ == "__main__": 31 | 32 | uvicorn.run(app, host="0.0.0.0", port=8000) -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "20240407-test-mcp" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "fastapi[standard]>=0.115.12", 9 | "httpx>=0.28.1", 10 | "mcp[cli]>=1.6.0", 11 | ] 12 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.13" 4 | 5 | [[package]] 6 | name = "20240407-test-mcp" 7 | version = "0.1.0" 8 | source = { virtual = "." } 9 | dependencies = [ 10 | { name = "fastapi", extra = ["standard"] }, 11 | { name = "httpx" }, 12 | { name = "mcp", extra = ["cli"] }, 13 | ] 14 | 15 | [package.metadata] 16 | requires-dist = [ 17 | { name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" }, 18 | { name = "httpx", specifier = ">=0.28.1" }, 19 | { name = "mcp", extras = ["cli"], specifier = ">=1.6.0" }, 20 | ] 21 | 22 | [[package]] 23 | name = "annotated-types" 24 | version = "0.7.0" 25 | source = { registry = "https://pypi.org/simple" } 26 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 29 | ] 30 | 31 | [[package]] 32 | name = "anyio" 33 | version = "4.9.0" 34 | source = { registry = "https://pypi.org/simple" } 35 | dependencies = [ 36 | { name = "idna" }, 37 | { name = "sniffio" }, 38 | ] 39 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } 40 | wheels = [ 41 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, 42 | ] 43 | 44 | [[package]] 45 | name = "certifi" 46 | version = "2025.1.31" 47 | source = { registry = "https://pypi.org/simple" } 48 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } 49 | wheels = [ 50 | { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, 51 | ] 52 | 53 | [[package]] 54 | name = "click" 55 | version = "8.1.8" 56 | source = { registry = "https://pypi.org/simple" } 57 | dependencies = [ 58 | { name = "colorama", marker = "sys_platform == 'win32'" }, 59 | ] 60 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 61 | wheels = [ 62 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 63 | ] 64 | 65 | [[package]] 66 | name = "colorama" 67 | version = "0.4.6" 68 | source = { registry = "https://pypi.org/simple" } 69 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 70 | wheels = [ 71 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 72 | ] 73 | 74 | [[package]] 75 | name = "dnspython" 76 | version = "2.7.0" 77 | source = { registry = "https://pypi.org/simple" } 78 | sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 } 79 | wheels = [ 80 | { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, 81 | ] 82 | 83 | [[package]] 84 | name = "email-validator" 85 | version = "2.2.0" 86 | source = { registry = "https://pypi.org/simple" } 87 | dependencies = [ 88 | { name = "dnspython" }, 89 | { name = "idna" }, 90 | ] 91 | sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 } 92 | wheels = [ 93 | { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 }, 94 | ] 95 | 96 | [[package]] 97 | name = "fastapi" 98 | version = "0.115.12" 99 | source = { registry = "https://pypi.org/simple" } 100 | dependencies = [ 101 | { name = "pydantic" }, 102 | { name = "starlette" }, 103 | { name = "typing-extensions" }, 104 | ] 105 | sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } 106 | wheels = [ 107 | { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, 108 | ] 109 | 110 | [package.optional-dependencies] 111 | standard = [ 112 | { name = "email-validator" }, 113 | { name = "fastapi-cli", extra = ["standard"] }, 114 | { name = "httpx" }, 115 | { name = "jinja2" }, 116 | { name = "python-multipart" }, 117 | { name = "uvicorn", extra = ["standard"] }, 118 | ] 119 | 120 | [[package]] 121 | name = "fastapi-cli" 122 | version = "0.0.7" 123 | source = { registry = "https://pypi.org/simple" } 124 | dependencies = [ 125 | { name = "rich-toolkit" }, 126 | { name = "typer" }, 127 | { name = "uvicorn", extra = ["standard"] }, 128 | ] 129 | sdist = { url = "https://files.pythonhosted.org/packages/fe/73/82a5831fbbf8ed75905bacf5b2d9d3dfd6f04d6968b29fe6f72a5ae9ceb1/fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e", size = 16753 } 130 | wheels = [ 131 | { url = "https://files.pythonhosted.org/packages/a1/e6/5daefc851b514ce2287d8f5d358ae4341089185f78f3217a69d0ce3a390c/fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4", size = 10705 }, 132 | ] 133 | 134 | [package.optional-dependencies] 135 | standard = [ 136 | { name = "uvicorn", extra = ["standard"] }, 137 | ] 138 | 139 | [[package]] 140 | name = "h11" 141 | version = "0.14.0" 142 | source = { registry = "https://pypi.org/simple" } 143 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 144 | wheels = [ 145 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 146 | ] 147 | 148 | [[package]] 149 | name = "httpcore" 150 | version = "1.0.7" 151 | source = { registry = "https://pypi.org/simple" } 152 | dependencies = [ 153 | { name = "certifi" }, 154 | { name = "h11" }, 155 | ] 156 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } 157 | wheels = [ 158 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, 159 | ] 160 | 161 | [[package]] 162 | name = "httptools" 163 | version = "0.6.4" 164 | source = { registry = "https://pypi.org/simple" } 165 | sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } 166 | wheels = [ 167 | { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214 }, 168 | { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431 }, 169 | { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121 }, 170 | { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805 }, 171 | { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858 }, 172 | { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042 }, 173 | { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 }, 174 | ] 175 | 176 | [[package]] 177 | name = "httpx" 178 | version = "0.28.1" 179 | source = { registry = "https://pypi.org/simple" } 180 | dependencies = [ 181 | { name = "anyio" }, 182 | { name = "certifi" }, 183 | { name = "httpcore" }, 184 | { name = "idna" }, 185 | ] 186 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 187 | wheels = [ 188 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 189 | ] 190 | 191 | [[package]] 192 | name = "httpx-sse" 193 | version = "0.4.0" 194 | source = { registry = "https://pypi.org/simple" } 195 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 196 | wheels = [ 197 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 198 | ] 199 | 200 | [[package]] 201 | name = "idna" 202 | version = "3.10" 203 | source = { registry = "https://pypi.org/simple" } 204 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 205 | wheels = [ 206 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 207 | ] 208 | 209 | [[package]] 210 | name = "jinja2" 211 | version = "3.1.6" 212 | source = { registry = "https://pypi.org/simple" } 213 | dependencies = [ 214 | { name = "markupsafe" }, 215 | ] 216 | sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } 217 | wheels = [ 218 | { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, 219 | ] 220 | 221 | [[package]] 222 | name = "markdown-it-py" 223 | version = "3.0.0" 224 | source = { registry = "https://pypi.org/simple" } 225 | dependencies = [ 226 | { name = "mdurl" }, 227 | ] 228 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 229 | wheels = [ 230 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 231 | ] 232 | 233 | [[package]] 234 | name = "markupsafe" 235 | version = "3.0.2" 236 | source = { registry = "https://pypi.org/simple" } 237 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } 238 | wheels = [ 239 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, 240 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, 241 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, 242 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, 243 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, 244 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, 245 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, 246 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, 247 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, 248 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, 249 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, 250 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, 251 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, 252 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, 253 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, 254 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, 255 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, 256 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, 257 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, 258 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, 259 | ] 260 | 261 | [[package]] 262 | name = "mcp" 263 | version = "1.6.0" 264 | source = { registry = "https://pypi.org/simple" } 265 | dependencies = [ 266 | { name = "anyio" }, 267 | { name = "httpx" }, 268 | { name = "httpx-sse" }, 269 | { name = "pydantic" }, 270 | { name = "pydantic-settings" }, 271 | { name = "sse-starlette" }, 272 | { name = "starlette" }, 273 | { name = "uvicorn" }, 274 | ] 275 | sdist = { url = "https://files.pythonhosted.org/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723", size = 200031 } 276 | wheels = [ 277 | { url = "https://files.pythonhosted.org/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0", size = 76077 }, 278 | ] 279 | 280 | [package.optional-dependencies] 281 | cli = [ 282 | { name = "python-dotenv" }, 283 | { name = "typer" }, 284 | ] 285 | 286 | [[package]] 287 | name = "mdurl" 288 | version = "0.1.2" 289 | source = { registry = "https://pypi.org/simple" } 290 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 291 | wheels = [ 292 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 293 | ] 294 | 295 | [[package]] 296 | name = "pydantic" 297 | version = "2.11.2" 298 | source = { registry = "https://pypi.org/simple" } 299 | dependencies = [ 300 | { name = "annotated-types" }, 301 | { name = "pydantic-core" }, 302 | { name = "typing-extensions" }, 303 | { name = "typing-inspection" }, 304 | ] 305 | sdist = { url = "https://files.pythonhosted.org/packages/b0/41/832125a41fe098b58d1fdd04ae819b4dc6b34d6b09ed78304fd93d4bc051/pydantic-2.11.2.tar.gz", hash = "sha256:2138628e050bd7a1e70b91d4bf4a91167f4ad76fdb83209b107c8d84b854917e", size = 784742 } 306 | wheels = [ 307 | { url = "https://files.pythonhosted.org/packages/bf/c2/0f3baea344d0b15e35cb3e04ad5b953fa05106b76efbf4c782a3f47f22f5/pydantic-2.11.2-py3-none-any.whl", hash = "sha256:7f17d25846bcdf89b670a86cdfe7b29a9f1c9ca23dee154221c9aa81845cfca7", size = 443295 }, 308 | ] 309 | 310 | [[package]] 311 | name = "pydantic-core" 312 | version = "2.33.1" 313 | source = { registry = "https://pypi.org/simple" } 314 | dependencies = [ 315 | { name = "typing-extensions" }, 316 | ] 317 | sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } 318 | wheels = [ 319 | { 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 }, 320 | { 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 }, 321 | { 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 }, 322 | { 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 }, 323 | { 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 }, 324 | { 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 }, 325 | { 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 }, 326 | { 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 }, 327 | { 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 }, 328 | { 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 }, 329 | { 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 }, 330 | { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, 331 | { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, 332 | { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, 333 | { 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 }, 334 | { 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 }, 335 | { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, 336 | ] 337 | 338 | [[package]] 339 | name = "pydantic-settings" 340 | version = "2.8.1" 341 | source = { registry = "https://pypi.org/simple" } 342 | dependencies = [ 343 | { name = "pydantic" }, 344 | { name = "python-dotenv" }, 345 | ] 346 | sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } 347 | wheels = [ 348 | { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, 349 | ] 350 | 351 | [[package]] 352 | name = "pygments" 353 | version = "2.19.1" 354 | source = { registry = "https://pypi.org/simple" } 355 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 356 | wheels = [ 357 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 358 | ] 359 | 360 | [[package]] 361 | name = "python-dotenv" 362 | version = "1.1.0" 363 | source = { registry = "https://pypi.org/simple" } 364 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } 365 | wheels = [ 366 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, 367 | ] 368 | 369 | [[package]] 370 | name = "python-multipart" 371 | version = "0.0.20" 372 | source = { registry = "https://pypi.org/simple" } 373 | sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } 374 | wheels = [ 375 | { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, 376 | ] 377 | 378 | [[package]] 379 | name = "pyyaml" 380 | version = "6.0.2" 381 | source = { registry = "https://pypi.org/simple" } 382 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 383 | wheels = [ 384 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 385 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 386 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 387 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 388 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 389 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 390 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 391 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 392 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 393 | ] 394 | 395 | [[package]] 396 | name = "rich" 397 | version = "14.0.0" 398 | source = { registry = "https://pypi.org/simple" } 399 | dependencies = [ 400 | { name = "markdown-it-py" }, 401 | { name = "pygments" }, 402 | ] 403 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } 404 | wheels = [ 405 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, 406 | ] 407 | 408 | [[package]] 409 | name = "rich-toolkit" 410 | version = "0.14.1" 411 | source = { registry = "https://pypi.org/simple" } 412 | dependencies = [ 413 | { name = "click" }, 414 | { name = "rich" }, 415 | { name = "typing-extensions" }, 416 | ] 417 | sdist = { url = "https://files.pythonhosted.org/packages/2e/ea/13945d58d556a28dfb0f774ad5c8af759527390e59505a40d164bf8ce1ce/rich_toolkit-0.14.1.tar.gz", hash = "sha256:9248e2d087bfc01f3e4c5c8987e05f7fa744d00dd22fa2be3aa6e50255790b3f", size = 104416 } 418 | wheels = [ 419 | { url = "https://files.pythonhosted.org/packages/66/e8/61c5b12d1567fdba41a6775db12a090d88b8305424ee7c47259c70d33cb4/rich_toolkit-0.14.1-py3-none-any.whl", hash = "sha256:dc92c0117d752446d04fdc828dbca5873bcded213a091a5d3742a2beec2e6559", size = 24177 }, 420 | ] 421 | 422 | [[package]] 423 | name = "shellingham" 424 | version = "1.5.4" 425 | source = { registry = "https://pypi.org/simple" } 426 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 427 | wheels = [ 428 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, 429 | ] 430 | 431 | [[package]] 432 | name = "sniffio" 433 | version = "1.3.1" 434 | source = { registry = "https://pypi.org/simple" } 435 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 436 | wheels = [ 437 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 438 | ] 439 | 440 | [[package]] 441 | name = "sse-starlette" 442 | version = "2.2.1" 443 | source = { registry = "https://pypi.org/simple" } 444 | dependencies = [ 445 | { name = "anyio" }, 446 | { name = "starlette" }, 447 | ] 448 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } 449 | wheels = [ 450 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, 451 | ] 452 | 453 | [[package]] 454 | name = "starlette" 455 | version = "0.46.1" 456 | source = { registry = "https://pypi.org/simple" } 457 | dependencies = [ 458 | { name = "anyio" }, 459 | ] 460 | sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } 461 | wheels = [ 462 | { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, 463 | ] 464 | 465 | [[package]] 466 | name = "typer" 467 | version = "0.15.2" 468 | source = { registry = "https://pypi.org/simple" } 469 | dependencies = [ 470 | { name = "click" }, 471 | { name = "rich" }, 472 | { name = "shellingham" }, 473 | { name = "typing-extensions" }, 474 | ] 475 | sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } 476 | wheels = [ 477 | { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, 478 | ] 479 | 480 | [[package]] 481 | name = "typing-extensions" 482 | version = "4.13.1" 483 | source = { registry = "https://pypi.org/simple" } 484 | sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633 } 485 | wheels = [ 486 | { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739 }, 487 | ] 488 | 489 | [[package]] 490 | name = "typing-inspection" 491 | version = "0.4.0" 492 | source = { registry = "https://pypi.org/simple" } 493 | dependencies = [ 494 | { name = "typing-extensions" }, 495 | ] 496 | sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } 497 | wheels = [ 498 | { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, 499 | ] 500 | 501 | [[package]] 502 | name = "uvicorn" 503 | version = "0.34.0" 504 | source = { registry = "https://pypi.org/simple" } 505 | dependencies = [ 506 | { name = "click" }, 507 | { name = "h11" }, 508 | ] 509 | sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } 510 | wheels = [ 511 | { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, 512 | ] 513 | 514 | [package.optional-dependencies] 515 | standard = [ 516 | { name = "colorama", marker = "sys_platform == 'win32'" }, 517 | { name = "httptools" }, 518 | { name = "python-dotenv" }, 519 | { name = "pyyaml" }, 520 | { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, 521 | { name = "watchfiles" }, 522 | { name = "websockets" }, 523 | ] 524 | 525 | [[package]] 526 | name = "uvloop" 527 | version = "0.21.0" 528 | source = { registry = "https://pypi.org/simple" } 529 | sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } 530 | wheels = [ 531 | { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123 }, 532 | { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325 }, 533 | { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806 }, 534 | { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068 }, 535 | { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428 }, 536 | { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, 537 | ] 538 | 539 | [[package]] 540 | name = "watchfiles" 541 | version = "1.0.5" 542 | source = { registry = "https://pypi.org/simple" } 543 | dependencies = [ 544 | { name = "anyio" }, 545 | ] 546 | sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537 } 547 | wheels = [ 548 | { url = "https://files.pythonhosted.org/packages/c7/62/435766874b704f39b2fecd8395a29042db2b5ec4005bd34523415e9bd2e0/watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d", size = 401531 }, 549 | { url = "https://files.pythonhosted.org/packages/6e/a6/e52a02c05411b9cb02823e6797ef9bbba0bfaf1bb627da1634d44d8af833/watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763", size = 392417 }, 550 | { url = "https://files.pythonhosted.org/packages/3f/53/c4af6819770455932144e0109d4854437769672d7ad897e76e8e1673435d/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40", size = 453423 }, 551 | { url = "https://files.pythonhosted.org/packages/cb/d1/8e88df58bbbf819b8bc5cfbacd3c79e01b40261cad0fc84d1e1ebd778a07/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563", size = 458185 }, 552 | { url = "https://files.pythonhosted.org/packages/ff/70/fffaa11962dd5429e47e478a18736d4e42bec42404f5ee3b92ef1b87ad60/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04", size = 486696 }, 553 | { url = "https://files.pythonhosted.org/packages/39/db/723c0328e8b3692d53eb273797d9a08be6ffb1d16f1c0ba2bdbdc2a3852c/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f", size = 522327 }, 554 | { url = "https://files.pythonhosted.org/packages/cd/05/9fccc43c50c39a76b68343484b9da7b12d42d0859c37c61aec018c967a32/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a", size = 499741 }, 555 | { url = "https://files.pythonhosted.org/packages/23/14/499e90c37fa518976782b10a18b18db9f55ea73ca14641615056f8194bb3/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827", size = 453995 }, 556 | { url = "https://files.pythonhosted.org/packages/61/d9/f75d6840059320df5adecd2c687fbc18960a7f97b55c300d20f207d48aef/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a", size = 629693 }, 557 | { url = "https://files.pythonhosted.org/packages/fc/17/180ca383f5061b61406477218c55d66ec118e6c0c51f02d8142895fcf0a9/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936", size = 624677 }, 558 | { url = "https://files.pythonhosted.org/packages/bf/15/714d6ef307f803f236d69ee9d421763707899d6298d9f3183e55e366d9af/watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc", size = 277804 }, 559 | { url = "https://files.pythonhosted.org/packages/a8/b4/c57b99518fadf431f3ef47a610839e46e5f8abf9814f969859d1c65c02c7/watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11", size = 291087 }, 560 | ] 561 | 562 | [[package]] 563 | name = "websockets" 564 | version = "15.0.1" 565 | source = { registry = "https://pypi.org/simple" } 566 | sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } 567 | wheels = [ 568 | { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 }, 569 | { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 }, 570 | { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 }, 571 | { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 }, 572 | { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 }, 573 | { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 }, 574 | { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 }, 575 | { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 }, 576 | { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 }, 577 | { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, 578 | { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, 579 | { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, 580 | ] 581 | -------------------------------------------------------------------------------- /weather.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import httpx 3 | from mcp.server.fastmcp import FastMCP 4 | 5 | # Initialize FastMCP server 6 | mcp = FastMCP("weather") 7 | 8 | # Constants 9 | NWS_API_BASE = "https://api.weather.gov" 10 | USER_AGENT = "weather-app/1.0" 11 | 12 | async def make_nws_request(url: str) -> dict[str, Any] | None: 13 | """Make a request to the NWS API with proper error handling.""" 14 | headers = { 15 | "User-Agent": USER_AGENT, 16 | "Accept": "application/geo+json" 17 | } 18 | async with httpx.AsyncClient() as client: 19 | try: 20 | response = await client.get(url, headers=headers, timeout=30.0) 21 | response.raise_for_status() 22 | return response.json() 23 | except Exception: 24 | return None 25 | 26 | def format_alert(feature: dict) -> str: 27 | """Format an alert feature into a readable string.""" 28 | props = feature["properties"] 29 | return f""" 30 | Event: {props.get('event', 'Unknown')} 31 | Area: {props.get('areaDesc', 'Unknown')} 32 | Severity: {props.get('severity', 'Unknown')} 33 | Description: {props.get('description', 'No description available')} 34 | Instructions: {props.get('instruction', 'No specific instructions provided')} 35 | """ 36 | 37 | @mcp.tool() 38 | async def get_alerts(state: str) -> str: 39 | """Get weather alerts for a US state. 40 | 41 | Args: 42 | state: Two-letter US state code (e.g. CA, NY) 43 | """ 44 | url = f"{NWS_API_BASE}/alerts/active/area/{state}" 45 | data = await make_nws_request(url) 46 | 47 | if not data or "features" not in data: 48 | return "Unable to fetch alerts or no alerts found." 49 | 50 | if not data["features"]: 51 | return "No active alerts for this state." 52 | 53 | alerts = [format_alert(feature) for feature in data["features"]] 54 | return "\n---\n".join(alerts) 55 | 56 | @mcp.tool() 57 | async def get_forecast(latitude: float, longitude: float) -> str: 58 | """Get weather forecast for a location. 59 | 60 | Args: 61 | latitude: Latitude of the location 62 | longitude: Longitude of the location 63 | """ 64 | # First get the forecast grid endpoint 65 | points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" 66 | points_data = await make_nws_request(points_url) 67 | 68 | if not points_data: 69 | return "Unable to fetch forecast data for this location." 70 | 71 | # Get the forecast URL from the points response 72 | forecast_url = points_data["properties"]["forecast"] 73 | forecast_data = await make_nws_request(forecast_url) 74 | 75 | if not forecast_data: 76 | return "Unable to fetch detailed forecast." 77 | 78 | # Format the periods into a readable forecast 79 | periods = forecast_data["properties"]["periods"] 80 | forecasts = [] 81 | for period in periods[:5]: # Only show next 5 periods 82 | forecast = f""" 83 | {period['name']}: 84 | Temperature: {period['temperature']}°{period['temperatureUnit']} 85 | Wind: {period['windSpeed']} {period['windDirection']} 86 | Forecast: {period['detailedForecast']} 87 | """ 88 | forecasts.append(forecast) 89 | 90 | return "\n---\n".join(forecasts) 91 | 92 | 93 | if __name__ == "__main__": 94 | # Initialize and run the server 95 | mcp.run(transport='stdio') 96 | --------------------------------------------------------------------------------