Ana içeriğe geç

Middleware

Makine çevirisi

Bu sayfa İngilizce dokümantasyondan otomatik olarak çevrildi; esas alınması gereken sürüm İngilizce sayfadır. Yanlış görünen bir şey varsa, nasıl bildireceğinizi Çeviriler sayfası açıklar.

Middleware (ara katman), sunucunun aldığı her mesajı saran tek bir asenkron fonksiyondur.

Onu async (ctx, call_next) biçiminde yazar ve server.middleware listesine eklersiniz. API'nin tamamı bu.

Warning

Middleware listesi kaynak kodda geçici (provisional) olarak işaretlidir: imzası ve anlamı bir 2.x ara sürümünde değişebilir. Onu mesajları gözlemlemek (zamanlama, log tutma, izleme) ve reddetmek için kullanın; sunucunuzun üzerinde durduğu temel haline getirmeyin.

MCPServer listeyi oluşturulurken alır (MCPServer(name, middleware=[...])) ve onu mcp.middleware olarak sunar; alt düzey Server aynı listeyi server.middleware olarak sunar. Aşağıdaki örnek alt düzey Server'ı kullanır; Server(name, on_call_tool=...) size yeniyse önce Alt düzey Server sayfasını okuyun.

Bir zamanlama middleware'i

Bir sunucu, bir araç ve her mesajın ne kadar sürdüğünü loglayan bir middleware:

server.py
import logging
import time

from mcp.server import Server, ServerRequestContext
from mcp.server.context import CallNext, HandlerResult
from mcp.types import (
    CallToolRequestParams,
    CallToolResult,
    ListToolsResult,
    PaginatedRequestParams,
    TextContent,
    Tool,
)

logger = logging.getLogger(__name__)


async def on_list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:
    return ListToolsResult(
        tools=[
            Tool(
                name="search_books",
                description="Search the catalog by title or author.",
                input_schema={
                    "type": "object",
                    "properties": {"query": {"type": "string"}},
                    "required": ["query"],
                },
            )
        ]
    )


async def on_call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
    query = (params.arguments or {})["query"]
    return CallToolResult(content=[TextContent(type="text", text=f"Found 3 books matching {query!r}.")])


async def log_timing(ctx: ServerRequestContext, call_next: CallNext) -> HandlerResult:
    start = time.perf_counter()
    try:
        return await call_next(ctx)
    finally:
        elapsed_ms = (time.perf_counter() - start) * 1000
        logger.info("%s took %.1f ms", ctx.method, elapsed_ms)


server = Server("Bookshop", on_list_tools=on_list_tools, on_call_tool=on_call_tool)
server.middleware.append(log_timing)
  • ctx, işleyicilerinizin aldığı ServerRequestContext'in aynısıdır. ctx.method ham metot dizgesidir; ctx.params ise herhangi bir doğrulamadan önceki ham parametrelerdir.
  • call_next(ctx) zincirin geri kalanını çalıştırır: doğrulama, işleyici araması, işleyiciniz. Onun döndürdüğünü döndürürseniz yanıta dokunulmaz.
  • try/finally bilinçli bir tercihtir: istisna fırlatan bir işleyicinin de süresi ölçülür, çünkü hata middleware'inize call_next'ten çıkan istisna olarak ulaşır.
  • server.middleware.append(...) onu kaydeder. Liste dıştan içe doğru çalışır, yani middleware[0] ağ tarafına en yakın olandır.

Deneyin

Bir istemci bağlayın, araçları listeleyin, birini çağırın. Logunuzda üç satır var:

server/discover took 18.3 ms
tools/list took 0.1 ms
tools/call took 0.1 ms

İki çağrı yaptınız ve üç satır elde ettiniz. İlki server/discover: siz herhangi bir şey istemeden önce, istemcinin bağlantıyı kurmak için gönderdiği istek.

İşin özü de bu. Middleware gelen her mesajı sarar:

  • Bağlantı kurulumu: server/discover ya da eski nesil bir oturumda initialize ve notifications/initialized.
  • Her istek ve her bildirim. Bir bildirimde ctx.request_id is None olur, call_next(ctx) None döndürür ve sizin döndürdüğünüz her şey atılır.
  • Sunucunun işleyicisi olmayan bir metot bile: call_next, MCPError(-32601, "Method not found") istisnasını istemciye giderken middleware'inizin içinden fırlatır.

