Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
151 changes: 108 additions & 43 deletions ai/mcp_server_fastmcp/README.md → ai/fastmcp-server/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
FastMCP — Fast, tiny MCP server example
======================================
FastMCP — Weather MCP Server
============================

Overview
--------
This directory contains a minimal FastMCP server example and a companion client. FastMCP demonstrates a tiny, runnable implementation of an MCP-compatible service (Model Context Protocol) intended for local testing and quick deployment to cloud platforms.
This directory contains a FastMCP weather server example and a companion client. The server provides real-time weather information and forecasts using the free Open-Meteo API. It demonstrates a practical implementation of an MCP-compatible service (Model Context Protocol) for weather data, intended for local testing and quick deployment to [IBM Cloud Code Engine](https://www.ibm.com/products/code-engine).

![](./images/architecture.png)

Expand Down Expand Up @@ -32,40 +32,84 @@ Why Code Engine?
- **Simple deployment**: Integrates with container registries and CI/CD pipelines.
- **Managed endpoint**: Provides a secure http endpoint with a managed certificate.

A very tiny MCP Server
----------------------
Weather MCP Server Features
---------------------------

The server provides three weather-related tools:

1. **search_location**: Search for locations by name to get coordinates
2. **get_current_weather**: Get current weather conditions for specific coordinates
3. **get_weather_forecast**: Get weather forecast for 1-16 days

The Python source code for the MCP server is located in [./server/main.py](./server/main.py):

```python
from typing import Any
from fastmcp import FastMCP
from art import text2art
import weather_api

mcp = FastMCP("My FastMCP Server on Code Engine")
mcp = FastMCP("Weather MCP Server on Code Engine")

@mcp.tool
async def search_location(query: str) -> str:
"""
Search for a location by name to get coordinates for weather queries.
Args:
query: The location name to search for (e.g., "London", "New York", "Tokyo")
Returns:
A formatted string with matching locations and their coordinates
"""
try:
data = await weather_api.search_location(query)
return weather_api.format_location_results(data)
except Exception as e:
return f"Error searching for location: {str(e)}"

@mcp.tool
def ascii_art(input: str) -> str:
"""Take an arbitraty input and return it as an ASCII art banner"""

if input == "Code Engine":
response = ". ___ __ ____ ____\n"
response += "./ __)/ \\( \\( __)\n"
response += "( (__( O )) D ( ) _)\n"
response += ".\\___)\\__/(____/(____)\n"
response += ".____ __ _ ___ __ __ _ ____\n"
response += "( __)( ( \\ / __)( )( ( \\( __)\n"
response += ".) _) / /( (_ \\ )( / / ) _)\n"
response += "(____)\\_)__) \\___/(__)\\_)__)(____)\n"
else:
response: str = text2art(input)

return response
async def get_current_weather(latitude: float, longitude: float) -> str:
"""
Get the current weather conditions for a specific location.
Args:
latitude: The latitude of the location (e.g., 51.5074 for London)
longitude: The longitude of the location (e.g., -0.1278 for London)
Returns:
A formatted string with current weather information
"""
try:
data = await weather_api.get_current_weather(latitude, longitude)
return weather_api.format_current_weather(data)
except Exception as e:
return f"Error getting current weather: {str(e)}"

@mcp.tool
async def get_weather_forecast(latitude: float, longitude: float, days: int = 7) -> str:
"""
Get the weather forecast for a specific location.
Args:
latitude: The latitude of the location
longitude: The longitude of the location
days: Number of days to forecast (1-16, default: 7)
Returns:
A formatted string with daily weather forecast
"""
try:
data = await weather_api.get_weather_forecast(latitude, longitude, days)
return weather_api.format_weather_forecast(data)
except Exception as e:
return f"Error getting weather forecast: {str(e)}"

if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8080)
```

The weather API functions are separated in [./server/weather_api.py](./server/weather_api.py) and use the free Open-Meteo API (no API key required).

Deploying the server to Code Engine
----------------------------------

Expand Down Expand Up @@ -99,20 +143,21 @@ https://fastmcp.26n4g2nfyw7s.eu-de.codeengine.appdomain.cloud/mcp

Testing the deployed server with call_tool.sh
---------------------------------------------
The `call_tool.sh` script provides a quick way to test your deployed MCP server directly from the command line. It demonstrates the complete MCP protocol flow:
The `call_tool.sh` script provides a quick way to test your deployed MCP server directly from the command line. It demonstrates the complete MCP protocol flow with weather data for Stuttgart:

1. Initializes an MCP session with the server
2. Lists all available tools
3. Calls the `ascii_art` tool with "Code Engine" as input
4. Displays the ASCII art output
3. Searches for Stuttgart location
4. Gets current weather for Stuttgart (coordinates: 48.7758, 9.1829)
5. Gets 7-day weather forecast for Stuttgart

Run the script from the `mcp_server_fastmcp` directory:

```bash
./call_tool.sh
```

The script will automatically connect to your deployed FastMCP application and execute a test call. You should see output similar to:
The script will automatically connect to your deployed FastMCP application and execute weather queries for Stuttgart. You should see output similar to:

```
FastMCP application is reachable under the following url:
Expand All @@ -122,16 +167,32 @@ initialize session
Session initialized: <session-id>
List tools
Call tool 'ascii_art' with input 'Code Engine'
. ___ __ ____ ____
./ __)/ \( \( __)
.( (__( O )) D ( ) _)
.\___)\\__/(____/(____)
.____ __ _ ___ __ __ _ ____
.( __)( ( \ / __)( )( ( \( __)
..) _) / /( (_ \ )( / / ) _)
.(____)\\_)__) \\___/(__)\\_)__)(____)
==========================================
WEATHER TOOLS DEMONSTRATION FOR STUTTGART
==========================================
1. Search for 'Stuttgart' location
Stuttgart, Baden-Württemberg, Germany (lat: 48.7758, lon: 9.1829)
2. Get current weather for Stuttgart
Current Weather:
Condition: Partly cloudy
Temperature: 15.2°C
Feels like: 14.8°C
Humidity: 65%
Wind Speed: 12.5 km/h
Precipitation: 0.0 mm
3. Get 7-day weather forecast for Stuttgart
Weather Forecast:
2026-03-20:
Condition: Partly cloudy
Temperature: 8.5°C - 16.2°C
Precipitation: 0.2 mm (probability: 20%)
Max Wind Speed: 18.5 km/h
...
SUCCESS
```
Expand All @@ -147,7 +208,7 @@ Example `claude_desktop_config.json` entry that point to your deployed applicati
```json
{
"mcpServer": {
"ASCII Art FastMCP (Code Engine)": {
"Weather FastMCP (Code Engine)": {
"command": "npx",
"args": [
"mcp-remote",
Expand All @@ -164,21 +225,20 @@ Save settings and restart Claude Desktop; the remote MCP server should appear as

You can now chat with the MCP Server, e.g.

**_"Create ASCII art for: Hello, World!"_**
**_"Get the current weather and forecast of New York using the tool deployed in Code Engine :-)"_**

Claude will detect the tool and call the `ascii_art` function, responding with ASCII art output for your input text.
Claude will detect the appropriate weather tools and call them to provide current weather conditions or forecasts for the requested locations.

![](./images/claude_chat.png)

Note, the LLM even detected the simplicity of our MCP Server.

Building and using the Python client
-----------------------------
The `client` directory contains a small Python client to exercise the server.
The `client` directory contains a small Python client to exercise the weather server.

1. Create a virtual environment and install dependencies:

```bash
cd client
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Expand All @@ -192,8 +252,13 @@ Start the client by replacing the application URL from above as the `MCP_SERVER_
MCP_SERVER_URL="https://fastmcp.26n4g2nfyw7s.eu-de.codeengine.appdomain.cloud/mcp" python client.py
```

The client will demonstrate all three weather tools using Stuttgart as an example location:
- Search for Stuttgart location
- Get current weather for Stuttgart
- Get 7-day weather forecast for Stuttgart

3. Inspect and adapt
- Open `client.py` to find example calls. The client is intentionally minimal so you can adapt it to your tooling or CI.
- Open `client.py` to find example calls. The client demonstrates how to use all weather tools and can be adapted to query different locations or forecast periods.


What's next?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,16 @@ curl -s -X POST $url/mcp \
"params": {}
}' | grep "data" | sed s/"data: "//g | jq .

