Context, progres și lifespan în SDK-ul Python (MCPServer) în Model Context Protocol (MCP)

Dezvoltarea Serverelor MCP (Python & TypeScript) MCP Python SDK v2 (pachetul mcp 2.x, specificația 2026-07-28)

Obiectul Context injectat în unelte, raportarea progresului, resursele pornite o singură dată prin lifespan și ce s-a schimbat la logging și sampling în SDK-ul Python v2.

Specificație & cod MCP
async def unealta(arg: str, ctx: Context[AppContext]) -> str
mcp = MCPServer("Nume", lifespan=app_lifespan)

Ce face Context, progres și lifespan în SDK-ul Python (MCPServer)

Un parametru adnotat cu Context (din mcp.server.mcpserver) este injectat de SDK în unelte, resurse și prompturi; numele parametrului nu contează și nu apare în schema văzută de model. Contextul oferă ctx.request_id, await ctx.read_resource(uri) pentru resursele proprii ale serverului, await ctx.report_progress(progress, total, message) (care trimite notifications/progress), ctx.elicit() și ctx.elicit_url(), ctx.headers (pe HTTP) și ctx.request_context.lifespan_context. Pe conexiunile 2026-07-28, notificările de schimbare se trimit cu await ctx.notify_tools_changed() și metodele înrudite, către stream-urile subscriptions/listen.

Lifespan: un @asynccontextmanager care primește serverul și face yield unui singur obiect (de exemplu un dataclass cu pool-ul de conexiuni). Codul dinainte de yield rulează la pornire, cel din finally la oprire, o singură dată pentru toată viața serverului; în v2, și pe Streamable HTTP lifespan-ul rulează o singură dată, partajat de toate cererile. Obiectul ajunge în ctx.request_context.lifespan_context, iar Context[AppContext] îl face tipizat. Fără lifespan=, valoarea este un dict gol, nu None.

Logging și sampling. Logging-ul prin protocol este depreciat de specificația 2026-07-28 și nu are înlocuitor: ctx.log(), ctx.debug(), ctx.info(), ctx.warning(), ctx.error() și ctx.session.send_log_message() emit MCPDeprecationWarning. Documentația SDK recomandă modulul standard logging, care scrie pe stderr; nivelul se setează cu MCPServer(..., log_level="DEBUG"). La fel, ctx.session.create_message() (sampling) funcționează doar pe conexiuni din era 2025 cu canal înapoi; forma suportată este un parametru Resolve(...) al cărui resolver întoarce Sample(...), sau un InputRequiredResult întors direct.

Sintaxă și opțiuni

Opțiune / Parametru Descriere
ctx: Context Injectat automat; invizibil pentru model.
ctx.request_id Id-ul cererii servite.
await ctx.read_resource(uri) Citește o resursă a serverului prin același registru ca resources/read.
await ctx.report_progress(progress, total, message) Progres către client; progress trebuie să crească la fiecare apel.
ctx.request_context.lifespan_context Obiectul întors de lifespan la pornire.
ctx.notify_tools_changed() / notify_resource_updated(uri) Notificări de schimbare către abonamentele subscriptions/listen.
ctx.info() / ctx.error() / ctx.log() Depreciate (logging prin protocol); folosește logging.
Resolve(...) + Sample(...) Forma suportată pentru sampling în v2; clientul trebuie să declare capabilitatea sampling.

Exemple practice

Exemplul 1: Lifespan tipizat cu o conexiune la baza de date

Database este o clasă de exemplu din documentație.

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass

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


@dataclass
class AppContext:
    db: Database


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


mcp = MCPServer("Bookshop", lifespan=app_lifespan)


@mcp.tool()
def count_books(genre: str, ctx: Context[AppContext]) -> str:
    """Count the books in a genre."""
    db = ctx.request_context.lifespan_context.db
    return f"{db.query()} books in {genre!r}."

Exemplul 2: Raportarea progresului

@mcp.tool()
async def import_catalog(urls: list[str], ctx: Context) -> str:
    """Import book records from a list of catalog URLs."""
    for done, url in enumerate(urls, start=1):
        await ctx.report_progress(done, total=len(urls), message=f"Imported {url}")
    return f"Imported {len(urls)} records."

Exemplul 3: Logging recomandat în v2

import logging

from mcp.server import MCPServer

logger = logging.getLogger(__name__)

mcp = MCPServer("Bookshop")


@mcp.tool()
def search_books(query: str) -> str:
    """Search the catalog by title or author."""
    logger.info("Searching for %r", query)
    return f"Found 3 books matching {query!r}."

Greșeli frecvente și bune practici

  • Folosești ctx.info() ca să trimiți loguri clientului. Este depreciat odată cu logging-ul din protocol; logul ajunge oricum doar la operator, nu la model.
  • Deschizi o conexiune la baza de date în fiecare apel de unealtă. Pune-o în lifespan, care rulează o singură dată.
  • Apelezi ctx.session.create_message() pe o conexiune 2026-07-28. Nu există canal înapoi; folosește Resolve cu Sample sau InputRequiredResult.

Comenzi înrudite