├── .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 | 
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 |
--------------------------------------------------------------------------------