Mastering Lifespan in FastAPI and MCP Server: Context Managers for Robust Resource Management

This article explains how to use Python's context manager protocol and FastAPI's lifespan feature to automatically initialize and clean up resources in both standard FastAPI applications and MCP Server, covering implementation methods, code examples, and differences across transport modes.

AI Large Model Application Practice
AI Large Model Application Practice
AI Large Model Application Practice
Mastering Lifespan in FastAPI and MCP Server: Context Managers for Robust Resource Management

The official MCP SDK documentation is very terse, so developers often need to explore advanced techniques themselves. This guide focuses on the Lifespan mechanism—a server‑side lifecycle manager used in MCP, FastAPI, and Starlette—to automatically handle resource initialization and cleanup.

1. Python Context Managers

A context manager is an object that defines two special methods, __enter__ (or __aenter__ for async) and __exit__ (or __aexit__), which the interpreter calls when entering and leaving a with block. Example:

with CustomFile("data.txt") as f:
    # use f
    data = f.read()
# f is automatically closed here

Implementation options:

Class‑based manager : define __enter__ to acquire the resource and return it, and __exit__ to release it.

class CustomFile:
    def __init__(self, filename):
        self.filename = filename
        self.file = None
    def __enter__(self):
        self.file = open(self.filename, "r")
        return self.file
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()

Function‑based manager using contextlib.contextmanager (or asynccontextmanager) to wrap a generator that yields the resource.

from contextlib import contextmanager
@contextmanager
def custom_file(filename):
    f = open(filename, "r")
    try:
        yield f
    finally:
        f.close()

2. FastAPI Lifespan

FastAPI (built on Starlette) recommends passing an asynchronous context manager to the lifespan parameter of the FastAPI constructor. The manager runs code before the first request (initialization) and after the server shuts down (cleanup).

from fastapi import FastAPI
from contextlib import asynccontextmanager

async def init_database():
    return DatabaseConnection()

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.db = await init_database()
    try:
        yield  # server runs here
    finally:
        await app.state.db.close()

app = FastAPI(lifespan=lifespan)

@app.get("/users")
async def list_users():
    results = app.state.db.query("SELECT * FROM users;")
    return {"users": results}

The yield point pauses execution while the application handles requests; when the process stops, the code after yield runs to clean up resources.

3. MCP Server Lifespan

MCP Server, also based on Starlette, can use the same pattern. A dataclass groups all resources that need lifecycle management.

from dataclasses import dataclass
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from mcp.server.fastmcp import FastMCP, Context

@dataclass
class AppContext:
    db: Database  # hypothetical Database class

@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
    logger.info("App starting, initializing resources…")
    db_connection = await Database.connect()
    try:
        yield AppContext(db=db_connection)
    finally:
        logger.info("App shutting down, cleaning up…")
        await db_connection.disconnect()

mcp = FastMCP("My App", lifespan=app_lifespan)

@mcp.tool()
def query_data(ctx: Context, query: str) -> str:
    """Example tool that uses the lifespan‑provided database."""
    db = ctx.request_context.lifespan_context.db
    return db.execute_query(query)

This mirrors FastAPI’s approach but runs inside the MCP server instance.

4. Different Transport Modes

When the MCP server runs in stdio mode , it starts as a subprocess and triggers the lifespan initialization immediately; the logs show the yield ‑pre‑initialization messages. In streamableHTTP mode , the server starts under Uvicorn and waits for a client connection. The lifespan code runs only after a client connects, and cleanup runs when the client disconnects, effectively making the lifespan a per‑session manager.

Typical log output for stdio mode shows the initialization block before any request handling, while streamableHTTP mode shows no initialization until a client establishes a session.

5. Binding Lifespan to Starlette Directly

For resources that must live for the entire server process (e.g., a shared connection pool), bind the lifespan to a standalone Starlette app instead of the MCP server instance, then mount the MCP router. Example:

from starlette.applications import Starlette
from starlette.routing import Mount
from starlette.lifespan import Lifespan
import uvicorn

@asynccontextmanager
async def starlette_lifespan(app):
    # global init
    app.state.pool = await create_pool()
    try:
        yield
    finally:
        await app.state.pool.close()

starlette_app = Starlette(
    debug=True,
    routes=[
        Mount("/mcp", app=lambda request: global_session_manager.handle_request(request)),
    ],
    lifespan=starlette_lifespan,
)
config = uvicorn.Config(starlette_app, host="127.0.0.1", port=5050)
server = uvicorn.Server(config)

In the newer streamableHTTP mode, the lifespan must also initialise a StreamableHTTPSessionManager inside the context.

Overall, understanding Python’s context‑manager protocol and FastAPI/Starlette’s lifespan feature enables developers to write clean, exception‑safe resource management code for both standard FastAPI services and MCP‑based AI agents.

FastAPIcontext-managerasynccontextmanagerlifespanmcp-server
AI Large Model Application Practice
Written by

AI Large Model Application Practice

Focused on deep research and development of large-model applications. Authors of "RAG Application Development and Optimization Based on Large Models" and "MCP Principles Unveiled and Development Guide". Primarily B2B, with B2C as a supplement.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.