fastmcp icon indicating copy to clipboard operation
fastmcp copied to clipboard

Additional Examples

Open strawgate opened this issue 8 months ago • 1 comments

We should publish additional examples of more complex MCP servers that help users understand:

  1. How to log errors and warnings
  2. How to raise exceptions
  3. How to handle client setup via lifespan
  4. How to use Context to utilize clients
  5. How to use classes to implement tool functionality
  6. How to register tools/prompts/resources at runtime

Some of these may already exist but we should be sure!

strawgate avatar Apr 13 '25 12:04 strawgate

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:

  1. Error handling and logging - No dedicated example showing comprehensive logging patterns
  2. Lifespan management - Factory pattern documented but no example showing async client setup
  3. Class-based tools - No examples using classes for tool implementations
  4. 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.mdx with 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: lifespan parameter mentioned in docs/servers/server.mdx:47-49
  • CLI documentation shows factory pattern at docs/patterns/cli.mdx:122-152
  • Examples: The memory.py example creates clients but doesn't use the lifespan pattern
  • Gap: No example showing lifespan parameter 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 extra parameter
  • 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

pamelafox avatar Dec 02 '25 17:12 pamelafox