docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
50 changed files with 89737 additions and 54 deletions
123
i18n/tr/pages/advanced/apps.md
Normal file
123
i18n/tr/pages/advanced/apps.md
Normal 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
|
||||
```
|
||||
172
i18n/tr/pages/advanced/extensions.md
Normal file
172
i18n/tr/pages/advanced/extensions.md
Normal 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.
|
||||
34
i18n/tr/pages/advanced/index.md
Normal file
34
i18n/tr/pages/advanced/index.md
Normal 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.
|
||||
207
i18n/tr/pages/advanced/low-level-server.md
Normal file
207
i18n/tr/pages/advanced/low-level-server.md
Normal 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)**.
|
||||
122
i18n/tr/pages/advanced/middleware.md
Normal file
122
i18n/tr/pages/advanced/middleware.md
Normal 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)**.
|
||||
85
i18n/tr/pages/advanced/pagination.md
Normal file
85
i18n/tr/pages/advanced/pagination.md
Normal 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.
|
||||
122
i18n/tr/pages/client/caching.md
Normal file
122
i18n/tr/pages/client/caching.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9e7b9a1710e5aeba, b74ca4c1d2ddddee, fa8714e61bf90c5a, 04db67a886b7271c, 857690fb8f876800]
|
||||
tool: 1
|
||||
---
|
||||
# Önbellekleme ipuçları {#caching-hints}
|
||||
|
||||
2026-07-28 protokolünde bir sunucunun `tools/list`, `prompts/list`, `resources/list`, `resources/templates/list`, `resources/read` ve `server/discover` için döndürdüğü her sonuç iki alan taşır: `ttlMs`, istemcinin sonucu kaç milisaniye boyunca taze sayabileceğini; `cacheScope` ise önbelleğe alınmış bir sonucun kullanıcılar arasında paylaşılıp paylaşılamayacağını (`"public"`) yoksa tek bir yetkilendirme bağlamına mı ait olduğunu (`"private"`) belirtir.
|
||||
|
||||
Sunucu hiçbir şeyi önbelleğe almaz. Bu alanlar bir *beyandır*: "bu araç listesi herkes için aynı ve bir dakika boyunca değişmeyecek." Bunun üzerine bir istemci (ya da önünüzdeki bir ağ geçidi) turu atlayabilir. İpuçlarına uymak istemcinin tercihidir; onları yayımlamak sunucunun işidir ve bunu sizin yerinize SDK yapar.
|
||||
|
||||
Varsayılan olarak her sonuç `ttlMs: 0, cacheScope: "private"` der: anında bayat, asla paylaşılmaz. Bu her zaman güvenli ve her zaman uyumludur. Listeleriniz gerçekten kararlıysa ve tüm çağıranlar için aynıysa, bunu oluşturma sırasında belirtin:
|
||||
|
||||
```python title="server.py" hl_lines="5-8"
|
||||
--8<-- "docs_src/caching/tutorial001.py"
|
||||
```
|
||||
|
||||
* Eşleme **yöntem adına** göre anahtarlanır ve geçerli anahtarlar yalnızca önbelleğe alınabilir altı yöntemdir. Parametrenin türü `Mapping[CacheableMethod, CacheHint]` olduğundan düzenleyiciniz anahtarları otomatik tamamlar ve bir yazım hatasını siz çalıştırmadan önce işaretler; tür denetleyicisinden kaçan her şey oluşturma sırasında istisna fırlatır.
|
||||
* Anmadığınız bir yöntem varsayılanları korur. Eşleme bir manifesto değil, bir geçersiz kılma kümesidir.
|
||||
* `CacheHint(ttl_ms=5_000)` `scope`'u ayarlamadı, bu yüzden `"private"` kalır: çağıran başına beş saniyelik tazelik. Kapsam ve TTL birbirinden bağımsız kararlardır.
|
||||
* `"server/discover"` da geçerli bir anahtardır, çünkü keşif sonucu herhangi bir liste gibi önbelleğe alınabilir.
|
||||
|
||||
!!! warning
|
||||
`cacheScope: "public"`, önbelleğe alınmış yanıtınızın *herkese* sunulabileceği anlamına gelir.
|
||||
Paylaşımlı bir ağ geçidi, istek kimliği doğrulanmış olsa bile, bir kullanıcının sonucunu başka
|
||||
birine rahatlıkla verir. Bir sonucu yalnızca her çağıran için aynı olduğunda `"public"` olarak
|
||||
işaretleyin ve `cacheScope`'u asla erişim denetimi olarak kullanmayın: o bir etikettir, kilit değil.
|
||||
|
||||
## İşleyici başına geçersiz kılma {#per-handler-override}
|
||||
|
||||
Alt düzey `Server`'da işleyiciler sonuçlarını elle oluşturur ve `ttl_ms` / `cache_scope` sonuç modellerindeki sıradan alanlardır. Bunları açıkça ayarlayan bir işleyici, alan alan, her zaman oluşturucu eşlemesine üstün gelir:
|
||||
|
||||
```python title="server.py" hl_lines="10 16"
|
||||
--8<-- "docs_src/caching/tutorial002.py"
|
||||
```
|
||||
|
||||
İşleyici `ttl_ms=1_000` dedi, kapsam hakkında ise hiçbir şey söylemedi. İletilen veride: `ttlMs: 1000` (eşlemenin `60_000`'i değil, işleyicininki) ve `cacheScope: "public"` (eşlemeninki, çünkü işleyici onu ayarlamadı). Açık olan yapılandırılanı, yapılandırılan da varsayılanı yener. Bu alan başına geçerlidir; yani bir işleyici bir alanı sabitleyip diğerini sunucu genelindeki politikaya bırakabilir.
|
||||
|
||||
Bu aynı zamanda oluşturucunun bilemeyeceği dinamikler için kaçış kapısıdır: `resources/read`'i kullanıcıya göre filtreleyen bir işleyici, geri kalanı public olan bir sunucudan tek bir URI için `cache_scope="private"` döndürebilir.
|
||||
|
||||
Sayfalandırılmış listelerle ilgili bir uyarı: protokol bir listenin **her sayfasında aynı `cacheScope`'u** şart koşar. Oluşturucu eşlemesi bunu yapısı gereği sağlar, çünkü sayfaya değil yönteme göre anahtarlanır. Ancak kapsamı kendisi geçersiz kılan bir işleyici bu tutarlılıktan kendisi sorumludur: kapsamı *her* sayfada geçersiz kılın, asla yalnızca bir imleç varken değil; yoksa birinci sayfa ile ikinci sayfa çelişir.
|
||||
|
||||
## İstemcinin gördükleri {#what-the-client-sees}
|
||||
|
||||
2026-07-28 oturumunda `Client` ipuçlarına sizin yerinize uyar: varsayılan olarak açık, yerleşik bir yanıt önbelleği vardır. `ttlMs` taşıyarak gelen bir sonuç saklanır ve o TTL içinde yapılan özdeş bir çağrı hiç tur atılmadan önbellekten sunulur. *Hiç* ipucu taşımayan bir sonuç önbelleğe alınmaz: ipucu taşımayan sonuçlar `CacheConfig.default_ttl_ms` değerini alır, bu da varsayılan olarak `0`'dır (anında bayat); dolayısıyla hiçbir şey beyan etmeyen bir sunucu, her zaman gördüğü çağrı başına bir istek trafiğinin aynısını görür.
|
||||
|
||||
```python title="client.py" hl_lines="33 35 38"
|
||||
--8<-- "docs_src/caching/tutorial003.py"
|
||||
```
|
||||
|
||||
Dört çağrı, üç getirme. İkinci çağrı taze bir girdi buldu ve sunucuya hiç ulaşmadı; (enjekte edilen) saati TTL'nin ötesine ilerletmek üçüncünün yeniden getirmesine yol açtı; dördüncü `cache_mode="refresh"` dedi. Bu anahtar sözcük argümanı önbellekleme yapan beş fiilde bulunur (`list_tools`, `list_prompts`, `list_resources`, `list_resource_templates`, `read_resource`):
|
||||
|
||||
* `"use"` (varsayılan) varsa taze bir girdiyi sunar, yoksa getirilen sonucu saklar.
|
||||
* `"refresh"` asla önbellekten sunmaz: sonucu getirir ve saklar, önbellekte ne varsa onun yerine koyar.
|
||||
* `"bypass"` önbelleğe hiç dokunmadan turu atar: ne okuma ne yazma.
|
||||
|
||||
`"use"`'un üzerinde bir kural vardır: **`meta` taşıyan çağrılar her zaman sunucuya ulaşır.** `meta` ayarlanmış bir istek (bir ilerleme token'ı, izleme alanları) ağ üzerinde gerçek bir istek bekler; bu yüzden `cache_mode="use"` altında `"refresh"` gibi ele alınır: önbellek okuması atlanır ve getirilen sonuç yine de önbellekteki girdinin yerine geçer. `"bypass"` ve açık bir `"refresh"` her zamanki gibi davranır.
|
||||
|
||||
Önbelleklemeyi tamamen kapatmak için `Client(server, cache=None)` ile oluşturun: her çağrı yeniden bir tur olur ve `cache_mode` hâlâ kabul edilse de hiçbir şey yapmaz.
|
||||
|
||||
Kapsama da otomatik olarak uyulur: `"private"` girdiler önbelleğin *bölümüne* (partition, aşağıda) göre anahtarlanır, `"public"` olanlar ise daha geniş paylaşıma katılmayı seçebilir. Ayrıca **bildirimler TTL'yi yener**, ama yalnızca tam olarak adlandırdıkları girdiler için: bir `list_changed` bildirimi eşleşen önbellekteki listeyi çıkarır, `resources/updated` ise tam olarak kendi URI'si altında saklanan önbellekteki okumayı çıkarır; ne kadar taze olurlarsa olsunlar. 2026-07-28 bağlantısında bu bildirimler `client.listen(...)` ile açtığınız bir `subscriptions/listen` akışı üzerinden gelir ve çıkarma, izleyiciniz olayı görmeden önce tamamlanır; bunun sayfası **[Abonelikler](subscriptions.md)**.
|
||||
|
||||
`resources/updated` ile ilgili bir uyarı: çıkarma yalnızca tam URI eşleşmesiyle olur. Depo sözleşmesinde listeleme ya da tarama işlemi yoktur (referans TypeScript gerçekleştirimiyle aynı); bu yüzden bir *alt* kaynak URI'si taşıyan bir bildirim, üst kaynağının önbellekteki okumasını çıkarmaz. Sunucunuz alt kaynakları bu şekilde bildiriyorsa üst kaynağı `cache_mode="refresh"` ile yeniden getirin.
|
||||
|
||||
### Yapılandırma: `CacheConfig` {#configuring-it-cacheconfig}
|
||||
|
||||
```python
|
||||
from mcp.client import CacheConfig
|
||||
|
||||
client = Client("https://api.example.com/mcp", cache=CacheConfig(default_ttl_ms=5_000))
|
||||
```
|
||||
|
||||
* `store`: girdilerin yaşadığı yer. Varsayılan, istemci başına yeni bir bellek içi depodur; bir önbelleği istemciler ya da süreçler arasında paylaşmak için kendi `ResponseCacheStore` gerçekleştiriminizi (örneğin Redis destekli) geçirin. Sözleşme türleri (`ResponseCacheStore`, `CacheKey`, `CacheEntry` ve varsayılan `InMemoryResponseCacheStore`) `mcp.client`'tan içe aktarılabilir. Bir arama depoya art arda en fazla iki `get` gönderebilir (önce private kol, sonra public olan); uzak bir deponun gecikme beklentilerini buna göre belirleyin. Özel bir depo açık bir `partition` **gerektirir**.
|
||||
* `partition`: paylaşımlı bir depo içinde bir principal'ın `"private"` girdilerinin başka birine sunulmasını engelleyen yetkilendirme bağlamı etiketi.
|
||||
* `target_id`: özel aktarımlar ve süreç içi sunucular için açık sunucu kimliği (aşağıda).
|
||||
* `default_ttl_ms`: `ttlMs` ipucu taşımayan sonuçlara uygulanan TTL. Varsayılan `0`, ipucu taşımayan sonuçları önbelleğe almadan bırakır.
|
||||
* `share_public`: sunucunun `"public"` olarak bildirdiği girdileri bölümler arasında sunar (aşağıda). Varsayılan olarak kapalıdır.
|
||||
* `clock`: epoch saniyesi cinsinden duvar saati kaynağı. Yukarıdaki örnekte olduğu gibi bir tane enjekte edin; böylece süre dolumu testlerinde uyumaya gerek kalmaz.
|
||||
|
||||
!!! warning "Partition = doğrulanmış principal"
|
||||
`partition`'ı, doğrulanmış bir token'ın subject'i gibi **doğrulanmış bir kimlik bilgisinden** türetin. Onu asla istekle gelen veriden türetmeyin, sunucu URL'sinden de asla (sunucu kimliği ayrı bir anahtar eksenidir). SDK kendi kimlik doğrulaması olmayan bir kütüphanedir: güven çıpası `CacheConfig`'i kim oluşturuyorsa odur; bu da kiracı değil, dağıtımdır. Çok kiracılı bir ağ geçidi, kimliği doğrulanmış her principal için bir `CacheConfig` üretir.
|
||||
|
||||
Bölüm ayrıca `Client`'ın ömrü boyunca sabittir. Bağlantının yetkilendirme bağlamı oturum ortasında değişirse (örneğin farklı bir principal olarak yeniden kimlik doğrulama), önbellek bunu takip etmez; yeni principal için yeni bir `Client` oluşturun.
|
||||
|
||||
Önbellek anahtarları ayrıca **sunucunun kimliğini** de taşır: bağlandığınız URL dizesi, varsa `user:pass@` kullanıcı bilgisi çıkarılmış ve bunun dışında bayt bayt aynı hâliyle. Büyük/küçük harf katlama yok, sorgu yeniden sıralama yok, sondaki eğik çizgi temizliği yok. Az normalleştirmek yalnızca paylaşımdan ödün verir, aşırı normalleştirmek ise iki kiracıyı birleştirebilir (`?tenant=a` ve `?tenant=b`); bu yüzden yüzeysel olarak farklı URL'ler girdi paylaşmaz, o kadar. URL olmadığında (süreç içi bir sunucu ya da bir `Transport` örneği) istemci bunun yerine örnek başına rastgele bir kimlik alır; sunucuya ad vermek için `CacheConfig.target_id`'yi ayarlayın (özel bir depoyla bu zorunludur ve oluşturma bunu söyler). Kimlik, anahtar malzemesine girmeden önce sha256 ile özetlenir; dolayısıyla sorgu dizesinde sır taşıyan bir URL depo anahtarlarında asla görünmez. Özet öncesi hâlini siz de loglamayın.
|
||||
|
||||
!!! warning "`share_public` sunucuya tüm filo genelinde güvenir"
|
||||
Varsayılan olarak `"public"` girdiler bile kendi bölümlerinde kalır. `share_public=True`, sunucunun `cacheScope: "public"` olarak işaretlediği girdileri depoyu kullanan **her** bölüme sunar; sunucunun sınıflandırmasına hepsi adına güvenir. Kiracıya özgü veriye (hata ya da kötü niyet sonucu) `"public"` damgası vuran bir sunucu, o zaman bir kiracının yanıtını diğerlerine sızdırır. Bayrak bilerek yalnızca oluşturucu düzeyindedir: çağrı başına `cache_mode` önbelleklemeyi daraltabilir, ama çağrı başına hiçbir şey paylaşımı genişletemez.
|
||||
|
||||
### Önbelleğin asla yapmadıkları {#what-the-cache-never-does}
|
||||
|
||||
* **Oturum katmanındaki çağrılar onu atlar.** `client.session.list_tools()` ve benzerleri her zaman turu atar; önbellek `Client` fiillerinde yaşar.
|
||||
* **`server/discover` bunun dışında kalır.** Keşif sonucu bir kez, bağlanırken teslim edilir ve `ttlMs` taşısa bile asla yanıt önbelleğine girmez. Yeniden bağlanma yoklamasını atlamak için birini kendiniz kalıcı olarak saklarsanız ([`prior_discover`](../protocol-versions.md#reconnecting-with-prior_discover)), tazeliğinin takibi size kalır: `DiscoverResult` tam da bu amaçla, zaten ayrıştırılmış `ttl_ms` ve `cache_scope` alanlarını taşır.
|
||||
* **Devam sayfaları asla önbelleğe alınmaz.** Yalnızca imleçsiz çağrılar katılır. Süresi dolmuş bir imleç nedeniyle reddedilen bir devam sayfası ise önbellekteki listeyi *çıkarır*, çünkü liste onun altında değişmiştir.
|
||||
* **Çok turlu (multi-round-trip) okumalar asla önbelleğe alınmaz.** `input_responses`/`request_state` ile tohumlanan ya da girdi turları üzerinden çözümlenen bir `read_resource` asla önbelleğe girmez (belirtimde bir MUST).
|
||||
* **Bildirimle çıkarma için bildirim gerekir.** Çıkarma ancak aktarımın teslimi kadar iyidir ve modern süreç içi yol (varsayılan `mode="auto"` ile `Client(server)`) bugün bağımsız bildirimleri teslim etmez.
|
||||
* **Çıkarma anlık değil, er geç gerçekleşir.** Ağ yolundan gelen bildirimler ayrı başlatılan görevlerden dağıtılır; bu yüzden bir bildirimin gelişiyle yarışan bir çağrıya çıkarma öncesi girdi bir kez daha sunulabilir. Pencere dağıtım gecikmesiyle sınırlıdır ve çıkarma yine de gerçekleşir.
|
||||
* **stale-if-error yok.** Süresi dolmuş bir girdi, yeniden getirme başarısız oldu diye asla sunulmaz; hata yayılır.
|
||||
* **Erken yeniden getirme yok.** Saklanan bir girdi TTL'si dolana kadar sunulur ve ondan sonraki ilk çağrı turun bedelini öder; arka planda hiçbir şey yenilenmez.
|
||||
* **Birleştirme yok.** Eşzamanlı iki özdeş çağrı iki getirme demektir.
|
||||
* **24 saati aşan TTL yok.** Daha büyük bir `ttlMs`, ister sunucudan gelsin ister yapılandırılmış olsun, saklanırken kırpılır (`mcp.client.caching.MAX_TTL_MS`); bu da ipucu ne kadar cömert olursa olsun herhangi bir girdinin ne kadar süre sunulabileceğini sınırlar.
|
||||
* **Paylaşımlı bir depoda** istemciler birbirleriyle yarışır. Her istemci, bir çıkarma yoldaki getirmeyi geçtiğinde kendi yazmasını düşürür; ancak *komşu kiracı* bir istemci, hiç görmediği bir çıkarmanın kaldırdığı bir girdiyi yine de geri yazabilir. Bu yarış takibinin kendisi de sınırlıdır: izlenen 4096 anahtarı geçince önce en eski anahtarın koruması düşürülür. Her iki pencere de kabul edilmiştir ve yukarıdaki TTL üst sınırıyla kapatılır.
|
||||
* **Protokol nesilleri arasında sunum yok.** Girdiler anlaşılan protokol sürümüyle kapsamlanır: paylaşımlı kalıcı bir depoda bir oturum, farklı bir anlaşılan sürüm altında yazılmış bir girdiyi asla sunmaz (aynı liste nesle göre gerçekten farklıdır, çünkü SDK eski oturumlar için 2026 alanlarını çıkarır). Çıkarma da aynı şekilde yalnızca geçerli neslin girdilerine dokunur; başka bir neslin girdileri TTL ile kendiliğinden eskiyip gider.
|
||||
|
||||
### İpuçlarını kendiniz okuma {#reading-the-hints-yourself}
|
||||
|
||||
Yerleşik önbelleğin üzerine (ya da yerine) kendi takibinizi katmanlamak isterseniz, ipuçları her önbelleğe alınabilir sonuçta ayrıca düz alanlar olarak da bulunur (`result.ttl_ms` ve `result.cache_scope`, zaten ayrıştırılmış).
|
||||
|
||||
**Daha eski bir sunucuya** karşı (2026 öncesi protokol) alanlar iletilen veride yoktur, o kadar; modeller de ihtiyatlı varsayılanlarını gösterir: `ttl_ms == 0` ve `cache_scope == "private"`, bayat ve paylaşılmamış; hiçbir şey beyan etmemiş bir sunucu için doğru varsayım. Önbellek eski nesil bir oturumu aynı şekilde ele alır: orada ipuçlarına asla bakılmaz (iletilen veride hangi anahtarlar görünürse görünsün), yalnızca `default_ttl_ms` uygulanır ve onun varsayılanı `0` hiçbir şeyi önbelleğe almaz; böylece 2026 öncesi bir bağlantı tam olarak önbellek var olmadan önceki gibi davranır. "Sunucu 0 dedi" ile "sunucu hiçbir şey demedi" arasında ayrım yapmanız gerekiyorsa `"ttl_ms" in result.model_fields_set` ifadesini kontrol edin: yalnızca alan gerçekten geldiğinde ayarlıdır.
|
||||
|
||||
## Daha eski istemciler {#older-clients}
|
||||
|
||||
2026 öncesi protokol sürümlerindeki istemciler iki alanı da asla görmez; SDK bu bağlantılar için onları serileştirme sırasında çıkarır. İpuçlarınızı bir kez yapılandırın; sürüme özgü yazılacak hiçbir şey yok.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* Altı yöntem `ttlMs`/`cacheScope` taşır; SDK bunları varsayılan olarak `0`/`"private"` yapar: bayat ve paylaşılmamış, her zaman güvenli.
|
||||
* Oluşturma sırasında `cache_hints={method: CacheHint(...)}` (hem `MCPServer` hem `Server`) yöntem başına sunucu genelinde değerler ayarlar.
|
||||
* Alanları sonucunda ayarlayan bir işleyici eşlemeyi alan bazında geçersiz kılar.
|
||||
* `"public"`, sonucun her çağıran için aynı olduğuna dair bir sözdür. Erişim denetimi değildir.
|
||||
* `Client` ipuçlarına otomatik olarak uyar: yanıt önbelleği varsayılan olarak açıktır, yeniden getirmek yerine taze girdileri sunar ve ipucu sağlamayan sunucular (ya da oturumlar) için hiçbir şeyi önbelleğe almaz.
|
||||
* Çağrı başına `cache_mode="refresh"` yeniden getirir, `"bypass"` önbelleği atlar; oluşturma sırasında `cache=None` onu tamamen kapatır.
|
||||
155
i18n/tr/pages/client/callbacks.md
Normal file
155
i18n/tr/pages/client/callbacks.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
translation:
|
||||
sections: [adf3c545b5be46b6, 916cd3ab1c03f461, e9be7a8d0eb0a456, 565890a636288ecf, 6af7e49db9129ec3, 06b0238c174186af, 90c6043be435fcb0]
|
||||
tool: 1
|
||||
---
|
||||
# İstemci callback'leri {#client-callbacks}
|
||||
|
||||
MCP'de neredeyse her istek tek yöne gider: istemciden sunucuya.
|
||||
|
||||
Sunucu da **istemciden** bir şeyler isteyebilir: kullanıcıya soru sormasını, kullanıcının modelinden örnekleme yapmasını, kullanıcının çalışma alanı klasörlerini listelemesini. Bu istekleri `Client(...)`'a **callback'ler** (geri çağırma işlevleri) geçirerek yanıtlarsınız.
|
||||
|
||||
## Soru soran bir sunucu {#a-server-that-asks}
|
||||
|
||||
İşte aracı kendi başına tamamlanamayan bir sunucu:
|
||||
|
||||
```python title="server.py" hl_lines="16"
|
||||
--8<-- "docs_src/client_callbacks/tutorial001.py"
|
||||
```
|
||||
|
||||
* `ctx.elicit(...)`, **istemciye** bir `elicitation/create` isteği gönderir ve bekler.
|
||||
* Araç, birisi (form dolduran bir kişi ya da kodunuz) bir `name` sağlayana kadar dönmez.
|
||||
|
||||
Bu, işin sunucu tarafı; ona **[Elicitation](../handlers/elicitation.md)** sayfası bakar. Bu sayfa ise hattın öteki ucu.
|
||||
|
||||
## Elicitation callback'i {#the-elicitation-callback}
|
||||
|
||||
```python title="client.py" hl_lines="6-10 16-17"
|
||||
--8<-- "docs_src/client_callbacks/tutorial002.py"
|
||||
```
|
||||
|
||||
* Bir elicitation (kullanıcıdan bilgi isteme) callback'i `async (context, params) -> ElicitResult` biçimindedir.
|
||||
* `params.message` sorudur. `params.requested_schema`, sunucunun beklediği yanıtın JSON Schema'sıdır. Gerçek bir istemci bundan bir form üretir; buradaki ise otomatik doldurur.
|
||||
* `ElicitResult(action="accept", content={...})` döndürürsünüz; ya da `action="decline"` veya `action="cancel"`. Bunların dışındaki tek seçenek, isteği reddedip çağrının tamamını başarısız kılan `ErrorData(...)`'dır.
|
||||
* `context` bir `ClientRequestContext`'tir: canlı `session`, sunucunun `request_id`'si ve eklediği her türlü `meta`.
|
||||
|
||||
!!! tip
|
||||
`params`, iki elicitation kipinin birleşimidir (union). Burada `params.mode` değeri `"form"`; bir `"url"`
|
||||
isteği ise şema yerine `params.url` taşır. Tek callback ikisini de karşılar; `params.mode` üzerinden dallanın.
|
||||
Kalıbın tamamı **[Elicitation](../handlers/elicitation.md)** sayfasında.
|
||||
|
||||
### Deneyin {#try-it}
|
||||
|
||||
`issue_card`'ı çağırın ve iki ucu da izleyin.
|
||||
|
||||
Callback'iniz sunucunun sorusunu hazır ayrıştırılmış olarak alır:
|
||||
|
||||
```python
|
||||
params.mode # 'form'
|
||||
params.message # 'What name should go on the card?'
|
||||
params.requested_schema # {'properties': {'name': {'title': 'Name', 'type': 'string'}},
|
||||
# 'required': ['name'], 'title': 'CardHolder', 'type': 'object'}
|
||||
```
|
||||
|
||||
Yanıt verir, `ctx.elicit(...)` aracın içinde kaldığı yerden devam eder ve araç tamamlanır:
|
||||
|
||||
```python
|
||||
result.content # [TextContent(type='text', text='Card issued to Ada Lovelace.')]
|
||||
```
|
||||
|
||||
Sizden tek bir `tools/call`, sunucudan geriye tek bir `elicitation/create`, onu yanıtlayan da sizin fonksiyonunuz; hepsi tek bir araç çağrısının içinde.
|
||||
|
||||
!!! info
|
||||
`Client(...)` çağrısındaki `mode="legacy"` gerçekten iş yapıyor. Varsayılan olarak `Client(...)` modern
|
||||
protokol yolunu müzakere eder ve o yolda sunucudan istemciye gelen istekler için bir geri kanal (back-channel)
|
||||
yoktur: `ctx.elicit`, callback'iniz daha çalışmadan başarısız olur. Buna aktarım karar vermez; müzakere edilen
|
||||
protokol karar verir, bellek içinde de bir URL üzerinden de aynı şekilde. İstemcinizin böyle bir isteği
|
||||
yanıtlaması gerektiğinde `mode="legacy"`'yi sabitleyin; bu sayfanın arkasındaki her test bunu yapar.
|
||||
Ayrıntıların tamamı **[Protokol sürümleri](../protocol-versions.md)** sayfasında.
|
||||
|
||||
Bir 2026-07-28 oturumunda callback ölü değildir, yalnızca farklı beslenir: bir araç, `ElicitRequest` taşıyan bir
|
||||
`InputRequiredResult` döndürdüğünde `Client` o girdiyi aynı `elicitation_callback`'e yönlendirir ve çağrıyı
|
||||
sizin adınıza yeniden dener. Bu akış **[Çok turlu istekler](../handlers/multi-round-trip.md)** sayfasında.
|
||||
|
||||
## Callback bir yetenektir {#a-callback-is-a-capability}
|
||||
|
||||
İstemcinizin elicitation isteklerini yanıtlayabildiğini sunucuya hiç söylemediniz. SDK söyledi.
|
||||
|
||||
Bir istemci bağlandığında `capabilities`'ini, yani sunucununkinin aynadaki yansımasını bildirir. O nesneyi siz yazmazsınız. **Callback'i kaydetmek bildirimin ta kendisidir.**
|
||||
|
||||
| geçirdiğiniz | istemcinin bildirdiği |
|
||||
| --- | --- |
|
||||
| `elicitation_callback=` | `"elicitation": {"form": {}, "url": {}}` |
|
||||
| `sampling_callback=` | `"sampling": {}` |
|
||||
| `list_roots_callback=` | `"roots": {"listChanged": true}` |
|
||||
| hiçbiri | `{}` |
|
||||
|
||||
Tek ince ayar örnekleme alt yetenekleridir: örnekleyiciniz `tools` / `tool_choice` parametrelerini işliyorsa `sampling_callback`'in yanında `sampling_capabilities=SamplingCapability(tools=SamplingToolsCapability())` geçirin. Sunucular bunları gönderebilmek için önce `sampling.tools`'un bildirildiğini görmelidir.
|
||||
|
||||
`logging_callback` ve `message_handler` tabloda yok. Onlar bildirimleri işler ve bildirimler yetenek gerektirmez.
|
||||
|
||||
Sunucu bildirimi `ctx.session.check_client_capability(...)` ile geri okur. Bunu yapan bir araç ekleyin:
|
||||
|
||||
```python title="server.py" hl_lines="23-31"
|
||||
--8<-- "docs_src/client_callbacks/tutorial003.py"
|
||||
```
|
||||
|
||||
Yalnızca `elicitation_callback` ile bağlanın ve aracı çağırın:
|
||||
|
||||
```python
|
||||
result.structured_content # {'result': ['elicitation']}
|
||||
```
|
||||
|
||||
Üç callback'i de geçirirseniz `['elicitation', 'sampling', 'roots']` alırsınız. Hiçbirini geçirmezseniz `[]` alırsınız.
|
||||
|
||||
!!! check
|
||||
Şimdi yanlış olanı yapın: `elicitation_callback` **olmadan** bağlanın ve yine de `issue_card`'ı çağırın.
|
||||
|
||||
Sunucunun `elicitation/create` isteği yine istemcinize ulaşır ve SDK onu sizin yerinize yanıtlar; ama bir hatayla,
|
||||
çünkü bunu karşılayabileceğinizi hiç söylemediniz. O hata çağrının tamamını batırır.
|
||||
`call_tool` bir `is_error` sonucu döndürmez; istisna fırlatır:
|
||||
|
||||
```text
|
||||
MCPError: Elicitation not supported
|
||||
```
|
||||
|
||||
Bu bir araç hatası değil, bir protokol hatasıdır (`-32600`, *invalid request*): modelin okuyup yeniden
|
||||
deneyebileceği bir şey yoktur. `client_features`'ın değerli olmasının nedeni de bu: uslu bir sunucu
|
||||
sormadan önce kontrol eder.
|
||||
|
||||
## Kullanım dışı ikili {#the-deprecated-pair}
|
||||
|
||||
`sampling_callback`, `sampling/createMessage`'ı yanıtlar: sunucunun *sizin* modelinizden bir şeyi tamamlamasını istemesi. `list_roots_callback`, `roots/list`'i yanıtlar: sunucunun hangi dizinlerde çalışabileceğini sorması.
|
||||
|
||||
İkisi de çalışır. İkisi de yukarıdaki kurala uyar. Ve ikisi de **2026-07-28 spesifikasyonunun kaldırdığı** RPC'lere hizmet eder: modern bir sunucu istek ortasında istemcinizi geri çağırmaz, isteği araç sonucunun bir parçası olarak size geri verir (**[Çok turlu istekler](../handlers/multi-round-trip.md)**). Callback'lerin kendisi ölü değildir. Bir `InputRequiredResult`, `CreateMessageRequest` veya `ListRootsRequest` taşıdığında `Client`'ın otomatik döngüsü onu burada kaydettiğiniz aynı `sampling_callback` veya `list_roots_callback`'e yönlendirir. Listenin tamamı **[Kullanım dışı özellikler](../deprecated.md)** sayfasında.
|
||||
|
||||
Henüz geçiş yapmamış sunucularla konuşmak için callback'lere hâlâ ihtiyacınız var. İmzalar:
|
||||
|
||||
```python title="client.py"
|
||||
--8<-- "docs_src/client_callbacks/tutorial004.py"
|
||||
```
|
||||
|
||||
* Bir örnekleme (sampling) callback'i `CreateMessageRequestParams`'ın tamamını (`messages`, `model_preferences`, `max_tokens`) alır ve bir `CreateMessageResult` döndürür. Modeli *siz* çalıştırırsınız, nasıl isterseniz öyle; SDK yalnızca isteği taşır.
|
||||
* Bir kök dizinler (roots) callback'i hiç parametre almaz ve bir `ListRootsResult` döndürür.
|
||||
* Her ikisi de reddetmek için bunun yerine `ErrorData(...)` döndürebilir.
|
||||
|
||||
Bunları `Client(...)`'a tıpkı `elicitation_callback` gibi geçirin.
|
||||
|
||||
## Bildirim callback'leri {#the-notification-callbacks}
|
||||
|
||||
İki tane daha. Hiçbiri bir şey bildirmez.
|
||||
|
||||
`logging_callback`, sunucunun gönderdiği `notifications/message`'ı `LoggingMessageNotificationParams` (`level`, `logger`, `data`) olarak alır. Protokol log'lamasının kendisi 2026-07-28 spesifikasyonuyla kullanım dışı bırakıldı (yerine ne yapılacağı **[Log kaydı](../handlers/logging.md)** sayfasında); bu yüzden bu callback, hâlâ bunu yayan sunucular için var. 2026 neslinden bir bağlantıda callback tek başına size hiçbir şey kazandırmaz, çünkü 2026 sunucuları log mesajlarını yalnızca bunu talep eden isteklere gönderir: bu talebi her isteğe damgalamak ve o düzey ile üstünü almak için `Client(...)`'a `log_level="info"` (veya başka bir düzey) geçirin. 2026 öncesi sunucular bunu yok sayar ve `logging/setLevel` davranışlarını sürdürür.
|
||||
|
||||
`message_handler` her şeyi yakalayandır: oturumun yüzeye çıkardığı her sunucu bildirimi ona ulaşır (kendi özel callback'inin yanı sıra), akış tabanlı bir aktarımda aktarım düzeyindeki her `Exception` da öyle. İkisi asla ulaşmaz: `notifications/cancelled` yüzeye çıkarılmak yerine SDK tarafından uygulanır ve canlı bir `listen()` akışının abonelik onayı o akış tarafından tüketilir. Parametreye `IncomingMessage` (`ServerNotification | Exception`, `mcp.client`'tan dışa aktarılır) tür ipucunu verin. Bilmeye değer tek kalıp `if isinstance(message, Exception): raise message`'dır; böylece kopan bir bağlantı sessizce kaybolmak yerine gürültüyle başarısız olur.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* Sunucu istemciye istek gönderebilir. Bunları `Client(...)`'a geçirdiğiniz callback'lerle yanıtlarsınız.
|
||||
* Güncel olan elicitation callback'idir: `async (context, params) -> ElicitResult`, hem form hem URL kipi için tek fonksiyon.
|
||||
* **Callback'i kaydetmek yeteneği bildirmektir.** O olmadan SDK sunucunun isteğini sizin adınıza reddeder ve çağrının tamamı `MCPError` ile başarısız olur.
|
||||
* Sunucu, sormadan önce `ctx.session.check_client_capability(...)` ile öğrenir.
|
||||
* `sampling_callback` ve `list_roots_callback` aynı şekilde çalışır ama kullanım dışı özelliklere hizmet eder; modern sunucular bunun yerine çok turlu istekler (multi-round-trip) kullanır.
|
||||
* `logging_callback` ve `message_handler` bildirimleri alır. Hiçbir şey bildirmezler.
|
||||
|
||||
`Client(...)`'ın ilk argümanı bir aktarım nesnesidir. **[İstemci aktarımları](transports.md)** her türünü ele alır.
|
||||
155
i18n/tr/pages/client/identity-assertion.md
Normal file
155
i18n/tr/pages/client/identity-assertion.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
translation:
|
||||
sections: [a91322c46111d16d, 8e6fd6d6f59bb568, e7828fd2729b2c9d, a03ec26bfc678b65, 1034c653c0bcf1b0]
|
||||
tool: 1
|
||||
---
|
||||
# Kimlik beyanı {#identity-assertion}
|
||||
|
||||
Sıradan bir OAuth sağlayıcısı (**[OAuth istemcileri](oauth-clients.md)**) işe MCP sunucusuna bir soru sorarak başlar: *hangi yetkilendirme sunucusuna güveniyorsun?* Yanıt nereyi gösteriyorsa oraya gider; ardından ya bir kişi oturum açar ya da önceden paylaşılmış bir gizli anahtar onun yerini tutar.
|
||||
|
||||
Bir kurum ise bunların hiçbirinin sunucu başına kararlaştırılmasını istemez. Zaten bir kimlik sağlayıcısı işletir (Okta, Microsoft Entra ID, kendi yazdığınız); kullanıcı ona bu sabah zaten oturum açmıştır ve güvenlik ekibinin kimin neye erişebileceğine karar vermek istediği tek yer orasıdır. **Enterprise-Managed Authorization** uzantısı olan [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990), kararı oraya taşır. IdP kısa ömürlü bir JWT imzalar: bir **Identity Assertion JWT Authorization Grant**, kısaca **ID-JAG**. Bu, *şu kullanıcının*, *şu istemci* aracılığıyla *şu MCP sunucusuna* erişebileceğini söyleyen bir beyandır. İstemci onu sıradan bir erişim token'ıyla takas eder. Tarayıcı yok, onay ekranı yok, dinamik kayıt yok.
|
||||
|
||||
Bu sayfa o takasın iki ucunu da anlatır. MCP sunucusunun kendisi hiç değişmez: hâlâ **[Yetkilendirme](../run/authorization.md)** sayfasındaki kaynak sunucusudur ve önüne hangi token gelirse onu denetler.
|
||||
|
||||
## İki token isteği {#two-token-requests}
|
||||
|
||||
İşin içinde iki farklı otorite var ve bu sayfayı anlamanın büyük kısmı ikisini ayrı adlarla anmaktan geçer. **Kurumsal IdP**, kuruluşunuzun kimlik sağlayıcısıdır: çalışanın kim olduğunu bilir, politikanın bulunduğu yerdir ve ID-JAG'i o düzenler. SDK onunla hiç konuşmaz. **MCP yetkilendirme sunucusu** ise **[Yetkilendirme](../run/authorization.md)** sayfasındaki aynı taraftır: MCP sunucusunun meta verisinde adı geçen issuer, o MCP sunucusunun kabul ettiği token'ları basan şey. Sıradan bir OAuth akışında bu iki rol genellikle tek bir kutudur. Burada iki ayrı kutudur ve grant'in tamamı, ikincisinin birincisine güvenmeyi kabul etmesinden ibarettir.
|
||||
|
||||
İstemci her birine birer token isteği gönderir.
|
||||
|
||||
1. **Kurumsal IdP'ye.** İstemci, kullanıcının oturum açma bilgisini (OpenID Connect ID token'ını) ID-JAG ile takas eder. Bu bir [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token takasıdır, tamamen IdP'nizin API'sidir ve **bu isteği SDK yapmaz**. Siz yaparsınız, tek bir asenkron callback'in içinde. Politika kararı da burada verilir: hayır diyen bir IdP ID-JAG'i hiç düzenlemez ve ortada sunulacak bir şey kalmaz.
|
||||
2. **MCP yetkilendirme sunucusuna.** İstemci ID-JAG'i [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523) `jwt-bearer` grant'i kapsamında sunar (`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, ID-JAG de `assertion` olarak) ve erişim token'ını alır. **SDK'nın yaptığı istek budur** ve bu sayfanın bir yetkilendirme sunucusuna eklediği tek şey de onu kabul etmektir.
|
||||
|
||||
Aşağıdaki her şey ikinci istekle ilgilidir: onu gönderen istemci ve yanıtlayan yetkilendirme sunucusu.
|
||||
|
||||
## İstemci {#the-client}
|
||||
|
||||
**`IdentityAssertionOAuthProvider`**, `mcp.client.auth.extensions.identity_assertion` modülünde bulunur. **[OAuth istemcileri](oauth-clients.md)** sayfasındaki her sağlayıcı gibi o da bir `httpx2.Auth` nesnesidir: bir tane oluşturun, `auth=` parametresine verin, `httpx2.AsyncClient`'ı aktarıma teslim edin.
|
||||
|
||||
```python title="client.py" hl_lines="49-50 53-61"
|
||||
--8<-- "docs_src/identity_assertion/tutorial001.py"
|
||||
```
|
||||
|
||||
Aşağıdan yukarıya okuyun.
|
||||
|
||||
* `main()`, standart OAuth istemcisi `main()`'idir (**[OAuth istemcileri](oauth-clients.md)**), satırı satırına aynı. Mesele de bu: sağlayıcı bir kez var olduktan sonra, akışın devamındaki hiçbir şey token'ı hangi grant'in ürettiğini bilmez.
|
||||
* Sağlayıcı, diğer sağlayıcıların keşfedemeyeceği şeyleri alır: birinin yetkilendirme sunucusuna **önceden kaydettirdiği** bir `client_id` ve `client_secret`, o yetkilendirme sunucusunun `issuer`'ı ve `assertion_provider`, yani istendiğinde taze bir ID-JAG döndüren asenkron bir callback.
|
||||
* `storage` aynı `TokenStorage` protokolüdür. Yalnızca iki token metodu çağrılır; burada dinamik kayıt olmadığından hatırlanacak bir `client_info` da yoktur.
|
||||
|
||||
### Beyan sağlayıcı {#the-assertion-provider}
|
||||
|
||||
Yazdığınız tek kod `fetch_id_jag(audience, resource)` fonksiyonudur. Her token takasında bir kez await edilir; oluşturma sırasında asla, ve ancak yetkilendirme sunucusunun meta verisi alınıp doğrulandıktan *sonra*. Böylece yanlış yapılandırılmış bir issuer hiçbir zaman bir beyan sızdırmaz. İki argümanı, ID-JAG'in basılırken taşıması gereken claim'lerden ikisidir: `audience` yetkilendirme sunucusunun issuer'ıdır (ID-JAG'deki `aud`), `resource` ise MCP sunucusunun kanonik tanımlayıcısıdır (ID-JAG'deki `resource`). Üçüncüsü zaten elinizde: ID-JAG'in `client_id` claim'i, sağlayıcıya verdiğiniz `client_id`'yi göstermelidir; yoksa yetkilendirme sunucusu takası reddeder.
|
||||
|
||||
Onun üstündeki `idp_issue_id_jag` **sizin kodunuz değildir**. Kimlik sağlayıcısının yerini tutar; dosya eksiksiz olsun ve bir ID-JAG'in taşıdığı her claim'i okuyabilesiniz diye beyanı süreç içinde imzalar. Gerçek bir `fetch_id_jag` ise bunun yerine önceki bölümdeki ilk token isteğini yapar: IdP'nize karşı bir [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token takası. Bu takası, [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) belgesinin profil olarak daralttığı Identity Assertion JWT Authorization Grant taslağı tanımlar. Oturum açmış kullanıcının ID token'ı `subject_token` olarak girer, `requested_token_type` ID-JAG'in kendi URN'idir (`urn:ietf:params:oauth:token-type:id-jag`), `audience` ve `resource` olduğu gibi aktarılır ve yanıt ID-JAG'i taşır. IdP'nizin belgelerinde aramanız gereken şey, bu adlarla anılan bu takastır.
|
||||
|
||||
!!! tip
|
||||
Her takas için taze bir ID-JAG istenir ve amaç da budur: tek kullanımlık, ömrü dakikalarla
|
||||
ölçülen bir grant'tir ve bu sayfadaki yetkilendirme sunucusu aynısını ikinci kez kabul etmez.
|
||||
Onu önbelleğe almayın. Yeniden kullanılan şey, size kazandırdığı erişim token'ıdır.
|
||||
|
||||
### Yapılandırma olarak issuer {#the-issuer-is-configuration}
|
||||
|
||||
Tersine çevirme işte burada. `OAuthClientProvider`, kaynak sunucusuna hangi yetkilendirme sunucusunu kullanacağını sorar ve yanıt nereyi gösteriyorsa oraya gider. Bu sağlayıcı bunu reddeder: `issuer` zorunludur, [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) meta verisi o issuer'ın kendi well-known yolundan alınır, token endpoint'i o issuer'ın origin'inde olmalıdır ve kaynak sunucusuna hiçbir şey sorulmaz.
|
||||
|
||||
Uzantı bunu şart koşmaz; bu, bilerek yapılmış daha katı bir tercihtir. Bu istemci çalınmaya değer iki şey taşır: önceden kaydedilmiş bir gizli anahtar ve audience'a bağlı bir beyan. Ele geçirilmiş bir MCP sunucusunun kendisini saldırganın yetkilendirme sunucusuna yönlendirmesine izin veren bir istemci, ikisini de oraya gönderirdi. Oluşturma sırasında issuer'ı sabitlemek bu konuşmayı ortadan kaldırır.
|
||||
|
||||
!!! warning
|
||||
Yapılandırılan `issuer`, meta veri belgesinin `issuer` alanıyla RFC 8414 §3.3'teki basit dize
|
||||
karşılaştırmasıyla karşılaştırılır: karakter karakter, sondaki eğik çizgi dahil, normalleştirme
|
||||
olmadan. Tahmin etmeyin. Yetkilendirme sunucunuzdan `/.well-known/oauth-authorization-server`
|
||||
belgesini alın ve döndürdüğü `issuer` değerini kopyalayın. Bu sayfadaki yetkilendirme sunucusu
|
||||
için bu değer, eğik çizgisiyle birlikte `https://auth.example.com/` adresidir; çünkü issuer'ı
|
||||
bir pydantic URL nesnesinden oluşturulmuştur. Bir uyuşmazlık, tek bir kimlik bilgisi ya da
|
||||
beyan gönderilmeden akışı `OAuthFlowError: Authorization server metadata issuer
|
||||
mismatch` hatasında durdurur.
|
||||
|
||||
### Gizli istemci {#a-confidential-client}
|
||||
|
||||
`client_secret` zorunludur; yapıcı onsuz `ValueError` fırlatır. [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) belgesinin dayandığı IETF profili bu grant'i gizli istemcilere ayırır, SEP-990 istemcinin kimliğini doğrulamasını şart koşar ve bu SDK, paylaşılan bir gizli anahtarda ısrar ederek ikisini de uygular. `token_endpoint_auth_method`, anahtarın nereden gideceğini seçer: `client_secret_post` (varsayılan, form gövdesinde) veya `client_secret_basic` (bir HTTP Basic başlığı). Profil `private_key_jwt` yöntemine de izin verir; bu sağlayıcı onu desteklemez.
|
||||
|
||||
!!! tip
|
||||
`client_secret`'ı ortam değişkenlerinden veya bir gizli anahtar yöneticisinden okuyun, asla
|
||||
kaynak kod deposundan değil.
|
||||
|
||||
### Sağlayıcının sizin için yaptıkları {#what-the-provider-does-for-you}
|
||||
|
||||
İlk istek kimlik doğrulaması olmadan gider ve sunucunun `401` yanıtı akışı başlatır.
|
||||
|
||||
1. **Keşif.** Yetkilendirme sunucusu meta verisini yapılandırılan issuer'ın [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) well-known yolundan alır, belgenin `issuer` alanının eşleştiğini denetler ve token endpoint'inin issuer'ın origin'inde olduğunu denetler.
|
||||
2. **Beyan.** `assertion_provider`'ınızı await eder.
|
||||
3. **Takas.** `jwt-bearer` grant'ini token endpoint'ine POST eder, `OAuthToken`'ı saklar ve özgün isteğinizi `Authorization: Bearer ...` ile yeniden gönderir.
|
||||
|
||||
`WWW-Authenticate` başlığında `insufficient_scope` geçen bir `403`, 2. ve 3. adımları sizin `scope`'unuz ile yanıtın istediği kapsamın birleşimiyle yeniden çalıştırır. (`scope` yalnızca bir istektir; bu sayfadaki yetkilendirme sunucusu ID-JAG ne diyorsa onu verir, başka bir şey değil.) Bunun hiçbir yerinde yenileme token'ı yoktur: erişim token'ının süresi dolduğunda bir sonraki `401` taze bir ID-JAG bastırır ve takas yeniden yapılır; IdP'nin elinde tuttuğu kaldıraç işte *budur*. Hatalar, **[OAuth istemcileri](oauth-clients.md)** sayfasının geri kalanındaki aynı iki istisnadır: keşif ve doğrulama için `OAuthFlowError`, token endpoint'i hayır dediğinde onun alt sınıfı `OAuthTokenError`.
|
||||
|
||||
## Yetkilendirme sunucusu {#the-authorization-server}
|
||||
|
||||
Çoğu zaman burada durursunuz. MCP yetkilendirme sunucusu başkasının ürünüdür, ID-JAG kabul etmek o ürünün açılacak bir yapılandırmasıdır ve SDK'nın [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) içindeki payı yukarıdaki istemcidir.
|
||||
|
||||
SDK yetkilendirme sunucusunun kendisi de *olabilir*: `create_auth_routes`, yetkilendirme sunucusunun route'larını herhangi bir Starlette uygulamasının bağlayabileceği bir liste olarak döndürür; depodaki `examples/servers/simple-auth/` da bir tanesini böyle çalıştırır. SEP-990 bu yüzeye bir bayrak ve bir metot ekler:
|
||||
|
||||
```python title="auth_server.py" hl_lines="48-50 105-107"
|
||||
--8<-- "docs_src/identity_assertion/tutorial002.py"
|
||||
```
|
||||
|
||||
* `identity_assertion_enabled=True` her şeyin kapısıdır. Kapalıyken (varsayılan budur) `/token`, hook'u uygulamış olsanız bile bu grant'e `unsupported_grant_type` ile yanıt verir ve meta veri ondan söz etmez. Açıkken meta veri `jwt-bearer` grant türünü kazanır ve uzantının desteği duyurmak için kullandığı alan olan `authorization_grant_profiles_supported` içinde `urn:ietf:params:oauth:grant-profile:id-jag` değerini listeler. (Bu SDK'nın istemcisi onu hiç okumaz: tek bir issuer için hazırlanmıştır ve doğrudan sorar.)
|
||||
* **`exchange_identity_assertion`** hook'un kendisidir. O çalışmadan önce SDK istemcinin kimliğini doğrulamış, açık (public) istemcileri reddetmiş ve kaydında bu grant'in listelenmediği istemcileri reddetmiştir. Size bir `IdentityAssertionParams` gelir (ham `assertion`, istenen `scopes` ve `resource`) ve düz bir `OAuthToken` döndürürsünüz.
|
||||
* Dinamik istemci kaydı bu grant'i koşulsuz reddeder; bu yüzden buradaki `get_client` elle hazırlanmış bir istemci sunar. Bir ID-JAG istemcisi kendi kendini kaydederek var olamaz.
|
||||
* Sınıfın yarısı retlerden oluşur. `OAuthAuthorizationServerProvider` yetkilendirme sunucusunun *tamamıdır*, bu yüzden yetkilendirme kodu akışını da ister; kullanıcılara oturum da açtıran bir sunucu onları gerçekten uygular, bunun ise tam olarak tek bir kapısı var.
|
||||
|
||||
!!! warning
|
||||
SDK beyanın kodunu hiçbir zaman çözmez: hangi IdP'ye güvendiğini ve o IdP'nin hangi
|
||||
anahtarları yayımladığını yalnızca sizin dağıtımınız bilir; bu yüzden
|
||||
`exchange_identity_assertion` içindeki her şey yük taşır. İmzayı IdP'nin yayımladığı
|
||||
anahtarlara karşı (JWKS'i; buradaki paylaşılan gizli anahtar demoya aittir), `iss` ve `exp`
|
||||
değerlerini de [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523) §3 uyarınca doğrulayın. JWT başlığındaki `typ` değerinin
|
||||
`oauth-id-jag+jwt` olmasını şart koşun; bu, profilin başka bir JWT'nin grant olarak yeniden
|
||||
oynatılmasına karşı koyduğu korumadır. `aud` değerinin kendi issuer'ınız olmasını şart koşun.
|
||||
ID-JAG'in `client_id` claim'inin işleyicinin kimliğini doğruladığı istemciye eşit olmasını,
|
||||
`resource` claim'inin de gerçekten sunduğunuz bir kaynağı göstermesini şart koşun. Beyan
|
||||
yalnızca bir kez kabul edilsin diye `jti` değerini beyanın `exp` süresine kadar takip edin.
|
||||
Verilen kapsamları ve her şeyden önce düzenlenen token'ın `resource` değerini istekten değil,
|
||||
doğrulanmış ID-JAG'den alın: `params.resource` istemci ne yazdıysa odur. İşleme kurallarının
|
||||
tamamı [Enterprise-Managed Authorization spesifikasyonunda](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization) yer alır.
|
||||
|
||||
Kötü bir beyanı `TokenError("invalid_grant", ...)` ile reddedin. Bu akıştaki diğer hata kodu `invalid_target`'tır: sunmadığınız bir kaynağı gösteren bir ID-JAG onunla reddedilir; bu sunucunun başkasının kaynağı için token basmasını engelleyen de budur. Verilen kapsamlar ise ID-JAG'in `scope` claim'inden gelir (bu claim'i olmayan bir beyan da reddedilir); sizinki bunun yerine kullanıcının gruplarını eşleyebilir.
|
||||
|
||||
Döndürülen `OAuthToken`'ın ne taşımadığına da dikkat edin: bir yenileme token'ı. IdP, bir sonraki ID-JAG'i düzenleyip düzenlemeyeceğine karar vererek bu kullanıcının erişimi ne kadar süre koruyacağına karar verir. Burada basılacak bir yenileme token'ı o kararı sessizce geri teslim ederdi.
|
||||
|
||||
!!! info
|
||||
Yetkilendirme sunucusunu hâlâ `auth_server_provider=` ile içine gömen bir sunucu, aynı koda
|
||||
`AuthSettings(identity_assertion_enabled=True)` üzerinden ulaşır. Yeni sunucuların neden oradan
|
||||
başlamaması gerektiğini **[Yetkilendirme](../run/authorization.md)** sayfası açıklar.
|
||||
|
||||
!!! check
|
||||
Bu sayfadaki iki dosyayı birbirine bağlayın; grant'in tamamı tek bir `POST /token` olur:
|
||||
|
||||
```text
|
||||
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
|
||||
assertion=eyJhbGciOiJIUzI1NiIsInR5cCI6Im9hdXRoLWlkLWphZytqd3QifQ...
|
||||
client_id=finance-agent
|
||||
resource=http://localhost:8001/mcp
|
||||
scope=notes:read
|
||||
client_secret=finance-agent-secret
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
{"access_token": "mcp_...", "token_type": "Bearer", "expires_in": 300, "scope": "notes:read"}
|
||||
```
|
||||
|
||||
`/authorize` yok, `/register` yok, korumalı kaynak meta verisi isteği yok. Ağ üzerindeki tek
|
||||
istekler `401`'i çeken istek, well-known isteği, bu takas ve ardından bearer eklenmiş sıradan
|
||||
MCP trafiğidir. Doğrulayıcınızın ID-JAG'den okuduğu `sub` da bir aracın içinde
|
||||
`get_access_token().subject`'in bildirdiği değerin ta kendisidir.
|
||||
|
||||
### Deneyin {#try-it}
|
||||
|
||||
SDK deposundaki `examples/stories/identity_assertion/`, bu sayfanın gerçekten çalışan hâlidir: aynı `exchange_identity_assertion` doğrulayıcısı, onun token'larıyla korunan bir MCP sunucusu, yerine geçen bir IdP ve istemci; hepsi kendi kendini denetleyen tek bir programda. `uv run python -m stories.identity_assertion.client --http` takasın tamamını çalıştırır ve IdP'nin adını verdiği kullanıcının, aracın gördüğü kullanıcı olduğunu doğrular.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990), bir istemcinin hangi MCP sunucularına erişebileceğine son kullanıcının değil, kurumsal kimlik sağlayıcısının karar vermesini sağlar. IdP o kararı imzalayıp bir **ID-JAG** içine koyar.
|
||||
* ID-JAG'i elde etmek *IdP'nize* karşı yapılan bir [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token takasıdır ve SDK bunu yapmaz. Onu MCP yetkilendirme sunucusuna sunmak [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523) `jwt-bearer` grant'idir ve SDK bunun iki tarafını da üstlenir.
|
||||
* `IdentityAssertionOAuthProvider` bir başka `httpx2.Auth` nesnesidir: önceden kaydedilmiş gizli bir istemci, sabitlenmiş bir `issuer` ve tek bir `assertion_provider(audience, resource)` callback'i. Tarayıcı yok, kayıt yok, yenileme token'ı yok.
|
||||
* Yetkilendirme sunucusu hiçbir zaman kaynak sunucusundan keşfedilmez. `issuer`'ı, meta veri belgesinin sunduğu dizenin tıpatıp aynısı olarak yapılandırın; karşılaştırma karakter karakter yapılır.
|
||||
* Sunucu tarafında `identity_assertion_enabled=True` artı `exchange_identity_assertion`. SDK istemcinin kimliğini doğrular ve grant'in kapısını tutar; ID-JAG'i doğrulamak tamamen size aittir ve düzenlenen token isteğin değil, ID-JAG'in `resource` değerine bağlanır.
|
||||
|
||||
Bu sayfanın hiç dokunmadığı tek taraf MCP sunucusudur. Az önce bastığınız token'la ne yapıyorsa, onu **[Yetkilendirme](../run/authorization.md)** sayfasında zaten yapıyordu.
|
||||
217
i18n/tr/pages/client/index.md
Normal file
217
i18n/tr/pages/client/index.md
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ebef1e7a0df854f4, a4c687d3d627d516, 8e79141fc2985342, b345dd05b9c3c7ab, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||||
tool: 1
|
||||
---
|
||||
# İstemci {#the-client}
|
||||
|
||||
**`Client`**, bir Python programının bir MCP sunucusuyla konuşmasını sağlayan nesnedir.
|
||||
|
||||
Tek bir yaşam döngüsü olan tek bir nesnedir: oluşturun, `async with` bloğuna girin, yöntemleri çağırın. Her protokol fiili (araçları listeleme, birini çağırma, bir kaynağı okuma, bir prompt'u oluşturma) bu nesne üzerinde, türü belirli bir sonuç döndüren bir `async` yöntemdir.
|
||||
|
||||
## İlk istemciniz {#your-first-client}
|
||||
|
||||
```python title="client.py" hl_lines="14-18"
|
||||
--8<-- "docs_src/client/tutorial001.py"
|
||||
```
|
||||
|
||||
Üstteki sunucu yalnızca bağlanacak bir şeyiniz olsun diye orada. İstemci, vurgulanan beş satırdan ibaret.
|
||||
|
||||
* `Client(mcp)` çağrısına **sunucu nesnesinin kendisi** verilir. Bu, bellek içi aktarımdır: alt süreç yok, port yok, HTTP yok. Bu sayfadaki her örnek ve yazdığınız her test böyle bağlanır.
|
||||
* `async with` **yaşam döngüsüdür**. Bloğa girdiğinizde bağlantı kurulur ve anlaşma yapılır; çıktığınızda bağlantı kesilir. `connect()` / `close()` çifti yoktur ve blok bittikten sonra bir `Client` yeniden kullanılamaz.
|
||||
* Bloğun içinde bağlantı bilgileri düz özellikler olarak zaten hazırdır.
|
||||
|
||||
### `Client`'a geçirebilecekleriniz {#what-you-can-pass-to-client}
|
||||
|
||||
`Client` tek bir konumsal argüman alır ve aktarımı onun türünden çözümler:
|
||||
|
||||
* Bir `MCPServer` (veya düşük seviyeli `Server`) örneği: **süreç içinde** bağlanır.
|
||||
* Bir URL dizesi (`Client("http://localhost:8000/mcp")`): Streamable HTTP, yani üretim yolu.
|
||||
* Bir **aktarım**: `async with ... as (read, write)` ile kullanabileceğiniz herhangi bir şey; örneğin bir alt süreci saran `stdio_client(...)`.
|
||||
|
||||
Bu sayfadaki geri kalan her şey üçünde de aynıdır. Başlıklar, alt süreçler, zaman aşımları ve `Transport` protokolünün kendi sayfası var: **[İstemci aktarımları](transports.md)**.
|
||||
|
||||
### Bağlı bir istemcide bulunanlar {#whats-on-a-connected-client}
|
||||
|
||||
Bloğa girdiğiniz anda doldurulan dört salt okunur özellik:
|
||||
|
||||
* `client.server_info`: sunucunun kimliği; kimlik bildirmeyen 2026 neslinden bir sunucu için `None` (python-sdk sunucuları varsayılan olarak bildirir). Burada `server_info.name` `"Bookshop"`, `server_info.version` ise sunucu ne bildiriyorsa odur.
|
||||
* `client.server_capabilities`: sunucunun neler yapabildiği (`tools`, `resources`, `prompts`, `completions`, ...). Sunucuda olmayan bir yetenek `None` olur.
|
||||
* `client.protocol_version`: iki tarafın üzerinde anlaştığı protokol sürümü. Burada `"2026-07-28"`.
|
||||
* `client.instructions`: sunucunun `instructions=` dizesi; sunucu bir tane ayarlamadıysa `None`.
|
||||
|
||||
Hiç protokol sürümü seçmediniz. Varsayılan olarak `Client` sunucuyu yoklar ve eski sunucularda klasik el sıkışmaya geri döner; böylece tek bir istemci her nesilden sunucuyla çalışır. Bunu denetlemeniz gerektiğinde ayrıntıların tamamı **[Protokol sürümleri](../protocol-versions.md)** sayfasında.
|
||||
|
||||
!!! tip
|
||||
`client.session`, alttaki `ClientSession`'dır; düşük seviyeli kaçış kapısı.
|
||||
Bu sayfadaki hiçbir şey için ona ihtiyacınız olmaz.
|
||||
|
||||
## Araçları listeleme {#listing-tools}
|
||||
|
||||
```python title="client.py" hl_lines="15-20"
|
||||
--8<-- "docs_src/client/tutorial002.py"
|
||||
```
|
||||
|
||||
`list_tools()` bir `ListToolsResult` döndürür; araçlar `.tools` içindedir. Her biri, bir host'un modele vereceği eksiksiz tanımdır:
|
||||
|
||||
```python
|
||||
tool.name # 'search_books'
|
||||
tool.title # 'Search the catalog'
|
||||
tool.description # 'Search the catalog by title or author.'
|
||||
```
|
||||
|
||||
`tool.input_schema` ise sunucunun fonksiyonun tür ipuçlarından türettiği JSON Schema'dır:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"title": "Query", "type": "string"},
|
||||
"limit": {"default": 10, "title": "Limit", "type": "integer"}
|
||||
},
|
||||
"required": ["query"],
|
||||
"title": "search_booksArguments"
|
||||
}
|
||||
```
|
||||
|
||||
Bu şema, bir arayüzün argüman formu oluşturması için gereken her şeydir; bir modelin geçerli argümanlar üretmesi için gereken her şey de odur.
|
||||
|
||||
!!! tip
|
||||
`title` isteğe bağlıdır; bu yüzden araçları bir insana gösteren arayüzün seçim yapması gerekir: varsa `title`,
|
||||
yoksa `name`. `from mcp.shared.metadata_utils import get_display_name` tam olarak bunu yapar;
|
||||
araçlar, kaynaklar, kaynak şablonları ve prompt'lar için.
|
||||
|
||||
## Bir aracı çağırma {#calling-a-tool}
|
||||
|
||||
`call_tool(name, arguments)` aracı çalıştırır ve size bir `CallToolResult` geri verir.
|
||||
|
||||
```python title="client.py" hl_lines="26-33"
|
||||
--8<-- "docs_src/client/tutorial003.py"
|
||||
```
|
||||
|
||||
Sunucunun `lookup_book` aracı bir Pydantic `Book` döndürür. İstemcinin gördüğü şudur:
|
||||
|
||||
```python
|
||||
result.content # [TextContent(type='text', text='{\n "title": "Dune",\n "author": "Frank Herbert",\n "year": 1965\n}')]
|
||||
result.structured_content # {'title': 'Dune', 'author': 'Frank Herbert', 'year': 1965}
|
||||
result.is_error # False
|
||||
```
|
||||
|
||||
Tek dönüş değeri, okunacak üç şey. Her birinin tüketicisi farklı.
|
||||
|
||||
### `content`: modelin okuduğu {#content-what-the-model-reads}
|
||||
|
||||
`content`, **içerik bloklarından** oluşan bir `list`'tir ve bir içerik bloğu bir birleşim (union) türüdür: `TextContent`, `ImageContent`, `AudioContent`, `ResourceLink` veya `EmbeddedResource`. Bir araç farklı türlerden birkaç tane döndürebilir.
|
||||
|
||||
`main`'in `block.text`'e dokunmadan önce `isinstance(block, TextContent)` ile türü daraltmasının nedeni budur. `isinstance` dışında hiç `.text` olmadığına dikkat edin: tür denetleyicisi buna izin vermez, çünkü `ImageContent`'te `.text` değil `.data` vardır. Birleşim türü, bir aracın size ne gönderebileceği konusunda dürüsttür; kodunuz da öyle olmalı.
|
||||
|
||||
### `structured_content`: uygulamanızın okuduğu {#structured_content-what-your-application-reads}
|
||||
|
||||
`structured_content`, aracın JSON olarak dönüş değeridir ve aracın bildirdiği `output_schema` ile eşleşir. Dize ayrıştırma yok, tahmin yürütme yok.
|
||||
|
||||
İkisi de varsa aynı şeyi bilerek iki kez söylerler: `content` model için, `structured_content` kod içindir. Yapılandırılmış yarının nereden geldiği ve nasıl denetleneceği **[Yapılandırılmış çıktı](../servers/structured-output.md)** sayfasında.
|
||||
|
||||
### `is_error`: aracın başarısız olup olmadığı {#is_error-whether-the-tool-failed}
|
||||
|
||||
İstisna fırlatan bir araç, istemcinizde istisna **fırlatmaz**. `is_error=True` taşıyan sıradan bir sonuç olarak geri döner.
|
||||
|
||||
!!! check
|
||||
`lookup_book`'tan `"Solaris"`'i isteyin (katalogda olmayan bir başlık); fonksiyon
|
||||
`ValueError` fırlatır. Çağrı yine de normal biçimde döner:
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
result.content # [TextContent(type='text', text="Error executing tool lookup_book: No book titled 'Solaris' in the catalog.")]
|
||||
result.structured_content # None
|
||||
```
|
||||
|
||||
İstisnanın mesajı `content`'e düştü; **model** onu orada okuyup yeniden deneyebilir. Bu
|
||||
kasıtlıdır: bir araç hatası çökme değil, konuşmanın bir parçasıdır. `structured_content`'e
|
||||
güvenmeden önce her zaman `is_error`'a bakın.
|
||||
|
||||
!!! warning
|
||||
`is_error=True`, kendi `raise`'inizden fazlasını kapsar. Sunucuda hiç olmayan bir araç isteyin
|
||||
(`call_tool("does_not_exist", {})`); hiçbir şey fırlatılmaz. Aynı şekil geri gelir:
|
||||
`content`'te `Unknown tool: does_not_exist` ile birlikte `is_error=True`. Bir `Client` yöntemi
|
||||
yalnızca sunucu sonuç yerine bir JSON-RPC **hatası** ile yanıt verdiğinde `MCPError` fırlatır;
|
||||
sunucunun hangisini ne zaman ürettiği **[Hataları ele alma](../servers/handling-errors.md)** sayfasında.
|
||||
|
||||
## Kaynaklar {#resources}
|
||||
|
||||
Kaynak fiilleri çift gelir: listelemenin iki yolu, okumanın tek yolu.
|
||||
|
||||
```python title="client.py" hl_lines="22-31"
|
||||
--8<-- "docs_src/client/tutorial004.py"
|
||||
```
|
||||
|
||||
* `list_resources()` **somut** kaynakları, yani sabit URI'si olanları döndürür. Burada: `['catalog://genres']`.
|
||||
* `list_resource_templates()` **parametreli** olanları döndürür. Burada: `['catalog://genres/{genre}']`. İki ayrı liste olmalarının nedeni, bir şablonun siz onu doldurana kadar okunabilir olmamasıdır.
|
||||
* `read_resource(uri)` düz bir `str` URI alır ve ikisinde de çalışır: `"catalog://genres/poetry"` geçirin, sunucu onu şablonla eşleştirir.
|
||||
|
||||
`read_resource`, `TextResourceContents` veya `BlobResourceContents` öğelerinden oluşan bir liste olan `contents` döndürür. Araç içeriğiyle aynı fikir: `isinstance` ile daraltın, sonra `.text`'i (veya `.blob`'u) okuyun.
|
||||
|
||||
Bir istemciye bir kaynağın ne zaman değiştiği de bildirilebilir. 2025 neslinden bağlantılarda bu, `subscribe_resource(uri)` / `unsubscribe_resource(uri)` çiftidir; `MCPServer`'ın uygulamadığı bir yöntem çifti olduğundan, 2026-07-28 sürümündeki bağlantıda (bu fiillerin artık var olmadığı yerde) istek `-32601`, *Method not found* ile yanıtlanır. 2026'daki karşılığı, `MCPServer`'ın gerçekten *sunduğu* bir `subscriptions/listen` akışıdır (orada `server_capabilities.resources.subscribe` değeri `True`'dur) ve onu `client.listen(...)` ile tüketmek bu bölümün **[Abonelikler](subscriptions.md)** sayfasının konusudur.
|
||||
|
||||
## Prompt'lar {#prompts}
|
||||
|
||||
```python title="client.py" hl_lines="15-20"
|
||||
--8<-- "docs_src/client/tutorial005.py"
|
||||
```
|
||||
|
||||
`list_prompts()` size sunucunun neler sunduğunu ve her prompt'un neye ihtiyaç duyduğunu söyler:
|
||||
|
||||
```python
|
||||
prompt.name # 'recommend'
|
||||
prompt.title # 'Recommend a book'
|
||||
prompt.arguments # [PromptArgument(name='genre', required=True)]
|
||||
```
|
||||
|
||||
`get_prompt(name, arguments)` onu oluşturur. Argümanlar sözlüğü `str -> str` biçimindedir: prompt argümanları her zaman dizedir. Sonuç `messages`'dır; her biri bir `role` ve bir `content` bloğu taşıyan `PromptMessage` öğelerinden oluşan bir liste:
|
||||
|
||||
```python
|
||||
message.role # 'user'
|
||||
message.content # TextContent(type='text', text='Recommend one poetry book from the catalog and say why.')
|
||||
```
|
||||
|
||||
Host bu mesajları doğrudan modele verir. Özelliğin tamamı bu.
|
||||
|
||||
## Tamamlamalar {#completions}
|
||||
|
||||
Tamamlama işleyicisi olan bir sunucu, kullanıcı yazdıkça prompt ve kaynak şablonu argümanlarını otomatik tamamlayabilir.
|
||||
|
||||
```python title="client.py" hl_lines="27-31"
|
||||
--8<-- "docs_src/client/tutorial006.py"
|
||||
```
|
||||
|
||||
* `ref`, *hangi* prompt'u veya şablonu doldurduğunuzu söyler: bir `PromptReference` ya da `ResourceTemplateReference`.
|
||||
* `argument`, `{"name": ..., "value": ...}` biçimindedir: argüman ve kullanıcının şimdiye kadar yazdığı.
|
||||
|
||||
Yanıt `result.completion.values` içindedir. `"p"` yazın, sunucu `['poetry']` ile döner. Sunucu tarafı ve bir işleyicinin önerilerini daraltmak için önceden doldurulmuş *diğer* argümanları nasıl kullandığı **[Tamamlamalar](../servers/completions.md)** sayfasında.
|
||||
|
||||
## Sayfalama {#pagination}
|
||||
|
||||
Her `list_*` yöntemi bir `cursor=` anahtar sözcüğü alır ve her sonuç bir `next_cursor` taşır. `next_cursor` `None` olduğunda her şeyi almışsınız demektir.
|
||||
|
||||
```python title="client.py" hl_lines="22-30"
|
||||
--8<-- "docs_src/client/tutorial007.py"
|
||||
```
|
||||
|
||||
Bu döngü her sunucuya karşı doğrudur. `MCPServer` her şeyi tek sayfada döndürür; bu yüzden `next_cursor` `None` olur ve döngü bir kez çalışır. Çoğu kodun bunu hiç yazmamasının nedeni budur. Gerçekten sayfalayan sunucular ve imleçlerin uyduğu kurallar **[Sayfalama](../advanced/pagination.md)** sayfasında.
|
||||
|
||||
## Testlerde {#in-tests}
|
||||
|
||||
Süreç ve port olmadan `Client(mcp)`, sunucunuz için zaten bir test düzeneğidir.
|
||||
|
||||
Bunun için yapılmış tek bir kurucu bayrağı var: `Client(mcp, raise_exceptions=True)`. Yalnızca bellek içi bağlantılarda etkisi olur; onu açıklayan ve bütün kalıbı onun etrafında kuran sayfa ise **[Test etme](../get-started/testing.md)**.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* `Client(x)` bir sunucu nesnesine bellek içinden, bir URL dizesine Streamable HTTP üzerinden, geri kalan her şeye de bir aktarım aracılığıyla bağlanır.
|
||||
* `async with` yaşam döngüsünün tamamıdır. İçinde `server_capabilities` ve `protocol_version` zaten doludur; sunucu sağladığında `server_info` ve `instructions` da öyle.
|
||||
* `list_tools()` size her aracın `name`, `title`, `description` ve `input_schema` değerlerini verir.
|
||||
* `call_tool()` model için `content`, kodunuz için `structured_content` ve `is_error` döndürür. İstisna fırlatan bir araç istisna değil, sonuçtur.
|
||||
* `content` blok türlerinin bir birleşimidir; okumadan önce `isinstance` ile daraltın.
|
||||
* `list_resources` / `list_resource_templates` / `read_resource`, `list_prompts` / `get_prompt` ve `complete` fiilleri tamamlar.
|
||||
* Her `list_*` `cursor=` alır; `next_cursor` `None` olana kadar döngüye devam edin.
|
||||
|
||||
Bir sunucunun *istemciden* isteyebilecekleri ve bunları nasıl yanıtlayacağınız **[İstemci callback'leri](callbacks.md)** sayfasında.
|
||||
153
i18n/tr/pages/client/oauth-clients.md
Normal file
153
i18n/tr/pages/client/oauth-clients.md
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
---
|
||||
translation:
|
||||
sections: [c6899d3892bd9fa0, 79372cff3cc48a88, 63878d29e87c3e73, 13175843d3588af4, e7e2b9fd516f77de, 758f06399b513c1f, a05d7278487d610b]
|
||||
tool: 1
|
||||
---
|
||||
# OAuth istemcileri {#oauth-clients}
|
||||
|
||||
Bazı MCP sunucuları korumalıdır. Onlara token'sız bir istek gönderin, `401 Unauthorized` yanıtını verirler.
|
||||
|
||||
Token'ı edinmenin yolu **`OAuthClientProvider`**'dır. Bu bir MCP nesnesi bile değildir. Bir `httpx2.Auth`'tur; httpx2'nin "her isteğe bir şey yap" için sunduğu standart kancadır. Onu bir `httpx2.AsyncClient`'a takarsınız, o istemciyi Streamable HTTP aktarımına verirsiniz ve konuyu unutursunuz.
|
||||
|
||||
Bu sayfa istemci tarafını anlatır. Kendi sunucunuzun token talep etmesini sağlamak **[Yetkilendirme](../run/authorization.md)** sayfasının konusudur.
|
||||
|
||||
## Sağlayıcı {#the-provider}
|
||||
|
||||
```python title="client.py" hl_lines="44-54"
|
||||
--8<-- "docs_src/oauth_clients/tutorial001.py"
|
||||
```
|
||||
|
||||
Ona dört şey verirsiniz:
|
||||
|
||||
* `server_url`: bağlandığınız MCP endpoint'i. Sağlayıcı geri kalan her şeyi buradan keşfeder.
|
||||
* `client_metadata`: bir yetkilendirme sunucusunun "uygulama kaydet" formuna yazacağınız bilgiler.
|
||||
* `storage`: token'ların çalıştırmalar arasında saklandığı yer.
|
||||
* `redirect_handler` ve `callback_handler`: bir insanın devreye girdiği iki an.
|
||||
|
||||
Dosyada OAuth'tan söz eden başka hiçbir şey yok. `main()` hiçbir zaman bir token görmez.
|
||||
|
||||
### İstemci metadatası {#client-metadata}
|
||||
|
||||
`OAuthClientMetadata`, gerçek [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) kayıt belgesinin Pydantic modeli hâlidir.
|
||||
|
||||
Üç alan ayarlarsınız. Gerisini varsayılanlar doldurur: `grant_types` zaten `["authorization_code", "refresh_token"]`, `response_types` ise zaten `["code"]`; bu sağlayıcının çalıştırdığı akış da tam olarak budur.
|
||||
|
||||
!!! check
|
||||
Bir Pydantic modeli olduğu için doğrulamayı **ağa tek bir bayt bile gitmeden** yapar.
|
||||
`redirect_uris` alanını atlarsanız oluşturma, alanın adını veren bir `ValidationError` ile
|
||||
anında başarısız olur:
|
||||
|
||||
```text
|
||||
redirect_uris
|
||||
Field required [type=missing, input_value={'client_name': 'Bookshop Agent'}, input_type=dict]
|
||||
```
|
||||
|
||||
Ne bir tarayıcı açılır ne de yetkilendirme sunucusunda yarım kalmış bir kayıt bırakılır.
|
||||
|
||||
### Token deposu {#token-storage}
|
||||
|
||||
**`TokenStorage`**, dört asenkron metodu olan bir `Protocol`'dür. Hiçbir şeyden kalıtım almazsınız; metotları yazın, herhangi bir sınıf bir token deposu olur:
|
||||
|
||||
* `get_tokens` / `set_tokens`, `OAuthToken`'ı tutar: erişim token'ı, yenileme token'ı, geçerlilik süresi, kapsam.
|
||||
* `get_client_info` / `set_client_info`, sağlayıcı sizi kaydettiğinde yetkilendirme sunucusunun verdiği `OAuthClientInformationFull`'u tutar; `client_id`'niz de bunun içindedir.
|
||||
|
||||
Yukarıdaki bellek içi sürüm çalışır. Ancak süreç sona erdiğinde her şeyi unutur; bu yüzden bir sonraki çalıştırma bütün süreci baştan yapar. Onu bir dosyada ya da platformunuzun anahtarlığında kalıcı hâle getirin, bir sonraki çalıştırma sessiz geçer.
|
||||
|
||||
!!! tip
|
||||
Yalnızca token'ları değil, `client_info`'yu da saklayın. Sağlayıcı, depoda `client_info`
|
||||
bulamadığı ilk seferde dinamik olarak kayıt yaptırır. Onu atarsanız her çalıştırmada yeni bir
|
||||
kayıt üretirsiniz.
|
||||
|
||||
### İki işleyici {#the-two-handlers}
|
||||
|
||||
Yetkilendirme kodu akışı bir insana tam olarak bir kez ihtiyaç duyar: birinin oturum açıp "allow" düğmesine tıklaması gerekir.
|
||||
|
||||
* **`redirect_handler`**, tamamen hazırlanmış yetkilendirme URL'siyle await edilir. `client_id`, `redirect_uri`, `state` ve PKCE challenge'ı zaten içindedir. Tek işiniz bir tarayıcıyı oraya götürmektir. Bir masaüstü uygulaması `webbrowser.open`'ı çağırır; bu dosya URL'yi yazdırır.
|
||||
* Ardından **`callback_handler`** await edilir. Kullanıcı `redirect_uri`'nize geri dönene kadar bekler ve o yönlendirmenin sorgu parametrelerini bir `AuthorizationCodeResult` olarak döndürür.
|
||||
|
||||
Gerçek bir istemci `input()` çağırmak yerine yönlendirme URI'si üzerinde küçük bir yerel HTTP sunucusu çalıştırır. Biçim aynıdır: yönlendirilin, `code`, `state` ve `iss` değerlerini geri verin.
|
||||
|
||||
!!! warning
|
||||
`state` ve `iss` değerlerini tam geldikleri gibi aktarın. Sağlayıcı `state`'i kendi ürettiğiyle,
|
||||
`iss`'i de keşfettiği yayıncıyla karşılaştırır ve uyuşmazlığı reddeder. Bunlar CSRF ve
|
||||
sunucu karışıklığı (mix-up) savunmalarıdır.
|
||||
|
||||
### `Client`'a bağlama {#into-the-client}
|
||||
|
||||
`main()`'e bakın. Sağlayıcı **httpx2 istemcisine** takılır, httpx2 istemcisi `streamable_http_client(url, http_client=...)`'a verilir, bu aktarım da `Client`'a gider.
|
||||
|
||||
`streamable_http_client`'ın `auth=` diye bir anahtar sözcük argümanı yoktur. HTTP düzeyindeki her şey (kimlik doğrulama, başlıklar, zaman aşımları, vekil sunucular) sizin getirdiğiniz `httpx2.AsyncClient`'a aittir. Bu katmanlama **[İstemci aktarımları](transports.md)** sayfasında anlatılır.
|
||||
|
||||
## Sağlayıcının sizin için yaptıkları {#what-the-provider-does-for-you}
|
||||
|
||||
`Client` ilk kez bir istek gönderdiğinde sunucu `401` yanıtını verir. Sağlayıcı devralır:
|
||||
|
||||
1. **Keşif.** `WWW-Authenticate` başlığını okur, sunucunun Protected Resource Metadata belgesini `/.well-known/oauth-protected-resource` adresinden alır, bu kaynağı hangi yetkilendirme sunucusunun koruduğunu öğrenir ve *o* sunucunun metadatasını alır.
|
||||
2. **Kayıt.** Depoda bir şey yok mu? `OAuthClientMetadata`'nızla sizi dinamik olarak kaydeder ve sonucu saklar.
|
||||
3. **Yetkilendirme.** PKCE çiftini ve bir `state` üretir, yetkilendirme URL'sini oluşturur, `redirect_handler`'ınızı await eder, ardından kod için `callback_handler`'ınızı await eder.
|
||||
4. **Değişim.** Kodu bir `OAuthToken` ile takas eder, onu saklar ve özgün isteğinizi `Authorization: Bearer ...` ile yeniden gönderir.
|
||||
|
||||
Bundan sonra sessizdir. Token'lar depodan gelir, süresi dolmuş bir erişim token'ı yenileme token'ıyla yenilenir ve ancak bunların hiçbiri işe yaramadığında akışı yeniden çalıştırır.
|
||||
|
||||
Bunların hiçbirini siz yazmadınız. Geriye iki anahtar sözcük argümanı kalır (`client_metadata_url` ve `validate_resource_url`) ve bu dosyanın ikisine de ihtiyacı yoktur. Bilmeye değer olanı `client_metadata_url`'dir; aşağıda kendi bölümü var.
|
||||
|
||||
### Deneyin {#try-it}
|
||||
|
||||
Bu belgelerdeki örneklerin çoğunu bellek içi bir `Client(server)` ile sınayabilirsiniz. Bunu değil: akışın bütün amacı bir HTTP `401`'idir ve bellek içi bir istemci ile sunucusu arasında HTTP yoktur.
|
||||
|
||||
Depo canlı sürümü içerir. `examples/servers/simple-auth/` bağımsız bir yetkilendirme sunucusu ile korumalı bir MCP sunucusu çalıştırır; `examples/clients/simple-auth-client/` ise bu sayfadaki istemcinin küçük bir CLI'a dönüşmüş hâlidir. README'sinde iki komut var: sunucuları başlatın, istemciyi onlara karşı çalıştırın ve dört adımın geçişini izleyin.
|
||||
|
||||
## Client ID Metadata Documents {#client-id-metadata-documents}
|
||||
|
||||
Belirtimin 2026-07-28 sürümü, dinamik istemci kaydını **Client ID Metadata Documents** (CIMD) lehine kullanım dışı bırakır. İstemciniz karşılaştığı her yetkilendirme sunucusuna yeni bir kayıt POST etmek yerine, kendisi hakkında tek bir JSON belgesini kararlı bir HTTPS URL'sinde yayımlar ve `client_id`'si bu URL'nin *ta kendisidir*. Belgeyi yetkilendirme sunucusu alır; sağlayıcı ona hiç dokunmaz.
|
||||
|
||||
SDK bunu zaten destekler: sağlayıcıyı oluştururken URL'yi `client_metadata_url=` olarak geçirin. Yetkilendirme sunucusunun metadatası `client_id_metadata_document_supported: true` bildiriyorsa sağlayıcı `/register` isteğini tamamen atlar: URL akışa `client_id` olarak girer ve `client_secret` yoktur. Sunucu bunu bildirmiyorsa (çoğu henüz bildirmiyor) ya da hiç URL geçirmediyseniz sağlayıcı **sessizce** dinamik kayda geri döner ve yukarıdaki her şey tam anlatıldığı gibi çalışır. Saklanmış `client_info` yine de ikisinin de önüne geçer.
|
||||
|
||||
URL, kök olmayan bir yola sahip HTTPS olmalıdır; başka her şey, herhangi bir ağ trafiği olmadan oluşturma sırasında bir `ValueError`'dır. Depodaki `examples/clients/simple-auth-client/` bunu `MCP_CLIENT_METADATA_URL` ortam değişkeni olarak alır.
|
||||
|
||||
## Makineden makineye {#machine-to-machine}
|
||||
|
||||
Bir gece görevi, bir CI adımı, başka bir servis. Tarayıcı yok, "allow" düğmesine tıklayacak kimse de yok. Bu **client credentials** yetkilendirme türüdür: elinizde zaten bir `client_id` ve bir `client_secret` vardır, akışın tamamı da token endpoint'idir.
|
||||
|
||||
`ClientCredentialsOAuthProvider` aynı `httpx2.Auth`'tur, insan hariç:
|
||||
|
||||
```python title="client.py" hl_lines="4 27-33"
|
||||
--8<-- "docs_src/oauth_clients/tutorial002.py"
|
||||
```
|
||||
|
||||
Neler değişti:
|
||||
|
||||
* `OAuthClientMetadata` yok, işleyiciler yok. `client_id` ve `client_secret` geçirirsiniz; sağlayıcı bunların etrafında asgari bir `client_credentials` kaydı oluşturur ve dinamik kaydı tamamen atlar.
|
||||
* `scope`, boşlukla ayrılmış bir dizedir; OAuth'un iletilen verideki biçimi budur.
|
||||
* Bundan sonraki her şey aynıdır: aynı `TokenStorage`, aynı `httpx2.AsyncClient(auth=...)`, aynı `streamable_http_client`.
|
||||
|
||||
Varsayılan olarak sır, token isteğinde HTTP Basic kimlik doğrulaması olarak gider (`client_secret_basic`). Onu bunun yerine form gövdesine koymak için `token_endpoint_auth_method="client_secret_post"` geçirin. Bazı yetkilendirme sunucuları ikisinden yalnızca birini kabul eder.
|
||||
|
||||
!!! tip
|
||||
`client_secret`'ı ortamdan ya da bir sır yöneticisinden okuyun, asla kaynak kontrolünden değil.
|
||||
|
||||
!!! info
|
||||
`mcp.client.auth.extensions.client_credentials` içinde bir sağlayıcı daha var:
|
||||
paylaşılan bir sır yerine JWT ile kimlik doğrulayan istemciler için **`PrivateKeyJWTOAuthProvider`**
|
||||
(`private_key_jwt`; anahtar çifti ve iş yükü kimliği türü). Aynı kalıbı izler:
|
||||
bir tane oluşturun, `auth=`'a koyun. Aynı modül, onun assertion'ını oluşturan iki yardımcıyı da
|
||||
sunar: `SignedJWTParameters` ve `static_assertion_provider`.
|
||||
|
||||
İnsansız bir durum daha var: istemci, hangi MCP sunucularına erişebileceğine kullanıcının değil kimlik sağlayıcısının karar verdiği bir kuruluşa aittir. Bu, kendi güven modeli ve kendi sayfası olan farklı bir yetkilendirme türüdür: **[Kimlik beyanı](identity-assertion.md)**.
|
||||
|
||||
## Başarısız olduğunda {#when-it-fails}
|
||||
|
||||
OAuth akışı ters gittiğinde sağlayıcı, `mcp.client.auth` içinden bir `OAuthFlowError` fırlatır. İki alt sınıfı vardır. `OAuthRegistrationError`, kaydın kullanabileceğiniz bir istemci üretmediği anlamına gelir: yetkilendirme sunucusu sizi kaydetmeyi reddetti ya da kaydetti ama bu akışın kullanamayacağı kimlik bilgileriyle (örneğin uygulamadığı bir kimlik doğrulama yöntemiyle). `OAuthTokenError` ise bir token alınamadığı anlamına gelir: token endpoint'i hayır dedi ya da saklanan bir istemci kaydı bu istemcinin uygulayamayacağı bir kimlik doğrulama yöntemi taşıyor; bu durum gönderilmek yerine token isteği oluşturulurken bildirilir. Tek bir `except OAuthFlowError:` keşfi, kaydı, yetkilendirmeyi ve değişimi kapsar.
|
||||
|
||||
Her şey bir akış hatası değildir. Ağ yine de başarısız olabilir; bunlar sıradan `httpx2` istisnalarıdır ve dokunulmadan geçer.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* `OAuthClientProvider` bir `httpx2.Auth`'tur. Onu bir `httpx2.AsyncClient`'a koyun, bunu `streamable_http_client(url, http_client=...)`'a geçirin; `Client` OAuth'un gerçekleştiğini hiç bilmez.
|
||||
* Dört şey sağlarsınız: sunucu URL'si, bir `OAuthClientMetadata`, bir `TokenStorage` ve redirect/callback işleyici çifti.
|
||||
* `TokenStorage` bir `Protocol`'dür: dört asenkron metot, taban sınıf yok. Token'ların yanı sıra `client_info`'yu da kalıcı hâle getirin.
|
||||
* Keşif, kayıt (dinamik ya da bir **Client ID Metadata Document** aracılığıyla), PKCE, `state` ve `iss` denetimleri ile token yenileme sağlayıcının işidir, sizin değil.
|
||||
* `ClientCredentialsOAuthProvider` insansız sürümdür: `client_id` + `client_secret`, işleyici yok, tarayıcı yok.
|
||||
* Her OAuth hatası bir `OAuthFlowError`'dır; `OAuthRegistrationError` ve `OAuthTokenError` onun alt sınıflarıdır.
|
||||
|
||||
Bu el sıkışmanın diğer yarısı, yani *sunucunuzun* token talep etmesini sağlamak **[Yetkilendirme](../run/authorization.md)** sayfasındadır.
|
||||
87
i18n/tr/pages/client/session-groups.md
Normal file
87
i18n/tr/pages/client/session-groups.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
translation:
|
||||
sections: [09c857a25a9dc37a, 43bc6a76a243a50e, 0a716022a88768df, 4b7f78042bfcfff7, c112662e61b03315, 58974ba1f489a8b4, d18adbdbb835ea73]
|
||||
tool: 1
|
||||
---
|
||||
# Oturum grupları {#session-groups}
|
||||
|
||||
Bir `Client` tek bir sunucuya bağlanır. Gerçek uygulamalar ise çoğu zaman birden fazlasını ister (bir arama sunucusu, bir veritabanı sunucusu, dahili bir API) ve her biri için ayrı bir bağlantı ile ayrı bir araç listesiyle uğraşmak zorunda kalır.
|
||||
|
||||
**`ClientSessionGroup`**, birçok bağlantıyı tutan ve bunların sunduğu her şeyi tek bir görünümde birleştiren tek bir nesnedir.
|
||||
|
||||
## İki sunucu {#two-servers}
|
||||
|
||||
İki sıradan sunucuyla başlayın. Birbirleriyle hiçbir ilgileri yok, bu yüzden ikisi de doğal olarak aracına `search` adını vermiş:
|
||||
|
||||
```python title="library_server.py" hl_lines="7"
|
||||
--8<-- "docs_src/session_groups/tutorial001.py"
|
||||
```
|
||||
|
||||
```python title="web_server.py" hl_lines="7"
|
||||
--8<-- "docs_src/session_groups/tutorial002.py"
|
||||
```
|
||||
|
||||
## Tek grup {#one-group}
|
||||
|
||||
Bir `ClientSessionGroup` oluşturun ve her sunucu için bir kez **`connect_to_server`**'ı çağırın:
|
||||
|
||||
```python title="client.py" hl_lines="10-12"
|
||||
--8<-- "docs_src/session_groups/tutorial003.py"
|
||||
```
|
||||
|
||||
* `connect_to_server` bir sunucu nesnesi değil, aktarım parametreleri alır: bir alt süreç başlatmak için `StdioServerParameters` (`mcp`'den) ya da zaten bir URL'de dinleyen bir sunucu için `StreamableHttpParameters` / `SseServerParameters` (`mcp.client.session_group`'tan).
|
||||
* `group.tools`, bağlı tüm sunucuların araçlarını içeren bir `dict[str, Tool]`'dur. `group.resources` ve `group.prompts` da aynı biçimdedir.
|
||||
* `group.call_tool(name, arguments)` adı arar, ona sahip olan oturumu bulur ve çağrıyı iletir. Hangi sunucu olduğunu hiçbir zaman söylemezsiniz.
|
||||
|
||||
!!! check
|
||||
`client.py` dosyasını iki sunucunun yanına koyun ve çalıştırın. İkinci `connect_to_server` reddeder:
|
||||
|
||||
```text
|
||||
mcp.shared.exceptions.MCPError: {'search'} already exist in group tools.
|
||||
```
|
||||
|
||||
Bu, ikinci sunucudan herhangi bir şey kaydedilmeden önce fırlatılan bir `MCPError`'dır. Bir ad
|
||||
grubun **tamamında** benzersiz olmalıdır ve sizin denetiminizde olmayan iki sunucu eninde sonunda çakışır.
|
||||
|
||||
## `component_name_hook` {#component_name_hook}
|
||||
|
||||
Bunu sunucularda değil, grupta düzeltirsiniz. `(name, server_info)` alan bir fonksiyon geçirin; grup, kaydettiği her ad üzerinde onu çalıştırır:
|
||||
|
||||
```python title="client.py" hl_lines="7-8 15"
|
||||
--8<-- "docs_src/session_groups/tutorial004.py"
|
||||
```
|
||||
|
||||
Yeniden çalıştırın. `print(sorted(group.tools))` artık ikisini de gösterir:
|
||||
|
||||
```text
|
||||
['Library.search', 'Web.search']
|
||||
```
|
||||
|
||||
* **Anahtar** sizindir. `by_server` onu `server_info.name`'den, yani her `MCPServer(...)`'ın oluşturulduğu addan üretti.
|
||||
* İçindeki `Tool`'a dokunulmaz: `group.tools["Web.search"].name` hâlâ `"search"`'tür ve `call_tool`'un ağ üzerinde gönderdiği ad budur. Önek hiçbir zaman sürecinizin dışına çıkmaz.
|
||||
* Bu yalnızca araçlarla sınırlı değil. Kütüphanenin `hours` kaynağı `Library.hours` olarak kaydedilir.
|
||||
|
||||
!!! tip
|
||||
Kanca yalnızca çakışmalarda değil, **her** sunucudan gelen **her** ad üzerinde çalışır: yalnızca
|
||||
çakışmada önek ekleyen bir kip yoktur. Bir şema seçin ve her yerde uygulanmasına izin verin.
|
||||
|
||||
## Sunucu ekleme ve kaldırma {#adding-and-removing-servers}
|
||||
|
||||
`connect_to_server`, açtığı `ClientSession`'ı döndürür. O sunucuyu bir gün kaldırmak isterseniz bunu saklayın: `await group.disconnect_from_server(session)` sunucunun araçlarını, kaynaklarını ve prompt'larını gruptan kaldırır.
|
||||
|
||||
Elinizde zaten bağlı bir `ClientSession` varsa (`Client.session` bunlardan biridir), yeni bir aktarım açmak yerine onu `await group.connect_with_session(server_info, session)`'a verin. Aynı şekilde birleştirilir. Grup, kendisinin açmadığı bir oturumu hiçbir zaman kapatmaz. `server_info`, bileşen önekleri için sunucuya ad verir; 2026 neslinden bir bağlantıda `client.server_info` `None` olabilir (kimlik isteğe bağlıdır), bu durumda kendi `Implementation(name=..., version=...)`'ınızı geçirin.
|
||||
|
||||
## Klasik el sıkışma {#the-classic-handshake}
|
||||
|
||||
`ClientSessionGroup`, `Client` üzerine değil `ClientSession` üzerine kuruludur. Her `connect_to_server` klasik `initialize` el sıkışmasını yürütür. **[Protokol sürümleri](../protocol-versions.md)** sayfasında anlatılan `server/discover` yoklamasını hiçbir zaman göndermez. Her MCP sunucusu bu el sıkışmayı anlar; bu yüzden uyumluluk açısından hiçbir şey kaybetmezsiniz. Bunun tek anlamı, grubun daha iyisini yapabilecek bir sunucuya giderken daha eski ve daha yavaş yolu izlemesidir.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* `ClientSessionGroup` birçok sunucu bağlantısını tutar ve bunların araçlarını, kaynaklarını ve prompt'larını birer `dict`'te birleştirir.
|
||||
* Her sunucu için `connect_to_server(params)`. Aktarım parametreleri alır; bir `Client`'ın aldığı sunucu nesnesini ya da URL'yi asla almaz.
|
||||
* `group.call_tool(name, arguments)` çağrıyı sizin yerinize sahibi olan sunucuya yönlendirir.
|
||||
* Adlar grubun tamamında benzersiz olmalıdır; `search` aracı olan iki sunucu kendi hâllerine bırakılırsa bir arada bulunamaz.
|
||||
* `component_name_hook=` kaydedilen her adı yeniden yazar. Sözlük anahtarı değişir, ağ üzerindeki ad değişmez.
|
||||
* `connect_with_session` elinizde zaten olan bir oturumu ekler; `disconnect_from_server` bir oturumu kaldırır.
|
||||
|
||||
Bir grubun konuştuğu el sıkışma (ve bir `Client`'ın tercih ettiği daha hızlı olanı), **[Protokol sürümleri](../protocol-versions.md)** sayfasının konusudur.
|
||||
91
i18n/tr/pages/client/subscriptions.md
Normal file
91
i18n/tr/pages/client/subscriptions.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
translation:
|
||||
sections: [8f9558e57f29eee1, a88c587739e0465c, 46ebfd5b325ed041, 4d10b00b57ce4bd9, 2cdb0edd1f59b3e2]
|
||||
tool: 1
|
||||
---
|
||||
# Abonelikler {#subscriptions}
|
||||
|
||||
Bir sunucunun kataloğu sabit değildir. Çalışma zamanında yeni araçlar ortaya çıkar, bir kaynak URI'sinin arkasındaki içerik değişir. İstemci bundan `client.listen(...)` aracılığıyla haberdar olur: yanıtı akışın *kendisi* olan tek bir `subscriptions/listen` isteği. Akış açık kalır ve istemcinin istediği değişiklik bildirimlerini taşır.
|
||||
|
||||
Bu sayfa işin istemci tarafını anlatır: akışı açma, ana iş akışınızın yanında izleme ve sonlanmalarını ele alma. Değişiklikleri yayımlama, filtreleme ve yöntemi sunma ise hikâyenin sunucu tarafıdır; *İşleyicinin içinde* bölümündeki **[Abonelikler](../handlers/subscriptions.md)** sayfasında anlatılır. Buradaki örnekler orada kurulan sprint panosu sunucusuyla konuşur.
|
||||
|
||||
## Akışı izleme {#watching-the-stream}
|
||||
|
||||
Bir abonelik tek bir bağlam yöneticisidir. İçine girmek isteği gönderir (anahtar sözcük argümanlarınız abonelik filtresi olur) ve sunucunun onayını bekler; böylece blok başladığında akış canlıdır.
|
||||
|
||||
```python title="client.py" hl_lines="15 18 28"
|
||||
--8<-- "docs_src/subscriptions/tutorial003.py"
|
||||
```
|
||||
|
||||
Yineleme türü belli dört olay üretir: `ToolsListChanged`, `PromptsListChanged`, `ResourcesListChanged` ve `ResourceUpdated(uri=...)`.
|
||||
|
||||
Bir olay *neyin* değiştiğini söyler, asla *nasıl* değiştiğini değil. `follow_board`'un `read_resource` ve `list_tools`'u çağırmasının nedeni budur: olay, yeniden getirmek için bir işarettir. Hangi kaynağın değiştiğini varsaymak yerine `event.uri` alanını okuyun: bir filtre birkaç URI sayabilir ve sunucu bunlardan birinin alt kaynağındaki bir değişikliği bildirebilir.
|
||||
|
||||
Tüketilmeyi bekleyen yinelenen olaylar tek bir olaya indirgenir; yeniden getirdiğinizde yine güncel durumu alırsınız. Yalnızca özdeş olaylar birleşir: farklı URI'ler için iki `ResourceUpdated`, iki ayrı olaydır.
|
||||
|
||||
Tutamacın (handle) iki özelliği daha var:
|
||||
|
||||
* `sub.honored`, sunucunun onayladığı filtredir: geçirdiğiniz alanları taşıyan ve öznitelik olarak okunan bir `SubscriptionFilter` (`sub.honored.prompts_list_changed`). `MCPServer` istediğiniz her türü kabul eder, bu yüzden isteğinizi olduğu gibi geri yansıtır. Daha az tür destekleyen bir sunucu daha azını onaylar; onaylanmış bir tür yine de hiç tetiklenmeyebilir. Sunucu isteği onaylamak yerine tümüyle reddedebilir de (sunucu sayfasındaki [Kimin izleyebileceğine karar verme](../handlers/subscriptions.md#deciding-who-may-watch) bölümüne bakın); bu, isteğin hatası olarak yüzeye çıkar.
|
||||
* `sub.subscription_id`, listen isteğinin kimliğidir; bu akışın her çerçevesine damgalanan kimlik budur. Aynı anda birkaç abonelik açık olabilir; her biri kendi kimliğiyle ayrıştırılır.
|
||||
|
||||
## Engellemeden izleme {#watching-without-blocking}
|
||||
|
||||
`follow_board`, sunucu akışı kapatana kadar çalışır, ki bu hiç olmayabilir; bu yüzden tek başına bırakıldığında programınızı ele geçirir. Gerçek istemciler izleyiciyi ana iş akışının *yanında* ister: bir izleyici bir önbelleği ya da arayüzü güncel tutarken ajan araçları çağırır.
|
||||
|
||||
Önce aboneliği açın, ardından izleyiciyi başlatın ve işinize devam edin.
|
||||
|
||||
=== "asyncio"
|
||||
|
||||
```python title="app.py" hl_lines="18 20"
|
||||
--8<-- "docs_src/subscriptions/tutorial004_asyncio.py"
|
||||
```
|
||||
|
||||
=== "trio"
|
||||
|
||||
```python title="app.py" hl_lines="18 21"
|
||||
--8<-- "docs_src/subscriptions/tutorial004_trio.py"
|
||||
```
|
||||
|
||||
=== "anyio"
|
||||
|
||||
```python title="app.py" hl_lines="18 21"
|
||||
--8<-- "docs_src/subscriptions/tutorial004_anyio.py"
|
||||
```
|
||||
|
||||
!!! note
|
||||
`app.py`, `BOARD` ve `read_board`'u ilk örnekten içe aktarır; bu depo o örneği
|
||||
`tutorial003.py` olarak saklar. Oluşturulan dosyaları `client.py` ve `app.py` adlarıyla yan yana
|
||||
kaydederseniz bunun yerine `from client import BOARD, read_board` yazın. Aşağıdaki `watch.py`
|
||||
örneği de `read_board`'u aynı şekilde içe aktarır.
|
||||
|
||||
Önemli olan sıradır. Hiçbir şey yeniden oynatılmaz; bu yüzden akışınız var olmadan önce yayımlanan bir olay kaçar. `client.listen(...)` bloğuna girmek onayı bekler; dolayısıyla o andan itibaren her değişiklik izleyicinize ulaşır ve blok içinde aldığınız anlık görüntü hiçbirini kaçıramaz.
|
||||
|
||||
Açık bir akışın yanında istekler, izleyici görevinden de başka herhangi bir görevden de, aynı istemci üzerinde serbestçe çalışır. Tüketilmemiş *yinelenen* olaylar birleştiği için, yoğun bir ana iş akışı üç yerine tek bir yeniden getirmeyle sonuçlanabilir. Farklı olaylar birleşmez: çok sayıda URI sayan bir filtre, URI başına bir bekleyen olayı kuyruğa alır.
|
||||
|
||||
İzlemeyi bırakmak için bloktan çıkın: `unsubscribe` diye bir çağrı yoktur. Bloğun sahibi olan görevi iptal etmek bunu sizin yerinize yapar; SDK da listen isteğini aktarımın beklediği biçimde iptal eder: Streamable HTTP üzerinde, o isteğin akışını kapatarak. Uygulamanızın ömrü boyunca çalışan bir izleyici kendiliğinden asla dönmez; bu yüzden kapanışta onu ya da görev grubunun kapsamını iptal edin.
|
||||
|
||||
## Akışların sona ermesi {#streams-end}
|
||||
|
||||
Bir akış iki yoldan biriyle sona erer; ikisi de sıradan denetim akışıdır. Sunucunun düzgün bir kapatması `async for` döngüsünü bitirir; ani bir kopma `SubscriptionLost` fırlatır.
|
||||
|
||||
Aradaki fark tanı amaçlıdır, sonra ne yapılacağıyla ilgili değildir: akış gitmiştir, hiçbir şey yeniden oynatılmamıştır ve hâlâ ilgilenen bir izleyici yeniden dinler ve yeniden getirir.
|
||||
|
||||
```python title="watch.py" hl_lines="16 20"
|
||||
--8<-- "docs_src/subscriptions/tutorial005.py"
|
||||
```
|
||||
|
||||
Sunucular akışları kendi gerekçeleriyle düzgünce kapatır; birikimi fazla büyüyen bir aboneyi bırakmak da bunlardan biridir. Bu yüzden temiz bir sonlanma, izlemeyi bırakma işareti değildir. Yeniden dinlemeden önce biraz bekleyin.
|
||||
|
||||
`SubscriptionLost`'un yerel bir nedeni de vardır. İstemci en fazla 1024 tüketilmemiş olay tutar; bu kadar geride kalan bir tüketici, sınırsızca büyümek yerine aboneliği kaybeder. `async for` gövdesini kısa tutun, yavaş işleri başka yerde yapın.
|
||||
|
||||
`keep_following` yalnızca `SubscriptionLost`'u yakalar. `listen()`'a girmek ayrıca `MCPError` (bağlantı başarısız oldu ya da sunucu yöntemi sunmuyor), `TimeoutError` (onay gelmedi) ve `ListenNotSupportedError` (2026 öncesi bir bağlantı) da fırlatabilir. İzleyicinizin bunlardan hangilerini yeniden denemesi gerektiğine karar verin: sonuncusu asla düzelmez.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* `async with client.listen(...)` bloğuna girin; giriş onayı bekler, bu yüzden ondan sonra yayımlanan hiçbir şey kaçmaz.
|
||||
* `async for event in sub` ile yineleyin. Olaylar yeniden getirmek için birer işarettir, asla yük (payload) değildir.
|
||||
* Aboneliği açın, ardından izleyiciyi bir görev olarak çalıştırın; araç çağrıları onun yanında akmaya devam eder.
|
||||
* Temiz bir sonlanma döngüyü durdurur; kopma `SubscriptionLost` fırlatır. Her iki durumda da: yeniden dinleyin, yeniden getirin, ama önce biraz bekleyin.
|
||||
* Bloktan çıkmak abonelikten çıkmaktır.
|
||||
|
||||
Bu olayları yayımlamak, filtreyi daraltmak ve tek bir sürecin ötesine ölçeklemek sunucunun hikâyesidir: **[Abonelikler](../handlers/subscriptions.md)**. Aynı olaylar istemci tarafındaki bir önbelleği de dürüst tutar; sıradaki sayfa **[Önbellekleme](caching.md)**.
|
||||
127
i18n/tr/pages/client/transports.md
Normal file
127
i18n/tr/pages/client/transports.md
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
|
||||
tool: 1
|
||||
---
|
||||
# İstemci aktarımları {#client-transports}
|
||||
|
||||
Her `Client`, sunucusuyla bir **aktarım** üzerinden konuşur: mesajları fiilen taşıyan şey budur.
|
||||
|
||||
Aktarımı hiçbir zaman ayrıca yapılandırmazsınız. `Client` tek bir konumsal argüman alır ve aktarımı bu argümanın türünden çıkarır.
|
||||
|
||||
Her birinin *sunucu* tarafı (`mcp.run()`'ın ne yaptığı ve neyi dağıttığınız) **[Sunucunuzu çalıştırma](../run/index.md)** sayfasında.
|
||||
|
||||
## Bellek içinde {#in-memory}
|
||||
|
||||
Sunucu nesnesinin kendisini geçirin:
|
||||
|
||||
```python title="client.py" hl_lines="14"
|
||||
--8<-- "docs_src/client_transports/tutorial001.py"
|
||||
```
|
||||
|
||||
Alt süreç yok, port yok, ağ üzerinde tek bir bayt yok. İstemci ve sunucu aynı süreçteki iki nesnedir; yine de çağrı gerçek protokol katmanından geçer: `search_books`, HTTP üzerinden nasıl olacaksa tam olarak öyle listelenir, doğrulanır ve çağrılır.
|
||||
|
||||
Bu, onu aynı anda iki şey yapar:
|
||||
|
||||
* **Bir test düzeneği.** Bu belgelerdeki her örnek bu şekilde çalıştırılır ve **[Test etme](../get-started/testing.md)** sayfası tüm deseni bunun üzerine kurar.
|
||||
* **Bir gömme API'si.** Sunucuyu oluşturan bir uygulamanın, araçlarını çağırmak için ağ üzerinden bir sıçrama yapmasına gerek yoktur.
|
||||
|
||||
## Streamable HTTP {#streamable-http}
|
||||
|
||||
Bir URL dizesi geçirin; arkasına dağıtım yaptığınız aktarım olan **Streamable HTTP**'yi elde edersiniz:
|
||||
|
||||
```python title="client.py" hl_lines="5"
|
||||
--8<-- "docs_src/client_transports/tutorial002.py"
|
||||
```
|
||||
|
||||
Üretim istemcisinin tamamı bu. `Client`, URL'yi sizin için `streamable_http_client(...)` ile sarar; bunu da MCP'nin gerektirdiği şekilde yapılandırılmış bir `httpx2.AsyncClient` üzerine kurar: `follow_redirects=True`, connect/write/pool için 30 saniyelik zaman aşımı ve sunucu bir yanıt akışını açık tutabileceği için 300 saniyelik okuma zaman aşımı.
|
||||
|
||||
!!! check
|
||||
Oluşturduğunuz bir `Client` bağlı **değildir**. Oluşturma yalnızca aktarımı seçer;
|
||||
onu açan `async with`'tir. İçine girmeden bağlantıya uzanırsanız SDK bunu size söyler:
|
||||
|
||||
```text
|
||||
RuntimeError: Client must be used within an async context manager
|
||||
```
|
||||
|
||||
`Client("http://...")` yazdığınızda hiçbir şey çözümlenmedi, getirilmedi ya da başlatılmadı. O satır bedava.
|
||||
|
||||
### Kendi `httpx2.AsyncClient`'ınızı getirme {#bring-your-own-httpx2asyncclient}
|
||||
|
||||
Bir `Authorization` başlığına, bir çereze, bir vekil sunucuya, mTLS'e ya da farklı bir zaman aşımına ihtiyaç duyduğunuz anda `httpx2.AsyncClient`'ı kendiniz oluşturun ve `streamable_http_client`'a verin:
|
||||
|
||||
```python title="client.py" hl_lines="8-14"
|
||||
--8<-- "docs_src/client_transports/tutorial003.py"
|
||||
```
|
||||
|
||||
Dikkat edilecek iki şey:
|
||||
|
||||
* `httpx2.AsyncClient`'ın sahibi sizsiniz, bu yüzden içine **siz** girer ve **siz** çıkarsınız. SDK, kendi oluşturmadığı bir istemciyi asla kapatmaz.
|
||||
* `streamable_http_client(url, http_client=...)` bir aktarım döndürür ve `Client(transport)` onu diğer her şey gibi kabul eder.
|
||||
|
||||
TLS ile ilgili bir not: `httpx2`, sertifikaları paketle gelen bir CA listesine göre değil, işletim sisteminin güven deposuna göre doğrular (
|
||||
[`truststore`](https://pypi.org/project/truststore/) aracılığıyla). Kullanılabilir bir sistem CA deposu olmayan bir ortamda (bazı minimal kapsayıcılar) standart `SSL_CERT_FILE`/`SSL_CERT_DIR`
|
||||
ortam değişkenlerini ayarlayın ya da `httpx2.AsyncClient`'ınıza açıkça bir `verify=ssl_context` geçirin
|
||||
(arka plan bilgisi için
|
||||
[`httpx` ve `httpx-sse`'nin yerini `httpx2` aldı](../migration.md#httpx-and-httpx-sse-replaced-by-httpx2)).
|
||||
|
||||
!!! warning
|
||||
`streamable_http_client` eskiden `headers=` ve `timeout=` parametrelerini doğrudan alırdı. Artık almıyor:
|
||||
tek parametreleri `url`, `http_client` ve `terminate_on_close`. Alışkanlıkla `headers=`'a
|
||||
uzanırsanız şunu alırsınız:
|
||||
|
||||
```text
|
||||
TypeError: streamable_http_client() got an unexpected keyword argument 'headers'
|
||||
```
|
||||
|
||||
HTTP'yle ilgili her şey artık geçirdiğiniz o tek `httpx2.AsyncClient` üzerinde bulunur.
|
||||
|
||||
!!! info
|
||||
`httpx2`, tanıdık `httpx` API'sini korur; yani `httpx`'i biliyorsanız kimlik doğrulama,
|
||||
vekil sunucular, olay kancaları, yeniden denemeler ve bağlantı sınırlarının burada nasıl yapılacağını zaten biliyorsunuz. SDK üzerine hiçbir şey eklemez,
|
||||
hiçbir şeyi de eksiltmez. OAuth'un takıldığı yer de burası:
|
||||
`httpx2.AsyncClient(auth=OAuthClientProvider(...))`. Bu akışın tamamı **[OAuth istemcileri](oauth-clients.md)** sayfasında.
|
||||
|
||||
## stdio {#stdio}
|
||||
|
||||
Bir **stdio** sunucusu bir alt süreçtir. İstemci onu başlatır, stdin'ine JSON-RPC yazar ve stdout'undan JSON-RPC okur. Bir masaüstü host'un makinenizde bir sunucuyu çalıştırma biçimi budur: bir host, bu kod artı bir kullanıcı arayüzü*dür* ve **[Gerçek bir host'a bağlanma](../get-started/real-host.md)**, aynı ilişkinin host'un tarafından, bir yapılandırma dosyası olarak görülen halidir.
|
||||
|
||||
Süreci `StdioServerParameters` ile tanımlayın, `stdio_client` ile bir aktarıma dönüştürün ve `Client`'a *onu* verin:
|
||||
|
||||
```python title="client.py" hl_lines="4-8 12"
|
||||
--8<-- "docs_src/client_transports/tutorial004.py"
|
||||
```
|
||||
|
||||
`Client`, parametre nesnesini tek başına kabul etmez. `StdioServerParameters` yapılandırmadır; `stdio_client(server)` ise ondan bir süreç başlatmayı bilen aktarımdır. Her zaman sarın.
|
||||
|
||||
`async with` bloğundan çıkmak alt süreci de kapatır: stdin'i kapat, bekle, oyalanıyorsa sonlandır. Onu hiçbir zaman kendiniz temizlemezsiniz.
|
||||
|
||||
!!! warning
|
||||
Alt süreç ortamınızı **devralmaz**. Minimal bir izin listesi alır (POSIX'te `HOME`, `LOGNAME`,
|
||||
`PATH`, `SHELL`, `TERM` ve `USER`); böylece sizin yazmamış olabileceğiniz bir sürece hassas hiçbir şey
|
||||
sızmaz.
|
||||
|
||||
Bir API anahtarına ihtiyaç duyan bir sunucu onu orada bulamaz. `env=` ile açıkça geçirin; bu
|
||||
değişkenler izin listesinin üstüne birleştirilir. Yukarıda `BOOKSHOP_API_KEY`'in yaptığı budur.
|
||||
|
||||
## SSE {#sse}
|
||||
|
||||
`mcp.client.sse` içindeki `sse_client(url)`, Streamable HTTP'nin yerini aldığı HTTP aktarımıdır. Hâlâ onu konuşan bir sunucuyla konuşmak için aynı şekilde sarın, `Client(sse_client("http://localhost:8000/sse"))`, ve üzerine yeni hiçbir şey kurmayın.
|
||||
|
||||
## `Transport` protokolü {#the-transport-protocol}
|
||||
|
||||
`Client` için yukarıdakilerin hepsi aynı şeydir.
|
||||
|
||||
Bir **aktarım**, `(read, write)` mesaj akışı çifti veren herhangi bir asenkron bağlam yöneticisidir: resmi olarak `mcp.client` içindeki `Transport` protokolü. `Client`, argümanını türüne göre çözümler: bir sunucu nesnesi süreç içinde bağlanır, bir `str` `streamable_http_client(url)` olur ve geri kalan her şeye doğrudan bir aktarım olarak girilir. `stdio_client(...)`, `streamable_http_client(...)` ve `sse_client(...)`'in hepsinin aynı yuvaya oturmasının ve kendinizinkini yazabilmenizin nedeni bu son kuraldır.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* `Client(mcp)` (sunucu nesnesi) bellek içinde bağlanır. Testler ve gömme için kullanın.
|
||||
* `Client("http://.../mcp")` (bir URL), üretim aktarımı olan Streamable HTTP üzerinden bağlanır.
|
||||
* Başlıklar, kimlik doğrulama, vekil sunucular ve zaman aşımları, `streamable_http_client(url, http_client=...)`'a geçirdiğiniz bir `httpx2.AsyncClient` üzerinde yer alır. `headers=` anahtar sözcüğü yoktur.
|
||||
* stdio `Client(stdio_client(StdioServerParameters(...)))`'tır; asla tek başına parametre nesnesi değil.
|
||||
* Alt süreç sizinkini değil, izin listesine göre oluşturulmuş bir ortam alır; `env=` buna ekleme yapar.
|
||||
* Bir aktarım, `async with x as (read, write)` yapabildiğiniz herhangi bir şeydir. `Client`, sunucu nesnesi ya da URL olmayan her şeyi doğrudan bu protokole verir.
|
||||
* Bir `Client` oluşturmak aktarımı seçer. Onu `async with` açar.
|
||||
|
||||
Aktarım açıldıktan sonra iki tarafın bir protokol sürümünde anlaşması gerekir. Normalde bunu hiç düşünmezsiniz; düşünmeniz gerektiğinde gidilecek sayfa **[Protokol sürümleri](../protocol-versions.md)**'dir.
|
||||
96
i18n/tr/pages/deprecated.md
Normal file
96
i18n/tr/pages/deprecated.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
translation:
|
||||
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
|
||||
tool: 1
|
||||
---
|
||||
# Kullanım dışı özellikler {#deprecated-features}
|
||||
|
||||
2026-07-28 spesifikasyonu beş şeyi emekliye ayırıyor. SDK hâlâ hepsini uygular ve artık her biri bir **kullanım dışı bırakma uyarısı** taşır.
|
||||
|
||||
Aşağıdaki tablo kullanım dışı bırakılan her özelliği, neden gittiğini ve yerine neyin üzerine inşa etmeniz gerektiğini gösterir.
|
||||
|
||||
## Neler kullanım dışı {#what-is-deprecated}
|
||||
|
||||
| Kullanım dışı | Neden | Bunun yerine ne yaparsınız |
|
||||
|---|---|---|
|
||||
| **Kök dizinler (roots)**: `ctx.session.list_roots()`, `client.send_roots_list_changed()`, `Client(...)`'a geçirdiğiniz `list_roots_callback=` | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) bu yeteneği emekliye ayırıyor. | Yolları sıradan araç argümanları veya kaynak URI'leri olarak alın ya da bir `InputRequiredResult` içine bir `ListRootsRequest` gömün (bkz. **[Çok turlu istekler (multi-round-trip)](handlers/multi-round-trip.md)**). |
|
||||
| **Sunucunun başlattığı örnekleme (sampling)**: `ctx.session.create_message()`, `Client(...)`'a geçirdiğiniz `sampling_callback=` | SEP-2577 bu yeteneği emekliye ayırıyor. | `InputRequiredResult` döndürün ve çağrıyı istemcinin yeniden denemesine bırakın (bkz. **[Çok turlu istekler](handlers/multi-round-trip.md)**). |
|
||||
| **Protokol üzerinden log tutma**: `ctx.log()`, `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`, `ctx.session.send_log_message()`, `client.set_logging_level()` | SEP-2577 bu yeteneği emekliye ayırıyor. Protokol içinde yerine geçen bir şey yok. | stderr'e yazan sıradan `import logging` (bkz. **[Log tutma](handlers/logging.md)**). |
|
||||
| **`ping`**: `client.send_ping()` | Yalnızca kullanım dışı bırakılmadı, protokolden **kaldırıldı**. 2026-07-28 sürümünde `ping` yöntemi yok. | Hiçbir şey. Yalnızca `mode="legacy"` bağlantısında çalışır. |
|
||||
| **İstemciden sunucuya ilerleme**: `client.send_progress_notification()` | 2026-07-28 ilerlemeyi yalnızca sunucudan istemciye yönlü yapar. | Gönderecek bir şey yok. İlerlemeyi *sunucunuz* `ctx.report_progress()` ile bildirir (bkz. **[İlerleme](handlers/progress.md)**). |
|
||||
|
||||
Bu tablodan üç şey çıkar:
|
||||
|
||||
* Kök dizinler, örnekleme ve log tutma bir arada gider. Tek bir öneri, **SEP-2577**, üç yeteneği birden kullanım dışı bırakır.
|
||||
* Örnekleme ve kök dizinler daha derin bir sorunu paylaşır: bunlar bir **sunucunun** **istemciye** **istek** gönderdiği yerlerdir. 2026-07-28 sürümünün **[Çok turlu istekler](handlers/multi-round-trip.md)** ile değiştirdiği şey tam da bu yöndür. Giden, bağımsız RPC yöntemleridir (`sampling/createMessage`, `roots/list` ve push tarzı `elicitation/create`); `CreateMessageRequest` / `ListRootsRequest` / `ElicitRequest` yük türleri `InputRequiredResult.input_requests` içine gömülü olarak yaşamaya devam eder ve istemcide aynı callback'lere ulaşır.
|
||||
* `ping` diğerlerinden ayrılır. Protokol onu kullanım dışı bırakmaz, kaldırır. SDK yöntemi yine de uyarır (mesajı *deprecated* değil *removed* der) ve modern bir bağlantıda çağrıldığında *"Method not found"* yanıtı gelir.
|
||||
|
||||
## Kullanım dışı bırakma tavsiye niteliğindedir {#deprecated-is-advisory}
|
||||
|
||||
Bugün hiçbir şey bozulmaz.
|
||||
|
||||
Yukarıdaki her yöntem, **2025-11-25 veya daha eski** bir sürümle anlaşmış her oturumda çalışmaya devam eder. İstemcide `mode="legacy"` sabitleyin, 2026 öncesi davranışın aynısını elde edersiniz. İletilen veride hiçbir değişiklik yoktur ve yetenek anlaşması aynıdır.
|
||||
|
||||
Değişen şey, her biri ilk kez çalıştığında görünür bir uyarı almanızdır:
|
||||
|
||||
```text
|
||||
MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
|
||||
```
|
||||
|
||||
`MCPDeprecationWarning`, `DeprecationWarning`'in **değil**, `UserWarning`'in alt sınıfıdır. Bu kasıtlıdır: Python'ın varsayılan filtresi `DeprecationWarning`'i yalnızca doğrudan `__main__` olarak çalıştırılan kodda gösterir; kütüphaneler bir şeyleri böyle kullanım dışı bırakır ve iki yıl boyunca kimse fark etmez. Bu uyarı ise her yerde, `-W` bayrağı olmadan görünür.
|
||||
|
||||
!!! warning
|
||||
"Tavsiye niteliği" iletilen veriye gelince biter. Örnekleme ve kök dizinler sunucudan
|
||||
istemciye giden *isteklerdir* ve 2026-07-28 oturumunda bunları taşıyacak bir kanal yoktur.
|
||||
Modern bir bağlantıda bir aracın içinde `ctx.session.create_message()`'ı çağırın: uyarı
|
||||
yine tetiklenir, ardından gönderim bir hatayla başarısız olur:
|
||||
|
||||
```text
|
||||
Cannot send 'sampling/createMessage': this transport context has no back-channel
|
||||
for server-initiated requests.
|
||||
```
|
||||
|
||||
Bu sırayla iki sinyal. `MCPDeprecationWarning`, yöntemi çağırdığınız anda, her
|
||||
bağlantıda tetiklenir. Hata ise SDK ardından göndermeyi denediğinde geri dönen şeydir.
|
||||
Bu ikisi yalnızca, istemcisi eşleşen callback'i kaydetmiş bir `mode="legacy"`
|
||||
bağlantısında uçtan uca çalışır.
|
||||
|
||||
## Uyarıyı susturma {#silencing-the-warning}
|
||||
|
||||
Yeni kodda susturmayın.
|
||||
|
||||
Ancak bakımını yaptığınız ve gerçekten 2026 öncesi istemcilere hizmet veren bir sunucunun sessiz bir log'a sonuna kadar hakkı vardır. Kategoriyi, ilk kullanım dışı çağrı çalışmadan önce filtreleyin:
|
||||
|
||||
```python
|
||||
import warnings
|
||||
|
||||
from mcp import MCPDeprecationWarning
|
||||
|
||||
warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
|
||||
```
|
||||
|
||||
API'nin tamamı bu. Yöntem başına bir anahtar yok, zaten istemezsiniz de: tek kategori olmasının anlamı, tek satırın onu susturması ve tek satırın geri getirmesidir.
|
||||
|
||||
!!! check
|
||||
Filtreyi ters yönde çalıştırın, bedava bir regresyon testi elde edersiniz. pytest
|
||||
yapılandırmanızdaki `filterwarnings` ayarına `"error::mcp.MCPDeprecationWarning"`
|
||||
ekleyin; kullanım dışı çağrı uyarmak yerine **istisna fırlatır**. Hâlâ `ctx.info()`'yu
|
||||
çağıran `old_log` adlı bir araç artık geçmez ve şunu bildirmeye başlar:
|
||||
|
||||
```text
|
||||
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
|
||||
```
|
||||
|
||||
Tek satır pytest yapılandırmasıyla, kullanım dışı bir çağrı bir testi başarısız kılmadan
|
||||
kod tabanınıza bir daha asla sızamaz.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* 2026-07-28 spesifikasyonu **kök dizinleri**, sunucunun başlattığı **örneklemeyi** ve protokol üzerinden **log tutmayı** kullanım dışı bırakır (hepsi [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), **ilerlemeyi** sunucudan istemciye yönle sınırlar ve **`ping`**'i kaldırır.
|
||||
* Yerine geçenler sütunu sizi ileriye yönlendirir: örnekleme ve kök dizinler için **[Çok turlu istekler](handlers/multi-round-trip.md)**, log tutma için **[Log tutma](handlers/logging.md)**, ilerleme için **[İlerleme](handlers/progress.md)**. `ping` için hiçbir şey gerekmez.
|
||||
* Kullanım dışı bırakma tavsiye niteliğindedir: iletilen veride değişiklik yok, her şey 2026 öncesi oturumlarda çalışmaya devam eder ve görünür bir `MCPDeprecationWarning` alırsınız (bir `UserWarning`, dolayısıyla varsayılan olarak açık).
|
||||
* Örnekleme ve kök dizinler ayrıca, 2026-07-28 oturumunda bulunmayan bir geri kanala (back-channel) ihtiyaç duyar. Modern bir bağlantıda önce uyarır, sonra istisna fırlatırlar.
|
||||
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` tüm kategoriyi susturur; pytest'te `"error::mcp.MCPDeprecationWarning"` bunu bir test hatasına dönüştürür.
|
||||
* Yeni kod bunların hiçbiri üzerine kurulmamalıdır.
|
||||
|
||||
Bu belgelerdeki diğer tüm sayfalar güncel API'yi anlatır.
|
||||
144
i18n/tr/pages/get-started/first-steps.md
Normal file
144
i18n/tr/pages/get-started/first-steps.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
---
|
||||
translation:
|
||||
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
|
||||
tool: 1
|
||||
---
|
||||
# İlk adımlar {#first-steps}
|
||||
|
||||
**[Giriş sayfası](../index.md)** hızlı ilerler: bir sunucu yazın, çalıştırın, bir araç çağırın.
|
||||
|
||||
Bu sayfa ise ağırdan alır: bir sunucunun sunabileceği üç şeyin hepsini ele alır ve yol boyunca her şeye bir ad verir.
|
||||
|
||||
## Host, istemci ve sunucu {#host-client-and-server}
|
||||
|
||||
Bundan sonraki her sayfada göreceğiniz üç sözcük:
|
||||
|
||||
* **Host**, LLM uygulamasıdır: Claude, bir IDE, bir ajan çalışma zamanı. Kullanıcının konuştuğu şey odur.
|
||||
* **İstemci**, host'un içinde yaşar ve MCP konuşur. Host, bağlandığı her sunucu için bir istemci çalıştırır.
|
||||
* **Sunucu**, bu SDK ile sizin oluşturduğunuz şeydir. İstemcilere bir şeyler sunar. Modelle hiçbir zaman doğrudan konuşmaz.
|
||||
|
||||
Sunucuyu siz yazarsınız. Host'lar başkasının ürünüdür. SDK size bir de `Client` verir. Onu sunucularınızı test etmek için kullanırsınız; bu sayfanın ilerisinde karşınıza çıkar.
|
||||
|
||||
## Üç temel öğe {#the-three-primitives}
|
||||
|
||||
Bir sunucu tam olarak üç tür şey sunar. Onları birbirinden ayıran, **kullanılmalarına kimin karar verdiğidir**:
|
||||
|
||||
| Temel öğe | Kontrol eden | Nedir | Örnek |
|
||||
|----------------|-----------------|---------------------------------------------------------------|-----------------------------------------------|
|
||||
| **Araçlar** | Model | Modelin bir eylemde bulunmak için çağırdığı fonksiyon | Bir API çağrısı, bir veritabanı yazma işlemi |
|
||||
| **Kaynaklar** | Uygulama | Host'un modelin bağlamına yüklediği veri | Bir dosyanın içeriği, bir API yanıtı |
|
||||
| **Prompt'lar** | Kullanıcı | Kullanıcının adıyla çağırdığı, yeniden kullanılabilir mesaj şablonu | Bir slash komutu, bir menü girdisi |
|
||||
|
||||
"Kontrol eden", bu ayrımın özüdür. Bir araç, **model** onu çağırmaya karar verdiği için çalışır. Bir kaynak, **uygulama** modelin ona ihtiyacı olduğuna karar verdiği için eklenir. Bir prompt, **kullanıcı** onu seçtiği için çalışır.
|
||||
|
||||
!!! info
|
||||
Daha önce bir web API'si geliştirdiyseniz sezginin çoğu zaten sizde var: **kaynak** bir `GET`'tir
|
||||
(veri yükler, hiçbir şeyi değiştirmez), **araç** ise bir `POST`'tur (iş yapar ve yan etkileri
|
||||
olabilir). **Prompt**'un HTTP'de karşılığı yoktur; kullanıcının adıyla çalıştırdığı kayıtlı bir
|
||||
sorguya daha yakındır.
|
||||
|
||||
## Tek sunucu, üçü birden {#one-server-all-three}
|
||||
|
||||
```python title="server.py" hl_lines="6 12 18"
|
||||
--8<-- "docs_src/first_steps/tutorial001.py"
|
||||
```
|
||||
|
||||
Üç sade fonksiyon, üç dekoratör. Her dekoratör kaydın tamamıdır:
|
||||
|
||||
* `@mcp.tool()`, `add`'i bir **araç** yapar.
|
||||
* `@mcp.resource("greeting://{name}")`, `greeting`'i bir **kaynak şablonu** yapar: URI içindeki `{name}`, fonksiyonun parametresidir.
|
||||
* `@mcp.prompt()`, `summarize`'ı bir **prompt** yapar. Döndürdüğü dize bir kullanıcı mesajına dönüşür.
|
||||
|
||||
Geri kalan her şeyi (adı, açıklamayı, argüman şemasını) SDK fonksiyonun kendisinden okur: adından, docstring'inden, tür ipuçlarından. Hiçbirini ayrıca bildirmediniz.
|
||||
|
||||
!!! tip
|
||||
SDK'nın iki yarısının iki ayrı import yolu vardır: `from mcp import Client` ve
|
||||
`from mcp.server import MCPServer`. `from mcp import MCPServer` diye bir şey yoktur.
|
||||
|
||||
### Deneyin {#try-it}
|
||||
|
||||
MCP Inspector ile çalıştırın:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Yazdırdığı URL'yi açın. Inspector'da her temel öğe için bir sekme var; sırayla üzerinden geçin.
|
||||
|
||||
**Tools.** Tek bir girdi: `add`, açıklaması *Add two numbers.* Formda `a` için zorunlu bir tamsayı alanı, `b` için de bir tane daha var. Doldurun, çağırın; sonuç `3`. Inspector bu formu `a: int, b: int` ifadesinden oluşturdu. Diğer tüm istemciler de öyle yapar.
|
||||
|
||||
**Resources.** *Resources* listesi boş. `greeting`, **Resource Templates** altında; çünkü `greeting://{name}` bir parametre içerir: biri bir `name` verene kadar listelenecek tek bir kaynak yoktur. Ona `World` verin ve okuyun:
|
||||
|
||||
```text
|
||||
Hello, World!
|
||||
```
|
||||
|
||||
**Prompts.** Tek bir girdi: tek bir zorunlu `text` argümanı olan `summarize`. Biraz metinle getirin; `role: user` taşıyan ve içeriği işlenmiş dizeniz olan tek bir mesaj alırsınız. Bir prompt'un hepsi budur: mesaj oluşturan bir fonksiyon.
|
||||
|
||||
Inspector sunucunuzu **stdio** üzerinden çalıştırdı; bu, bir MCP sunucusunun konuşabileceği aktarımlardan biridir. Henüz bir tane seçmiyorsunuz; bunun sayfası **[Sunucunuzu çalıştırma](../run/index.md)**.
|
||||
|
||||
## Yetenekler {#capabilities}
|
||||
|
||||
Inspector'da üç sekme gördünüz. Üç tane olduğunu nereden bildi?
|
||||
|
||||
Bir istemci bağlandığında sunucu **yeteneklerini** beyan eder: hangi istek ailelerini yanıtlayacağını. İstemci, neyi isteyeceğine karar vermek için bu beyanı kullanır. Bunu siz hiç yazmadınız; `MCPServer` sizin yerinize beyan eder.
|
||||
|
||||
Kendiniz bakın. SDK'nın `Client`'ı sunucu nesnesini doğrudan kabul eder ve ona **bellek içinde** bağlanır (alt süreç yok, port yok):
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
print(client.server_capabilities.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```text
|
||||
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
|
||||
```
|
||||
|
||||
Bu sözlük, sunucunuzun beyan ettiği **yeteneklerdir**. Bağlanan her istemcinin öğrendiği ilk şey budur:
|
||||
|
||||
| Yetenek | İstemci artık şunları çağırabilir |
|
||||
|-------------|----------------------------------------------------------------|
|
||||
| `tools` | `tools/list`, `tools/call` |
|
||||
| `resources` | `resources/list`, `resources/templates/list`, `resources/read` |
|
||||
| `prompts` | `prompts/list`, `prompts/get` |
|
||||
|
||||
`MCPServer` üç temel öğenin hepsini sunar; bu yüzden üçü de her zaman beyan edilir.
|
||||
|
||||
Orada ne olmadığına dikkat edin. `completions` (kaynak şablonları ve prompt'lar için argüman otomatik tamamlama) sizin yazacağınız bir işleyici gerektirir; bu sunucuda yok, dolayısıyla yetenek de yok ve uslu bir istemci sormaz. İsteğe bağlı her şey için kural budur: şeyi kaydedin, yetenek belirir; **[Tamamlamalar](../servers/completions.md)** bunu kanıtlar.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)`, bu belgelerdeki her örneğin test edildiği aynı bellek içi istemcidir;
|
||||
sizinkileri de böyle test edeceksiniz. Kendine ait koca bir sayfası var: **[Test etme](testing.md)**.
|
||||
|
||||
## Yazmadıklarınız {#what-you-did-not-write}
|
||||
|
||||
Bu sayfaya dönüp bir bakın. Üç küçük Python fonksiyonu yazdınız. Şunları **yazmadınız**:
|
||||
|
||||
* Bir JSON Schema. `a: int, b: int`, `add` şemasının *ta kendisidir*.
|
||||
* Bir istek işleyici. `tools/list`, `resources/read`, `prompts/get`: hepsi sizin yerinize sunulur.
|
||||
* Bir yetenek beyanı. `MCPServer` onu sizin yerinize yaptı.
|
||||
* Tek satır protokol. Sürüm anlaşması, JSON-RPC çerçevelemesi, yetenek değiş tokuşu: hepsi `mcp dev` ve `Client(mcp)` içinde oldu ve siz hiçbirini görmediniz.
|
||||
|
||||
SDK'nın bütün meselesi bu oran.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* **Host** LLM uygulamasıdır, **istemci** onun MCP konuşan yarısıdır, **sunucu** ise sizin oluşturduğunuz şeydir.
|
||||
* Araçları **model**, kaynakları **uygulama**, prompt'ları **kullanıcı** kontrol eder.
|
||||
* Her temel öğe için bir dekoratör: `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. Ad, açıklama ve şema fonksiyondan gelir.
|
||||
* İçinde `{param}` olan bir URI, somut kaynaklardan ayrı listelenen bir kaynak **şablonu** oluşturur.
|
||||
* Sunucunun **yetenekleri** sizin yerinize beyan edilir ve bir istemci yalnızca sunucunun beyan ettiklerini ister.
|
||||
* `Client(mcp)` sunucu nesnesine bellek içinde bağlanır: ilk günden test düzeneğiniz.
|
||||
|
||||
Sırada **[Gerçek bir host'a bağlanma](real-host.md)** var: bu sunucu, gerçekten, Claude Desktop'ın ya da bir IDE'nin içinde. Ardından **[Test etme](testing.md)**: bir sayfa, bir bellek içi istemci ve çalışıp çalışmadığını bir daha asla tahmin etmek zorunda kalmazsınız. Ondan sonra her temel öğenin kendi sayfası var; modelin yönettiğiyle başlıyoruz: **[Araçlar](../servers/tools.md)**.
|
||||
57
i18n/tr/pages/get-started/index.md
Normal file
57
i18n/tr/pages/get-started/index.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ed4a756b4c53c585, 97e2fb315b7fe398, 4d04f1c6f4bf6c1d, 577d73078fc62baf]
|
||||
tool: 1
|
||||
---
|
||||
# Başlarken {#get-started}
|
||||
|
||||
MCP'de ya da bu SDK'da yeni misiniz? Buradan başlayın. Bu sayfalar sizi sıfırdan
|
||||
çalışan, test edilmiş bir sunucuya götürür: [SDK'yı kurun](installation.md),
|
||||
[ilk sunucunuzu](first-steps.md) yazın, [onu gerçek bir host'a bağlayın](real-host.md) ve
|
||||
bellek içi bir istemciyle [test edin](testing.md).
|
||||
|
||||
## Kodu çalıştırma {#run-the-code}
|
||||
|
||||
Kod bloklarının tamamı doğrudan kopyalanıp kullanılabilir: hepsi eksiksiz, çalışan dosyalardır.
|
||||
|
||||
Takip etmek için bir bloğu `server.py` dosyasına yapıştırın ve MCP Inspector'da açın:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Kodu yazmanız (ya da kopyalamanız), düzenlemeniz ve yerelde çalıştırmanız **ŞİDDETLE önerilir**. Asıl meseleyi kendi editörünüzde kullanırken görürsünüz: ne kadar az kod yazdığınızı, otomatik tamamlamayı, daha hiçbir şeyi çalıştırmadan hataları yakalayan tür denetimlerini.
|
||||
|
||||
## Tahmin yürütmeyeceksiniz {#you-will-not-be-guessing}
|
||||
|
||||
Bu belgelerdeki her örnek, SDK'nın kendi deposunda [`docs_src/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/docs_src) altında duran eksiksiz bir dosyadır ve her biri SDK'nın test paketi tarafından **bellek içi bir istemci** aracılığıyla çalıştırılır:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_add() -> None:
|
||||
async with Client(mcp) as client:
|
||||
result = await client.call_tool("add", {"a": 1, "b": 2})
|
||||
assert result.structured_content == {"result": 3}
|
||||
```
|
||||
|
||||
Alt süreç yok, port yok, aktarım yok. `Client(mcp)` sunucu nesnesine doğrudan bağlanır.
|
||||
|
||||
SDK'daki bir değişiklik bu sayfalardan birindeki örneği bozarsa, sayfadan önce CI kırmızıya döner. Burada okuduğunuz kod, çalışan kodun ta kendisidir.
|
||||
|
||||
Bunu [Test etme](testing.md) sayfasında kendiniz de kullanacaksınız; kendi sunucularınızı da böyle test edersiniz.
|
||||
|
||||
## Bundan sonra nereye {#where-to-go-next}
|
||||
|
||||
Bir sunucuyu çalıştırdıktan sonra bu belgelerin geri kalanı bir kurs değil, bir başvuru kaynağıdır.
|
||||
Her sayfa kendi başına ayakta durur; bu yüzden doğrudan ihtiyacınız olana atlayın:
|
||||
|
||||
* Bir sunucunun ne sunduğu (araçlar, kaynaklar, prompt'lar) **[Sunucular](../servers/index.md)** bölümünde.
|
||||
* Kaydettiğiniz fonksiyonların içinde nelerin kullanılabildiği **[İşleyicinin içinde](../handlers/index.md)** bölümünde.
|
||||
* Sunucuyu istemcilerin önüne çıkarma (stdio, HTTP, mevcut FastAPI uygulamanız) **[Sunucunuzu çalıştırma](../run/index.md)** bölümünde.
|
||||
* Diğer tarafı, yani MCP sunucularını *kullanan* bir uygulamayı oluşturma **[İstemciler](../client/index.md)** bölümünde.
|
||||
47
i18n/tr/pages/get-started/installation.md
Normal file
47
i18n/tr/pages/get-started/installation.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
translation:
|
||||
sections: [6e2f9bab94d5ed36, 8cf653388f69e28b, 6fd9ea2f65de0df6]
|
||||
tool: 1
|
||||
---
|
||||
# Kurulum {#installation}
|
||||
|
||||
Python SDK, PyPI'da [`mcp`](https://pypi.org/project/mcp/) adıyla yayımlanır. **Python 3.10+** gerektirir.
|
||||
|
||||
Bu belgeler, güncel kararlı sürüm hattı olan **v2**'yi anlatır:
|
||||
|
||||
=== "uv"
|
||||
|
||||
```bash
|
||||
uv add "mcp[cli]"
|
||||
```
|
||||
|
||||
=== "pip"
|
||||
|
||||
```bash
|
||||
pip install "mcp[cli]"
|
||||
```
|
||||
|
||||
!!! note "v1'den mi geliyorsunuz?"
|
||||
v2, geriye dönük uyumsuz değişiklikler içeren bir ana sürümdür; **[Geçiş kılavuzu](../migration.md)**
|
||||
bunların her birini ele alır. *Paketiniz* `mcp`'ye bağımlıysa ve henüz geçişe hazır değilse,
|
||||
sabitlenmemiş bir çözümlemenin 1.x hattında kalması için `<2` üst sınırını koruyun (örneğin `mcp>=1.28,<2`).
|
||||
|
||||
## Neler kurulur {#what-gets-installed}
|
||||
|
||||
SDK'yı kullanmak için bunların hiçbirini bilmeniz gerekmez. Yine de her bağımlılığın ne işe yaradığını merak ediyorsanız:
|
||||
|
||||
* `mcp-types`: tüm protokol türleri (istekler, sonuçlar, içerik blokları), SDK ile birebir aynı sürüm numarasıyla yayımlanan ayrı bir paket olarak gelir. `mcp`'ye bağımlı kod bunu `mcp.types` takma adı üzerinden içe aktarır (bu belgelerdeki her `from mcp.types import ...` satırı böyledir); `mcp_types`'ı doğrudan yalnızca `mcp-types`'ı SDK olmadan kuran bir projede içe aktarın.
|
||||
* [`anyio`](https://anyio.readthedocs.io/): asenkron çalışma zamanı. SDK'nın tamamı anyio üzerine yazıldığı için hem `asyncio` hem de `trio` üzerinde çalışır.
|
||||
* [`pydantic`](https://docs.pydantic.dev/): her `mcp.types` modelinin temeli; ayrıca tüm şema üretimi ve doğrulaması.
|
||||
* [`httpx2`](https://pypi.org/project/httpx2/): Streamable HTTP ve SSE *istemci* aktarımlarının arkasındaki HTTP istemcisi; server-sent events desteği yerleşik olarak gelir.
|
||||
* [`starlette`](https://www.starlette.io/), [`uvicorn`](https://www.uvicorn.org/), [`sse-starlette`](https://pypi.org/project/sse-starlette/) ve [`python-multipart`](https://pypi.org/project/python-multipart/): HTTP *sunucu* aktarımları.
|
||||
* [`jsonschema`](https://pypi.org/project/jsonschema/): bir aracın yapılandırılmış çıktısını, bildirdiği çıktı şemasına göre doğrular.
|
||||
* [`pyjwt[crypto]`](https://pyjwt.readthedocs.io/): yetkilendirme için OAuth token işleme.
|
||||
* [`opentelemetry-api`](https://opentelemetry-python.readthedocs.io/): yalnızca hafif API; bu sayede siz bir OpenTelemetry SDK'sı ve dışa aktarıcı kurmadıkça SDK'nın izleme middleware'inin hiçbir maliyeti olmaz.
|
||||
* [`typing-extensions`](https://typing-extensions.readthedocs.io/) ve [`typing-inspection`](https://pypi.org/project/typing-inspection/): Python 3.10'da modern tür özellikleri.
|
||||
* [`pywin32`](https://pypi.org/project/pywin32/): yalnızca Windows'ta, `stdio` alt süreç yönetimi için kullanılır.
|
||||
|
||||
## İsteğe bağlı ekler {#optional-extras}
|
||||
|
||||
* `mcp[cli]`, `mcp` komut satırı aracı (`mcp dev`, `mcp run`, `mcp install`) için [`typer`](https://typer.tiangolo.com/) ve [`python-dotenv`](https://pypi.org/project/python-dotenv/) paketlerini ekler. Geliştirme sırasında bunu istersiniz; dağıtılmış bir sunucuda gerekmeyebilir.
|
||||
* `mcp[rich]`, daha okunaklı sunucu log'ları için [`rich`](https://rich.readthedocs.io/) paketini ekler.
|
||||
183
i18n/tr/pages/get-started/real-host.md
Normal file
183
i18n/tr/pages/get-started/real-host.md
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
---
|
||||
translation:
|
||||
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
|
||||
tool: 1
|
||||
---
|
||||
# Gerçek bir host'a bağlanma {#connect-to-a-real-host}
|
||||
|
||||
**Host**, sunucunuzun sonunda içine girdiği uygulamadır: Claude Desktop, Claude Code, bir IDE. Kullanıcının konuştuğu şey host'tur. Onun içinde bir MCP **istemcisi** sunucunuzu bir alt süreç olarak başlatır ve onunla o sürecin stdin'i ve stdout'u üzerinden konuşur.
|
||||
|
||||
Yani bir host'a bağlanmak tek bir eylemdir: ona **sunucunuzu başlatan komutu** söylersiniz. Bu sayfadaki her şey (iki CLI komutu, üç JSON dosyası) aynı komutu koyacağınız farklı bir yerdir.
|
||||
|
||||
## Tek sunucu, her host {#one-server-every-host}
|
||||
|
||||
```python title="server.py" hl_lines="3 33-34"
|
||||
--8<-- "docs_src/real_host/tutorial001.py"
|
||||
```
|
||||
|
||||
İki araç ve bir kaynak, tek dosya. Bu dosyayla ilgili üç şey aşağıdaki her host için önemlidir:
|
||||
|
||||
* Argümansız `mcp.run()` bir **stdio** sunucusu başlatır: bloklar, protokol mesajlarını stdin'den okur ve stdout'a yazar. Bu sayfadaki her host'un konuştuğu aktarım budur. Host dosyanızı bir alt süreç olarak başlatır ve bu iki kanalın sahibidir; bağlanmanın her zaman yalnızca "işte komut" olmasının nedeni de budur. Hiçbir zaman port seçmezsiniz ve hiçbir şey bir portu dinlemez.
|
||||
* `run()`, `if __name__ == "__main__":` altındadır. Aşağıdaki her şey bu dosyayı çalıştırmak yerine **import eder**; bu yüzden korumasız bir `run()`, modülü herhangi bir şey yüklediği anda bir sunucu başlatırdı.
|
||||
* Sunucu nesnesi, `mcp` adında modül düzeyinde bir globaldir. `mcp run`'ın aradığı ad budur (`server` ve `app` de olur). Başka bir ad verirseniz açıkça belirtirsiniz: `mcp run server.py:bookshop`.
|
||||
|
||||
Bu, bu sayfadaki son Python satırı. Buradan aşağısı tamamen host yapılandırması.
|
||||
|
||||
## Başlatma komutu {#the-launch-command}
|
||||
|
||||
Aşağıdaki her host aynı komutu alır:
|
||||
|
||||
```bash
|
||||
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
Hepsi için tek komut, çünkü `uv run --with` SDK'yı anında yeni bir ortama çözümler: herhangi bir dizinden çalışır, ne bir projeye ne de etkinleştirilecek bir sanal ortama ihtiyaç duyar. Bu, burada başka her yerden daha önemlidir; çünkü host sunucunuzu sizin kabuğunuzdan değil, *kendi* çalışma dizininden ve neredeyse boş bir ortamla başlatır.
|
||||
|
||||
Bu aynı zamanda `mcp install`'un sizin için Claude Desktop'ın yapılandırmasına yazdığı komuttur (aşağıda). Böylece elle yazdığınız ile aracın ürettiği, aracın eklediği tam sürüm sabitlemesi dışında örtüşür.
|
||||
|
||||
!!! tip "Host `uv`'yi bulamazsa"
|
||||
Host sunucunuzu asgari bir `PATH` ile başlatır ve `uv` bunun üzerinde olmayabilir. Yalın
|
||||
`uv`'yi `which uv` (macOS/Linux) veya `where uv` (Windows) çıktısındaki mutlak yolla değiştirin.
|
||||
`mcp install`'un yazdığı da tam olarak budur.
|
||||
|
||||
!!! note "Bu sayfa yerel senaryoyu anlatır"
|
||||
Buradaki her şey sunucunuzu host'un bulunduğu makinede çalıştırır: host dosyanızı stdio
|
||||
üzerinden başlatır. Kişisel ya da tek makinelik bir araç için bu tam olarak doğru olandır.
|
||||
Dosyanıza sahip *olmayan* insanlara bir sunucu vermek için komut değil **URL** dağıtırsınız:
|
||||
aynı `mcp` nesnesi, Streamable HTTP üzerinden sunulur. **[Sunucunuzu çalıştırma](../run/index.md)**
|
||||
bu kararı tek bir tabloda verir, **[Dağıtım ve ölçekleme](../run/deploy.md)** ise oradan
|
||||
gerçek bir ana bilgisayar adına giden yoldur.
|
||||
|
||||
Ve host, içinde bir MCP istemcisi olan bir uygulamadan başka bir şey değildir; bu yüzden kendi
|
||||
Python kodunuz host rolünü oynayabilir: **[İstemci aktarımları](../client/transports.md)**
|
||||
bu aynı dosyayı `stdio_client(...)` ile bir alt süreç olarak başlatır, **[Test etme](testing.md)**
|
||||
ise ona hiç süreç olmadan bellek içinde bağlanır.
|
||||
|
||||
## Claude Desktop {#claude-desktop}
|
||||
|
||||
SDK'nın sizin için yapılandırabildiği tek host:
|
||||
|
||||
```bash
|
||||
uv run mcp install server.py
|
||||
```
|
||||
|
||||
Hepsi bu. `mcp install` sunucunun adını okumak için dosyayı import eder, Claude Desktop'ın yapılandırma dosyasını bulur ve başlatma komutunu içine yazar. Bu arada yolunuzu mutlak bir yola çevirir, sizin yapmanıza gerek kalmaz.
|
||||
|
||||
Kafa karıştıracak bir şey yok. Yazdığı kayıt şu:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"Bookshop": {
|
||||
"command": "/absolute/path/to/uv",
|
||||
"args": [
|
||||
"run",
|
||||
"--frozen",
|
||||
"--with",
|
||||
"mcp[cli]==2.0.0",
|
||||
"mcp",
|
||||
"run",
|
||||
"/absolute/path/to/server.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bu, yukarıdaki bölümdeki başlatma komutunun üç eklemeli hâli: `uv`'nin mutlak yolu, `uv` yakınında bulunduğu bir kilit dosyasını asla yeniden yazmasın diye `--frozen` ve kurulu `mcp` sürümüne tam bir sabitleme. Şurada bulunan `claude_desktop_config.json` dosyasına yazılır:
|
||||
|
||||
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
|
||||
Bu dosyayı elle yazabilirsiniz. `mcp install`, bunu yaparken klasik hatayı (göreli yol) yapmayın diye vardır.
|
||||
|
||||
Claude Desktop'tan tamamen çıkın (yalnızca penceresini kapatmayın) ve yeniden açın.
|
||||
|
||||
!!! warning
|
||||
Claude Desktop'ın yapılandırma *dizini* henüz yoksa `mcp install`, `Claude app not found`
|
||||
hatasıyla başarısız olur. Claude Desktop'ı kurun ve bir kez çalıştırın: dizini oluşturan budur.
|
||||
|
||||
!!! tip
|
||||
Claude Desktop sunucunuzu kendi sürecinde başlatır; bu yüzden kabuğunuzun ortam değişkenleri
|
||||
orada yoktur. `uv run mcp install server.py -v API_KEY=abc123` (veya `-f .env`) bunları kaydın
|
||||
`env` alanına işler. `--name` kayıt adını geçersiz kılar; varsayılan olarak sunucunun `name`
|
||||
değeridir.
|
||||
|
||||
## Claude Code {#claude-code}
|
||||
|
||||
Düzenlenecek dosya yok. Sunucuyu `claude` CLI ile kaydedin; `--` sonrasındaki her şey başlatma komutudur.
|
||||
|
||||
```bash
|
||||
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
`bookshop`'un bağlı olduğunu ve araçlarının listelendiğini doğrulamak için bir Claude Code oturumunda `/mcp` çalıştırın.
|
||||
|
||||
## Cursor {#cursor}
|
||||
|
||||
Proje kök dizininizde `.cursor/mcp.json` dosyasını oluşturun.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"bookshop": {
|
||||
"command": "uv",
|
||||
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Aynı `command` artı `args`, Claude Desktop'ın kullandığı aynı `mcpServers` anahtarı altında. Sunucu, Cursor'ın MCP ayarlarında iki araç da listelenmiş olarak görünür.
|
||||
|
||||
## VS Code {#vs-code}
|
||||
|
||||
Proje kök dizininizde `.vscode/mcp.json` dosyasını oluşturun.
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"bookshop": {
|
||||
"type": "stdio",
|
||||
"command": "uv",
|
||||
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Cursor'ın dosyasından iki fark var ve yalnızca bu ikisi: sarmalayıcı anahtar `mcpServers` değil `servers`'tır ve her kayıt `type`'ını bildirir. Güven iletişim kutusunu onaylayın; ardından Command Palette'teki **MCP: List Servers**, `bookshop`'u çalışır durumda gösterir.
|
||||
|
||||
!!! note
|
||||
**GitHub Copilot** eklentisiyle oturum açılmış VS Code 1.99 veya üzeri gerekir (Copilot Free
|
||||
yeterli) ve Copilot Chat **Agent** modunda olmalıdır; çünkü başka hiçbir mod araç çağırmaz.
|
||||
|
||||
## Görünmüyor {#it-doesnt-show-up}
|
||||
|
||||
Herhangi bir host yapılandırmasına dokunmadan önce başlatma komutunu kendiniz çalıştırın:
|
||||
|
||||
```bash
|
||||
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
Hiçbir şey yazdırmaz ve geri dönmez. Bu sessizlik doğrudur: stdio sunucusu, bir host'un stdin'de ilk konuşan taraf olmasını bekler (durdurmak için `Ctrl-C`). Asıl hata bir traceback ya da anında çıkıştır; artık onu bir host üzerinden tahmin etmeye çalışmak yerine okuyabilirsiniz.
|
||||
|
||||
Bu komut oturup beklediğinde, geriye kalan neredeyse her zaman üç şeyden biridir:
|
||||
|
||||
* **Göreli yol.** Host sunucunuzu kaydı yaptığınız dizinden değil, *kendi* çalışma dizininden başlatır. `/absolute/path/to/server.py` gereken yerde `server.py` yazmak, açık ara en yaygın hatadır. Host `uv`'yi de bulamıyorsa o yol da mutlak olmalıdır.
|
||||
* **Host hâlâ eski yapılandırmasını çalıştırıyor.** Host'lar yapılandırmalarını başlarken okur. Özellikle Claude Desktop'tan, `claude_desktop_config.json` üzerindeki bir düzenleme etkili olmadan önce *tamamen çıkılması* (yalnızca penceresinin kapatılması değil) ve yeniden açılması gerekir.
|
||||
* **Yönlendirilen pencerenin dışında stdout'a bir şey ulaştı.** stdio'da stdout protokolün *ta kendisidir*. SDK, hizmet verirken flush edilmiş başıboş çıktıyı stderr'e yönlendirir; ancak o andan önce stdout'a flush edilen çıktı (bir sarmalayıcı betiğin echo'su, tamponsuz bir süreçte import anında bir `print()`) ya da yorumlayıcı çıkışında boşaltılan tamponlanmış bir `print()`, host'a bozuk bir mesaj verir ve host bağlantıyı keser. stderr işleyicisi her kaydı flush eden varsayılan `logging` yapılandırmasıyla log tutun; özel işleyiciler de stdout'tan uzak durmalıdır. Ayrıntıların tamamı **[Logging](../handlers/logging.md)** sayfasında.
|
||||
|
||||
Claude Desktop her sunucu için bir log tutar: `mcp-server-<NAME>.log` sunucunuzun stderr'idir, bağlantılar için `mcp.log`'un yanında; macOS'te `~/Library/Logs/Claude`, Windows'ta `%APPDATA%\Claude\logs` altında.
|
||||
|
||||
Bu üçünün ötesindeki her şey için doğru sayfa **[Sorun giderme](../troubleshooting.md)**.
|
||||
|
||||
## Özet {#recap}
|
||||
|
||||
* **Host** (Claude Desktop, bir IDE), sunucunuzu stdio üzerinden bir alt süreç olarak başlatan bir MCP istemcisi çalıştırır. Bağlanmak, ona tek bir başlatma komutu vermek demektir.
|
||||
* O komut `uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py`: etkinleştirilecek venv yok, her dizinden çalışır.
|
||||
* **Claude Desktop**, `mcp install`'un sizin için yapılandırdığı tek host'tur. Aynı komutu (artı `uv`'nin mutlak yolu, `--frozen` ve kurulu sürüme tam bir sabitleme) `claude_desktop_config.json` dosyasına yazar; böylece sizin yapmanıza hiç gerek kalmaz.
|
||||
* **Claude Code** için `claude mcp add bookshop -- <launch command>`. **Cursor** için `mcpServers` altında `.cursor/mcp.json`. **VS Code** için `servers` altında `.vscode/mcp.json`, her kayıtta bir `type` ile.
|
||||
* Her yerde mutlak yollar, yapılandırmasını düzenledikten sonra host'u yeniden başlatın ve SDK dışında hiçbir şeyin stdout'a yazmasına izin vermeyin.
|
||||
|
||||
Bu sayfadaki her host aynı dosyaya, aynı komutla bağlandı. O dosyanın neler *sunabileceği* ise bu belgelerin geri kalanı: **[Araçlar](../servers/tools.md)**, **[Kaynaklar](../servers/resources.md)** ve stdio dışındaki tüm aktarımlar için **[Sunucunuzu çalıştırma](../run/index.md)**.
|
||||
116
i18n/tr/pages/get-started/testing.md
Normal file
116
i18n/tr/pages/get-started/testing.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
translation:
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
|
||||
tool: 1
|
||||
---
|
||||
# Test etme {#testing}
|
||||
|
||||
Python SDK, **bellek içi aktarıma** sahip bir `Client` sınıfıyla gelir: ona sunucu nesnenizi geçirirsiniz, o da doğrudan bağlanır.
|
||||
|
||||
Alt süreç yok. Port yok. Hiç aktarım yok. FastAPI'nin `TestClient`'ıyla aynı fikir.
|
||||
|
||||
## Temel kullanım {#basic-usage}
|
||||
|
||||
Tek bir aracı olan basit bir sunucunuz olduğunu varsayalım:
|
||||
|
||||
```python title="server.py"
|
||||
--8<-- "docs_src/testing/tutorial001.py"
|
||||
```
|
||||
|
||||
Aşağıdaki testi çalıştırmak için iki ek (geliştirme) bağımlılığına ihtiyacınız var:
|
||||
|
||||
=== "uv"
|
||||
|
||||
```bash
|
||||
uv add --dev pytest inline-snapshot
|
||||
```
|
||||
|
||||
=== "pip"
|
||||
|
||||
```bash
|
||||
pip install pytest inline-snapshot
|
||||
```
|
||||
|
||||
!!! info
|
||||
Bu belgeler [`pytest`](https://docs.pytest.org/en/stable/)'i zaten bildiğinizi varsayar.
|
||||
|
||||
Aşağıdaki test, sonuç nesnesinin tamamını tek satırda doğrulamak için
|
||||
[`inline-snapshot`](https://15r10nk.github.io/inline-snapshot/latest/) kullanır. Bir testin
|
||||
çıktısını, gördüğünüz `snapshot(...)` değişmezi olarak kaydeder. Kullanmak istemezseniz
|
||||
import satırını silin ve herhangi bir testte olduğu gibi ilgilendiğiniz alanları doğrulayın
|
||||
(`result.content[0].text == "3"`).
|
||||
|
||||
Şimdi test:
|
||||
|
||||
```python title="test_server.py"
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
from mcp import Client
|
||||
from mcp.types import CallToolResult, TextContent
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def anyio_backend(): # (1)!
|
||||
return "asyncio"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(): # (2)!
|
||||
async with Client(mcp, raise_exceptions=True) as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_call_add_tool(client: Client):
|
||||
result = await client.call_tool("add", {"a": 1, "b": 2})
|
||||
# Drop the server identity stamp in `_meta`; it is not what this test is about.
|
||||
result.meta = None
|
||||
assert result == snapshot(
|
||||
CallToolResult(
|
||||
content=[TextContent(type="text", text="3")],
|
||||
structured_content={"result": 3},
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
1. `trio` kullanıyorsanız bunun yerine `"trio"` döndürün. Ayrıntılar için [anyio belgelerine](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on) bakın.
|
||||
2. Fixture, bağlı bir istemci üretir. `client` alan her test, aynı sunucuya yeni bir bellek içi bağlantı alır.
|
||||
|
||||
İşte bu kadar! Artık testlerinizi daha fazla senaryoyu kapsayacak şekilde genişletebilirsiniz.
|
||||
|
||||
## Neden `raise_exceptions=True`? {#why-raise_exceptionstrue}
|
||||
|
||||
İki farklı şey ters gidebilir ve bu bayrak yalnızca birine dokunur.
|
||||
|
||||
**Araçlarınızdan** birinin içindeki bir istisna, protokol hatası değildir. `is_error=True` taşıyan
|
||||
normal bir sonuca dönüşür ve model mesajı okur. `raise_exceptions` bunu değiştirmez: onunla da
|
||||
onsuz da `call_tool` aynı `is_error=True` sonucunu döndürür. Bu konuda ayrı bir sayfa var:
|
||||
**[Hataları ele alma](../servers/handling-errors.md)**.
|
||||
|
||||
Araç gövdesinin **dışındaki** bir hata ise farklıdır. `Client(mcp)`'nin size verdiği bağlantıda
|
||||
sunucu, istemci görmeden önce onu genel bir `"Internal server error"` mesajına dönüştürerek
|
||||
temizler. Beklenmedik bir çökmenin ayrıntılarını uzak bir çağırana asla sızdırmamalısınız. Bir
|
||||
testte ise tam olarak *istemediğiniz* şey budur ve `raise_exceptions=True`'nun değiştirdiği de
|
||||
budur: testiniz temizlenmiş mesaj yerine gerçek mesajı görür.
|
||||
|
||||
Testlerde açık bırakın. Üretim kodunda bir anlamı yoktur.
|
||||
|
||||
## Varsayılan olarak süreç içi {#in-process-by-default}
|
||||
|
||||
!!! note
|
||||
`Client(mcp)` süreç içinde bağlanır ve varsayılan olarak **nesilden bağımsızdır**: sunucuyu
|
||||
yoklar ve uygun protokol yolunu seçer. Testiniz eski nesle özgü anlamları (örnekleme (sampling)
|
||||
veya elicitation (kullanıcıdan bilgi isteme) itmesi, `message_handler`) sınıyorsa `mode="legacy"`
|
||||
olarak sabitleyin ve orada `raise_exceptions=True`'yu kaldırın: eski nesil bir bağlantı zaten
|
||||
hiçbir zaman temizleme yapmaz ve bayrak, hatayı testinizde değil sunucu görevinin içinde
|
||||
yeniden fırlatır.
|
||||
|
||||
Bu belgelerin, örneklerinin çalıştığı sözünü verebilmesinin nedeni de o tek satırdır: her örnek
|
||||
dosya SDK'nın kendi test paketinde çalıştırılır, neredeyse hepsi tam olarak bu istemci
|
||||
üzerinden. SDK'nın kendi üzerinde kullandığı aracın aynısını kullanıyorsunuz.
|
||||
|
||||
Çalışan, test edilmiş bir sunucunuz var. Onu gerçek bir uygulamanın (Claude Desktop, bir IDE)
|
||||
içine koymak **[Gerçek bir host'a bağlanma](real-host.md)** sayfasında; sunmanın diğer tüm
|
||||
yolları ise **[Sunucunuzu çalıştırma](../run/index.md)** sayfasında.
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue