MCP Server Development
Time to BUILD: a production MCP server — typed tools, exposed resources, tests, an Inspector, deployment with auth, and observability. FastMCP makes the first 80% minutes, not weeks.
▶ Watch this reelWhat you'll learn
- FastMCP: first server
- Resources & typed design
- Testing with the Inspector
- Deployment: HTTP, auth, observability
Remember this
- FastMCP: typed functions + docstrings become tools/resources in minutes — your signature is the schema, your docstring is the model's documentation
- Design small surfaces: resources for data, prompts for repeated shapes, explicit handles for state; test with the Inspector plus scripted protocol tests
- Remote deployment = operating a service: streamable HTTP, spec-aligned OAuth, per-call observability, pinned revisions and deprecation tracking
FastMCP
- Decorators: typed function → tool; docstring → description; Pydantic → schema.
- C# SDK mirrors it (attributes + ASP.NET hosting).
- You still own AG-02: descriptions, validation, actionable errors.
Design
- Resources with mental-model URIs · prompts for repeated shapes · structured outputs.
- Small surfaces: ~5 legible tools; split by domain.
- Stateless: explicit handles as arguments for multi-call state.
Testing
- MCP Inspector: interactive poke-list-invoke loop — test the model's experience.
- Scripted: in-process sessions in pytest — schema/docs drift, failure quality, round-trips.
Deployment
- streamable HTTP · load balancer friendly · spec-aligned OAuth (or gateway).
- Observability: per-call structured logs (hash args, log user), RED metrics, cost/call.
- Pin spec revision · track deprecations · backward-compatible surface.
Code: The same server, deployed: HTTP + logging + auth hook
from mcp.server.fastmcp import FastMCP
import logging, structlog
log = structlog.get_logger("mcp.orders")
mcp = FastMCP("orders")
@mcp.tool()
def search_orders(customer: str, limit: int = 5) -> list[dict]:
"""Search orders by customer name fragment … (same docstring!)"""
log.info("tool.call", tool="search_orders",
arg_hash=hash((customer, limit)), # not raw args
user=current_user_id()) # auth context from hook
...
# Local: mcp.run() # stdio
# Remote: mcp.run(transport="http",
# host="0.0.0.0", port=8080,
# auth=oauth_provider) # spec-aligned OAuth
#
# Ops: per-call logs · latency histograms · error-rate alerts ·
# cost-per-tool dashboards · pinned spec revision in CI