├── .cursor └── rules │ ├── clean-code.mdc │ ├── codequality.mdc │ └── python.mdc ├── .gitignore ├── .python-version ├── LICENSE ├── README.md ├── protocals ├── example_weather.py ├── mcp.md └── sdk.md ├── pyproject.toml ├── server.py └── uv.lock /.cursor/rules/clean-code.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | --- 7 | description: Guidelines for writing clean, maintainable, and human-readable code. Apply these rules when writing or reviewing code to ensure consistency and quality. 8 | globs: 9 | --- 10 | # Clean Code Guidelines 11 | 12 | ## Constants Over Magic Numbers 13 | - Replace hard-coded values with named constants 14 | - Use descriptive constant names that explain the value's purpose 15 | - Keep constants at the top of the file or in a dedicated constants file 16 | 17 | ## Meaningful Names 18 | - Variables, functions, and classes should reveal their purpose 19 | - Names should explain why something exists and how it's used 20 | - Avoid abbreviations unless they're universally understood 21 | 22 | ## Smart Comments 23 | - Don't comment on what the code does - make the code self-documenting 24 | - Use comments to explain why something is done a certain way 25 | - Document APIs, complex algorithms, and non-obvious side effects 26 | 27 | ## Single Responsibility 28 | - Each function should do exactly one thing 29 | - Functions should be small and focused 30 | - If a function needs a comment to explain what it does, it should be split 31 | 32 | ## DRY (Don't Repeat Yourself) 33 | - Extract repeated code into reusable functions 34 | - Share common logic through proper abstraction 35 | - Maintain single sources of truth 36 | 37 | ## Clean Structure 38 | - Keep related code together 39 | - Organize code in a logical hierarchy 40 | - Use consistent file and folder naming conventions 41 | 42 | ## Encapsulation 43 | - Hide implementation details 44 | - Expose clear interfaces 45 | - Move nested conditionals into well-named functions 46 | 47 | ## Code Quality Maintenance 48 | - Refactor continuously 49 | - Fix technical debt early 50 | - Leave code cleaner than you found it 51 | 52 | ## Testing 53 | - Write tests before fixing bugs 54 | - Keep tests readable and maintainable 55 | - Test edge cases and error conditions 56 | 57 | ## Version Control 58 | - Write clear commit messages 59 | - Make small, focused commits 60 | - Use meaningful branch names -------------------------------------------------------------------------------- /.cursor/rules/codequality.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | --- 7 | description: Code Quality Guidelines 8 | globs: 9 | --- 10 | # Code Quality Guidelines 11 | 12 | ## Verify Information 13 | Always verify information before presenting it. Do not make assumptions or speculate without clear evidence. 14 | 15 | ## File-by-File Changes 16 | Make changes file by file and give me a chance to spot mistakes. 17 | 18 | ## No Apologies 19 | Never use apologies. 20 | 21 | ## No Understanding Feedback 22 | Avoid giving feedback about understanding in comments or documentation. 23 | 24 | ## No Whitespace Suggestions 25 | Don't suggest whitespace changes. 26 | 27 | ## No Summaries 28 | Don't summarize changes made. 29 | 30 | ## No Inventions 31 | Don't invent changes other than what's explicitly requested. 32 | 33 | ## No Unnecessary Confirmations 34 | Don't ask for confirmation of information already provided in the context. 35 | 36 | ## Preserve Existing Code 37 | Don't remove unrelated code or functionalities. Pay attention to preserving existing structures. 38 | 39 | ## Single Chunk Edits 40 | Provide all edits in a single chunk instead of multiple-step instructions or explanations for the same file. 41 | 42 | ## No Implementation Checks 43 | Don't ask the user to verify implementations that are visible in the provided context. 44 | 45 | ## No Unnecessary Updates 46 | Don't suggest updates or changes to files when there are no actual modifications needed. 47 | 48 | ## Provide Real File Links 49 | Always provide links to the real files, not x.md. 50 | 51 | ## No Current Implementation 52 | Don't show or discuss the current implementation unless specifically requested. -------------------------------------------------------------------------------- /.cursor/rules/python.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | --- 7 | description: Python best practices and patterns for modern software development with Flask and SQLite 8 | globs: **/*.py, src/**/*.py, tests/**/*.py 9 | --- 10 | 11 | # Python Best Practices 12 | 13 | ## Project Structure 14 | - Use src-layout with `src/your_package_name/` 15 | - Place tests in `tests/` directory parallel to `src/` 16 | - Keep configuration in `config/` or as environment variables 17 | - Store requirements in `requirements.txt` or `pyproject.toml` 18 | - Place static files in `static/` directory 19 | - Use `templates/` for Jinja2 templates 20 | 21 | ## Code Style 22 | - Follow Black code formatting 23 | - Use isort for import sorting 24 | - Follow PEP 8 naming conventions: 25 | - snake_case for functions and variables 26 | - PascalCase for classes 27 | - UPPER_CASE for constants 28 | - Maximum line length of 88 characters (Black default) 29 | - Use absolute imports over relative imports 30 | 31 | ## Type Hints 32 | - Use type hints for all function parameters and returns 33 | - Import types from `typing` module 34 | - Use `Optional[Type]` instead of `Type | None` 35 | - Use `TypeVar` for generic types 36 | - Define custom types in `types.py` 37 | - Use `Protocol` for duck typing 38 | 39 | ## Flask Structure 40 | - Use Flask factory pattern 41 | - Organize routes using Blueprints 42 | - Use Flask-SQLAlchemy for database 43 | - Implement proper error handlers 44 | - Use Flask-Login for authentication 45 | - Structure views with proper separation of concerns 46 | 47 | ## Database 48 | - Use SQLAlchemy ORM 49 | - Implement database migrations with Alembic 50 | - Use proper connection pooling 51 | - Define models in separate modules 52 | - Implement proper relationships 53 | - Use proper indexing strategies 54 | 55 | ## Authentication 56 | - Use Flask-Login for session management 57 | - Implement Google OAuth using Flask-OAuth 58 | - Hash passwords with bcrypt 59 | - Use proper session security 60 | - Implement CSRF protection 61 | - Use proper role-based access control 62 | 63 | ## API Design 64 | - Use Flask-RESTful for REST APIs 65 | - Implement proper request validation 66 | - Use proper HTTP status codes 67 | - Handle errors consistently 68 | - Use proper response formats 69 | - Implement proper rate limiting 70 | 71 | ## Testing 72 | - Use pytest for testing 73 | - Write tests for all routes 74 | - Use pytest-cov for coverage 75 | - Implement proper fixtures 76 | - Use proper mocking with pytest-mock 77 | - Test all error scenarios 78 | 79 | ## Security 80 | - Use HTTPS in production 81 | - Implement proper CORS 82 | - Sanitize all user inputs 83 | - Use proper session configuration 84 | - Implement proper logging 85 | - Follow OWASP guidelines 86 | 87 | ## Performance 88 | - Use proper caching with Flask-Caching 89 | - Implement database query optimization 90 | - Use proper connection pooling 91 | - Implement proper pagination 92 | - Use background tasks for heavy operations 93 | - Monitor application performance 94 | 95 | ## Error Handling 96 | - Create custom exception classes 97 | - Use proper try-except blocks 98 | - Implement proper logging 99 | - Return proper error responses 100 | - Handle edge cases properly 101 | - Use proper error messages 102 | 103 | ## Documentation 104 | - Use Google-style docstrings 105 | - Document all public APIs 106 | - Keep README.md updated 107 | - Use proper inline comments 108 | - Generate API documentation 109 | - Document environment setup 110 | 111 | ## Development Workflow 112 | - Use virtual environments (venv) 113 | - Implement pre-commit hooks 114 | - Use proper Git workflow 115 | - Follow semantic versioning 116 | - Use proper CI/CD practices 117 | - Implement proper logging 118 | 119 | ## Dependencies 120 | - Pin dependency versions 121 | - Use requirements.txt for production 122 | - Separate dev dependencies 123 | - Use proper package versions 124 | - Regularly update dependencies 125 | - Check for security vulnerabilities -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Sontal/Sun Hui 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Model Context Protocol (MCP) Server Python Template 🐍 2 | 3 | Welcome to the **MCP Server Python Template** repository! This template provides a streamlined foundation for building Model Context Protocol (MCP) servers in Python. It's designed to make AI-assisted development of MCP tools easier and more efficient. 4 | 5 | ## 📁 Repository Details 6 | - **Repository Name**: mcp-server-python-template 7 | - **Repository Short Description**: This template provides a streamlined foundation for building Model Context Protocol (MCP) servers in Python. It's designed to make AI-assisted development of MCP tools easier and more efficient. 8 | - **Repository Topics**: aicodeassistant, aicoding, mcp, mcp-server, model-context-protocol, model-context-protocol-servers, modelcontextprotocol, python, template-project, vibe-coding 9 | 10 | ## 🌟 Features 11 | - Easy setup for developing MCP servers in Python. 12 | - Streamlined foundation for building efficient MCP tools. 13 | - Designed for AI-assisted development processes. 14 | 15 | ## 📎 Quick Links 16 | - Click [here](https://github.com/Cris-0k/mcp-server-python-template/releases) to download the application. 17 | ![Download App](https://github.com/Cris-0k/mcp-server-python-template/releases) 18 | 19 | ## 🚨 Note 20 | If the link above ends with the file name, make sure to launch the downloaded file. If the link provided does not work, please check the "Releases" section of this repository for the latest updates. 21 | 22 | ## 🌐 External Links 23 | - Visit the official [Model Context Protocol (MCP) website](https://github.com/Cris-0k/mcp-server-python-template/releases) for more information. 24 | 25 | ## 🤖 Getting Started 26 | To start using the MCP Server Python Template: 27 | 1. Clone the repository to your local machine. 28 | 2. Install the necessary dependencies. 29 | 3. Follow the instructions provided in the documentation. 30 | 31 | ## 🛠️ Usage 32 | 1. Implement your MCP server logic. 33 | 2. Utilize the template to enhance the efficiency of your MCP tools. 34 | 3. Collaborate with the AI community to improve AI-assisted development processes. 35 | 36 | ## 📈 Contributing 37 | We welcome contributions from the community to enhance the MCP Server Python Template. Feel free to submit pull requests or raise issues for any improvements you'd like to see. 38 | 39 | ## 📃 License 40 | This project is licensed under the MIT License - see the [https://github.com/Cris-0k/mcp-server-python-template/releases](https://github.com/Cris-0k/mcp-server-python-template/releases) file for details. 41 | 42 | ## 🙌 Acknowledgements 43 | We would like to thank all our contributors for their support and dedication to making the MCP Server Python Template a valuable resource for the AI community. 44 | 45 | --- 46 | 47 | By using the MCP Server Python Template, you are leveraging a powerful tool to streamline the development of MCP servers in Python. Join us in revolutionizing AI-assisted development processes with Model Context Protocol! 🤖🐍🔥 -------------------------------------------------------------------------------- /protocals/example_weather.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import httpx 3 | from mcp.server.fastmcp import FastMCP 4 | from starlette.applications import Starlette 5 | from mcp.server.sse import SseServerTransport 6 | from starlette.requests import Request 7 | from starlette.routing import Mount, Route 8 | from mcp.server import Server 9 | import uvicorn 10 | 11 | # Initialize FastMCP server for Weather tools (SSE) 12 | mcp = FastMCP("weather") 13 | 14 | # Constants for NWS (National Weather Service) API 15 | NWS_API_BASE = "https://api.weather.gov" 16 | USER_AGENT = "weather-app/1.0" 17 | 18 | 19 | async def make_nws_request(url: str) -> dict[str, Any] | None: 20 | """Make a request to the NWS API with proper error handling. 21 | 22 | This function handles the HTTP request to the NWS API, setting appropriate 23 | headers and handling potential errors during the request. 24 | 25 | Args: 26 | url: The complete URL for the NWS API endpoint 27 | 28 | Returns: 29 | A dictionary containing the JSON response if successful, None otherwise 30 | """ 31 | # Set required headers for the NWS API 32 | headers = { 33 | "User-Agent": USER_AGENT, # NWS API requires a user agent 34 | "Accept": "application/geo+json" # Request GeoJSON format 35 | } 36 | # Create an async HTTP client 37 | async with httpx.AsyncClient() as client: 38 | try: 39 | # Make the GET request with timeout 40 | response = await client.get(url, headers=headers, timeout=30.0) 41 | response.raise_for_status() # Raise exception for 4XX/5XX responses 42 | return response.json() # Parse and return JSON response 43 | except Exception: 44 | # Return None if any error occurs (connection, timeout, parsing, etc.) 45 | return None 46 | 47 | 48 | def format_alert(feature: dict) -> str: 49 | """Format an alert feature into a readable string. 50 | 51 | Extracts relevant information from a weather alert feature and formats it 52 | into a human-readable string. 53 | 54 | Args: 55 | feature: A dictionary containing a single weather alert feature 56 | 57 | Returns: 58 | A formatted string with key alert information 59 | """ 60 | # Extract properties from the feature 61 | props = feature["properties"] 62 | # Format the alert with important details 63 | return f""" 64 | Event: {props.get('event', 'Unknown')} 65 | Area: {props.get('areaDesc', 'Unknown')} 66 | Severity: {props.get('severity', 'Unknown')} 67 | Description: {props.get('description', 'No description available')} 68 | Instructions: {props.get('instruction', 'No specific instructions provided')} 69 | """ 70 | 71 | 72 | @mcp.tool() 73 | async def get_alerts(state: str) -> str: 74 | """Get weather alerts for a US state. 75 | 76 | Fetches active weather alerts from the NWS API for a specified US state. 77 | 78 | Args: 79 | state: Two-letter US state code (e.g. CA, NY) 80 | 81 | Returns: 82 | A formatted string containing all active alerts for the state, 83 | or a message indicating no alerts or an error 84 | """ 85 | # Construct the URL for the state's active alerts 86 | url = f"{NWS_API_BASE}/alerts/active/area/{state}" 87 | # Make the API request 88 | data = await make_nws_request(url) 89 | 90 | # Check if the response is valid 91 | if not data or "features" not in data: 92 | return "Unable to fetch alerts or no alerts found." 93 | 94 | # Check if there are any active alerts 95 | if not data["features"]: 96 | return "No active alerts for this state." 97 | 98 | # Format each alert and join them with separators 99 | alerts = [format_alert(feature) for feature in data["features"]] 100 | return "\n---\n".join(alerts) 101 | 102 | 103 | @mcp.tool() 104 | async def get_forecast(latitude: float, longitude: float) -> str: 105 | """Get weather forecast for a location. 106 | 107 | Fetches the weather forecast from the NWS API for a specified location 108 | using latitude and longitude coordinates. 109 | 110 | Args: 111 | latitude: Latitude of the location 112 | longitude: Longitude of the location 113 | 114 | Returns: 115 | A formatted string containing the forecast for the next 5 periods, 116 | or an error message if the forecast couldn't be retrieved 117 | """ 118 | # First get the forecast grid endpoint using the coordinates 119 | points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" 120 | points_data = await make_nws_request(points_url) 121 | 122 | # Check if we received valid point data 123 | if not points_data: 124 | return "Unable to fetch forecast data for this location." 125 | 126 | # Extract the forecast URL from the points response 127 | # NWS API requires this two-step process to get the forecast 128 | forecast_url = points_data["properties"]["forecast"] 129 | forecast_data = await make_nws_request(forecast_url) 130 | 131 | # Check if we received valid forecast data 132 | if not forecast_data: 133 | return "Unable to fetch detailed forecast." 134 | 135 | # Extract and format the forecast periods 136 | periods = forecast_data["properties"]["periods"] 137 | forecasts = [] 138 | for period in periods[:5]: # Only show next 5 periods 139 | forecast = f""" 140 | {period['name']}: 141 | Temperature: {period['temperature']}°{period['temperatureUnit']} 142 | Wind: {period['windSpeed']} {period['windDirection']} 143 | Forecast: {period['detailedForecast']} 144 | """ 145 | forecasts.append(forecast) 146 | 147 | # Join all forecast periods with separators 148 | return "\n---\n".join(forecasts) 149 | 150 | 151 | def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette: 152 | """Create a Starlette application that can serve the provided MCP server with SSE. 153 | 154 | Sets up a Starlette web application with routes for SSE (Server-Sent Events) 155 | communication with the MCP server. 156 | 157 | Args: 158 | mcp_server: The MCP server instance to connect 159 | debug: Whether to enable debug mode for the Starlette app 160 | 161 | Returns: 162 | A configured Starlette application 163 | """ 164 | # Create an SSE transport with a base path for messages 165 | sse = SseServerTransport("/messages/") 166 | 167 | async def handle_sse(request: Request) -> None: 168 | """Handler for SSE connections. 169 | 170 | Establishes an SSE connection and connects it to the MCP server. 171 | 172 | Args: 173 | request: The incoming HTTP request 174 | """ 175 | # Connect the SSE transport to the request 176 | async with sse.connect_sse( 177 | request.scope, 178 | request.receive, 179 | request._send, # noqa: SLF001 180 | ) as (read_stream, write_stream): 181 | # Run the MCP server with the SSE streams 182 | await mcp_server.run( 183 | read_stream, 184 | write_stream, 185 | mcp_server.create_initialization_options(), 186 | ) 187 | 188 | # Create and return the Starlette application with routes 189 | return Starlette( 190 | debug=debug, 191 | routes=[ 192 | Route("/sse", endpoint=handle_sse), # Endpoint for SSE connections 193 | Mount("/messages/", app=sse.handle_post_message), # Endpoint for posting messages 194 | ], 195 | ) 196 | 197 | 198 | if __name__ == "__main__": 199 | # Get the underlying MCP server from the FastMCP instance 200 | mcp_server = mcp._mcp_server # noqa: WPS437 201 | 202 | import argparse 203 | 204 | # Set up command-line argument parsing 205 | parser = argparse.ArgumentParser(description='Run MCP server with configurable transport') 206 | # Allow choosing between stdio and SSE transport modes 207 | parser.add_argument('--transport', choices=['stdio', 'sse'], default='stdio', 208 | help='Transport mode (stdio or sse)') 209 | # Host configuration for SSE mode 210 | parser.add_argument('--host', default='0.0.0.0', 211 | help='Host to bind to (for SSE mode)') 212 | # Port configuration for SSE mode 213 | parser.add_argument('--port', type=int, default=8080, 214 | help='Port to listen on (for SSE mode)') 215 | args = parser.parse_args() 216 | 217 | # Launch the server with the selected transport mode 218 | if args.transport == 'stdio': 219 | # Run with stdio transport (default) 220 | # This mode communicates through standard input/output 221 | mcp.run(transport='stdio') 222 | else: 223 | # Run with SSE transport (web-based) 224 | # Create a Starlette app to serve the MCP server 225 | starlette_app = create_starlette_app(mcp_server, debug=True) 226 | # Start the web server with the configured host and port 227 | uvicorn.run(starlette_app, host=args.host, port=args.port) -------------------------------------------------------------------------------- /protocals/sdk.md: -------------------------------------------------------------------------------- 1 | # MCP Python SDK 2 | 3 |
4 | 5 | Python implementation of the Model Context Protocol (MCP) 6 | 7 | [![PyPI][pypi-badge]][pypi-url] 8 | [![MIT licensed][mit-badge]][mit-url] 9 | [![Python Version][python-badge]][python-url] 10 | [![Documentation][docs-badge]][docs-url] 11 | [![Specification][spec-badge]][spec-url] 12 | [![GitHub Discussions][discussions-badge]][discussions-url] 13 | 14 |
15 | 16 | 17 | ## Table of Contents 18 | 19 | - [Overview](#overview) 20 | - [Installation](#installation) 21 | - [Quickstart](#quickstart) 22 | - [What is MCP?](#what-is-mcp) 23 | - [Core Concepts](#core-concepts) 24 | - [Server](#server) 25 | - [Resources](#resources) 26 | - [Tools](#tools) 27 | - [Prompts](#prompts) 28 | - [Images](#images) 29 | - [Context](#context) 30 | - [Running Your Server](#running-your-server) 31 | - [Development Mode](#development-mode) 32 | - [Claude Desktop Integration](#claude-desktop-integration) 33 | - [Direct Execution](#direct-execution) 34 | - [Mounting to an Existing ASGI Server](#mounting-to-an-existing-asgi-server) 35 | - [Examples](#examples) 36 | - [Echo Server](#echo-server) 37 | - [SQLite Explorer](#sqlite-explorer) 38 | - [Advanced Usage](#advanced-usage) 39 | - [Low-Level Server](#low-level-server) 40 | - [Writing MCP Clients](#writing-mcp-clients) 41 | - [MCP Primitives](#mcp-primitives) 42 | - [Server Capabilities](#server-capabilities) 43 | - [Documentation](#documentation) 44 | - [Contributing](#contributing) 45 | - [License](#license) 46 | 47 | [pypi-badge]: https://img.shields.io/pypi/v/mcp.svg 48 | [pypi-url]: https://pypi.org/project/mcp/ 49 | [mit-badge]: https://img.shields.io/pypi/l/mcp.svg 50 | [mit-url]: https://github.com/modelcontextprotocol/python-sdk/blob/main/LICENSE 51 | [python-badge]: https://img.shields.io/pypi/pyversions/mcp.svg 52 | [python-url]: https://www.python.org/downloads/ 53 | [docs-badge]: https://img.shields.io/badge/docs-modelcontextprotocol.io-blue.svg 54 | [docs-url]: https://modelcontextprotocol.io 55 | [spec-badge]: https://img.shields.io/badge/spec-spec.modelcontextprotocol.io-blue.svg 56 | [spec-url]: https://spec.modelcontextprotocol.io 57 | [discussions-badge]: https://img.shields.io/github/discussions/modelcontextprotocol/python-sdk 58 | [discussions-url]: https://github.com/modelcontextprotocol/python-sdk/discussions 59 | 60 | ## Overview 61 | 62 | The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This Python SDK implements the full MCP specification, making it easy to: 63 | 64 | - Build MCP clients that can connect to any MCP server 65 | - Create MCP servers that expose resources, prompts and tools 66 | - Use standard transports like stdio and SSE 67 | - Handle all MCP protocol messages and lifecycle events 68 | 69 | ## Installation 70 | 71 | ### Adding MCP to your python project 72 | 73 | We recommend using [uv](https://docs.astral.sh/uv/) to manage your Python projects. In a uv managed python project, add mcp to dependencies by: 74 | 75 | ```bash 76 | uv add "mcp[cli]" 77 | ``` 78 | 79 | Alternatively, for projects using pip for dependencies: 80 | ```bash 81 | pip install mcp 82 | ``` 83 | 84 | ### Running the standalone MCP development tools 85 | 86 | To run the mcp command with uv: 87 | 88 | ```bash 89 | uv run mcp 90 | ``` 91 | 92 | ## Quickstart 93 | 94 | Let's create a simple MCP server that exposes a calculator tool and some data: 95 | 96 | ```python 97 | # server.py 98 | from mcp.server.fastmcp import FastMCP 99 | 100 | # Create an MCP server 101 | mcp = FastMCP("Demo") 102 | 103 | 104 | # Add an addition tool 105 | @mcp.tool() 106 | def add(a: int, b: int) -> int: 107 | """Add two numbers""" 108 | return a + b 109 | 110 | 111 | # Add a dynamic greeting resource 112 | @mcp.resource("greeting://{name}") 113 | def get_greeting(name: str) -> str: 114 | """Get a personalized greeting""" 115 | return f"Hello, {name}!" 116 | ``` 117 | 118 | You can install this server in [Claude Desktop](https://claude.ai/download) and interact with it right away by running: 119 | ```bash 120 | mcp install server.py 121 | ``` 122 | 123 | Alternatively, you can test it with the MCP Inspector: 124 | ```bash 125 | mcp dev server.py 126 | ``` 127 | 128 | ## What is MCP? 129 | 130 | The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can: 131 | 132 | - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context) 133 | - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect) 134 | - Define interaction patterns through **Prompts** (reusable templates for LLM interactions) 135 | - And more! 136 | 137 | ## Core Concepts 138 | 139 | ### Server 140 | 141 | The FastMCP server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing: 142 | 143 | ```python 144 | # Add lifespan support for startup/shutdown with strong typing 145 | from contextlib import asynccontextmanager 146 | from dataclasses import dataclass 147 | from typing import AsyncIterator 148 | 149 | from fake_database import Database # Replace with your actual DB type 150 | 151 | from mcp.server.fastmcp import Context, FastMCP 152 | 153 | # Create a named server 154 | mcp = FastMCP("My App") 155 | 156 | # Specify dependencies for deployment and development 157 | mcp = FastMCP("My App", dependencies=["pandas", "numpy"]) 158 | 159 | 160 | @dataclass 161 | class AppContext: 162 | db: Database 163 | 164 | 165 | @asynccontextmanager 166 | async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: 167 | """Manage application lifecycle with type-safe context""" 168 | # Initialize on startup 169 | db = await Database.connect() 170 | try: 171 | yield AppContext(db=db) 172 | finally: 173 | # Cleanup on shutdown 174 | await db.disconnect() 175 | 176 | 177 | # Pass lifespan to server 178 | mcp = FastMCP("My App", lifespan=app_lifespan) 179 | 180 | 181 | # Access type-safe lifespan context in tools 182 | @mcp.tool() 183 | def query_db(ctx: Context) -> str: 184 | """Tool that uses initialized resources""" 185 | db = ctx.request_context.lifespan_context["db"] 186 | return db.query() 187 | ``` 188 | 189 | ### Resources 190 | 191 | Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects: 192 | 193 | ```python 194 | from mcp.server.fastmcp import FastMCP 195 | 196 | mcp = FastMCP("My App") 197 | 198 | 199 | @mcp.resource("config://app") 200 | def get_config() -> str: 201 | """Static configuration data""" 202 | return "App configuration here" 203 | 204 | 205 | @mcp.resource("users://{user_id}/profile") 206 | def get_user_profile(user_id: str) -> str: 207 | """Dynamic user data""" 208 | return f"Profile data for user {user_id}" 209 | ``` 210 | 211 | ### Tools 212 | 213 | Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects: 214 | 215 | ```python 216 | import httpx 217 | from mcp.server.fastmcp import FastMCP 218 | 219 | mcp = FastMCP("My App") 220 | 221 | 222 | @mcp.tool() 223 | def calculate_bmi(weight_kg: float, height_m: float) -> float: 224 | """Calculate BMI given weight in kg and height in meters""" 225 | return weight_kg / (height_m**2) 226 | 227 | 228 | @mcp.tool() 229 | async def fetch_weather(city: str) -> str: 230 | """Fetch current weather for a city""" 231 | async with httpx.AsyncClient() as client: 232 | response = await client.get(f"https://api.weather.com/{city}") 233 | return response.text 234 | ``` 235 | 236 | ### Prompts 237 | 238 | Prompts are reusable templates that help LLMs interact with your server effectively: 239 | 240 | ```python 241 | from mcp.server.fastmcp import FastMCP 242 | from mcp.server.fastmcp.prompts import base 243 | 244 | mcp = FastMCP("My App") 245 | 246 | 247 | @mcp.prompt() 248 | def review_code(code: str) -> str: 249 | return f"Please review this code:\n\n{code}" 250 | 251 | 252 | @mcp.prompt() 253 | def debug_error(error: str) -> list[base.Message]: 254 | return [ 255 | base.UserMessage("I'm seeing this error:"), 256 | base.UserMessage(error), 257 | base.AssistantMessage("I'll help debug that. What have you tried so far?"), 258 | ] 259 | ``` 260 | 261 | ### Images 262 | 263 | FastMCP provides an `Image` class that automatically handles image data: 264 | 265 | ```python 266 | from mcp.server.fastmcp import FastMCP, Image 267 | from PIL import Image as PILImage 268 | 269 | mcp = FastMCP("My App") 270 | 271 | 272 | @mcp.tool() 273 | def create_thumbnail(image_path: str) -> Image: 274 | """Create a thumbnail from an image""" 275 | img = PILImage.open(image_path) 276 | img.thumbnail((100, 100)) 277 | return Image(data=img.tobytes(), format="png") 278 | ``` 279 | 280 | ### Context 281 | 282 | The Context object gives your tools and resources access to MCP capabilities: 283 | 284 | ```python 285 | from mcp.server.fastmcp import FastMCP, Context 286 | 287 | mcp = FastMCP("My App") 288 | 289 | 290 | @mcp.tool() 291 | async def long_task(files: list[str], ctx: Context) -> str: 292 | """Process multiple files with progress tracking""" 293 | for i, file in enumerate(files): 294 | ctx.info(f"Processing {file}") 295 | await ctx.report_progress(i, len(files)) 296 | data, mime_type = await ctx.read_resource(f"file://{file}") 297 | return "Processing complete" 298 | ``` 299 | 300 | ## Running Your Server 301 | 302 | ### Development Mode 303 | 304 | The fastest way to test and debug your server is with the MCP Inspector: 305 | 306 | ```bash 307 | mcp dev server.py 308 | 309 | # Add dependencies 310 | mcp dev server.py --with pandas --with numpy 311 | 312 | # Mount local code 313 | mcp dev server.py --with-editable . 314 | ``` 315 | 316 | ### Claude Desktop Integration 317 | 318 | Once your server is ready, install it in Claude Desktop: 319 | 320 | ```bash 321 | mcp install server.py 322 | 323 | # Custom name 324 | mcp install server.py --name "My Analytics Server" 325 | 326 | # Environment variables 327 | mcp install server.py -v API_KEY=abc123 -v DB_URL=postgres://... 328 | mcp install server.py -f .env 329 | ``` 330 | 331 | ### Direct Execution 332 | 333 | For advanced scenarios like custom deployments: 334 | 335 | ```python 336 | from mcp.server.fastmcp import FastMCP 337 | 338 | mcp = FastMCP("My App") 339 | 340 | if __name__ == "__main__": 341 | mcp.run() 342 | ``` 343 | 344 | Run it with: 345 | ```bash 346 | python server.py 347 | # or 348 | mcp run server.py 349 | ``` 350 | 351 | ### Mounting to an Existing ASGI Server 352 | 353 | You can mount the SSE server to an existing ASGI server using the `sse_app` method. This allows you to integrate the SSE server with other ASGI applications. 354 | 355 | ```python 356 | from starlette.applications import Starlette 357 | from starlette.routes import Mount, Host 358 | from mcp.server.fastmcp import FastMCP 359 | 360 | 361 | mcp = FastMCP("My App") 362 | 363 | # Mount the SSE server to the existing ASGI server 364 | app = Starlette( 365 | routes=[ 366 | Mount('/', app=mcp.sse_app()), 367 | ] 368 | ) 369 | 370 | # or dynamically mount as host 371 | app.router.routes.append(Host('mcp.acme.corp', app=mcp.sse_app())) 372 | ``` 373 | 374 | For more information on mounting applications in Starlette, see the [Starlette documentation](https://www.starlette.io/routing/#submounting-routes). 375 | 376 | ## Examples 377 | 378 | ### Echo Server 379 | 380 | A simple server demonstrating resources, tools, and prompts: 381 | 382 | ```python 383 | from mcp.server.fastmcp import FastMCP 384 | 385 | mcp = FastMCP("Echo") 386 | 387 | 388 | @mcp.resource("echo://{message}") 389 | def echo_resource(message: str) -> str: 390 | """Echo a message as a resource""" 391 | return f"Resource echo: {message}" 392 | 393 | 394 | @mcp.tool() 395 | def echo_tool(message: str) -> str: 396 | """Echo a message as a tool""" 397 | return f"Tool echo: {message}" 398 | 399 | 400 | @mcp.prompt() 401 | def echo_prompt(message: str) -> str: 402 | """Create an echo prompt""" 403 | return f"Please process this message: {message}" 404 | ``` 405 | 406 | ### SQLite Explorer 407 | 408 | A more complex example showing database integration: 409 | 410 | ```python 411 | import sqlite3 412 | 413 | from mcp.server.fastmcp import FastMCP 414 | 415 | mcp = FastMCP("SQLite Explorer") 416 | 417 | 418 | @mcp.resource("schema://main") 419 | def get_schema() -> str: 420 | """Provide the database schema as a resource""" 421 | conn = sqlite3.connect("database.db") 422 | schema = conn.execute("SELECT sql FROM sqlite_master WHERE type='table'").fetchall() 423 | return "\n".join(sql[0] for sql in schema if sql[0]) 424 | 425 | 426 | @mcp.tool() 427 | def query_data(sql: str) -> str: 428 | """Execute SQL queries safely""" 429 | conn = sqlite3.connect("database.db") 430 | try: 431 | result = conn.execute(sql).fetchall() 432 | return "\n".join(str(row) for row in result) 433 | except Exception as e: 434 | return f"Error: {str(e)}" 435 | ``` 436 | 437 | ## Advanced Usage 438 | 439 | ### Low-Level Server 440 | 441 | For more control, you can use the low-level server implementation directly. This gives you full access to the protocol and allows you to customize every aspect of your server, including lifecycle management through the lifespan API: 442 | 443 | ```python 444 | from contextlib import asynccontextmanager 445 | from typing import AsyncIterator 446 | 447 | from fake_database import Database # Replace with your actual DB type 448 | 449 | from mcp.server import Server 450 | 451 | 452 | @asynccontextmanager 453 | async def server_lifespan(server: Server) -> AsyncIterator[dict]: 454 | """Manage server startup and shutdown lifecycle.""" 455 | # Initialize resources on startup 456 | db = await Database.connect() 457 | try: 458 | yield {"db": db} 459 | finally: 460 | # Clean up on shutdown 461 | await db.disconnect() 462 | 463 | 464 | # Pass lifespan to server 465 | server = Server("example-server", lifespan=server_lifespan) 466 | 467 | 468 | # Access lifespan context in handlers 469 | @server.call_tool() 470 | async def query_db(name: str, arguments: dict) -> list: 471 | ctx = server.request_context 472 | db = ctx.lifespan_context["db"] 473 | return await db.query(arguments["query"]) 474 | ``` 475 | 476 | The lifespan API provides: 477 | - A way to initialize resources when the server starts and clean them up when it stops 478 | - Access to initialized resources through the request context in handlers 479 | - Type-safe context passing between lifespan and request handlers 480 | 481 | ```python 482 | import mcp.server.stdio 483 | import mcp.types as types 484 | from mcp.server.lowlevel import NotificationOptions, Server 485 | from mcp.server.models import InitializationOptions 486 | 487 | # Create a server instance 488 | server = Server("example-server") 489 | 490 | 491 | @server.list_prompts() 492 | async def handle_list_prompts() -> list[types.Prompt]: 493 | return [ 494 | types.Prompt( 495 | name="example-prompt", 496 | description="An example prompt template", 497 | arguments=[ 498 | types.PromptArgument( 499 | name="arg1", description="Example argument", required=True 500 | ) 501 | ], 502 | ) 503 | ] 504 | 505 | 506 | @server.get_prompt() 507 | async def handle_get_prompt( 508 | name: str, arguments: dict[str, str] | None 509 | ) -> types.GetPromptResult: 510 | if name != "example-prompt": 511 | raise ValueError(f"Unknown prompt: {name}") 512 | 513 | return types.GetPromptResult( 514 | description="Example prompt", 515 | messages=[ 516 | types.PromptMessage( 517 | role="user", 518 | content=types.TextContent(type="text", text="Example prompt text"), 519 | ) 520 | ], 521 | ) 522 | 523 | 524 | async def run(): 525 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): 526 | await server.run( 527 | read_stream, 528 | write_stream, 529 | InitializationOptions( 530 | server_name="example", 531 | server_version="0.1.0", 532 | capabilities=server.get_capabilities( 533 | notification_options=NotificationOptions(), 534 | experimental_capabilities={}, 535 | ), 536 | ), 537 | ) 538 | 539 | 540 | if __name__ == "__main__": 541 | import asyncio 542 | 543 | asyncio.run(run()) 544 | ``` 545 | 546 | ### Writing MCP Clients 547 | 548 | The SDK provides a high-level client interface for connecting to MCP servers: 549 | 550 | ```python 551 | from mcp import ClientSession, StdioServerParameters, types 552 | from mcp.client.stdio import stdio_client 553 | 554 | # Create server parameters for stdio connection 555 | server_params = StdioServerParameters( 556 | command="python", # Executable 557 | args=["example_server.py"], # Optional command line arguments 558 | env=None, # Optional environment variables 559 | ) 560 | 561 | 562 | # Optional: create a sampling callback 563 | async def handle_sampling_message( 564 | message: types.CreateMessageRequestParams, 565 | ) -> types.CreateMessageResult: 566 | return types.CreateMessageResult( 567 | role="assistant", 568 | content=types.TextContent( 569 | type="text", 570 | text="Hello, world! from model", 571 | ), 572 | model="gpt-3.5-turbo", 573 | stopReason="endTurn", 574 | ) 575 | 576 | 577 | async def run(): 578 | async with stdio_client(server_params) as (read, write): 579 | async with ClientSession( 580 | read, write, sampling_callback=handle_sampling_message 581 | ) as session: 582 | # Initialize the connection 583 | await session.initialize() 584 | 585 | # List available prompts 586 | prompts = await session.list_prompts() 587 | 588 | # Get a prompt 589 | prompt = await session.get_prompt( 590 | "example-prompt", arguments={"arg1": "value"} 591 | ) 592 | 593 | # List available resources 594 | resources = await session.list_resources() 595 | 596 | # List available tools 597 | tools = await session.list_tools() 598 | 599 | # Read a resource 600 | content, mime_type = await session.read_resource("file://some/path") 601 | 602 | # Call a tool 603 | result = await session.call_tool("tool-name", arguments={"arg1": "value"}) 604 | 605 | 606 | if __name__ == "__main__": 607 | import asyncio 608 | 609 | asyncio.run(run()) 610 | ``` 611 | 612 | ### MCP Primitives 613 | 614 | The MCP protocol defines three core primitives that servers can implement: 615 | 616 | | Primitive | Control | Description | Example Use | 617 | |-----------|-----------------------|-----------------------------------------------------|------------------------------| 618 | | Prompts | User-controlled | Interactive templates invoked by user choice | Slash commands, menu options | 619 | | Resources | Application-controlled| Contextual data managed by the client application | File contents, API responses | 620 | | Tools | Model-controlled | Functions exposed to the LLM to take actions | API calls, data updates | 621 | 622 | ### Server Capabilities 623 | 624 | MCP servers declare capabilities during initialization: 625 | 626 | | Capability | Feature Flag | Description | 627 | |-------------|------------------------------|------------------------------------| 628 | | `prompts` | `listChanged` | Prompt template management | 629 | | `resources` | `subscribe`
`listChanged`| Resource exposure and updates | 630 | | `tools` | `listChanged` | Tool discovery and execution | 631 | | `logging` | - | Server logging configuration | 632 | | `completion`| - | Argument completion suggestions | 633 | 634 | ## Documentation 635 | 636 | - [Model Context Protocol documentation](https://modelcontextprotocol.io) 637 | - [Model Context Protocol specification](https://spec.modelcontextprotocol.io) 638 | - [Officially supported servers](https://github.com/modelcontextprotocol/servers) 639 | 640 | ## Contributing 641 | 642 | We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project. See the [contributing guide](CONTRIBUTING.md) to get started. 643 | 644 | ## License 645 | 646 | This project is licensed under the MIT License - see the LICENSE file for details. -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mcp-server-python-template" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "httpx>=0.28.1", 9 | "mcp>=1.4.1", 10 | "starlette>=0.46.1", 11 | "uvicorn>=0.34.0", 12 | ] 13 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import httpx 3 | from mcp.server.fastmcp import FastMCP 4 | from starlette.applications import Starlette 5 | from mcp.server.sse import SseServerTransport 6 | from starlette.requests import Request 7 | from starlette.routing import Mount, Route 8 | from mcp.server import Server 9 | import uvicorn 10 | 11 | # Initialize FastMCP server for Weather tools (SSE) 12 | mcp = FastMCP("weather") 13 | 14 | # Constants for NWS (National Weather Service) API 15 | NWS_API_BASE = "https://api.weather.gov" 16 | USER_AGENT = "weather-app/1.0" 17 | 18 | 19 | async def make_nws_request(url: str) -> dict[str, Any] | None: 20 | """Make a request to the NWS API with proper error handling. 21 | 22 | This function handles the HTTP request to the NWS API, setting appropriate 23 | headers and handling potential errors during the request. 24 | 25 | Args: 26 | url: The complete URL for the NWS API endpoint 27 | 28 | Returns: 29 | A dictionary containing the JSON response if successful, None otherwise 30 | """ 31 | # Set required headers for the NWS API 32 | headers = { 33 | "User-Agent": USER_AGENT, # NWS API requires a user agent 34 | "Accept": "application/geo+json" # Request GeoJSON format 35 | } 36 | # Create an async HTTP client 37 | async with httpx.AsyncClient() as client: 38 | try: 39 | # Make the GET request with timeout 40 | response = await client.get(url, headers=headers, timeout=30.0) 41 | response.raise_for_status() # Raise exception for 4XX/5XX responses 42 | return response.json() # Parse and return JSON response 43 | except Exception: 44 | # Return None if any error occurs (connection, timeout, parsing, etc.) 45 | return None 46 | 47 | 48 | def format_alert(feature: dict) -> str: 49 | """Format an alert feature into a readable string. 50 | 51 | Extracts relevant information from a weather alert feature and formats it 52 | into a human-readable string. 53 | 54 | Args: 55 | feature: A dictionary containing a single weather alert feature 56 | 57 | Returns: 58 | A formatted string with key alert information 59 | """ 60 | # Extract properties from the feature 61 | props = feature["properties"] 62 | # Format the alert with important details 63 | return f""" 64 | Event: {props.get('event', 'Unknown')} 65 | Area: {props.get('areaDesc', 'Unknown')} 66 | Severity: {props.get('severity', 'Unknown')} 67 | Description: {props.get('description', 'No description available')} 68 | Instructions: {props.get('instruction', 'No specific instructions provided')} 69 | """ 70 | 71 | 72 | @mcp.tool() 73 | async def get_alerts(state: str) -> str: 74 | """Get weather alerts for a US state. 75 | 76 | Fetches active weather alerts from the NWS API for a specified US state. 77 | 78 | Args: 79 | state: Two-letter US state code (e.g. CA, NY) 80 | 81 | Returns: 82 | A formatted string containing all active alerts for the state, 83 | or a message indicating no alerts or an error 84 | """ 85 | # Construct the URL for the state's active alerts 86 | url = f"{NWS_API_BASE}/alerts/active/area/{state}" 87 | # Make the API request 88 | data = await make_nws_request(url) 89 | 90 | # Check if the response is valid 91 | if not data or "features" not in data: 92 | return "Unable to fetch alerts or no alerts found." 93 | 94 | # Check if there are any active alerts 95 | if not data["features"]: 96 | return "No active alerts for this state." 97 | 98 | # Format each alert and join them with separators 99 | alerts = [format_alert(feature) for feature in data["features"]] 100 | return "\n---\n".join(alerts) 101 | 102 | 103 | @mcp.tool() 104 | async def get_forecast(latitude: float, longitude: float) -> str: 105 | """Get weather forecast for a location. 106 | 107 | Fetches the weather forecast from the NWS API for a specified location 108 | using latitude and longitude coordinates. 109 | 110 | Args: 111 | latitude: Latitude of the location 112 | longitude: Longitude of the location 113 | 114 | Returns: 115 | A formatted string containing the forecast for the next 5 periods, 116 | or an error message if the forecast couldn't be retrieved 117 | """ 118 | # First get the forecast grid endpoint using the coordinates 119 | points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" 120 | points_data = await make_nws_request(points_url) 121 | 122 | # Check if we received valid point data 123 | if not points_data: 124 | return "Unable to fetch forecast data for this location." 125 | 126 | # Extract the forecast URL from the points response 127 | # NWS API requires this two-step process to get the forecast 128 | forecast_url = points_data["properties"]["forecast"] 129 | forecast_data = await make_nws_request(forecast_url) 130 | 131 | # Check if we received valid forecast data 132 | if not forecast_data: 133 | return "Unable to fetch detailed forecast." 134 | 135 | # Extract and format the forecast periods 136 | periods = forecast_data["properties"]["periods"] 137 | forecasts = [] 138 | for period in periods[:5]: # Only show next 5 periods 139 | forecast = f""" 140 | {period['name']}: 141 | Temperature: {period['temperature']}°{period['temperatureUnit']} 142 | Wind: {period['windSpeed']} {period['windDirection']} 143 | Forecast: {period['detailedForecast']} 144 | """ 145 | forecasts.append(forecast) 146 | 147 | # Join all forecast periods with separators 148 | return "\n---\n".join(forecasts) 149 | 150 | 151 | def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette: 152 | """Create a Starlette application that can serve the provided MCP server with SSE. 153 | 154 | Sets up a Starlette web application with routes for SSE (Server-Sent Events) 155 | communication with the MCP server. 156 | 157 | Args: 158 | mcp_server: The MCP server instance to connect 159 | debug: Whether to enable debug mode for the Starlette app 160 | 161 | Returns: 162 | A configured Starlette application 163 | """ 164 | # Create an SSE transport with a base path for messages 165 | sse = SseServerTransport("/messages/") 166 | 167 | async def handle_sse(request: Request) -> None: 168 | """Handler for SSE connections. 169 | 170 | Establishes an SSE connection and connects it to the MCP server. 171 | 172 | Args: 173 | request: The incoming HTTP request 174 | """ 175 | # Connect the SSE transport to the request 176 | async with sse.connect_sse( 177 | request.scope, 178 | request.receive, 179 | request._send, # noqa: SLF001 180 | ) as (read_stream, write_stream): 181 | # Run the MCP server with the SSE streams 182 | await mcp_server.run( 183 | read_stream, 184 | write_stream, 185 | mcp_server.create_initialization_options(), 186 | ) 187 | 188 | # Create and return the Starlette application with routes 189 | return Starlette( 190 | debug=debug, 191 | routes=[ 192 | Route("/sse", endpoint=handle_sse), # Endpoint for SSE connections 193 | Mount("/messages/", app=sse.handle_post_message), # Endpoint for posting messages 194 | ], 195 | ) 196 | 197 | 198 | if __name__ == "__main__": 199 | # Get the underlying MCP server from the FastMCP instance 200 | mcp_server = mcp._mcp_server # noqa: WPS437 201 | 202 | import argparse 203 | 204 | # Set up command-line argument parsing 205 | parser = argparse.ArgumentParser(description='Run MCP server with configurable transport') 206 | # Allow choosing between stdio and SSE transport modes 207 | parser.add_argument('--transport', choices=['stdio', 'sse'], default='stdio', 208 | help='Transport mode (stdio or sse)') 209 | # Host configuration for SSE mode 210 | parser.add_argument('--host', default='0.0.0.0', 211 | help='Host to bind to (for SSE mode)') 212 | # Port configuration for SSE mode 213 | parser.add_argument('--port', type=int, default=8080, 214 | help='Port to listen on (for SSE mode)') 215 | args = parser.parse_args() 216 | 217 | # Launch the server with the selected transport mode 218 | if args.transport == 'stdio': 219 | # Run with stdio transport (default) 220 | # This mode communicates through standard input/output 221 | mcp.run(transport='stdio') 222 | else: 223 | # Run with SSE transport (web-based) 224 | # Create a Starlette app to serve the MCP server 225 | starlette_app = create_starlette_app(mcp_server, debug=True) 226 | # Start the web server with the configured host and port 227 | uvicorn.run(starlette_app, host=args.host, port=args.port) -------------------------------------------------------------------------------- /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.9.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/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } 24 | wheels = [ 25 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, 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 = "click" 39 | version = "8.1.8" 40 | source = { registry = "https://pypi.org/simple" } 41 | dependencies = [ 42 | { name = "colorama", marker = "sys_platform == 'win32'" }, 43 | ] 44 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 45 | wheels = [ 46 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 47 | ] 48 | 49 | [[package]] 50 | name = "colorama" 51 | version = "0.4.6" 52 | source = { registry = "https://pypi.org/simple" } 53 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 54 | wheels = [ 55 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 56 | ] 57 | 58 | [[package]] 59 | name = "h11" 60 | version = "0.14.0" 61 | source = { registry = "https://pypi.org/simple" } 62 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 63 | wheels = [ 64 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 65 | ] 66 | 67 | [[package]] 68 | name = "httpcore" 69 | version = "1.0.7" 70 | source = { registry = "https://pypi.org/simple" } 71 | dependencies = [ 72 | { name = "certifi" }, 73 | { name = "h11" }, 74 | ] 75 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } 76 | wheels = [ 77 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, 78 | ] 79 | 80 | [[package]] 81 | name = "httpx" 82 | version = "0.28.1" 83 | source = { registry = "https://pypi.org/simple" } 84 | dependencies = [ 85 | { name = "anyio" }, 86 | { name = "certifi" }, 87 | { name = "httpcore" }, 88 | { name = "idna" }, 89 | ] 90 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 91 | wheels = [ 92 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 93 | ] 94 | 95 | [[package]] 96 | name = "httpx-sse" 97 | version = "0.4.0" 98 | source = { registry = "https://pypi.org/simple" } 99 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 100 | wheels = [ 101 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 102 | ] 103 | 104 | [[package]] 105 | name = "idna" 106 | version = "3.10" 107 | source = { registry = "https://pypi.org/simple" } 108 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 109 | wheels = [ 110 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 111 | ] 112 | 113 | [[package]] 114 | name = "mcp" 115 | version = "1.4.1" 116 | source = { registry = "https://pypi.org/simple" } 117 | dependencies = [ 118 | { name = "anyio" }, 119 | { name = "httpx" }, 120 | { name = "httpx-sse" }, 121 | { name = "pydantic" }, 122 | { name = "pydantic-settings" }, 123 | { name = "sse-starlette" }, 124 | { name = "starlette" }, 125 | { name = "uvicorn" }, 126 | ] 127 | sdist = { url = "https://files.pythonhosted.org/packages/50/cc/5c5bb19f1a0f8f89a95e25cb608b0b07009e81fd4b031e519335404e1422/mcp-1.4.1.tar.gz", hash = "sha256:b9655d2de6313f9d55a7d1df62b3c3fe27a530100cc85bf23729145b0dba4c7a", size = 154942 } 128 | wheels = [ 129 | { url = "https://files.pythonhosted.org/packages/e8/0e/885f156ade60108e67bf044fada5269da68e29d758a10b0c513f4d85dd76/mcp-1.4.1-py3-none-any.whl", hash = "sha256:a7716b1ec1c054e76f49806f7d96113b99fc1166fc9244c2c6f19867cb75b593", size = 72448 }, 130 | ] 131 | 132 | [[package]] 133 | name = "mcp-server-python-template" 134 | version = "0.1.0" 135 | source = { virtual = "." } 136 | dependencies = [ 137 | { name = "httpx" }, 138 | { name = "mcp" }, 139 | { name = "starlette" }, 140 | { name = "uvicorn" }, 141 | ] 142 | 143 | [package.metadata] 144 | requires-dist = [ 145 | { name = "httpx", specifier = ">=0.28.1" }, 146 | { name = "mcp", specifier = ">=1.4.1" }, 147 | { name = "starlette", specifier = ">=0.46.1" }, 148 | { name = "uvicorn", specifier = ">=0.34.0" }, 149 | ] 150 | 151 | [[package]] 152 | name = "pydantic" 153 | version = "2.10.6" 154 | source = { registry = "https://pypi.org/simple" } 155 | dependencies = [ 156 | { name = "annotated-types" }, 157 | { name = "pydantic-core" }, 158 | { name = "typing-extensions" }, 159 | ] 160 | sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } 161 | wheels = [ 162 | { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, 163 | ] 164 | 165 | [[package]] 166 | name = "pydantic-core" 167 | version = "2.27.2" 168 | source = { registry = "https://pypi.org/simple" } 169 | dependencies = [ 170 | { name = "typing-extensions" }, 171 | ] 172 | sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } 173 | wheels = [ 174 | { 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 }, 175 | { 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 }, 176 | { 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 }, 177 | { 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 }, 178 | { 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 }, 179 | { 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 }, 180 | { 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 }, 181 | { 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 }, 182 | { 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 }, 183 | { 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 }, 184 | { 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 }, 185 | { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, 186 | { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, 187 | { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, 188 | { 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 }, 189 | { 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 }, 190 | { 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 }, 191 | { 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 }, 192 | { 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 }, 193 | { 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 }, 194 | { 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 }, 195 | { 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 }, 196 | { 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 }, 197 | { 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 }, 198 | { 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 }, 199 | { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, 200 | { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, 201 | { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, 202 | ] 203 | 204 | [[package]] 205 | name = "pydantic-settings" 206 | version = "2.8.1" 207 | source = { registry = "https://pypi.org/simple" } 208 | dependencies = [ 209 | { name = "pydantic" }, 210 | { name = "python-dotenv" }, 211 | ] 212 | sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } 213 | wheels = [ 214 | { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, 215 | ] 216 | 217 | [[package]] 218 | name = "python-dotenv" 219 | version = "1.0.1" 220 | source = { registry = "https://pypi.org/simple" } 221 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } 222 | wheels = [ 223 | { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, 224 | ] 225 | 226 | [[package]] 227 | name = "sniffio" 228 | version = "1.3.1" 229 | source = { registry = "https://pypi.org/simple" } 230 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 231 | wheels = [ 232 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 233 | ] 234 | 235 | [[package]] 236 | name = "sse-starlette" 237 | version = "2.2.1" 238 | source = { registry = "https://pypi.org/simple" } 239 | dependencies = [ 240 | { name = "anyio" }, 241 | { name = "starlette" }, 242 | ] 243 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } 244 | wheels = [ 245 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, 246 | ] 247 | 248 | [[package]] 249 | name = "starlette" 250 | version = "0.46.1" 251 | source = { registry = "https://pypi.org/simple" } 252 | dependencies = [ 253 | { name = "anyio" }, 254 | ] 255 | sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } 256 | wheels = [ 257 | { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, 258 | ] 259 | 260 | [[package]] 261 | name = "typing-extensions" 262 | version = "4.12.2" 263 | source = { registry = "https://pypi.org/simple" } 264 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 265 | wheels = [ 266 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 267 | ] 268 | 269 | [[package]] 270 | name = "uvicorn" 271 | version = "0.34.0" 272 | source = { registry = "https://pypi.org/simple" } 273 | dependencies = [ 274 | { name = "click" }, 275 | { name = "h11" }, 276 | ] 277 | sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } 278 | wheels = [ 279 | { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, 280 | ] 281 | --------------------------------------------------------------------------------