MCPMCP Python SDK 2.2 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
24 small wins to finish your pathNext lesson

Lifespan: a database the whole server shares

Connecting to a database on every call is slow. A lifespan connects once when the server starts and disconnects when it stops.

Exampleshop.py, part 1
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass

from mcp.server import MCPServer
from mcp.server.mcpserver import Context


class Database:
    def __init__(self):
        self.connected = False
        self.orders = {"A17": "shipped", "B42": "processing"}

    async def connect(self):
        self.connected = True
        print("database connected")

    async def disconnect(self):
        self.connected = False
        print("database disconnected")

A stand-in database: it only flips a flag and prints, so you can see exactly when it connects. A real one would open a connection pool here.

Exampleshop.py, part 2
@dataclass
class AppContext:
    db: Database


@asynccontextmanager
async def lifespan(server: MCPServer) -> AsyncIterator[AppContext]:
    db = Database()
    await db.connect()
    try:
        yield AppContext(db=db)
    finally:
        await db.disconnect()


mcp = MCPServer("Shop support", lifespan=lifespan)

A lifespan is an @asynccontextmanager function. Code before yield runs at startup and the finally block at shutdown. The object it yields, here an AppContext holding the database, is shared by every request.

Exampleshop.py, part 3
@mcp.tool()
def order_status(order_id: str, ctx: Context[AppContext]) -> str:
    """Look up an order's status in the database."""
    db = ctx.request_context.lifespan_context.db
    return f"{order_id}: {db.orders.get(order_id, 'unknown')} (connected={db.connected})"

ctx.request_context.lifespan_context is the yielded object. Context[AppContext] tells your editor its type, so .db autocompletes; the SDK docs note that this typed form works in tools, and resources and prompts take a bare Context.

Example
import asyncio

from mcp import Client
from shop import mcp


async def main():
    async with Client(mcp) as client:
        print("inside the client block")
        for order_id in ("A17", "B42"):
            result = await client.call_tool("order_status", {"order_id": order_id})
            print(result.content[0].text)
        print("leaving the client block")


asyncio.run(main())

The database connected once, when the client connected and the in-memory server started, before the first line inside the block. Both calls shared it. It disconnected after the client block ended, when the in-memory server shut down. Over HTTP, the lifespan lasts as long as the server process.

Try it yourself
  • Add a second tool that counts db.orders, and call both.
  • Move await db.connect() into the tool and watch how often it prints.
  • Remove lifespan=lifespan and print what lifespan_context is.

You understood something today that you didn't yesterday.