Additional Examples
We should publish additional examples of more complex MCP servers that help users understand:
- How to log errors and warnings
- How to raise exceptions
- How to handle client setup via lifespan
- How to use Context to utilize clients
- How to use classes to implement tool functionality
- How to register tools/prompts/resources at runtime
Some of these may already exist but we should be sure!
Claude finished @strawgate's task —— View job
Investigation Complete: Additional Examples Analysis
Recommendation
Create 4-5 targeted example files demonstrating patterns not currently well-represented in the examples directory. Based on my analysis, FastMCP already has substantial documentation and some existing examples, but the following gaps exist:
- Error handling and logging - No dedicated example showing comprehensive logging patterns
- Lifespan management - Factory pattern documented but no example showing async client setup
- Class-based tools - No examples using classes for tool implementations
- Runtime registration - No examples of dynamically registering tools/resources/prompts
The Context usage is well-demonstrated in existing examples (sampling.py, memory.py, atproto_mcp, etc.).
Findings
Existing Coverage Analysis
✅ Well-Covered Topics
1. How to raise exceptions
- Documentation: Comprehensive coverage in
docs/servers/logging.mdx - Examples: Multiple examples show exception handling (
memory.py,smart_home/lights/server.py) - Status: Adequately covered
2. How to use Context to utilize clients
- Examples:
sampling.py,memory.py, atproto (client setup via_atproto/_client.py) - The atproto example demonstrates the pattern of initializing external clients and using them through Context
- Status: Examples exist showing this pattern
⚠️ Partially Covered Topics
3. How to log errors and warnings
- Documentation: Excellent docs at
docs/servers/logging.mdxwith examples - Examples: Examples show
ctx.info(),ctx.debug(),ctx.error(),ctx.warning() - Gap: No dedicated comprehensive example showcasing all logging levels with structured logging
- Status: Docs strong, but missing a focused example file
4. How to handle client setup via lifespan
- Documentation:
lifespanparameter mentioned indocs/servers/server.mdx:47-49 - CLI documentation shows factory pattern at
docs/patterns/cli.mdx:122-152 - Examples: The
memory.pyexample creates clients but doesn't use the lifespan pattern - Gap: No example showing
lifespanparameter usage for async client initialization - Status: Documented but no working example
❌ Not Covered
5. How to use classes to implement tool functionality
- The codebase supports this (seen in smart_home which uses TypedDict for structure)
- No examples showing class-based tool implementations
- Gap: No demonstration of OOP patterns for tool organization
6. How to register tools/prompts/resources at runtime
- The ToolManager/ResourceManager/PromptManager classes support programmatic registration
- No examples demonstrating dynamic registration patterns
- Gap: Missing examples of conditional registration, runtime tool generation, etc.
Detailed Action Plan
Recommended New Examples
1. examples/error_handling_logging.py
Purpose: Comprehensive demonstration of error handling and logging patterns
Should Include:
- All logging levels (debug, info, warning, error)
- Structured logging with
extraparameter - Exception handling with proper logging
- Try-except blocks with context logging
- Recovery patterns
@mcp.tool
async def process_data_with_logging(data: list[float], ctx: Context) -> dict:
"""Demonstrates comprehensive error handling and logging."""
await ctx.debug("Starting data processing")
await ctx.info(f"Processing {len(data)} items", extra={"count": len(data)})
try:
if not data:
await ctx.warning("Empty data provided")
raise ValueError("Data cannot be empty")
# ... processing logic
except ValueError as e:
await ctx.error(f"Validation failed: {e}", extra={"error_type": "validation"})
raise
2. examples/lifespan_client_setup.py
Purpose: Demonstrate async client initialization using lifespan pattern
Should Include:
- Async context manager for lifespan
- Client initialization on startup
- Proper cleanup on shutdown
- Using clients via Context
- Database connections, HTTP clients, etc.
@asynccontextmanager
async def lifespan(mcp: FastMCP):
# Startup: initialize clients
http_client = httpx.AsyncClient()
db_pool = await asyncpg.create_pool(...)
yield {"http": http_client, "db": db_pool}
# Shutdown: cleanup
await http_client.aclose()
await db_pool.close()
mcp = FastMCP("ClientSetupExample", lifespan=lifespan)
@mcp.tool
async def fetch_data(url: str, ctx: Context) -> dict:
client = ctx.fastmcp.lifespan_context["http"]
response = await client.get(url)
return response.json()
3. examples/class_based_tools.py
Purpose: Demonstrate OOP patterns for tool organization
Should Include:
- Class with multiple related methods
- Shared state between tools
- Decorating class methods as tools
- Dependency injection patterns
class DatabaseTools:
def __init__(self, connection_string: str):
self.connection_string = connection_string
self.cache = {}
def query(self, sql: str) -> list[dict]:
"""Execute SQL query."""
# Implementation
pass
def insert(self, table: str, data: dict) -> str:
"""Insert data into table."""
# Implementation
pass
# Register class methods as tools
db_tools = DatabaseTools("postgresql://...")
mcp.add_tool(db_tools.query)
mcp.add_tool(db_tools.insert)
4. examples/runtime_registration.py
Purpose: Show dynamic tool/resource/prompt registration
Should Include:
- Conditional tool registration based on config/env
- Runtime tool generation from schemas/APIs
- Enabling/disabling components dynamically
- Tool factory patterns
mcp = FastMCP("DynamicExample")
# Runtime registration based on configuration
if os.getenv("ENABLE_ADMIN_TOOLS"):
@mcp.tool
def admin_reset() -> str:
return "Admin tool"
# Dynamic tool creation
for endpoint in api_spec["endpoints"]:
def create_tool(endpoint):
async def tool_fn(**kwargs):
return await call_api(endpoint, kwargs)
return tool_fn
mcp.add_tool(Tool.from_function(create_tool(endpoint)))
# Runtime enable/disable
@mcp.tool
async def toggle_tool(tool_name: str, enabled: bool, ctx: Context) -> str:
tool = await ctx.fastmcp.get_tool(tool_name)
if enabled:
tool.enable()
else:
tool.disable()
return f"Tool {tool_name} {'enabled' if enabled else 'disabled'}"
5. (Optional) examples/comprehensive_context.py
Purpose: Single example showing all Context capabilities
Should Include:
- Logging (ctx.info, ctx.debug, ctx.error, ctx.warning)
- Progress reporting (ctx.report_progress)
- Sampling (ctx.sample)
- Elicitation (ctx.elicit)
- Resource reading (ctx.read_resource)
- Accessing server instance (ctx.fastmcp)
Related Files in Repository
| File | Relevance | Notes |
|---|---|---|
| examples/memory.py | Client setup pattern | Shows external client usage (OpenAI, asyncpg) but not via lifespan |
| examples/sampling.py | Context usage | Demonstrates ctx.sample() |
| examples/atproto_mcp/src/atproto_mcp/_atproto/_client.py | Client singleton pattern | Module-level client initialization |
| examples/smart_home/src/smart_home/lights/server.py | Structured types | Shows TypedDict usage, error handling patterns |
| docs/servers/logging.mdx | Logging documentation | Comprehensive logging docs with code snippets |
| docs/servers/server.mdx | Server configuration | Documents lifespan parameter (lines 47-49) |
| docs/patterns/cli.mdx | Factory pattern | Documents factory functions for setup code (lines 122-152) |
Related Issues and Pull Requests
| Repository | Item | Relevance |
|---|---|---|
| jlowin/fastmcp | #1841: Example for Context-Aware Tool Factories | Directly related - requests Context examples |
| jlowin/fastmcp | #1878: Add more Transformation Examples | Related - about adding examples |
| jlowin/fastmcp | #572: Dynamic servers | Discusses runtime tool registration patterns |
| jlowin/fastmcp | #1822: Add Example for Using import_server() With ASGI Applications |
Shows demand for lifespan/startup examples |
| jlowin/fastmcp | #1998: Document logging with OpenTelemetry | Related to logging patterns |
@strawgate For lifespan, is the answer here the correct way to go? It seems a bit awkward: https://github.com/jlowin/fastmcp/discussions/1763