İçinde neler yapabilirsiniz

Ne kadar tereddüt etmeniz gerektiğine göre artan sırayla:

  • Gözlemleyin. Süresini ölçün, sayın, loglayın. Yukarıdaki örnek.
  • Reddedin. call_next(ctx)'i çağırmak yerine bir MCPError fırlatın; o tek mesaj bir JSON-RPC hatasıyla yanıtlanır. Bağlantı ayakta kalır; sonraki mesaj geçer. Bir sunucu subscriptions/listen'ı çağıran başına böyle denetler: Abonelikler sayfasındaki Kimin izleyebileceğine karar verme bölümü bunu adım adım anlatır.
  • Yeniden yazın. ctx bir dataclass'tır: await call_next(dataclasses.replace(ctx, params=...)) zincirin geri kalanına istemcinin gönderdiğinden farklı parametreler verir. Bunu initialize için asla yapmayın: istemcinin geri aldığı sonuç sizin yeniden yazdığınız parametrelerden oluşturulur, ancak sunucu bağlantı durumunu ağdan gelen özgün parametrelere göre kaydeder. İki taraf el sıkışmayı neyi müzakere ettikleri konusunda anlaşamadan bitirebilir.
  • Yanıtlayın. call_next(ctx)'i çağırmadan bir sonuç döndürün; bu sonuç istemciye sizin yanıtınız olarak gider. call_next size tamamlanmış iletim biçimini verir ve işlem hattı döndürdüğünüzü asla yamalamaz; bu yüzden zarfın tamamı sizindir: 2026 neslinden bir bağlantıda buna serverInfo _meta damgası da dahildir. SDK bu damgayı işleyici sonuçlarına ekler, sizinkilere eklemez.

Check

initialize, middleware'in sardığı şeylerden biridir ve onun için elinizdeki tek kanca budur. Onu add_request_handler ile devralmaya çalışırsanız SDK reddeder:

ValueError: 'initialize' is handled by the server runner and cannot be overridden;
use Server.middleware to observe or wrap initialization

Warning

initialize satır içinde ele alınır: middleware zinciriniz dönene kadar sunucu başka gelen mesaj okumaz. Bu yüzden initialize'ı işlerken sunucudan istemciye bir isteği (ctx.session.send_request(...), bir elicitation) beklemek bağlantıyı kilitler: beklediğiniz yanıt asla okunamaz. Gönderip unutulan bildirimlerde sorun yoktur.

Varsayılan olarak açık gelen tek middleware

SDK tam olarak bir middleware ile gelir ve o zaten sunucunuzun listesindedir: her mesaj için bir OpenTelemetry span'i yayan middleware. Onu siz eklemezsiniz ve çoğu zaman aklınıza bile gelmez. Bir exporter kurana kadar hiçbir şey yapmaz ve kendi sayfası vardır: OpenTelemetry.

Info

ASGI middleware'i yazdıysanız bu yapıyı zaten biliyorsunuz. Starlette'in (scope, receive, send) üçlüsü (ctx, call_next) oldu ve aktarımdan sonra, ham HTTP isteği yerine çözülmüş mesaj üzerinde çalışır. İkisi birlikte kullanılabilir: streamable_http_app() üzerindeki Starlette middleware'i HTTP'yi görür; bu ise MCP'yi görür.

Özet

  • Bir middleware async (ctx, call_next) -> result biçimindedir; MCPServer(middleware=[...]) olarak geçirilir (ya da mcp.middleware listesine eklenir), alt düzey Server'da ise server.middleware listesine eklenir.
  • Gelen her mesajı sarar (server/discover, initialize, istekler, bildirimler, bilinmeyen metotlar) ve dıştan içe doğru çalışır.
  • Bir bildirimi bir istekten ctx.request_id is None ile ayırt edersiniz.
  • Tek bir mesajı reddetmek için call_next'i çağırmak yerine istisna fırlatın; bağlantı ayakta kalır.
  • SDK'nın kendi OpenTelemetry izlemesi de bir middleware'dir ve zaten listededir. Bkz. OpenTelemetry.
  • Yüzeyin tamamı geçicidir. Onunla gözlemleyin; üzerine inşa etmeyin.

Bir isteği saran her şey bu kadar. İsteğin çalışıp çalışmayacağına karar veren ise Yetkilendirme.