Merge branch 'main' into main

This commit is contained in:
evalstate 2025-01-03 20:39:59 +00:00 • committed by GitHub
commit 4e47a3ef73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 94 additions and 256 deletions

View file

@ -19,6 +19,7 @@ This page provides an overview of applications that support the Model Context Pr
| [LibreChat][LibreChat] | ❌ | ❌ | ✅ | ❌ | ❌ | Supports tools for Agents |
| [TheiaAI/TheiaIDE][TheiaAI/TheiaIDE] | ❌ | ❌ | ✅ | ❌ | ❌ | Supports tools for Agents in Theia AI and the AI-powered Theia IDE |
| [Superinterface][Superinterface] | ❌ | ❌ | ✅ | ❌ | ❌ | Supports tools |
| [5ire][5ire] | ❌ | ❌ | ✅ | ❌ | ❌ | Supports tools. |
[Claude]: https://claude.ai/download
[Zed]: https://zed.dev
@ -30,6 +31,7 @@ This page provides an overview of applications that support the Model Context Pr
[LibreChat]: https://github.com/danny-avila/LibreChat
[TheiaAI/TheiaIDE]: https://eclipsesource.com/blogs/2024/12/19/theia-ide-and-theia-ai-support-mcp/
[Superinterface]: https://superinterface.ai
[5ire]: https://github.com/nanbingxyz/5ire
[Resources]: https://modelcontextprotocol.io/docs/concepts/resources
[Prompts]: https://modelcontextprotocol.io/docs/concepts/prompts
@ -132,6 +134,15 @@ Theia AI and Theia IDE's MCP integration provide users with flexibility, making
- SSE transport support
- Use any AI model from any AI provider (OpenAI, Anthropic, Ollama, others)
### 5ire
[5ire](https://github.com/nanbingxyz/5ire) is an open source cross-platform desktop AI assistant that supports tools through MCP servers.
**Key features:**
- Built-in MCP servers can be quickly enabled and disabled.
- Users can add more servers by modifying the configuration file.
- It is open-source and user-friendly, suitable for beginners.
- Future support for MCP will be continuously improved.
## Adding MCP support to your application
If you've added MCP support to your application, we encourage you to submit a pull request to add it to this list. MCP integration can provide your users with powerful contextual AI capabilities and make your application part of the growing MCP ecosystem.

View file

@ -49,7 +49,8 @@ This quickstart assumes you have familiarity with:
### System requirements
For Python, make sure you have Python 3.9 or higher installed.
- Python 3.10 or higher installed.
- You must use the Python MCP SDK 1.2.0 or higher.
### Set up your environment
@ -82,15 +83,10 @@ uv venv
source .venv/bin/activate
# Install dependencies
uv add mcp httpx
uv add mcp[cli] httpx
# Remove template file
rm hello.py
# Create our files
mkdir -p src/weather
touch src/weather/__init__.py
touch src/weather/server.py
# Create our server file
touch weather.py
```
```powershell Windows
@ -103,154 +99,64 @@ uv venv
.venv\Scripts\activate
# Install dependencies
uv add mcp httpx
uv add mcp[cli] httpx
# Clean up boilerplate code
rm hello.py
# Create our files
md src
md src\weather
new-item src\weather\__init__.py
new-item src\weather\server.py
# Create our server file
new-item weather.py
```
</CodeGroup>
Add this code to `pyproject.toml`:
```toml
...rest of config
[build-system]
requires = [ "hatchling",]
build-backend = "hatchling.build"
[project.scripts]
weather = "weather:main"
```
Add this code to `__init__.py`:
```python src/weather/__init__.py
from . import server
import asyncio
def main():
"""Main entry point for the package."""
asyncio.run(server.main())
# Optionally expose other important items at package level
__all__ = ['main', 'server']
```
Now let's dive into building your server.
## Building your server
### Importing packages
### Importing packages and setting up the instance
Add these to the top of your `server.py`:
Add these to the top of your `weather.py`:
```python
from typing import Any
import asyncio
import httpx
from mcp.server.models import InitializationOptions
import mcp.types as types
from mcp.server import NotificationOptions, Server
import mcp.server.stdio
```
from mcp.server.fastmcp import FastMCP
### Setting up the instance
# Initialize FastMCP server
mcp = FastMCP("weather")
Then initialize the server instance and the base URL for the NWS API:
```python
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
server = Server("weather")
```
### Implementing tool listing
We need to tell clients what tools are available. The `list_tools()` decorator registers this handler:
```python
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
List available tools.
Each tool specifies its arguments using JSON Schema validation.
"""
return [
types.Tool(
name="get-alerts",
description="Get weather alerts for a state",
inputSchema={
"type": "object",
"properties": {
"state": {
"type": "string",
"description": "Two-letter state code (e.g. CA, NY)",
},
},
"required": ["state"],
},
),
types.Tool(
name="get-forecast",
description="Get weather forecast for a location",
inputSchema={
"type": "object",
"properties": {
"latitude": {
"type": "number",
"description": "Latitude of the location",
},
"longitude": {
"type": "number",
"description": "Longitude of the location",
},
},
"required": ["latitude", "longitude"],
},
),
]
```
This defines our two tools: `get-alerts` and `get-forecast`.
The FastMCP class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.
### Helper functions
Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
```python
async def make_nws_request(client: httpx.AsyncClient, url: str) -> dict[str, Any] | None:
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json"
}
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
def format_alert(feature: dict) -> str:
"""Format an alert feature into a concise string."""
"""Format an alert feature into a readable string."""
props = feature["properties"]
return (
f"Event: {props.get('event', 'Unknown')}\n"
f"Area: {props.get('areaDesc', 'Unknown')}\n"
f"Severity: {props.get('severity', 'Unknown')}\n"
f"Status: {props.get('status', 'Unknown')}\n"
f"Headline: {props.get('headline', 'No headline')}\n"
"---"
)
return f"""
Event: {props.get('event', 'Unknown')}
Area: {props.get('areaDesc', 'Unknown')}
Severity: {props.get('severity', 'Unknown')}
Description: {props.get('description', 'No description available')}
Instructions: {props.get('instruction', 'No specific instructions provided')}
"""
```
### Implementing tool execution
@ -258,142 +164,63 @@ def format_alert(feature: dict) -> str:
The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
```python
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
@mcp.tool()
async def get_alerts(state: str) -> str:
"""Get weather alerts for a US state.
Args:
state: Two-letter US state code (e.g. CA, NY)
"""
Handle tool execution requests.
Tools can fetch weather data and notify clients of changes.
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get weather forecast for a location.
Args:
latitude: Latitude of the location
longitude: Longitude of the location
"""
if not arguments:
raise ValueError("Missing arguments")
if name == "get-alerts":
state = arguments.get("state")
if not state:
raise ValueError("Missing state parameter")
# First get the forecast grid endpoint
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)
# Convert state to uppercase to ensure consistent format
state = state.upper()
if len(state) != 2:
raise ValueError("State must be a two-letter code (e.g. CA, NY)")
if not points_data:
return "Unable to fetch forecast data for this location."
async with httpx.AsyncClient() as client:
alerts_url = f"{NWS_API_BASE}/alerts?area={state}"
alerts_data = await make_nws_request(client, alerts_url)
# Get the forecast URL from the points response
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
if not alerts_data:
return [types.TextContent(type="text", text="Failed to retrieve alerts data")]
if not forecast_data:
return "Unable to fetch detailed forecast."
features = alerts_data.get("features", [])
if not features:
return [types.TextContent(type="text", text=f"No active alerts for {state}")]
# Format the periods into a readable forecast
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # Only show next 5 periods
forecast = f"""
{period['name']}:
Temperature: {period['temperature']}°{period['temperatureUnit']}
Wind: {period['windSpeed']} {period['windDirection']}
Forecast: {period['detailedForecast']}
"""
forecasts.append(forecast)
# Format each alert into a concise string
formatted_alerts = [format_alert(feature) for feature in features[:20]] # only take the first 20 alerts
alerts_text = f"Active alerts for {state}:\n\n" + "\n".join(formatted_alerts)
return [
types.TextContent(
type="text",
text=alerts_text
)
]
elif name == "get-forecast":
try:
latitude = float(arguments.get("latitude"))
longitude = float(arguments.get("longitude"))
except (TypeError, ValueError):
return [types.TextContent(
type="text",
text="Invalid coordinates. Please provide valid numbers for latitude and longitude."
)]
# Basic coordinate validation
if not (-90 <= latitude <= 90) or not (-180 <= longitude <= 180):
return [types.TextContent(
type="text",
text="Invalid coordinates. Latitude must be between -90 and 90, longitude between -180 and 180."
)]
async with httpx.AsyncClient() as client:
# First get the grid point
lat_str = f"{latitude}"
lon_str = f"{longitude}"
points_url = f"{NWS_API_BASE}/points/{lat_str},{lon_str}"
points_data = await make_nws_request(client, points_url)
if not points_data:
return [types.TextContent(type="text", text=f"Failed to retrieve grid point data for coordinates: {latitude}, {longitude}. This location may not be supported by the NWS API (only US locations are supported).")]
# Extract forecast URL from the response
properties = points_data.get("properties", {})
forecast_url = properties.get("forecast")
if not forecast_url:
return [types.TextContent(type="text", text="Failed to get forecast URL from grid point data")]
# Get the forecast
forecast_data = await make_nws_request(client, forecast_url)
if not forecast_data:
return [types.TextContent(type="text", text="Failed to retrieve forecast data")]
# Format the forecast periods
periods = forecast_data.get("properties", {}).get("periods", [])
if not periods:
return [types.TextContent(type="text", text="No forecast periods available")]
# Format each period into a concise string
formatted_forecast = []
for period in periods:
forecast_text = (
f"{period.get('name', 'Unknown')}:\n"
f"Temperature: {period.get('temperature', 'Unknown')}°{period.get('temperatureUnit', 'F')}\n"
f"Wind: {period.get('windSpeed', 'Unknown')} {period.get('windDirection', '')}\n"
f"{period.get('shortForecast', 'No forecast available')}\n"
"---"
)
formatted_forecast.append(forecast_text)
forecast_text = f"Forecast for {latitude}, {longitude}:\n\n" + "\n".join(formatted_forecast)
return [types.TextContent(
type="text",
text=forecast_text
)]
else:
raise ValueError(f"Unknown tool: {name}")
return "\n---\n".join(forecasts)
```
### Running the server
Finally, implement the main function to run the server:
```python
async def main():
# Run the server using stdin/stdout streams
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="weather",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
# This is needed if you'd like to connect to a custom client
if __name__ == "__main__":
asyncio.run(main())
```
Your server is complete! Run `uv run src/weather/server.py` to confirm that everything's working.
Your server is complete! Run `uv run weather.py` to confirm that everything's working.
Let's now test your server from an existing MCP host, Claude for Desktop.
@ -438,7 +265,7 @@ In this case, we'll add our single weather server like so:
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather"
"weather.py"
]
}
}
@ -455,7 +282,7 @@ In this case, we'll add our single weather server like so:
"--directory",
"C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
"run",
"weather"
"weather.py"
]
}
}