print_msg "\nCall tool 'ascii_art' with input 'Code Engine'"
text=$(curl -s -X POST $url/mcp \
# Stuttgart coordinates
STUTTGART_LAT=48.7758
STUTTGART_LON=9.1829

print_msg "\n=========================================="
print_msg "WEATHER TOOLS DEMONSTRATION FOR STUTTGART"
print_msg "==========================================\n"

print_msg "\n1. Search for 'Stuttgart' location"
curl -s -X POST $url/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Accept: text/event-stream" \
Expand All @@ -86,14 +94,51 @@ text=$(curl -s -X POST $url/mcp \
"id": 1,
"method": "tools/call",
"params": {
"name": "ascii_art",
"name": "search_location",
"arguments": {
"input": "Code Engine"
"query": "Stuttgart"
}
}
}' | grep "data" | sed s/"data: "//g | jq -r ".result.content[0].text"

print_msg "\n2. Get current weather for Stuttgart"
curl -s -X POST $url/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Accept: text/event-stream" \
-H "Mcp-Session-Id: $session" \
-d "{
\"jsonrpc\": \"2.0\",
\"id\": 1,
\"method\": \"tools/call\",
\"params\": {
\"name\": \"get_current_weather\",
\"arguments\": {
\"latitude\": $STUTTGART_LAT,
\"longitude\": $STUTTGART_LON
}
}
}' | grep "data" | sed s/"data: "//g | jq ".result.content[0].text")
}" | grep "data" | sed s/"data: "//g | jq -r ".result.content[0].text"

