Skip to content

Commit bd55e07

Browse files
committed
Add initial project structure with MCP server template
- Created .gitignore to exclude Python-generated files and virtual environments. - Added .python-version to specify Python version 3.12. - Introduced pyproject.toml for project metadata and dependencies including httpx, mcp, starlette, and uvicorn. - Added README.md with project overview, features, requirements, and getting started instructions. - Implemented server.py with FastMCP server setup and example weather service integration. - Included example weather service in protocals/example_weather.py. - Added comprehensive MCP documentation in protocals/mcp.md. - Created SDK documentation in protocals/sdk.md. - Generated dependency lock file uv.lock for package management.
1 parent c4c3a10 commit bd55e07

File tree

9 files changed

+8619
-0
lines changed

9 files changed

+8619
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Python-generated files
2+
__pycache__/
3+
*.py[oc]
4+
build/
5+
dist/
6+
wheels/
7+
*.egg-info
8+
9+
# Virtual environments
10+
.venv

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

README.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# MCP Server template for better AI Coding
2+
3+
> Inspired by [MCP Official Tutorial](https://modelcontextprotocol.io/tutorials/building-mcp-with-llms)
4+
5+
## Overview
6+
7+
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+
9+
## Features
10+
11+
- Ready-to-use MCP server implementation
12+
- Configurable transport modes (stdio, SSE)
13+
- Example weather service integration (NWS API)
14+
- Clean, well-documented code structure
15+
- Minimal dependencies
16+
- **Embedded MCP specifications and documentation** for improved AI tool understanding
17+
18+
## Integrated MCP Documentation
19+
20+
This template includes comprehensive MCP documentation directly in the project:
21+
22+
- **Complete MCP Specification** (`protocals/mcp.md`): The full Model Context Protocol specification that defines how AI models can interact with external tools and resources. This helps AI assistants understand MCP concepts and implementation details without requiring external references.
23+
24+
- **Python SDK Guide** (`protocals/sdk.md`): Detailed documentation for the MCP Python SDK, making it easier for AI tools to provide accurate code suggestions and understand the library's capabilities.
25+
26+
- **Example Implementation** (`protocals/example_weather.py`): A practical weather service implementation demonstrating real-world MCP server patterns and best practices.
27+
28+
Having these resources embedded in the project enables AI coding assistants to better understand MCP concepts and provide more accurate, contextually relevant suggestions during development.
29+
30+
## Requirements
31+
32+
- Python 3.12+
33+
- Dependencies:
34+
- `mcp>=1.4.1`
35+
- `httpx>=0.28.1`
36+
- `starlette>=0.46.1`
37+
- `uvicorn>=0.34.0`
38+
39+
## Getting Started
40+
41+
### Installation
42+
43+
1. Clone this repository:
44+
```bash
45+
git clone https://github.com/yourusername/mcp-server-python-template.git
46+
cd mcp-server-python-template
47+
```
48+
49+
2. Create a virtual environment and install dependencies:
50+
```bash
51+
python -m venv .venv
52+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
53+
pip install -e .
54+
```
55+
56+
### Running the Example Server
57+
58+
The template includes a weather service example that demonstrates how to build MCP tools:
59+
60+
```bash
61+
# Run with stdio transport (for CLI tools)
62+
python server.py --transport stdio
63+
64+
# Run with SSE transport (for web applications)
65+
python server.py --transport sse --host 0.0.0.0 --port 8080
66+
```
67+
68+
## Creating Your Own MCP Tools
69+
70+
To create your own MCP tools:
71+
72+
1. Import the necessary components from `mcp`:
73+
```python
74+
from mcp.server.fastmcp import FastMCP
75+
```
76+
77+
2. Initialize your MCP server with a namespace:
78+
```python
79+
mcp = FastMCP("your-namespace")
80+
```
81+
82+
3. Define your tools using the `@mcp.tool()` decorator:
83+
```python
84+
@mcp.tool()
85+
async def your_tool_function(param1: str, param2: int) -> str:
86+
"""
87+
Your tool description.
88+
89+
Args:
90+
param1: Description of param1
91+
param2: Description of param2
92+
93+
Returns:
94+
The result of your tool
95+
"""
96+
# Your implementation here
97+
return result
98+
```
99+
100+
4. Run your server using the appropriate transport:
101+
```python
102+
mcp.run(transport='stdio') # or set up SSE as shown in server.py
103+
```
104+
105+
## Project Structure
106+
107+
- `server.py`: Main MCP server implementation with example weather tools
108+
- `main.py`: Simple entry point for custom code
109+
- `protocals/`: Documentation and example protocols
110+
- `mcp.md`: Complete MCP specification (~7000 lines)
111+
- `sdk.md`: MCP Python SDK documentation
112+
- `example_weather.py`: Example weather service implementation
113+
- `pyproject.toml`: Project dependencies and metadata
114+
115+
## Understanding MCP
116+
117+
The Model Context Protocol (MCP) is a standardized way for AI models to interact with external tools and resources. Key concepts include:
118+
119+
- **Tools**: Functions that models can call to perform actions or retrieve information
120+
- **Resources**: External data sources that models can reference
121+
- **Transports**: Communication channels between clients and MCP servers (stdio, SSE)
122+
- **Namespaces**: Logical groupings of related tools
123+
124+
This template is specifically designed to make working with MCP more accessible, with the integrated documentation helping AI tools better understand and generate appropriate code for MCP implementations.
125+
126+
## Learning Resources
127+
128+
- [MCP Official Documentation](https://modelcontextprotocol.io/docs)
129+
- [Protocol Documentation](./protocals/mcp.md)
130+
- [SDK Guide](./protocals/sdk.md)
131+
132+
## Contributing
133+
134+
Contributions are welcome! Please feel free to submit a Pull Request.
135+
136+
## License
137+
138+
This project is licensed under the MIT License - see the LICENSE file for details.
139+

protocals/example_weather.py

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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)

0 commit comments

Comments
 (0)