docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
8 changed files with 89737 additions and 54 deletions
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.
|
||||
Loading…
Reference in a new issue