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:
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.