chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:10:27 +08:00
commit 49b9bb6724
992 changed files with 161690 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
"""MCPServer Complex inputs Example
Demonstrates validation via pydantic with complex models.
"""
from typing import Annotated
from pydantic import BaseModel, Field
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("Shrimp Tank")
class ShrimpTank(BaseModel):
class Shrimp(BaseModel):
name: Annotated[str, Field(max_length=10)]
shrimp: list[Shrimp]
@mcp.tool()
def name_shrimp(
tank: ShrimpTank,
# You can use pydantic Field in function signatures for validation.
extra_names: Annotated[list[str], Field(max_length=10)],
) -> list[str]:
"""List all shrimp names in the tank"""
return [shrimp.name for shrimp in tank.shrimp] + extra_names
+24
View File
@@ -0,0 +1,24 @@
"""MCPServer Desktop Example
A simple example that exposes the desktop directory as a resource.
"""
from pathlib import Path
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Demo")
@mcp.resource("dir://desktop")
def desktop() -> list[str]:
"""List the files in the user's desktop"""
desktop = Path.home() / "Desktop"
return [str(f) for f in desktop.iterdir()]
@mcp.tool()
def sum(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
@@ -0,0 +1,22 @@
"""MCPServer Echo Server with direct CallToolResult return"""
from typing import Annotated
from mcp_types import CallToolResult, TextContent
from pydantic import BaseModel
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("Echo Server")
class EchoResponse(BaseModel):
text: str
@mcp.tool()
def echo(text: str) -> Annotated[CallToolResult, EchoResponse]:
"""Echo the input text with structure and metadata"""
return CallToolResult(
content=[TextContent(type="text", text=text)], structured_content={"text": text}, _meta={"some": "metadata"}
)
+28
View File
@@ -0,0 +1,28 @@
"""MCPServer Echo Server"""
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Echo Server")
@mcp.tool()
def echo_tool(text: str) -> str:
"""Echo the input text"""
return text
@mcp.resource("echo://static")
def echo_resource() -> str:
return "Echo!"
@mcp.resource("echo://{text}")
def echo_template(text: str) -> str:
"""Echo the input text"""
return f"Echo: {text}"
@mcp.prompt("echo")
def echo_prompt(text: str) -> str:
return text
+56
View File
@@ -0,0 +1,56 @@
"""MCPServer Icons Demo Server
Demonstrates using icons with tools, resources, prompts, and implementation.
"""
import base64
from pathlib import Path
from mcp.server.mcpserver import Icon, MCPServer
# Load the icon file and convert to data URI
icon_path = Path(__file__).parent / "mcp.png"
icon_data = base64.standard_b64encode(icon_path.read_bytes()).decode()
icon_data_uri = f"data:image/png;base64,{icon_data}"
icon_data = Icon(src=icon_data_uri, mime_type="image/png", sizes=["64x64"])
# Create server with icons in implementation
mcp = MCPServer(
"Icons Demo Server", website_url="https://github.com/modelcontextprotocol/python-sdk", icons=[icon_data]
)
@mcp.tool(icons=[icon_data])
def demo_tool(message: str) -> str:
"""A demo tool with an icon."""
return message
@mcp.resource("demo://readme", icons=[icon_data])
def readme_resource() -> str:
"""A demo resource with an icon"""
return "This resource has an icon"
@mcp.prompt("prompt_with_icon", icons=[icon_data])
def prompt_with_icon(text: str) -> str:
"""A demo prompt with an icon"""
return text
@mcp.tool(
icons=[
Icon(src=icon_data_uri, mime_type="image/png", sizes=["16x16"]),
Icon(src=icon_data_uri, mime_type="image/png", sizes=["32x32"]),
Icon(src=icon_data_uri, mime_type="image/png", sizes=["64x64"]),
]
)
def multi_icon_tool(action: str) -> str:
"""A tool demonstrating multiple icons."""
return "multi_icon_tool"
if __name__ == "__main__":
# Run the server
mcp.run()
@@ -0,0 +1,31 @@
"""MCPServer Echo Server that sends log messages and progress updates to the client"""
import asyncio
from mcp.server.mcpserver import Context, MCPServer
# Create server
mcp = MCPServer("Echo Server with logging and progress updates")
@mcp.tool()
async def echo(text: str, ctx: Context) -> str:
"""Echo the input text sending log messages and progress updates during processing."""
await ctx.report_progress(progress=0, total=100)
await ctx.info("Starting to process echo for input: " + text)
await asyncio.sleep(2)
await ctx.info("Halfway through processing echo for input: " + text)
await ctx.report_progress(progress=50, total=100)
await asyncio.sleep(2)
await ctx.info("Finished processing echo for input: " + text)
await ctx.report_progress(progress=100, total=100)
# Progress notifications are process asynchronously by the client.
# A small delay here helps ensure the last notification is processed by the client.
await asyncio.sleep(0.1)
return text
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

+324
View File
@@ -0,0 +1,324 @@
# /// script
# dependencies = ["pydantic-ai-slim[openai]", "asyncpg", "numpy", "pgvector"]
# ///
# uv pip install 'pydantic-ai-slim[openai]' asyncpg numpy pgvector
"""Recursive memory system inspired by the human brain's clustering of memories.
Uses OpenAI's 'text-embedding-3-small' model and pgvector for efficient
similarity search.
"""
import asyncio
import math
import os
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Annotated, Self, TypeVar
import asyncpg
import numpy as np
from openai import AsyncOpenAI
from pgvector.asyncpg import register_vector # Import register_vector
from pydantic import BaseModel, Field
from pydantic_ai import Agent
from mcp.server.mcpserver import MCPServer
MAX_DEPTH = 5
SIMILARITY_THRESHOLD = 0.7
DECAY_FACTOR = 0.99
REINFORCEMENT_FACTOR = 1.1
DEFAULT_LLM_MODEL = "openai:gpt-4o"
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"
T = TypeVar("T")
mcp = MCPServer("memory")
DB_DSN = "postgresql://postgres:postgres@localhost:54320/memory_db"
# reset memory with rm ~/.mcp/{USER}/memory/*
PROFILE_DIR = (Path.home() / ".mcp" / os.environ.get("USER", "anon") / "memory").resolve()
PROFILE_DIR.mkdir(parents=True, exist_ok=True)
def cosine_similarity(a: list[float], b: list[float]) -> float:
a_array = np.array(a, dtype=np.float64)
b_array = np.array(b, dtype=np.float64)
return np.dot(a_array, b_array) / (np.linalg.norm(a_array) * np.linalg.norm(b_array))
async def do_ai(
user_prompt: str,
system_prompt: str,
result_type: type[T] | Annotated,
deps=None,
) -> T:
agent = Agent(
DEFAULT_LLM_MODEL,
system_prompt=system_prompt,
result_type=result_type,
)
result = await agent.run(user_prompt, deps=deps)
return result.data
@dataclass
class Deps:
openai: AsyncOpenAI
pool: asyncpg.Pool
async def get_db_pool() -> asyncpg.Pool:
async def init(conn):
await conn.execute("CREATE EXTENSION IF NOT EXISTS vector;")
await register_vector(conn)
pool = await asyncpg.create_pool(DB_DSN, init=init)
return pool
class MemoryNode(BaseModel):
id: int | None = None
content: str
summary: str = ""
importance: float = 1.0
access_count: int = 0
timestamp: float = Field(default_factory=lambda: datetime.now(timezone.utc).timestamp())
embedding: list[float]
@classmethod
async def from_content(cls, content: str, deps: Deps):
embedding = await get_embedding(content, deps)
return cls(content=content, embedding=embedding)
async def save(self, deps: Deps):
async with deps.pool.acquire() as conn:
if self.id is None:
result = await conn.fetchrow(
"""
INSERT INTO memories (content, summary, importance, access_count,
timestamp, embedding)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id
""",
self.content,
self.summary,
self.importance,
self.access_count,
self.timestamp,
self.embedding,
)
self.id = result["id"]
else:
await conn.execute(
"""
UPDATE memories
SET content = $1, summary = $2, importance = $3,
access_count = $4, timestamp = $5, embedding = $6
WHERE id = $7
""",
self.content,
self.summary,
self.importance,
self.access_count,
self.timestamp,
self.embedding,
self.id,
)
async def merge_with(self, other: Self, deps: Deps):
self.content = await do_ai(
f"{self.content}\n\n{other.content}",
"Combine the following two texts into a single, coherent text.",
str,
deps,
)
self.importance += other.importance
self.access_count += other.access_count
self.embedding = [(a + b) / 2 for a, b in zip(self.embedding, other.embedding)]
self.summary = await do_ai(self.content, "Summarize the following text concisely.", str, deps)
await self.save(deps)
# Delete the merged node from the database
if other.id is not None:
await delete_memory(other.id, deps)
def get_effective_importance(self):
return self.importance * (1 + math.log(self.access_count + 1))
async def get_embedding(text: str, deps: Deps) -> list[float]:
embedding_response = await deps.openai.embeddings.create(
input=text,
model=DEFAULT_EMBEDDING_MODEL,
)
return embedding_response.data[0].embedding
async def delete_memory(memory_id: int, deps: Deps):
async with deps.pool.acquire() as conn:
await conn.execute("DELETE FROM memories WHERE id = $1", memory_id)
async def add_memory(content: str, deps: Deps):
new_memory = await MemoryNode.from_content(content, deps)
await new_memory.save(deps)
similar_memories = await find_similar_memories(new_memory.embedding, deps)
for memory in similar_memories:
if memory.id != new_memory.id:
await new_memory.merge_with(memory, deps)
await update_importance(new_memory.embedding, deps)
await prune_memories(deps)
return f"Remembered: {content}"
async def find_similar_memories(embedding: list[float], deps: Deps) -> list[MemoryNode]:
async with deps.pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT id, content, summary, importance, access_count, timestamp, embedding
FROM memories
ORDER BY embedding <-> $1
LIMIT 5
""",
embedding,
)
memories = [
MemoryNode(
id=row["id"],
content=row["content"],
summary=row["summary"],
importance=row["importance"],
access_count=row["access_count"],
timestamp=row["timestamp"],
embedding=row["embedding"],
)
for row in rows
]
return memories
async def update_importance(user_embedding: list[float], deps: Deps):
async with deps.pool.acquire() as conn:
rows = await conn.fetch("SELECT id, importance, access_count, embedding FROM memories")
for row in rows:
memory_embedding = row["embedding"]
similarity = cosine_similarity(user_embedding, memory_embedding)
if similarity > SIMILARITY_THRESHOLD:
new_importance = row["importance"] * REINFORCEMENT_FACTOR
new_access_count = row["access_count"] + 1
else:
new_importance = row["importance"] * DECAY_FACTOR
new_access_count = row["access_count"]
await conn.execute(
"""
UPDATE memories
SET importance = $1, access_count = $2
WHERE id = $3
""",
new_importance,
new_access_count,
row["id"],
)
async def prune_memories(deps: Deps):
async with deps.pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT id, importance, access_count
FROM memories
ORDER BY importance DESC
OFFSET $1
""",
MAX_DEPTH,
)
for row in rows:
await conn.execute("DELETE FROM memories WHERE id = $1", row["id"])
async def display_memory_tree(deps: Deps) -> str:
async with deps.pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT content, summary, importance, access_count
FROM memories
ORDER BY importance DESC
LIMIT $1
""",
MAX_DEPTH,
)
result = ""
for row in rows:
effective_importance = row["importance"] * (1 + math.log(row["access_count"] + 1))
summary = row["summary"] or row["content"]
result += f"- {summary} (Importance: {effective_importance:.2f})\n"
return result
@mcp.tool()
async def remember(
contents: list[str] = Field(description="List of observations or memories to store"),
):
deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool())
try:
return "\n".join(await asyncio.gather(*[add_memory(content, deps) for content in contents]))
finally:
await deps.pool.close()
@mcp.tool()
async def read_profile() -> str:
deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool())
profile = await display_memory_tree(deps)
await deps.pool.close()
return profile
async def initialize_database():
pool = await asyncpg.create_pool("postgresql://postgres:postgres@localhost:54320/postgres")
try:
async with pool.acquire() as conn:
await conn.execute("""
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = 'memory_db'
AND pid <> pg_backend_pid();
""")
await conn.execute("DROP DATABASE IF EXISTS memory_db;")
await conn.execute("CREATE DATABASE memory_db;")
finally:
await pool.close()
pool = await asyncpg.create_pool(DB_DSN)
try:
async with pool.acquire() as conn:
await conn.execute("CREATE EXTENSION IF NOT EXISTS vector;")
await register_vector(conn)
await conn.execute("""
CREATE TABLE IF NOT EXISTS memories (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
summary TEXT,
importance REAL NOT NULL,
access_count INT NOT NULL,
timestamp DOUBLE PRECISION NOT NULL,
embedding vector(1536) NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_memories_embedding ON memories
USING hnsw (embedding vector_l2_ops);
""")
finally:
await pool.close()
if __name__ == "__main__":
asyncio.run(initialize_database())
@@ -0,0 +1,19 @@
"""MCPServer Example showing parameter descriptions"""
from pydantic import Field
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Parameter Descriptions Server")
@mcp.tool()
def greet_user(
name: str = Field(description="The name of the person to greet"),
title: str = Field(description="Optional title like Mr/Ms/Dr", default=""),
times: int = Field(description="Number of times to repeat the greeting", default=1),
) -> str:
"""Greet a user with optional title and repetition"""
greeting = f"Hello {title + ' ' if title else ''}{name}!"
return "\n".join([greeting] * times)
+18
View File
@@ -0,0 +1,18 @@
from mcp.server.mcpserver import MCPServer
# Create an MCP server
mcp = MCPServer("Demo")
# Add an addition tool
@mcp.tool()
def sum(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
+27
View File
@@ -0,0 +1,27 @@
"""MCPServer Screenshot Example
Give Claude a tool to capture and view screenshots.
"""
import io
from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.utilities.types import Image
# Create server
mcp = MCPServer("Screenshot Demo")
@mcp.tool()
def take_screenshot() -> Image:
"""Take a screenshot of the user's screen and return it as an image. Use
this tool anytime the user wants you to look at something they're doing.
"""
import pyautogui
buffer = io.BytesIO()
# if the file exceeds ~1MB, it will be rejected by Claude
screenshot = pyautogui.screenshot()
screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
return Image(data=buffer.getvalue(), format="jpeg")
+12
View File
@@ -0,0 +1,12 @@
"""MCPServer Echo Server"""
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Echo Server")
@mcp.tool()
def echo(text: str) -> str:
"""Echo the input text"""
return text
+67
View File
@@ -0,0 +1,67 @@
# /// script
# dependencies = []
# ///
"""MCPServer Text Me Server
--------------------------------
This defines a simple MCPServer server that sends a text message to a phone number via https://surgemsg.com/.
To run this example, create a `.env` file with the following values:
SURGE_API_KEY=...
SURGE_ACCOUNT_ID=...
SURGE_MY_PHONE_NUMBER=...
SURGE_MY_FIRST_NAME=...
SURGE_MY_LAST_NAME=...
Visit https://surgemsg.com/ and click "Get Started" to obtain these values.
"""
from typing import Annotated
import httpx
from pydantic import BeforeValidator
from pydantic_settings import BaseSettings, SettingsConfigDict
from mcp.server.mcpserver import MCPServer
class SurgeSettings(BaseSettings):
model_config: SettingsConfigDict = SettingsConfigDict(env_prefix="SURGE_", env_file=".env")
api_key: str
account_id: str
my_phone_number: Annotated[str, BeforeValidator(lambda v: "+" + v if not v.startswith("+") else v)]
my_first_name: str
my_last_name: str
# Create server
mcp = MCPServer("Text me")
surge_settings = SurgeSettings() # type: ignore
@mcp.tool(name="textme", description="Send a text message to me")
def text_me(text_content: str) -> str:
"""Send a text message to a phone number via https://surgemsg.com/"""
with httpx.Client() as client:
response = client.post(
"https://api.surgemsg.com/messages",
headers={
"Authorization": f"Bearer {surge_settings.api_key}",
"Surge-Account": surge_settings.account_id,
"Content-Type": "application/json",
},
json={
"body": text_content,
"conversation": {
"contact": {
"first_name": surge_settings.my_first_name,
"last_name": surge_settings.my_last_name,
"phone_number": surge_settings.my_phone_number,
}
},
},
)
response.raise_for_status()
return f"Message sent: {text_content}"
+59
View File
@@ -0,0 +1,59 @@
"""Example MCPServer server that uses Unicode characters in various places to help test
Unicode handling in tools and inspectors.
"""
from mcp.server.mcpserver import MCPServer
mcp = MCPServer()
@mcp.tool(description="🌟 A tool that uses various Unicode characters in its description: á é í ó ú ñ 漢字 🎉")
def hello_unicode(name: str = "世界", greeting: str = "¡Hola") -> str:
"""A simple tool that demonstrates Unicode handling in:
- Tool description (emojis, accents, CJK characters)
- Parameter defaults (CJK characters)
- Return values (Spanish punctuation, emojis)
"""
return f"{greeting}, {name}! 👋"
@mcp.tool(description="🎨 Tool that returns a list of emoji categories")
def list_emoji_categories() -> list[str]:
"""Returns a list of emoji categories with emoji examples."""
return [
"😀 Smileys & Emotion",
"👋 People & Body",
"🐶 Animals & Nature",
"🍎 Food & Drink",
"⚽ Activities",
"🌍 Travel & Places",
"💡 Objects",
"❤️ Symbols",
"🚩 Flags",
]
@mcp.tool(description="🔤 Tool that returns text in different scripts")
def multilingual_hello() -> str:
"""Returns hello in different scripts and writing systems."""
return "\n".join(
[
"English: Hello!",
"Spanish: ¡Hola!",
"French: Bonjour!",
"German: Grüß Gott!",
"Russian: Привет!",
"Greek: Γεια σας!",
"Hebrew: !שָׁלוֹם",
"Arabic: !مرحبا",
"Hindi: नमस्ते!",
"Chinese: 你好!",
"Japanese: こんにちは!",
"Korean: 안녕하세요!",
"Thai: สวัสดี!",
]
)
if __name__ == "__main__":
mcp.run()
+224
View File
@@ -0,0 +1,224 @@
"""MCPServer Weather Example with Structured Output
Demonstrates how to use structured output with tools to return
well-typed, validated data that clients can easily process.
"""
import asyncio
import json
import sys
from dataclasses import dataclass
from datetime import datetime
from typing import TypedDict
from pydantic import BaseModel, Field
from mcp.client import Client
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Weather Service")
# Example 1: Using a Pydantic model for structured output
class WeatherData(BaseModel):
"""Structured weather data response"""
temperature: float = Field(description="Temperature in Celsius")
humidity: float = Field(description="Humidity percentage (0-100)")
condition: str = Field(description="Weather condition (sunny, cloudy, rainy, etc.)")
wind_speed: float = Field(description="Wind speed in km/h")
location: str = Field(description="Location name")
timestamp: datetime = Field(default_factory=datetime.now, description="Observation time")
@mcp.tool()
def get_weather(city: str) -> WeatherData:
"""Get current weather for a city with full structured data"""
# In a real implementation, this would fetch from a weather API
return WeatherData(temperature=22.5, humidity=65.0, condition="partly cloudy", wind_speed=12.3, location=city)
# Example 2: Using TypedDict for a simpler structure
class WeatherSummary(TypedDict):
"""Simple weather summary"""
city: str
temp_c: float
description: str
@mcp.tool()
def get_weather_summary(city: str) -> WeatherSummary:
"""Get a brief weather summary for a city"""
return WeatherSummary(city=city, temp_c=22.5, description="Partly cloudy with light breeze")
# Example 3: Using dict[str, Any] for flexible schemas
@mcp.tool()
def get_weather_metrics(cities: list[str]) -> dict[str, dict[str, float]]:
"""Get weather metrics for multiple cities
Returns a dictionary mapping city names to their metrics
"""
# Returns nested dictionaries with weather metrics
return {
city: {"temperature": 20.0 + i * 2, "humidity": 60.0 + i * 5, "pressure": 1013.0 + i * 0.5}
for i, city in enumerate(cities)
}
# Example 4: Using dataclass for weather alerts
@dataclass
class WeatherAlert:
"""Weather alert information"""
severity: str # "low", "medium", "high"
title: str
description: str
affected_areas: list[str]
valid_until: datetime
@mcp.tool()
def get_weather_alerts(region: str) -> list[WeatherAlert]:
"""Get active weather alerts for a region"""
# In production, this would fetch real alerts
if region.lower() == "california":
return [
WeatherAlert(
severity="high",
title="Heat Wave Warning",
description="Temperatures expected to exceed 40 degrees",
affected_areas=["Los Angeles", "San Diego", "Riverside"],
valid_until=datetime(2024, 7, 15, 18, 0),
),
WeatherAlert(
severity="medium",
title="Air Quality Advisory",
description="Poor air quality due to wildfire smoke",
affected_areas=["San Francisco Bay Area"],
valid_until=datetime(2024, 7, 14, 12, 0),
),
]
return []
# Example 5: Returning primitives with structured output
@mcp.tool()
def get_temperature(city: str, unit: str = "celsius") -> float:
"""Get just the temperature for a city
When returning primitives as structured output,
the result is wrapped in {"result": value}
"""
base_temp = 22.5
if unit.lower() == "fahrenheit":
return base_temp * 9 / 5 + 32
return base_temp
# Example 6: Weather statistics with nested models
class DailyStats(BaseModel):
"""Statistics for a single day"""
high: float
low: float
mean: float
class WeatherStats(BaseModel):
"""Weather statistics over a period"""
location: str
period_days: int
temperature: DailyStats
humidity: DailyStats
precipitation_mm: float = Field(description="Total precipitation in millimeters")
@mcp.tool()
def get_weather_stats(city: str, days: int = 7) -> WeatherStats:
"""Get weather statistics for the past N days"""
return WeatherStats(
location=city,
period_days=days,
temperature=DailyStats(high=28.5, low=15.2, mean=21.8),
humidity=DailyStats(high=85.0, low=45.0, mean=65.0),
precipitation_mm=12.4,
)
if __name__ == "__main__":
async def test() -> None:
"""Test the tools by calling them through the server as a client would"""
print("Testing Weather Service Tools (via MCP protocol)\n")
print("=" * 80)
async with Client(mcp) as client:
# Test get_weather
result = await client.call_tool("get_weather", {"city": "London"})
print("\nWeather in London:")
print(json.dumps(result.structured_content, indent=2))
# Test get_weather_summary
result = await client.call_tool("get_weather_summary", {"city": "Paris"})
print("\nWeather summary for Paris:")
print(json.dumps(result.structured_content, indent=2))
# Test get_weather_metrics
result = await client.call_tool("get_weather_metrics", {"cities": ["Tokyo", "Sydney", "Mumbai"]})
print("\nWeather metrics:")
print(json.dumps(result.structured_content, indent=2))
# Test get_weather_alerts
result = await client.call_tool("get_weather_alerts", {"region": "California"})
print("\nWeather alerts for California:")
print(json.dumps(result.structured_content, indent=2))
# Test get_temperature
result = await client.call_tool("get_temperature", {"city": "Berlin", "unit": "fahrenheit"})
print("\nTemperature in Berlin:")
print(json.dumps(result.structured_content, indent=2))
# Test get_weather_stats
result = await client.call_tool("get_weather_stats", {"city": "Seattle", "days": 30})
print("\nWeather stats for Seattle (30 days):")
print(json.dumps(result.structured_content, indent=2))
# Also show the text content for comparison
print("\nText content for last result:")
for content in result.content:
if content.type == "text":
print(content.text)
async def print_schemas() -> None:
"""Print all tool schemas"""
print("Tool Schemas for Weather Service\n")
print("=" * 80)
tools = await mcp.list_tools()
for tool in tools:
print(f"\nTool: {tool.name}")
print(f"Description: {tool.description}")
print("Input Schema:")
print(json.dumps(tool.input_schema, indent=2))
if tool.output_schema:
print("Output Schema:")
print(json.dumps(tool.output_schema, indent=2))
else:
print("Output Schema: None (returns unstructured content)")
print("-" * 80)
# Check command line arguments
if len(sys.argv) > 1 and sys.argv[1] == "--schemas":
asyncio.run(print_schemas())
else:
print("Usage:")
print(" python weather_structured.py # Run tool tests")
print(" python weather_structured.py --schemas # Print tool schemas")
print()
asyncio.run(test())