Building Your Own MCP Server for AI
Parameters and Structured Results
Validate inputs, return JSON the model can use, and turn failures into clear errors instead of stack traces.
A tool that returns traceback text trains the model to apologize. A tool that returns {"ok": false, "error": "city required"} trains it to retry with a city.
Validate at the edge
Type hints catch many bad calls. Anything the schema cannot express, check yourself and return a structured error. Do not raise unless the host is supposed to treat it as a crash.
from typing import Any
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("weather-lite")
KNOWN = {"berlin": 12, "austin": 28, "oslo": 3}
@mcp.tool()
def temperature_c(city: str) -> dict[str, Any]:
'''Return a demo temperature in Celsius for a known city (berlin, austin, oslo).'''
key = city.strip().lower()
if not key:
return {"ok": False, "error": "city is required"}
if key not in KNOWN:
return {
"ok": False,
"error": "unknown city",
"hint": "Use berlin, austin, or oslo in this lesson.",
}
return {"ok": True, "city": key, "celsius": KNOWN[key]}Prefer objects over prose
Strings are fine for greetings. For anything the model must compare or repeat, use a dict (JSON object). Include units in field names (celsius) so the model does not invent Fahrenheit.
HTTP and other failures
Catch httpx.TimeoutException and httpx.HTTPStatusError. Return ok: false plus status when you have one. Never return an API key in an error body.
try:
response.raise_for_status()
except httpx.HTTPStatusError as exc:
return {"ok": False, "status": exc.response.status_code, "error": "upstream rejected the request"}Check it
- Call
temperature_cwithberlin—okis true. - Call it with an empty string and with
paris— bothokfalse, no traceback. - Add a new city to
KNOWNand confirm the schema did not need to change.