echo -e $text
print_msg "\n3. Get 7-day weather forecast for Stuttgart"
curl -s -X POST $url/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Accept: text/event-stream" \
-H "Mcp-Session-Id: $session" \
-d "{
\"jsonrpc\": \"2.0\",
\"id\": 1,
\"method\": \"tools/call\",
\"params\": {
\"name\": \"get_weather_forecast\",
\"arguments\": {
\"latitude\": $STUTTGART_LAT,
\"longitude\": $STUTTGART_LON,
\"days\": 7
}
}
}" | grep "data" | sed s/"data: "//g | jq -r ".result.content[0].text"



Expand Down
55 changes: 55 additions & 0 deletions ai/fastmcp-server/client/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import asyncio
import os
from fastmcp import Client, FastMCP

mcp_server_url = os.getenv("MCP_SERVER_URL", default="http://localhost:8080/mcp")

# HTTP server
client = Client(mcp_server_url)

async def main():
async with client:
# Basic server interaction
await client.ping()

# List available operations
tools = await client.list_tools()
print("Available tools:", tools)
resources = await client.list_resources()
print("Available resources:", resources)
prompts = await client.list_prompts()
print("Available prompts:", prompts)

# Stuttgart coordinates
stuttgart_lat = 48.7758
stuttgart_lon = 9.1829

print("\n" + "="*50)
print("WEATHER TOOLS DEMONSTRATION")
print("="*50)

# Search for Stuttgart location
print("\n1. Searching for 'Stuttgart'...")
result = await client.call_tool("search_location", {"query": "Stuttgart"})
print(result.content[0].text)

# Get current weather for Stuttgart
print("\n2. Getting current weather for Stuttgart...")
result = await client.call_tool("get_current_weather", {
"latitude": stuttgart_lat,
"longitude": stuttgart_lon
})
print(result.content[0].text)

# Get weather forecast for Stuttgart (7 days)
print("\n3. Getting 7-day weather forecast for Stuttgart...")
result = await client.call_tool("get_weather_forecast", {
"latitude": stuttgart_lat,
"longitude": stuttgart_lon,
"days": 7
})
print(result.content[0].text)

asyncio.run(main())

# Made with Bob
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ fi
project_guid=$(ibmcloud ce project get --name $ce_project_name --output json | jq -r '.guid')

print_msg "\nCreating the MCP Server as a code engine application"
ibmcloud ce app create --name fastmcp --build-source ./server --build-strategy buildpacks --cpu 1 --memory 4G -p 8080 --wait-timeout 600 --min-scale 0 --scale-down-delay 120 --visibility public
ibmcloud ce app create --name fastmcp --build-source ./server --build-strategy buildpacks --cpu 1 --memory 4G -p 8080 --wait-timeout 600 --min-scale 1 --scale-down-delay 120 --visibility public

while true; do
ibmcloud ce app list
Expand Down
Binary file added ai/fastmcp-server/images/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ai/fastmcp-server/images/claude_chat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ai/fastmcp-server/images/claude_settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading