Middleware
İngilizce sayfanın gerisinde kalmış çeviri
Bu çeviri yapıldıktan sonra İngilizce sayfa değişti; bu yüzden bazı bölümleri güncel olmayabilir. Şüpheye düştüğünüzde İngilizce sayfayı okuyun; çevrilmiş dokümantasyonun nasıl işlediğini Ç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:
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.methodham metot dizgesidir;ctx.paramsise 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/finallybilinçli bir tercihtir: istisna fırlatan bir işleyicinin de süresi ölçülür, çünkü hata middleware'inizecall_next'ten çıkan istisna olarak ulaşır.server.middleware.append(...)onu kaydeder. Liste dıştan içe doğru çalışır, yanimiddleware[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/discoverya da eski nesil bir oturumdainitializevenotifications/initialized. - Her istek ve her bildirim. Bir bildirimde
ctx.request_id is Noneolur,call_next(ctx)Nonedö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 birMCPErrorfırlatın; o tek mesaj bir JSON-RPC hatasıyla yanıtlanır. Bağlantı ayakta kalır; sonraki mesaj geçer. Bir sunucusubscriptions/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.
ctxbir dataclass'tır:await call_next(dataclasses.replace(ctx, params=...))zincirin geri kalanına istemcinin gönderdiğinden farklı parametreler verir. Bunuinitializeiç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_nextsize 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 bunaserverInfo_metadamgası 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) -> resultbiçimindedir;MCPServer(middleware=[...])olarak geçirilir (ya damcp.middlewarelistesine eklenir), alt düzeyServer'da iseserver.middlewarelistesine 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 Noneile 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.