docs: publish translated docs in twelve languages and the tool that maintains them (#3280)

This commit is contained in:
Max 2026-08-14 17:07:34 +01:00 • committed by GitHub
commit 52ad0a8876
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 89737 additions and 54 deletions

View file

@ -0,0 +1,123 @@
---
translation:
sections: [0355618e5f4d5fe4, 1821eaf50f2d0b64, 82e0b28ebd3abf5a, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
Bir **MCP App**, yüzü olan bir araçtır: araç, verisinin yanında host'un etkileşimli bir yüzey olarak çizdiği bir HTML belgesine de işaret eder.
İki parça, her zaman iki parça:
1. İşi yapan ve veri döndüren **bir araç**, tıpkı diğer araçlar gibi.
2. Host'un onun için gösterdiği HTML'i içeren **bir `ui://` kaynağı**.
Araç, kaynağa işaret eden bir `_meta.ui.resourceUri` referansı taşır. Host onu `resources/read` ile getirir, **korumalı (sandboxed) bir iframe** içinde çizer ve aracın sonucunu `postMessage` aracılığıyla bu iframe'e iter. Sunucu hiçbir `ui/*` mesajı göndermez ve almaz: bu trafik host ile iframe arasındadır. Siz bir araç ve bir HTML belgesi sunarsınız; gösteriyi host sahneler.
SDK bunu yerleşik `Apps` uzantısı (`io.modelcontextprotocol/ui`) olarak sunar. [Uzantılar](extensions.md) size yeniyse önce o sayfaya göz atın. Bir dakika, sonra geri dönün.
## Yüzü olan bir saat {#a-clock-with-a-face}
```python title="server.py" hl_lines="19 22 30 32"
--8<-- "docs_src/apps/tutorial001.py"
```
Dört hamle:
* `Apps()`: tek bir örnek, UI'ya bağlı araçlarınızı ve onların kaynaklarını tutar.
* `@apps.tool(resource_uri="ui://clock/app.html")`: sıradan bir araç, artı `_meta.ui.resourceUri` damgası. `@mcp.tool()`'un kabul ettiği her şey (name, title, description, ...) aynen geçer.
* `apps.add_html_resource("ui://clock/app.html", CLOCK_HTML)`: eşleşen kaynak, `text/html;profile=mcp-app` olarak sunulur. Bir host'a "bu bir uygulama, çiz" diyen şey tam olarak bu MIME türüdür.
* `MCPServer("clock", extensions=[apps])`: katılımı açın. Sunucu artık `capabilities.extensions` altında `io.modelcontextprotocol/ui` duyurur.
HTML'in kendisi host'un `postMessage`'ını dinler ve sonucu gösterir. Gerçek uygulamalar için HTML'inizin içinde resmi [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) tarayıcı SDK'sını kullanın. Ham mesaj olayları yerine size `ontoolresult`, `callServerTool`, `getHostContext` ve `onhostcontextchanged` verir.
## Zarifçe geri çekilme {#graceful-degradation}
Her istemci uygulamaları çizmez. Şartname bunun sizin için ne anlama geldiğini açıkça söyler:
> UI mevcut olsa bile araçlar anlamlı bir `content` dizisi **döndürmek ZORUNDADIR**.
Model `content`'i okur; iframe insanlar içindir. UI destekli bir host yine de metin sonucunu modele iletir, yalnızca metin destekleyen bir istemci ise *sadece* onu alır. Yani kanonik desen tek araç, iki yanıttır. `get_time`'a bir daha bakın:
```python title="server.py" hl_lines="23-27"
--8<-- "docs_src/apps/tutorial001.py"
```
`client_supports_apps(ctx)` yalnızca istemci `io.modelcontextprotocol/ui` uzantısını beyan ettiğinde **ve** `mimeTypes` ayarlarında `text/html;profile=mcp-app`'i listelediğinde `True` olur. Alan zorunludur, bu yüzden onu atlayan bir istemci sayılmaz. Aynı dosyadaki `main()` tam olarak bunu beyan eder: anlaşmanın istemci tarafı, ve zengin yanıt geri gelir.
!!! warning
Tek içerik olarak asla `"[Rendered UI]"` gibi bir yer tutucu döndürmeyin. Yedek metin işe yaramazsa araç, yalnızca metin destekleyen her istemci için ve modelin kendisi için işe yaramaz. O cümleyi yazın.
## iframe'i kilitleme {#locking-the-iframe-down}
Güvenlik metaverisini kaynak tarafı taşır: iframe'in neleri yükleyebileceği, hangi tarayıcı izinlerini istediği, nasıl çerçevelenmek istediği:
```python title="server.py" hl_lines="9 19-22"
--8<-- "docs_src/apps/tutorial002.py"
```
`csp` ve `permissions` sunucu davranışı değil, **host'a yapılan isteklerdir**. Host, iframe'in Content-Security-Policy ve Permissions-Policy değerlerini bunlardan oluşturur ve reddedebilir. İznin verildiğini varsaymak yerine JS kodunuzda özellik algılaması yapın.
`ResourceCsp`, alan alan (Python adı, iletilen verideki anahtar, host'un onunla ne yaptığı):
| Python | İletilen veri (`_meta.ui.csp`) | Denetlediği |
|---|---|---|
| `connect_domains` | `connectDomains` | `connect-src`: `fetch`/XHR nereye gidebilir |
| `resource_domains` | `resourceDomains` | `img-src`, `style-src`, ...: statik varlıklar |
| `frame_domains` | `frameDomains` | `frame-src`: iç içe iframe'ler |
| `base_uri_domains` | `baseUriDomains` | `base-uri`: `<base>` nereye işaret edebilir |
`ResourcePermissions`: her alan iframe için bir tarayıcı izni ister.
| Python | İletilen veri (`_meta.ui.permissions`) |
|---|---|
| `camera` | `camera` |
| `microphone` | `microphone` |
| `geolocation` | `geolocation` |
| `clipboard_write` | `clipboardWrite` |
!!! note
CSP ve izinler **kaynak** üzerinde yaşar, asla araç üzerinde değil. Şartnamenin araç metaverisinde bunlar için bir yer yoktur ve host'lar orada onları yok sayar. SDK bu hatayı ifade edilemez kılar: `@apps.tool()`'un `csp` parametresi yoktur.
### Görünürlük {#visibility}
Bir araçtaki `visibility=["app"]`, "bu, model için değil iframe için var" der:
* `"model"`: model onu çağırabilir.
* `"app"`: iframe onu çağırabilir (`callServerTool` aracılığıyla).
* Belirtilmezse: ikisi de, varsayılan budur.
Filtreleme **host'un** işidir. Sunucu yalnızca uygulamaya özel araçları `tools/list` içinde diğerleri gibi listeler; host onları modelden gizler. Sunucu tarafında filtrelemeyin.
## SDK'nın uyguladığı kurallar {#the-rules-the-sdk-enforces}
Bunların hepsi üretimde değil, başlangıçta hata verir:
* `ui://...` olmayan bir `resource_uri` veya kaynak URI'si, dekoratör/kayıt anında bir `ValueError`'dır.
* **Eşleşen kayıtlı bir kaynağı olmayan** bir URI'ye bağlanmış araç, `MCPServer(extensions=[apps])` uzantıyı tükettiğinde bir `ValueError`'dır. `resources/read`'de 404 dönen bir HTML duyuran araç bir yanlış yapılandırmadır, bu yüzden oluşturmayı reddeder.
* `@apps.tool()` üzerinde `meta={"ui": ...}` bir `ValueError`'dır. `_meta["ui"]` dekoratöre aittir; bunu `resource_uri=` ve `visibility=` ile söyleyin. Diğer `meta=` anahtarları yanına sorunsuzca birleşir.
Bugün ne TypeScript ext-apps SDK'sı ne de FastMCP bunların herhangi birini yakalar; bir host'tan önce sizin öğrenmenizi tercih ederiz.
## Satır içi HTML'in ötesi {#beyond-inline-html}
`add_html_resource` yaygın durumu karşılar: bir HTML dizesi. Bunun dışındaki her şey için (diskteki HTML veya üretilen içerik) kaynağı kendiniz oluşturup teslim edin:
```python title="server.py" hl_lines="12 18"
--8<-- "docs_src/apps/tutorial003.py"
```
`add_resource`, kaynak açıkça bir MIME türü belirtmediğinde `text/html;profile=mcp-app` MIME türünü doldurur ve açık bir uyuşmazlığı reddeder: başka herhangi bir MIME türü altındaki `ui://` kaynağını hiçbir host çizmez.
!!! tip
Hâlâ kullanım dışı bırakılmış düz `_meta["ui/resourceUri"]` anahtarını okuyan GA öncesi bir host'u mu hedefliyorsunuz? Kendiniz birleştirin:
`@apps.tool(resource_uri="ui://x", meta={"ui/resourceUri": "ui://x"})`.
İç içe `ui` nesnesi şartnamedeki biçimdir; düz anahtar kaldırılma yolunda.
## Çalışırken görün {#see-it-run}
`examples/stories/` içindeki `apps` hikâyesi, bu sayfanın çalıştırılabilir bir çift hâlidir: UI'ya bağlı bir saat aracı olan bir sunucu ve Apps anlaşmasını yapan, aracın `_meta.ui.resourceUri` değerini okuyan, HTML'i getiren ve aracı çağıran bir istemci.
```bash
uv run python -m stories.apps.client
```

View file

@ -0,0 +1,172 @@
---
translation:
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
tool: 1
---
# Uzantılar {#extensions}
**Uzantı**, tek bir tanımlayıcının arkasında toplanmış, isteğe bağlı olarak etkinleştirilen bir MCP davranışı paketidir.
Sunucuda araç, kaynak ve yeni istek metotları katkısında bulunabilir, `tools/call` isteğini sarmalayabilir. İstemcide ek `tools/call` sonuç biçimlerini sahiplenebilir ve satıcıya özgü bildirimleri gözlemleyebilir. Her iki taraf da kendi `capabilities.extensions` alanı altında duyuru yapar ve bunu istememiş hiç kimse için hiçbir şey değişmez. Sözleşme budur ([SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)) ve tek bir altın kuralı var: **uzantılar varsayılan olarak kapalıdır**.
## Bir uzantı kullanma {#using-an-extension}
Örnekleri oluşturma sırasında geçirin:
```python title="server.py"
--8<-- "docs_src/extensions/tutorial001.py"
```
Bu kadar. Sunucu artık `capabilities.extensions` altında `io.modelcontextprotocol/ui` duyurur ve uzantının katkıda bulunduğu her şeyi sunar.
`Apps` yerleşik başvuru uzantısıdır ve kendi sayfası var: **[MCP Apps](apps.md)**.
!!! note
Uzantılar oluşturma sırasında sabitlenir. Sonradan çağrılacak bir `add_extension` yoktur: istemciler bağlıyken bir sunucunun yetenek eşlemesi değişmemelidir.
Yetenek eşlemesi `server/discover` ile taşınır; bu da bir **2026-07-28** yoludur. Eski nesil `initialize` el sıkışmasında onu koyacak bir yer yoktur, bu yüzden eski nesil bir istemci uzantıyı görmez. Tasarımınızı buna göre yapın: bir uzantı sunucuyu *zenginleştirir*; sunucunun kullanılabilir olmasının tek yolu olmamalıdır.
## Kendi uzantınızı yazma {#writing-your-own}
`Extension`'dan alt sınıf türetin ve yalnızca ihtiyaç duyduklarınızı geçersiz kılın. Her metodun bir varsayılanı var.
### Tanımlayıcı {#the-identifier}
```python
--8<-- "docs_src/extensions/tutorial002.py"
```
Tanımlayıcı, spesifikasyonun `_meta` anahtar dilbilgisini izleyen bir `vendor-prefix/name` dizesidir: noktayla ayrılmış etiketler (her biri bir harfle başlar, bir harf veya rakamla biter), bir eğik çizgi, ardından ad. **Sınıf tanımlandığında** doğrulanır; yani bir yazım hatası sunucunun açılmasını beklemez:
```text
TypeError: Stamps.identifier must be a `vendor-prefix/name` string
(reverse-DNS prefix required), got 'stamps'
```
Önek olarak denetiminizdeki bir alan adı kullanın. `io.modelcontextprotocol/*`, MCP projesinin kendisinin belirlediği uzantılar içindir.
### Araç katkısında bulunma {#contributing-tools}
İşe yarar en küçük uzantı, bir araç ve bir ayarlar eşlemesidir:
```python title="server.py" hl_lines="17 19-20 22-23 26"
--8<-- "docs_src/extensions/tutorial003.py"
```
* `tools()`, `ToolBinding`'ler döndürür. Sunucu her birini, `mcp.add_tool(...)` çağrısını kendiniz yapmışsınız gibi kaydeder: aynı şema üretimi, aynı `Context` enjeksiyonu, her şey aynı.
* `settings()`, `capabilities.extensions["com.example/stamps"]` konumunda duyurulan değerdir. Uzantıyı ayarsız duyurmak için `{}` (varsayılan) döndürün.
* Uzantı sunucuyu hiçbir zaman almaz. Katkıları veri olarak beyan eder; bunları `MCPServer` tüketir. Değiştirilecek bir `self.server` yoktur.
Kanıtı da `main()`: doğrudan `mcp`'ye bağlanan bellek içi bir istemci:
```python title="server.py" hl_lines="29-34"
--8<-- "docs_src/extensions/tutorial003.py"
```
### Kendi metotlarınızı sunma {#serving-your-own-methods}
Bir uzantı **yeni istek metotları** kaydedebilir: spesifikasyonunkilerin yanında sunulan kendi fiilleri:
```python title="server.py" hl_lines="16-22 31 40-48"
--8<-- "docs_src/extensions/tutorial004.py"
```
* `SearchParams`, `RequestParams`'tan türer; böylece 2026 `_meta` zarfı tek biçimde ayrıştırılır ve işleyiciniz ham bir dict değil, her zaman doğrulanmış parametreler alır. İstemcinin denetlediği şeyi sınırlayın: `Field(ge=1, le=100)`, kodunuz onun için herhangi bir şey ayırmadan önce saçma bir `limit` değerini reddeder.
* `require_client_extension(ctx, EXTENSION_ID)` kontrol noktasıdır: uzantıyı beyan etmemiş bir istemci, spesifikasyonun istediği makine tarafından okunabilir `requiredCapabilities` yüküyle birlikte `-32021` (gerekli istemci yeteneği eksik) hatasını alır.
* `protocol_versions=frozenset({"2026-07-28"})` metodu tek bir protokol sürümüne sabitler. Başka herhangi bir sürümde istemci `METHOD_NOT_FOUND` alır; sanki metot orada hiç yokmuş gibi. O istemci için gerçekten de yoktur.
Metotlar **yalnızca ekleme niteliğindedir**. SDK bunu çalışma zamanında değil, oluşturma sırasında uygular:
* Spesifikasyonda tanımlı bir metot (`tools/list`, `completion/complete`, ...) için bir `MethodBinding`, bağlama oluşturulurken `ValueError` fırlatır. Çekirdek fiiller sunucuya aittir.
* Aynı metodu bağlayan iki uzantı, ikincisi kaydolurken hata fırlatır. Son yazan kazanır yaklaşımı, eklentilerin birbirini bozmasının yoludur; biz bunu yapmayız.
* Boş bir `protocol_versions` kümesi de hata fırlatır: hiçbir zaman sunulamayacak bir metot bir yapılandırma değil, bir hatadır.
### İstemci tarafı {#the-client-side}
Aynı dosyadaki `main()`, istemci tarafının tamamıdır; iki yarısıyla birlikte:
```python title="server.py" hl_lines="54-58"
--8<-- "docs_src/extensions/tutorial004.py"
```
* `Client(..., extensions=[advertise(EXTENSION_ID)])` uzantıyı beyan eder. Beyanlar `ClientCapabilities.extensions` hâline gelir: 2026-07-28 bağlantısında eşleme istek başına `_meta` zarfında taşınır, böylece sunucu onu **her** istekte görür; eski nesil bir bağlantıda `initialize` el sıkışmasıyla taşınır. Sunucu kodu hangisi olduğuyla ilgilenmez: `require_client_extension(ctx, ...)` ve `ctx.session.check_client_capability(...)` her iki yolda da doğru kaynağı okur.
* Satıcıya özgü metotlar bir katman aşağıya, `client.session.send_request(...)` düzeyine iner; `Client` yalnızca spesifikasyon fiilleri için birinci sınıf metotlar kazanır. `send_request` herhangi bir `Request` alt sınıfını kabul eder, bu yüzden satıcıya özgü istek olduğu gibi geçer.
### `tools/call` isteğini yakalama {#intercepting-toolscall}
Yakalayıcı nitelikteki tek kanca. Bir araç çağrısını gözlemlemek, kısa devre yapmak veya veto etmek için `intercept_tool_call`'u geçersiz kılın:
```python title="server.py" hl_lines="17-24"
--8<-- "docs_src/extensions/tutorial005.py"
```
* `params`, doğrulanmış `CallToolRequestParams` nesnesidir: ham JSON'a dokunmadan `params.name` ve `params.arguments` elinizdedir. Hangi araç çağrısının çalışacağına karar veren de odur: `call_next` üzerinden yeniden yazılmış bir bağlam geçirmek, araç çağrısını değil, işleyicinin `ctx` üzerinde gözlemlediğini değiştirir. İletim düzeyinde istek yeniden yazımı [Middleware](middleware.md) sayfasının konusudur.
* `call_next(ctx)` zincirin geri kalanını çalıştırır ve işleyicinin sonucunu döndürür. Onu değiştirmeden döndürün (gözlemleme), başka bir şey döndürün (değiştirme) ya da bir `MCPError` fırlatın (reddetme). Ne döndürürseniz döndürün, 2026 neslinin `serverInfo` kimlik damgası dâhil, herhangi bir işleyici sonucu gibi serileştirilir; bu yüzden kısa devre yapan bir yakalayıcı hiçbir zaman anonim veya şema dışı bir yanıt üretmez.
* Birden fazla uzantı olduğunda yakalayıcılar kayıt sırasına göre iç içe geçer: `extensions=[...]` içindeki ilk uzantı en dıştadır.
* Varsayılan gerçekleştirim doğrudan geçirir; uzantıları bu kancayı hiç geçersiz kılmayan bir sunucu, yalın `tools/call` işleyicisini olduğu gibi korur. Kullanmadığınız şeyin bedelini ödemezsiniz.
Kanca `tools/call` isteğini sarmalar, başka hiçbir şeyi değil. Her iletiyi ilgilendiren konular için [Middleware](middleware.md) kullanın. Onun işi budur.
## Bir istemci uzantısı kullanma {#using-a-client-extension}
**İstemci uzantısı**, aynı sözleşmenin tüketen taraftan görünüşüdür: tek bir tanımlayıcının arkasında toplanmış bir istemci tarafı davranış paketi. Örnekleri `Client(extensions=[...])` ile geçirin ve araçları normal şekilde çağırın:
```python title="client.py" hl_lines="66-68"
--8<-- "docs_src/extensions/tutorial006.py"
```
`call_tool("buy", ...)`, diğer her çağrı gibi düz bir `CallToolResult` döndürür. Uzantının değiştirdiği şu: sunucu artık `buy` çağrısını nihai bir sonuç yerine `receipt` adlı bir **sonuç biçimiyle** yanıtlayabilir ve `Receipts`, `call_tool` dönmeden önce onu tamamlar (burada makbuzu bir takip çağrısıyla kullanarak). Çağrı yerinde hiçbir şey değişmez.
Uzantıyı çıkarırsanız bunların hiçbiri olmaz: sunucunun kontrol noktası onu beyan etmemiş bir istemciyi reddeder (hata -32021) ve kontrolü atlayan bir sunucudan gelen sahiplenilmiş bir biçim, spesifikasyonun tanınmayan bir `resultType` için gerektirdiği gibi doğrulamadan geçemez. Bağlantının her iki ucunda da varsayılan olarak kapalı.
İstemci tarafında **hiçbir** davranışı olmayan bir tanımlayıcıyı duyurmak için (sunucu yeteneği kontrol eder, istemci hiçbir şey yapmaz; yukarıdaki arama istemcisinde olduğu gibi) `advertise()` kullanın:
```python
from mcp.client import advertise
client = Client(mcp, extensions=[advertise("com.example/search")])
```
## İstemci uzantısı yazma {#writing-a-client-extension}
`ClientExtension`'dan alt sınıf türetin ve yalnızca ihtiyaç duyduklarınızı geçersiz kılın. Her birinin varsayılanı olan üç katkı türü var: `settings()`, `claims()` ve `notifications()`.
```python title="client.py" hl_lines="17-18 43-44 46-47"
--8<-- "docs_src/extensions/tutorial006.py"
```
* Tanımlayıcı, sunucununkiyle aynı dilbilgisini izler ve sınıf tanımlandığında doğrulanır.
* `claims()`, `ResultClaim`'ler döndürür: iletilen veride bir etiket, onu ayrıştıran model ve onu tamamlayan çözümleyici. Model, etiketi `result_type: Literal["receipt"]` ile sabitlemelidir ve fiilin çekirdek sonuç türlerinden türememelidir; her ikisi de sahiplenme oluşturulurken uygulanır. `receipt_token` gibi satıcıya özgü alanlar ağ üzerinde olduğu gibi iletilir: yerine geçen bir biçim istemciye aynen ulaşır.
* Çözümleyici, ayrıştırılmış modeli ve bir `ClaimContext` alır; `ctx.session`, `client.session` ile aynı herkese açık tutamaçtır, bu yüzden takip çağrıları sıradan oturum çağrılarıdır. Fiilin normal `CallToolResult`'ını döndürür.
* `settings()`, `ClientCapabilities.extensions[identifier]` konumunda duyurulan değerdir; `Client` oluşturulurken bir kez okunur.
`notifications()`, gözlemlenecek satıcıya özgü sunucu bildirimlerini beyan eder:
```python
def notifications(self) -> Sequence[NotificationBinding[Any]]:
return [NotificationBinding(method="notifications/receipts", params_type=ReceiptEvent, handler=self.on_receipt)]
```
İşleyici doğrulanmış parametreleri gönderim sırasına göre tek tek alır. Gözlemler; veto edemez ve yanıt veremez.
İki sessiz kural. Sahiplenmeler yalnızca 2026-07-28 bağlantılarında etkindir ve yetenek duyurusu da onları izler: eski nesil bir bağlantıda sahiplenmeler ortadan kalkar, tanımlayıcı da onlarla birlikte duyurudan düşer; böylece istemci, biçimlerini reddedeceği bir uzantıyı asla duyurmaz. Sahiplenilen biçimi çözümleyici yerine kendiniz istediğinizde ise `client.session.call_tool(..., allow_claimed=True)` çağırın; bu bayrak olmadan, oturum katmanındaki bir çağırana ulaşan sahiplenilmiş bir biçim `UnexpectedClaimedResult` fırlatır.
### Uzantı fiilleri {#extension-verbs}
Bir uzantının kendi istek metotları istemci tarafında kayıt gerektirmez. Satıcıya özgü bir istek türü `mcp.types.Request`'ten türer ve [Kendi metotlarınızı sunma](#serving-your-own-methods) bölümündeki gibi `client.session.send_request` üzerinden gider. Tek bir ekleme var: bir params anahtarının `Mcp-Name` başlığında taşınması gerektiğinde (tasks gibi uzantı spesifikasyonları kendi fiilleri için bunu şart koşar) istek türü `name_param` beyan eder:
```python title="client.py" hl_lines="22-25 46-47"
--8<-- "docs_src/extensions/tutorial007.py"
```
Oturum, `params["jobId"]` değerini her gönderim yolunda `Mcp-Name` başlığına yansıtır; eksik bir değer ise gerekli bir başlığı sessizce atlamak yerine açıkça hata verir.
## Bir uzantının yapamayacakları {#what-an-extension-cannot-do}
Katkı yüzeyi bilerek **kapalıdır**. Sunucuda: ayarlar, araçlar, kaynaklar, metotlar, bir `tools/call` yakalayıcısı. İstemcide: ayarlar, sonuç sahiplenmeleri, bildirim bağlamaları. Bir uzantı şunları yapamaz:
* **Barındıran nesneye erişemez.** Veri beyan eder; sunucu veya istemci referansı tutmaz.
* **Çekirdek davranışın yerine geçemez.** Spesifikasyon metotları ve çekirdek sonuç etiketleri oluşturma sırasında reddedilir (`initialize` doğrudan çalıştırıcı tarafından ayrılmıştır); çekirdek söz dağarcığının gölgelediği bir bildirim bağlaması ise bir uyarıyla sessizce devre dışı kalır.
* **Geç kayıt olamaz.** `MCPServer(...)` veya `Client(...)` döndükten sonra uzantı kümesi neyse odur.
Bu duvarlarla boğuşuyorsanız bir uzantı yazmıyorsunuz. Bir fork yazıyorsunuz. Duvarlar özelliğin ta kendisidir: `extensions=[Apps(), Stamps()]` satırını okuyan bir kullanıcı, bu ikisinin dokunmuş olabileceği *her şeyi* bilir.

View file

@ -0,0 +1,34 @@
---
translation:
sections: [ca6988b7503cd2d3]
tool: 1
---
# İleri düzey {#advanced}
Sıradan bir sunucunun ya da istemcinin ihtiyaç duyduğu her şeyin yukarıdaki bölümlerde konusuna göre bir yeri var.
Bu bölüm ise `MCPServer`'ın kolaylık katmanı size engel olduğunda başvuracağınız
kaçış yollarını içerir:
* **[Alt düzey Server](low-level-server.md)**: `MCPServer`'ın üzerine kurulduğu sınıf.
Elle yazılmış şemalar, `on_*` işleyicileri, sizin yerinize denetlenen hiçbir şey yok
ve kendinize ait özel JSON-RPC metotları.
* **[Sayfalama](pagination.md)** ve **[Middleware](middleware.md)**: *yalnızca*
alt düzey `Server` üzerinde yapabileceğiniz iki şey.
* **[Uzantılar](extensions.md)** ve **[MCP Apps](apps.md)**: protokolün uzantı
yüzeyi. Uzantı paketlerini bir sunucuda bir araya getirin ya da kendinizinkini yazın.
Burada aramanız gayet doğal olan birkaç konu ise aslında onları kullanacağınız yerde
duruyor:
* **Yetkilendirme**, **[Sunucunuzu çalıştırma](../run/index.md)** altında; çünkü
bir sunucuyu dağıttığınız yerde korursunuz.
* **OAuth**, **kimlik beyanı**, **birden çok sunucuya** bağlanma ve yanıt
**önbelleği**, hepsi **[İstemciler](../client/index.md)** altında.
* **Çok turlu istekler** (multi-round-trip) ve **Abonelikler**,
**[İşleyicinin içinde](../handlers/index.md)** altında; çünkü ikisi de bir
işleyicinin *yaptığı* şeyler.
* **URI şablonları**, **[Sunucular](../servers/index.md)** altında, Kaynaklar'ın hemen yanında.
* **[Protokol sürümleri](../protocol-versions.md)** ve
**[Kullanım dışı özellikler](../deprecated.md)** sayfalarının her birinin kendi üst düzey sayfası var.
Bu bölüme ihtiyacınız olup olmadığından emin değilseniz, yok demektir.

View file

@ -0,0 +1,207 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
tool: 1
---
# Düşük seviyeli Server {#the-low-level-server}
`@mcp.tool()` bir katmandır. Altında ham MCP konuşan ikinci bir sunucu sınıfı, `Server`, vardır: protokol nesnelerini ona verirsiniz, o da hiç dokunmadan ağ üzerinden gönderir.
`MCPServer` onun üzerine kuruludur. Kolaylık katmanı size engel olduğunda alt katmana inersiniz:
* Python imzasından türetilmiş bir şema değil, **birebir** belirli bir şema (dosyadan yüklenen, veritabanından üretilen) yayımlamanız gerekir.
* Sonuç üzerinde tam denetim gerekir: `_meta`, `is_error`, `structured_content`'in her anahtarı.
* MCP'nin tanımlamadığı bir metodu ele almanız gerekir.
Geri kalan her şey için `MCPServer`'da kalın.
## Aynı araç, elle {#the-same-tool-by-hand}
Bu, **[Araçlar](../servers/tools.md)** sayfasında dokuz satır `@mcp.tool()` ile yazılan `search_books` aracının kolaylıklardan arındırılmış hali:
```python title="server.py" hl_lines="22 26 32"
--8<-- "docs_src/lowlevel/tutorial001.py"
```
Üç şey değişti ve düşük seviyeli API'nin tamamı bu üçü:
* **İşleyiciler yapıcı parametreleridir.** `on_list_tools=` ve `on_call_tool=`, `Server(...)` çağrısına gider. Burada dekoratör yoktur ve her işleyici aynı biçimdedir: `async (ctx, params) -> result`.
* **Girdi şemasını siz yazarsınız.** `Tool.input_schema`, düz bir JSON Schema `dict`'idir. Kimse onu tür ipuçlarından türetmez, çünkü türetilecek tür ipucu yoktur.
* **Sonucu siz oluşturursunuz.** `CallToolResult(content=[TextContent(...)])`, elle. Hiçbir şey sarmalanmaz, dönüştürülmez ya da bir dönüş anotasyonundan çıkarsanmaz.
`params` ayrıştırılmış istektir: `CallToolRequestParams` size `.name` ve `.arguments` verir. `ctx` bir `ServerRequestContext`'tir: istemciyle geri konuşmak için `ctx.session`, `ctx.lifespan_context`, `ctx.request_id` ve isteğin gelen `_meta`'sı olan `ctx.meta`.
!!! info
FastAPI kullandıysanız bu ilişkiyi zaten biliyorsunuz. `MCPServer`, dekoratörler ve tür ipuçları katmanıdır; `Server` ise alttaki Starlette'tir. Rakip değiller: `MCPServer` bir `Server` oluşturur ve üzerine tam da bunlar gibi işleyiciler kaydeder.
### Deneyin {#try-it}
Bunun için Inspector yok: `mcp dev` ve `mcp run` yalnızca `MCPServer` kabul eder. Bellek içi `Client` bunu umursamaz; düşük seviyeli bir `Server`'ı tıpkı bir `MCPServer`'ı aldığı gibi alır:
```python title="main.py"
import asyncio
from mcp import Client
from server import server
async def main() -> None:
async with Client(server) as client:
result = await client.call_tool("search_books", {"query": "dune", "limit": 5})
print(result.content)
asyncio.run(main())
```
```text
[TextContent(type='text', text="Found 3 books matching 'dune' (showing up to 5).", annotations=None, meta=None)]
```
`@mcp.tool()` sürümünün ürettiği metnin aynısı. İki gerçek fark var:
* `result.structured_content` değeri `None`. Yüksek seviyeli sunucu `-> str` dönüş türünü sizin yerinize `{"result": ...}` içine sarmalar; burada sizin oluşturmadığınızı kimse oluşturmaz.
* `list_tools`, **sizin** yazdığınız şemayı karakteri karakterine döndürür. Yüksek seviyeli sürümde her özellikte `"title": "Query"`, kökte de `"title": "search_booksArguments"` vardı: Pydantic'in bıraktığı izler. Burada ise ağa giden bir şey varsa onu oraya siz koymuşsunuzdur.
## Sizin yerinize hiçbir şey denetlenmez {#nothing-is-checked-for-you}
`MCPServer`, çağrıyı kendi ürettiği şemaya göre doğrulayarak hatalı bir argümanı fonksiyonunuz daha çalışmadan reddeder (**[Araçlar](../servers/tools.md)**).
`Server` bunu yapmaz. `input_schema`'nız istemciye *duyurulur*; `params.arguments`'a asla *uygulanmaz*.
!!! check
`search_books`'u `limit` olmadan çağırın; `args["limit"]` ifadeniz `KeyError` fırlatır. İstemci şunu görür:
```text
MCPError: Internal server error
```
`-32603` kodlu, mesajı kasıtlı olarak genel tutulmuş bir JSON-RPC hatası: SDK, traceback'inizi uzaktaki bir çağırana sızdırmaz. Model neyi yanlış yaptığını asla öğrenemez, bu yüzden yeniden deneyemez. (Testte `raise_exceptions=True` bunun yerine gerçek istisnayı yüzeye çıkarır; bkz. **[Test etme](../get-started/testing.md)**.)
Bu genellenebilir. Düşük seviyeli bir işleyiciden fırlatılan istisna **her zaman** bir protokol hatasıdır, asla `is_error=True` taşıyan bir araç sonucu değildir. Modelin hatayı okuyup toparlanmasını istiyorsanız `params.arguments`'ı kendiniz doğrulayın ve `CallToolResult(content=[TextContent(...)], is_error=True)` döndürün. Bu iki hata türü **[Hataları ele alma](../servers/handling-errors.md)** sayfasının konusu.
## İki araç, tek işleyici {#two-tools-one-handler}
`on_call_tool`, sunucudaki her araç için tek giriş noktasıdır. Yönlendirmeyi `params.name`'e göre yaparsınız:
```python title="server.py" hl_lines="38-43"
--8<-- "docs_src/lowlevel/tutorial002.py"
```
* `list_tools` ikisini de duyurur. `call_tool` ada göre yönlendirir.
* `else` dalı önemlidir: `Server`, hiç listelemediğiniz bir ad için gelen `tools/call` isteğini hiç sorgulamadan doğrudan işleyicinize iletir. Orada istisna fırlatmak çağrıyı yukarıdakiyle aynı `-32603` hatasına çevirir.
## Yapılandırılmış çıktı, elle {#structured-output-by-hand}
`Tool` üzerinde `output_schema` bildirin ve sonuca `structured_content` koyun. İkisi de sizin:
```python title="server.py" hl_lines="19-23 36"
--8<-- "docs_src/lowlevel/tutorial003.py"
```
Çağırın; sonuç iki gösterimi de taşır:
```json
{
"content": [{"type": "text", "text": "Found 3 books matching 'dune'."}],
"structuredContent": {"matches": 3, "query": "dune"},
"isError": false,
"resultType": "complete",
"_meta": {"io.modelcontextprotocol/serverInfo": {"name": "Bookshop", "version": "2.0.0"}}
}
```
`_meta` bloğu sunucunun kimlik damgasıdır: SDK bunu 2026 neslinden her sonuca, yapıcıdan gelen `version` ile birlikte ekler (hiç sürüm belirtmeyen bir sunucu boş bir dize bildirir). Kendini tanıtmaması gereken bir sunucu bu anahtarı bir middleware (ara katman) ile çıkarabilir; middleware döndürdüğü sonuçların sahibidir.
Sunucu bu iki alanı asla karşılaştırmaz. Bu SDK'nın `Client`'ı karşılaştırır: bildirdiğiniz `output_schema`'yı karşılamayan bir `structured_content` döndürün, `call_tool` `Invalid structured content returned by tool search_books` ile başlayıp `jsonschema` hatasını alıntılayarak devam eden bir `RuntimeError` fırlatır. Bir şema vaat etmek ucuzdur; sözünüzü tutmak size kalır. Dönüş türleri ve şemaların tüm basamakları **[Yapılandırılmış çıktı](../servers/structured-output.md)** sayfasında.
## `_meta`: model için değil, uygulama için {#\_meta-for-the-application-not-the-model}
`content`, yanıtın modelin okuduğu kısmıdır. `structured_content`, aynı yanıtın tür bilgisi taşıyan veri halidir. `_meta` üçüncü kanaldır: yanıtın hiçbir şekilde parçası olmadan, **istemci uygulama** için sonuçla birlikte yolculuk eden veri.
Kayıt kimlikleri, iz kimlikleri, kullanıcı arayüzünüzün ihtiyaç duyup prompt'unuzun duymadığı her şey için kullanın:
```python title="server.py" hl_lines="37"
--8<-- "docs_src/lowlevel/tutorial004.py"
```
* Onu ağ üzerindeki adıyla, `_meta=` olarak oluşturursunuz. İstemci onu `result.meta` olarak geri okur.
* Anahtarlarınıza ad alanı verin (`bookshop/record_ids`). `io.modelcontextprotocol/*` anahtarları protokole ayrılmıştır.
!!! warning
`_meta`, sizinle istemci uygulama arasındaki bir uzlaşıdır; modele neyin ulaştığına dair
bir garanti değildir. Neyi göstereceğine host karar verir. Bir araç sonucunun hiçbir yerine asla sır koymayın.
## Yetenekler işleyicilerinizi izler {#capabilities-follow-your-handlers}
Bir `Server`, tam olarak işleyici verdiğiniz metot ailelerini duyurur. Yukarıdaki `Bookshop`, `on_list_tools` ile `on_call_tool`'u geçirir, başka hiçbir şey geçirmez; dolayısıyla ona bağlanan bir istemci şunu görür:
```json
{"tools": {"listChanged": false}}
```
`resources` yok, `prompts` yok: arkalarında duracak bir şey yok. `on_list_prompts` geçirin, `prompts` belirir; `on_completion` geçirin, `completions` belirir.
`MCPServer`, siz kaydetmiş olun olmayın araçları, kaynakları ve prompt'ları her zaman duyurur; çünkü yöneticileri her zaman vardır. Burada ise beyan, yapıcı çağrısının *ta kendisidir*.
## Lifespan jenerik parametresi {#the-lifespan-generic}
`Server`, lifespan'inin (yaşam döngüsü) ürettiği türe göre jeneriktir. Bir kez tür açıklaması ekleyin; nesne ortaya çıktığı her yerde tür bilgisi taşır:
```python title="server.py" hl_lines="24-26 44-45 50"
--8<-- "docs_src/lowlevel/tutorial005.py"
```
* Lifespan, `Callable[[Server[Catalog]], AbstractAsyncContextManager[Catalog]]` türündedir; bir `async` üreteç üzerindeki `@asynccontextmanager` size tam olarak bunu verir.
* `yield` ettiği her neyse `ctx.lifespan_context` olur; işleyiciler `ServerRequestContext[Catalog]` olarak açıklandığı için de `.search(...)` otomatik tamamlanır ve tür denetiminden geçer.
* Sunucu başlarken bir kez girilir, dururken bir kez çıkılır. Başlatma, kapatma ve aynı fikrin `MCPServer` sürümü **[Lifespan](../handlers/lifespan.md)** sayfasında.
`lifespan=` olmadan `ctx.lifespan_context` boş bir `dict`'tir.
## Kendinize ait bir metot {#a-method-of-your-own}
Yapıcı, MCP'nin tanımladığı metotları kapsar. `add_request_handler` geri kalan her şeyi kapsar:
```python title="server.py" hl_lines="35-36 39-40 43-44 48"
--8<-- "docs_src/lowlevel/tutorial006.py"
```
* İlk argüman metot dizesidir. Bildirimlerin bir ikizi vardır: `add_notification_handler`.
* `params_type`, gelen `params`'ın işleyiciniz çalışmadan **önce** doğrulandığı modeldir; yani özel metotlar, araçların almadığı doğrulamayı *alır*. `_meta` alanının diğer her metotta olduğu gibi ayrıştırılması için `RequestParams`'tan alt sınıf türetin.
* İşleyici bir `BaseModel`, bir `dict` ya da `None` döndürür. SDK bunu JSON-RPC sonucuna serileştirir.
Dürüst bir uyarı: yüksek seviyeli `Client`'ta yalnızca MCP'nin tanımladığı metotlar için fiiller vardır, yani `client.reindex()` diye bir şey yoktur. Satıcıya özel bir metot, varlığından zaten haberdar olan bir eş içindir: sizin de dağıttığınız bir istemci ya da JSON-RPC konuşan başka bir servisiniz.
Sahiplenemeyeceğiniz tek bir metot var:
```text
ValueError: 'initialize' is handled by the server runner and cannot be overridden;
use Server.middleware to observe or wrap initialization
```
El sıkışma çalıştırıcıya aittir. `server/discover`, `ping` ve diğer tüm yerleşik metotları dilediğiniz gibi değiştirebilirsiniz.
!!! tip
O hatada adı geçen `Server.middleware`, `initialize` dahil gelen **her** mesajı sarmalar. İstediğiniz yeni bir metodu yanıtlamak değil de trafiği gözlemlemek ya da yeniden yazmaksa **[Middleware](middleware.md)** sayfasından başlayın.
## Diğer işleyiciler {#the-other-handlers}
Bunların her biri, artık kavramlarını bildiğiniz birer fikir; her birinin kendi sayfası var.
* `on_call_tool`, `on_get_prompt` ve `on_read_resource`, çağrıyı duraklatıp istemciden girdi istemek için normal sonuçları yerine bir `InputRequiredResult` döndürebilir; bkz. **[Çok turlu istekler](../handlers/multi-round-trip.md)** (multi-round-trip). Bu katmanın ruhuna uygun olarak sizin için hiçbir şey kurulmaz: `MCPServer` varsayılan olarak `requestState`'i mühürlerken burada ayarladığınız `request_state`, siz `server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[...]), default_audience=server.name))` ile katılana kadar ağı tam yazıldığı gibi geçer: `MCPServer`'ın yaptığı mühürleme ve doğrulamanın aynısı için tek satır (iki ad da `mcp.server.request_state`'ten içe aktarılır) (**[`requestState`'i koruma](../handlers/multi-round-trip.md#protecting-requeststate)**).
* `on_list_resources`, `on_read_resource`, `on_list_prompts`, `on_get_prompt`, `on_completion`, diğer ilkel öğeler için aynı `(ctx, params) -> result` biçimidir.
* `on_subscriptions_listen`, 2026-07-28 `subscriptions/listen` akışını sunar. Bir `SubscriptionBus` üzerine kurulu bir `ListenHandler` geçirin ve olayları diğer işleyicilerinizden veri yoluna yayımlayın; bileşimin tamamı için bkz. **[Abonelikler](../handlers/subscriptions.md)**.
* `server.streamable_http_app()`, `MCPServer`'ınkiyle aynı Starlette uygulamasını döndürür; onu **[Sunucunuzu çalıştırma](../run/index.md)** sayfasının herhangi bir ASGI uygulamasını dağıttığı gibi dağıtın. Burada `server.run(transport=...)` yoktur: `server.run(read_stream, write_stream, server.create_initialization_options())` bir akış çifti üzerinden tek bir bağlantıyı yürütür ve bu tek satır işin tamamıdır.
## Özet {#recap}
* Düşük seviyeli `Server`, işleyicilerini `on_*` **yapıcı parametreleri** olarak alır; her işleyici `async (ctx, params) -> result` biçimindedir.
* `input_schema` sözlüğünü siz yazar, `CallToolResult`'ı siz oluşturursunuz. Sizin yerinize hiçbir şey türetilmez, sarmalanmaz ya da doğrulanmaz.
* İşleyicideki bir istisna `-32603` protokol hatasıdır. Modelin okuyabileceği bir araç hatası, **sizin** döndürdüğünüz `is_error=True` taşıyan bir `CallToolResult`'tır.
* Sonuçtaki `_meta` modele değil, istemci uygulamaya yöneliktir.
* `Server[T]`, lifespan'inin ürettiği şeye göre jeneriktir; `ctx.lifespan_context` tür bilgisi taşıyan bir `T`'dir.
* `add_request_handler(method, params_type, handler)` her metodu sunar. `initialize` ayrılmıştır.
* Bir `Server`'ın duyurduğu yetenekler, hangi işleyicileri kaydettiğinizden türetilir.
`Client(server)` iki sunucuya da aynı davrandı, çünkü ikisi aynı protokolün *ta kendisi*; bütün mesele de bu. Bir alt katman ise bir sınıf bile değil: **[Middleware](middleware.md)**.

View file

@ -0,0 +1,122 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
tool: 1
---
# Middleware {#middleware}
**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](low-level-server.md)** sayfasını okuyun.
## Bir zamanlama middleware'i {#a-timing-middleware}
Bir sunucu, bir araç ve her mesajın ne kadar sürdüğünü loglayan bir middleware:
```python title="server.py" hl_lines="39-45 49"
--8<-- "docs_src/middleware/tutorial001.py"
```
* `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 {#try-it}
Bir istemci bağlayın, araçları listeleyin, birini çağırın. Logunuzda **üç** satır var:
```text
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 {#what-you-can-do-inside-one}
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](../handlers/subscriptions.md#deciding-who-may-watch)** 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:
```text
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 {#the-one-middleware-that-ships-on-by-default}
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](../run/opentelemetry.md)**.
!!! 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 {#recap}
* 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](../run/opentelemetry.md)**.
* 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](../run/authorization.md)**.

View file

@ -0,0 +1,85 @@
---
translation:
sections: [a9aba7a026c7bd85, ed32bda7ba9ae33a, 7e64cc5646abb91f, 22a0129ee78b3c63, d875373c06d8d2f9]
tool: 1
---
# Sayfalama {#pagination}
Çoğu sunucunun buna hiç ihtiyacı olmaz.
`MCPServer`, her `list_*` isteğini elindeki her şeyle, tek sayfada, `next_cursor=None` ile yanıtlar. Birkaç düzine araç, kaynak veya prompt için doğru yanıt budur ve yapılandıracak bir şey yoktur.
Sayfalama, kaynak listesi aslında bir veritabanı olan sunucu içindir: tek yanıtta serileştirmeyi reddettiği binlerce satır. Protokolün buna yanıtı **imleç**tir (cursor): sunucu bir sayfa ile birlikte opak bir token döndürür, istemci de sonraki sayfayı almak için bu token'ı geri gönderir.
`@mcp.resource()`'ta bunların hiçbiri için bir kanca yoktur. Sayfalamak için liste işleyicisini **[düşük seviyeli Server](low-level-server.md)** üzerinde kendiniz yazarsınız.
## Sayfalayan bir sunucu {#a-server-that-pages}
```python title="server.py" hl_lines="12 15-16"
--8<-- "docs_src/pagination/tutorial001.py"
```
* Düşük seviyeli bir `Server`'da işleyiciler dekoratör değil, kurucu argümanlarıdır. `on_list_resources` her `resources/list` isteğini yanıtlar; bağlantının tamamı bu.
* Sayfalanan her işleyicinin türü `params: PaginatedRequestParams | None`'dır ve örnek ikisini de kabul eder. Ancak bir bağlantı üzerinden SDK size hiçbir zaman `None` vermez (`params` üyesi olmayan bir istek, işleyiciye varsayılan değerleriyle model olarak ulaşır); bu yüzden önemli olan sinyal `params.cursor is None`'dır: **en baştan başla**.
* Bir imlecin *ne olduğuna* siz karar verirsiniz. Burada dizge olarak yazılmış bir ofsettir. Bir zaman damgası, bir birincil anahtar, bir base64 blob'u: çıkışta üretebileceğiniz ve dönüşte tanıyabileceğiniz herhangi bir şey.
* `next_cursor=None`, "bu son sayfaydı" demenin yoludur. Sayaç yok, toplam yok, `has_more` yok. Sinyalin tamamı `None`'dır.
!!! tip
10'luk bir `PAGE_SIZE` örneği okunur kılar. Kendinizinkini endpoint başına seçin:
tek satırlık kaynaklardan oluşan bir liste 500'lük bir sayfayı kaldırır; şişkin prompt
şablonlarından oluşan bir liste kaldıramaz. İstemcinin bu konuda söz hakkı yoktur ve bu bilinçli bir tasarımdır.
### Deneyin {#try-it}
`Client(server)`, düşük seviyeli bir `Server`'a bellek içinde, bir `MCPServer`'a bağlandığı gibi bağlanır.
`list_resources()`'ı argümansız çağırın. `book-1`'den `book-10`'a kadar on kaynak alırsınız ve `next_cursor`, `"10"` dizgesidir.
Bunu `list_resources(cursor="10")` ile geri verin; ilk kaynak `book-11`, yeni `next_cursor` ise `"20"` olur.
Onuncu sayfa, `next_cursor` değeri `None` olarak döner. Bitti.
## İstemci döngüsü {#the-client-loop}
`Client` üzerindeki her `list_*` metodu (`list_tools`, `list_resources`, `list_resource_templates`, `list_prompts`) bir `cursor=` anahtar kelimesi alır. Sayfalanmış bir listeyi sonuna kadar okumak tek bir `while True`'dur:
```python title="client.py" hl_lines="26-32"
--8<-- "docs_src/pagination/tutorial002.py"
```
* `cursor`, `None` olarak başlar; bu yüzden ilk istek imleç taşımaz.
* `next_cursor`'a bakmadan **önce** listeyi genişletin: son sayfada da kaynaklar vardır.
* Çıkış koşulu `next_cursor is None`'dır. Bunun dışındaki her şey, dokunulmadan doğrudan `cursor=`'a geri gider.
`main()`'ini çalıştırın; `100 resources` yazdırır: on tane onluk sayfa, on sayfa olduğundan hiç haberi olmayan bir döngü tarafından birleştirilmiş.
Bu, **[İstemci](../client/index.md)** sayfasının her `list_*` fiili için gösterdiği döngünün aynısıdır ve sayfalamayan bir sunucuya karşı hiçbir maliyeti yoktur: ilk yanıtta `next_cursor`, `None` olur ve döngü bir kez çalışır.
## Üç kural {#the-three-rules}
**İmleçler opaktır.** Bir istemci bir imleci asla ayrıştırmamalı, oluşturmamalı veya tahmin etmemelidir. Bir imlecin tek meşru kaynağı, bir önceki sayfanın `next_cursor`'ıdır; harfi harfine.
**Sayfa boyutunu sunucu seçer.** Protokolde `limit=` yoktur. Farklı bir sayfa boyutuna ihtiyacınız varsa sunucuyu değiştirirsiniz.
**Sayfalamayı yok sayan bir istemci yine de çalışır.** `list_resources()`'ı bir kez çağırır, ilk onu alır ve attığı `next_cursor`'ı hiç fark etmez. Hiçbir şey bozulmaz; yalnızca daha azını görür.
!!! check
Opak, opak demektir. Bir imleç uydurursanız (`list_resources(cursor="page-2")`) protokolün
sizin için yapabileceği hiçbir şey yoktur. Bu sunucu `int("page-2")`'yi dener, işleyici istisna fırlatır
ve istemciye dönen şudur:
```text
MCPError(-32603, 'Internal server error', None)
```
Sunucudan almadığınız bir imleç bir hatadır, bir özellik isteği değil.
## Özet {#recap}
* `MCPServer` her şeyi tek sayfada döndürür. Sayfalama isteğe bağlıdır ve buna düşük seviyeli `Server` üzerinde geçersiniz.
* `on_list_resources` (ve `on_list_tools`, `on_list_prompts`, `on_list_resource_templates`) `PaginatedRequestParams | None` alır; ilk sayfa için `params.cursor`, `None`'dır.
* Bir sayfa ile birlikte `next_cursor` döndürürsünüz: sonradan tanıyacağınız herhangi bir dizge ya da geriye bir şey kalmadığında `None`.
* İstemci döngüsü: `cursor=` geçirin, biriktirin, `next_cursor is None` olana kadar tekrarlayın.
* İmleçler opaktır, sayfa boyutu sunucunundur ve sayfalamayan bir istemci yine de birinci sayfayı alır.
Elle yazılan `Server` API'sinin geri kalanı (`on_call_tool`, `input_schema` dict'leri, `_meta`) **[Düşük seviyeli Server](low-level-server.md)** sayfasında.