Build a Weather Service Servlet with Python
If you want to start with some working servlets, see our official servlet repo: dylibso/mcp.run-servlets
Welcome! In this tutorial, you'll build your first servlet using Python. We'll create a weather service that AI models can use to fetch real-time weather data. By the end, you'll have a working servlet that can tell you the weather anywhere in the world!
What Are We Building?
A servlet is a tool that AI models can use to interact with external services. Our weather servlet will:
- Connect to the WeatherAPI service
- Fetch current weather conditions for any location
- Format the data in a user-friendly way
Here's what it looks like in action:
Pre-requisites
Before starting, ensure you have the following:
- uv
- extism-py
- A GitHub Account for mcp.run authentication
- A free API key from WeatherAPI
- The XTP CLI
Create an account on mcp.run
1. Create an account on mcp.run:
Visit https://mcp.run and click "Sign Up".
2. Register your first servlet:
Click "Publish" and go through the Registration flow. You can make your servlet "Private" while it is under development.
Use weather-service
as the name, and you can leave the rest of the fields
blank. We'll fill these out later!
Once you're done, click "Register Servlet" and you'll see some instructions to follow on your servlet page. Be sure to follow those closely.
Project Setup
First, let's create our project:
- Use the XTP CLI to genearate a new Servlet
xtp plugin init \
--extension-point ext_01je4jj1tteaktf0zd0anm8854 \
--feature stub-with-code-samples \
--path weather-service \
--name weather-service
The --name parameter (weather-service) MUST match what you'll use when publishing to mcp.run later.
-
Choose "Python" when prompted
-
Open the project in VS Code
Create a new file called plugin/weather_api.py
:
from dataclasses import dataclass
from typing import Optional
from dataclass_wizard import JSONWizard
from extism import Http
@dataclass
class WeatherLocation:
name: str
country: str
lat: float
lon: float
@dataclass
class WeatherCondition:
text: str
@dataclass
class CurrentWeather:
temp_c: float
temp_f: float
condition: WeatherCondition
humidity: int
@dataclass
class WeatherResponse(JSONWizard):
location: WeatherLocation
current: CurrentWeather
class Meta:
key_transform_with_load = 'SNAKE'
class WeatherAPI:
def __init__(self, api_key: str):
self.api_key = api_key
def get_current_weather(self, location: str) -> WeatherResponse:
"""
Get current weather for a given location.
Args:
location: City name, latitude/longitude, or other location identifier
Returns:
WeatherResponse object containing weather data
Raises:
Exception: If the API request fails
"""
url = f"http://api.weatherapi.com/v1/current.json?key={self.api_key}&q={location}&aqi=no"
response = Http.request(
url=url,
meth="GET"
)
if response.status_code != 200:
raise Exception(
f"Weather API error: {response.status_code} - {response.data_str()}"
)
return WeatherResponse.from_json(response.data_str())
And then edit plugin/plugin.py
to use our WeatherAPI
.
Replace the call
and describe
functions with:
from typing import Dict, Any # noqa: F401
from weather_api import WeatherAPI # noqa: F401
def handle_get_weather(api: WeatherAPI, args: Dict[str, Any]) -> CallToolResult:
location = args.get('location') if args else None
if not location:
return CallToolResult(
isError=True,
content=[Content(
type=ContentType.Text,
text="location parameter is required"
)]
)
try:
weather = api.get_current_weather(location)
response = (
f"Current weather in {weather.location.name}, {weather.location.country}:\n"
f"Temperature: {weather.current.temp_c:.1f}°C ({weather.current.temp_f:.1f}°F)\n"
f"Condition: {weather.current.condition.text}\n"
f"Humidity: {weather.current.humidity}%"
)
return CallToolResult(
content=[Content(
type=ContentType.Text,
text=response
)]
)
except Exception as err:
return CallToolResult(
isError=True,
content=[Content(
type=ContentType.Text,
text=str(err)
)]
)
# Called when the tool is invoked.
# If you support multiple tools, you must switch on the input.params.name to detect which tool is being called.
def call(input: CallToolRequest) -> CallToolResult:
# Get API key from config
api_key = extism.Config.get_str("WEATHER_API_KEY")
if not api_key:
return CallToolResult(
isError=True,
content=[Content(
type=ContentType.Text,
text="WeatherAPI key not configured"
)]
)
weather_api = WeatherAPI(api_key)
if input.params.name == "get-weather":
return handle_get_weather(weather_api, input.params.arguments)
else:
return CallToolResult(
isError=True,
content=[Content(
type=ContentType.Text,
text=f"Unknown tool: {input.params.name}"
)]
)
# Called by mcpx to understand how and why to use this tool.
# Note: Your servlet configs will not be set when this function is called,
# so do not rely on config in this function
def describe() -> ListToolsResult:
return ListToolsResult(
tools=[ToolDescription(
name="get-weather",
description="Get current weather conditions for any location",
inputSchema={
"type": "object",
"required": ["location"],
"properties": {
"location": {
"type": "string",
"description": "Location name, city, zip/postal code, or coordinates"
}
}
}
)]
)
This file defines how AI models interact with our weather tool. A servlet can
have multiple tools, but we're keeping it simple with just one: get-weather
.
There are two main functions:
describe()
: Tells AI models what our Servlet can docall()
: Handles incoming requests from AI models
Build and test your Servlet
You can use the XTP CLI to test your Servlet locally:
# Build your servlet
xtp plugin build
# Set your API key as an environment variable
export WEATHER_API_KEY="your-key-here"
# Test out calling the get-weather tool
xtp plugin call plugin.wasm call --input='{
"params": {
"name": "get-weather",
"arguments": {
"location": "London"
}
}
}' \
--wasi \
--allow-host api.weatherapi.com \
--config "WEATHER_API_KEY=$WEATHER_API_KEY"
You should see something like:
{
"content": [{
"text": "Current weather in London, United Kingdom:\nTemperature: 9.2°C (48.6°F)\nCondition: Mist\nHumidity: 93%",
"type": "text"
}]
}
Publish your Servlet on mcp.run
- Register your servlet at https://mcp.run/publish
- Name:
weather-service
(MUST match the name from earlier!) - Description: "Get real-time weather data for any location"
- Configuration:
- Add
WEATHER_API_KEY
variable for WeatherAPI key.It's also a nice touch to include an example value and some instructions or a URL in the description about where to find their API key, so it's easier for installers to get started.
- Add
- Domain permissions:
- Add
api.weatherapi.com
- Add
Click Register Servlet
- Upload:
xtp plugin push
That's it! Your servlet is now available for AI models to use.
Using Your Servlet
- Click on the
Install
button on your Servlet page - Provide your WeatherAPI key
- Now the AI model can use it you have installed the
mcpx
client, see this page for more details.
Support
If you get stuck and need some help, please reach out! Visit our support page to learn how best to get in touch.