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

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

146
i18n/tr/pages/run/asgi.md Normal file
View file

@ -0,0 +1,146 @@
---
translation:
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 70b9ece244ca1b0c, cba78e052898c3f6, f06bdb541cb0b469, fb82d526320b7cc3]
tool: 1
---
# Mevcut bir uygulamaya ekleme {#add-to-an-existing-app}
`mcp.run("streamable-http")` sizin için bir web sunucusu başlatır. Bazen bunu istemezsiniz: MCP sunucunuz daha büyük bir web uygulamasının bir parçasıdır ya da zaten bir ASGI dağıtımınız vardır.
Bunun için `mcp.streamable_http_app()` bir **Starlette uygulaması** döndürür.
Starlette uygulaması bir ASGI uygulamasıdır; dolayısıyla ASGI barındırabilen her şey (uvicorn, Hypercorn, başka bir Starlette, FastAPI) MCP sunucunuzu da barındırabilir.
## Uygulama {#the-app}
```python title="server.py" hl_lines="12"
--8<-- "docs_src/asgi/tutorial001.py"
```
`app` sıradan bir ASGI uygulamasıdır. Herhangi bir ASGI sunucusuna verin:
```console
uvicorn server:app
```
MCP endpoint'i `/mcp` yolundadır; yani istemci `http://127.0.0.1:8000/mcp` adresine bağlanır.
Uygulama hâlihazırda iki şey taşır:
* Tek bir rota, `/mcp`: Streamable HTTP endpoint'i.
* `mcp.session_manager`'ı başlatan bir **lifespan** (yaşam döngüsü); bu nesne, canlı her oturumun arka plan işlerinin sahibidir.
Uygulamayı tek başına çalıştırın (`uvicorn server:app`), ikisini de hiç düşünmeniz gerekmez.
!!! tip
`streamable_http_app()`, `mcp.run("streamable-http", ...)` ile aynı anahtar sözcük argümanlarını
alır; `port` hariç: port, uygulamayı sunan şeye aittir. `host` hâlâ kabul edilir ama burada
hiçbir şeye bağlanmaz; gerçekte neyi denetlediğini **[Dağıtım ve ölçekleme](deploy.md)** açıklar.
Seçeneklerin kendisi **[Sunucunuzu çalıştırma](index.md)** sayfasında.
`mcp.sse_app()` aynısını, yerini yenisine bırakmış SSE aktarımı için yapar.
## Siz aksini söyleyene kadar yalnızca localhost {#localhost-only-until-you-say-otherwise}
Varsayılan olarak uygulama **yalnızca** localhost'a gönderilen istekleri yanıtlar. `streamable_http_app()`
hangi ana bilgisayar adının arkasında sunulacağını bilemez; bu yüzden DNS rebinding korumasını
olabilecek en güvenli izin listesiyle etkinleştirir. Kendi makinenizde bu tam olarak doğru olandır.
Gerçek bir ana bilgisayar adının arkasına dağıtıldığında ise, `transport_security=` parametresine
gerçekte sunduğunuz adların izin listesini geçirene kadar **her istek `421 Misdirected Request` ile
reddedilir** demektir. Sizin yazdığınız hiçbir şeye önce danışılmaz bile. Bu izin listesi ve çalışan
bir uygulama ile gerçek bir ana bilgisayar adı arasındaki diğer her şey
**[Dağıtım ve ölçekleme](deploy.md)** sayfasında.
## Mount etme {#mounting-it}
MCP sunucusu daha büyük bir uygulamanın *parçası* olduğu anda uygulamayı bir `Mount` içine koyarsınız. Bunu yaptığınız anda da lifespan sizin sorununuz olur:
```python title="server.py" hl_lines="18-21 25-26"
--8<-- "docs_src/asgi/tutorial002.py"
```
* `Mount("/", ...)` ile varsayılan `/mcp` yolu birlikte endpoint'i `/mcp` yolunda tutar. Starlette rotaları sırayla dener ve `Mount("/")` **her** yolla eşleşir; bu yüzden kendi rotalarınız listede ondan *önce* gelir. Ondan sonraki hiçbir şeye ulaşılamaz.
* `lifespan` fonksiyonu, **ana** uygulamanın ömrü boyunca `mcp.session_manager.run()` içine girer. Herkesin unuttuğu satır budur.
* `mcp.session_manager` ancak `streamable_http_app()` çağrıldıktan *sonra* var olur. Rotaların modül düzeyinde kurulmasının ve yöneticiye yalnızca lifespan içinde dokunulmasının nedeni budur.
Starlette'in `Host` rotası aynı şekilde çalışır: yola göre değil ana bilgisayar adına göre yönlendirmek için `Mount("/", ...)` yerine `Host("mcp.example.com", ...)` koyun. Lifespan kuralı değişmez, aktarım güvenliği kuralı da. `Host("mcp.example.com", ...)` rotası yalnızca o ana bilgisayar adına gönderilen istekleri alır, ancak aktarımın kendi Host izin listesi (**[Dağıtım ve ölçekleme](deploy.md)**) yine de önce çalışır. Listede `"mcp.example.com"` yoksa bu rota o isteklerin her birini `421` ile yanıtlar.
!!! warning "Ana uygulama lifespan'in sahibidir"
`streamable_http_app()`, `session_manager.run()`'ı döndürdüğü Starlette'in lifespan'ine bağlar;
ancak **mount edilmiş bir alt uygulamanın lifespan'i hiçbir zaman çalışmaz**. Uygulamayı mount
edin, o yerleşik lifespan ölü kod olur. ASGI yığınınızın en üstünde hangi uygulama duruyorsa,
kendi lifespan'inde `mcp.session_manager.run()` içine girmelidir.
!!! check
`lifespan=lifespan` satırını silin ve sunucuyu başlatın. Başlar. Rota çözülür.
Sonra `/mcp` yoluna gelen ilk istek şu hatayla başarısız olur:
```text
RuntimeError: Task group is not initialized. Make sure to use run().
```
Oturum yöneticisini kendi `run()`'ından başka hiçbir şey başlatmaz.
## İki sunucu, tek uygulama {#two-servers-one-app}
Her `MCPServer`, kendi oturum yöneticisi olan ayrı bir uygulamadır. İstediğiniz kadarını mount edin; her yöneticiye tek ana lifespan'den girin:
```python title="server.py" hl_lines="27-30 35-36"
--8<-- "docs_src/asgi/tutorial003.py"
```
* `AsyncExitStack` iki yöneticiye de girer; birlikte başlar, ters sırada kapanırlar.
* Endpoint'ler `/notes/mcp` ve `/tasks/mcp`: mount öneki artı varsayılan yol.
## Yolu değiştirme {#changing-the-path}
Sondaki o `/mcp`, `streamable_http_path` değeridir. Bunu `"/"` yapın, mount öneki genel yolun tamamı olur:
```python title="server.py" hl_lines="25"
--8<-- "docs_src/asgi/tutorial004.py"
```
Artık istemciler `/notes/mcp` yoluna değil `/notes` yoluna bağlanır.
## Tarayıcı istemcileri için CORS {#cors-for-browser-clients}
Tarayıcı tabanlı bir istemcinin sizden iki izne ihtiyacı vardır: MCP istek başlıklarını **göndermek** ve MCP'nin geri gönderdiği başlığı **okumak**. İkisi de ana uygulamadaki CORS yapılandırmasıdır ve yukarıdaki aktarım güvenliği izin listesinin bununla uyuşması gerekir:
```python title="server.py" hl_lines="27-30 33 35-49"
--8<-- "docs_src/asgi/tutorial005.py"
```
* `allow_headers` herkesin unuttuğu yarıdır. Tarayıcı her MCP isteği için **preflight** yapar; çünkü `Content-Type: application/json` ve `Mcp-*` istek başlıkları CORS güvenli listesinde değildir ve preflight'ın izin vermediği bir başlık, tarayıcının asla göndermediği bir istek demektir. (`allow_headers=["*"]` da çalışır: Starlette bir preflight'ı ne istediyse onunla yanıtlar.)
* `expose_headers=["Mcp-Session-Id"]` okuma yarısıdır. Streamable HTTP oturum kimliğini bu yanıt başlığında döndürür ve tarayıcılar, CORS adlarıyla açığa çıkarmadıkça yanıt başlıklarını JavaScript'ten gizler. Bu olmadan istemci ikinci isteğini asla yapamaz.
* `allow_origins` MCP'nin değil sizin kararınızdır. Kesin olun ve yukarıdaki `allowed_origins=` ile birebir eşleştirin: CORS'u tarayıcı uygular, ama sunucu `Origin`'i kendisi de denetler ve aktarımın güvenmediği bir origin, temiz bir preflight'tan sonra bile `403` alır.
* `allow_methods` Streamable HTTP'nin kullandığı üç yöntemi listeler: ileti göndermek için `POST`, sunucudan istemciye akışı açmak için `GET`, oturumu sonlandırmak için `DELETE`.
## Özel rotalar {#custom-routes}
`@mcp.custom_route()` aynı uygulamada düz bir HTTP endpoint'i kaydeder; dağıtılan her servisin ihtiyaç duyduğu ama MCP ile hiçbir ilgisi olmayan şeyler için: sağlık denetimi, OAuth callback'i.
```python title="server.py" hl_lines="15-17"
--8<-- "docs_src/asgi/tutorial006.py"
```
* İşleyici düz Starlette'tir: `Request`'ten `Response`'a bir `async` fonksiyon.
* `streamable_http_app()` her özel rotayı alır. `app.routes` artık `/mcp` ve `/health`.
* `GET /health`, ortada hiç MCP olmadan `{"status": "ok"}` yanıtını verir.
!!! warning
Özel rotalar, sunucunun geri kalanı doğrulansa bile **hiçbir zaman kimlik doğrulamasından
geçmez**. Bu kasıtlıdır: sağlık denetimleri ve OAuth callback'leri herhangi bir token var
olmadan önce erişilebilir olmak zorundadır. Bunların arkasına özel hiçbir şey koymayın.
## Özet {#recap}
* `mcp.streamable_http_app()` tek rotası `/mcp` olan bir Starlette uygulaması döndürür. Herhangi bir ASGI sunucusu onu çalıştırabilir.
* Varsayılan olarak uygulama yalnızca localhost'a gönderilen istekleri yanıtlar; gerçek bir ana bilgisayar adının arkasında ise `transport_security=` parametresine bir izin listesi geçirene kadar her şeyi `421` ile reddeder. Bu konu ve üretime giden yolun geri kalanı **[Dağıtım ve ölçekleme](deploy.md)** sayfasında.
* `Mount` (veya `Host`) onu daha büyük bir Starlette ya da FastAPI uygulamasının içine koyar.
* **Mount etmek yerleşik lifespan'i devre dışı bırakır.** Ana uygulamanın lifespan'i `mcp.session_manager.run()` içine girmelidir, yoksa ilk istek başarısız olur.
* Tek uygulamada birden fazla sunucu, birden fazla mount ve her oturum yöneticisine giren tek bir lifespan demektir.
* `streamable_http_path="/"` endpoint'i mount önekinin kendisine taşır.
* Tarayıcı istemcilerinin CORS'a ihtiyacı vardır: `Mcp-*` istek başlıkları için `allow_headers`, yanıt için `expose_headers=["Mcp-Session-Id"]`.
* `@mcp.custom_route()`, `/mcp`'nin yanına düz, kimlik doğrulaması olmayan HTTP endpoint'leri ekler.
Sunucu gerçek bir URL'den erişilebilir olduğunda **[İstemci](../client/index.md)** ona bir sunucu nesnesi yerine o URL ile bağlanır.

View file

@ -0,0 +1,130 @@
---
translation:
sections: [d62c13457fc4a534, 80e73abaca6e0652, d1dc4c54cd00ec9c, 14ad3bc7904036bb, 5225f127bc1b9c77, fe1626fdd5aad1da, 4556cb7ea1a04a31]
tool: 1
---
# Yetkilendirme {#authorization}
Streamable HTTP üzerinden MCP sunucunuz sıradan bir web hizmetidir ve onu her web hizmetini koruduğunuz gibi korursunuz: OAuth 2.1 bearer token'larıyla.
OAuth terimleriyle sunucunuz bir **kaynak sunucusudur**. Hiç kimsenin oturumunu açmaz ve hiçbir zaman token vermez. Tek bir şey yapar: her istekteki `Authorization` başlığına bakar ve içindeki token'ın geçerli olup olmadığına karar verir.
Bu sayfa sunucu tarafını anlatır. Yetkilendirme sunucunuzu keşfeden ve token'ı alan istemci ise **[OAuth istemcileri](../client/oauth-clients.md)** sayfasında.
## Üç taraf {#the-three-parties}
* **Yetkilendirme sunucusu** kullanıcıların oturumunu açar ve erişim token'ları verir. Bunu siz yazmazsınız. Kimlik sağlayıcınızdır (Auth0, Keycloak, Entra ya da kendinizinki).
* **Kaynak sunucusu** MCP sunucunuzdur. Her istekte token'ı doğrular.
* **İstemci** hangi yetkilendirme sunucusuna güvendiğinizi keşfeder, ondan bir token alır ve size `Authorization: Bearer <token>` olarak geri gönderir.
Üçgenin tamamı bu. Bu sayfadaki her şey ortadaki maddeyle ilgili.
## Token doğrulayıcı {#a-token-verifier}
SDK'nın geçerli bir token'ın neye benzediği konusunda bir fikri yoktur. Bunu **`TokenVerifier`**'ı uygulayarak siz söylersiniz:
```python title="server.py" hl_lines="12-14 19-24"
--8<-- "docs_src/authorization/tutorial001.py"
```
* `TokenVerifier` tek bir asenkron metodu olan bir protokoldür. `verify_token`, `Authorization` başlığındaki ham token'ı alır; geçerliyse bir **`AccessToken`**, değilse `None` döndürür. Uygulanacak başka bir şey yok.
* Buradaki, token'ı bir tabloda arar. Gerçek bir doğrulayıcı JWT imzasını doğrular ya da yetkilendirme sunucusunun token-introspection endpoint'ini çağırır. O kod sizindir; SDK onu yalnızca çağırır.
* `token_verifier=` ve `auth=` her zaman birlikte kullanılır. Birini diğeri olmadan geçirirseniz `MCPServer(...)` daha tek bir istek sunmadan `ValueError` fırlatır.
`AuthSettings`, kaynak sunucunuzun dışa dönük yüzüdür:
* `issuer_url`: token'larınızı veren yetkilendirme sunucusu.
* `resource_server_url`: bu MCP endpoint'inin herkese açık URL'si. Bir token'ın *hangi* kaynak için olduğunu belirtir ve keşif belgesi burada bulunur.
* `required_scopes`: her token bunların hepsini taşımalıdır.
!!! tip
SDK deposundaki `examples/servers/simple-auth/` dizininde, gerçek bir yetkilendirme sunucusunun
[RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) endpoint'ini çağıran bir `IntrospectionTokenVerifier` var. Üretimdeki çoğu doğrulayıcı bu biçimdedir.
## HTTP üzerinden elinize geçenler {#what-you-get-over-http}
Yetkilendirme HTTP başlıklarında yaşar; bu yüzden yalnızca HTTP aktarımlarında vardır. Dağıttığınız aktarımda çalıştırın: `mcp.run(transport="streamable-http")` onu `http://127.0.0.1:8000/mcp` adresinde sunar; gerisi **[Sunucunuzu çalıştırma](index.md)** sayfasında. Uygulamanın artık iki rotası var:
```text
/mcp
/.well-known/oauth-protected-resource/mcp
```
Siz tek bir araç kaydettiniz. İkinci rota SDK'nındır.
### Keşif {#discovery}
Bu well-known yoluna `GET` isteği gönderin; doğrudan `AuthSettings` değerlerinizden oluşturulmuş **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) Protected Resource Metadata** belgesini alırsınız:
```json
{
"resource": "http://127.0.0.1:8000/mcp",
"authorization_servers": ["https://auth.example.com/"],
"scopes_supported": ["notes:read"],
"bearer_methods_supported": ["header"]
}
```
Sunucunuzu hiç duymamış bir istemci içeri giden yolu bu belgeyle bulur: `authorization_servers` alanını okur ve token almak için oraya gider. Bunun hiçbirini siz yazmadınız.
!!! check
`/mcp` yolunu token olmadan (ya da doğrulayıcınızın `None` döndürdüğü bir token'la) çağırın; istek
kapıda durdurulur:
```text
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Authentication required", resource_metadata="http://127.0.0.1:8000/.well-known/oauth-protected-resource/mcp"
{"error": "invalid_token", "error_description": "Authentication required"}
```
Hiçbir şey ayrıştırılmadı, hiçbir araç çalışmadı. `WWW-Authenticate` içindeki o `resource_metadata`
işaretçisi de keşfi otomatik hale getiren şeydir: 401 -> meta veri belgesi -> yetkilendirme sunucusu -> token -> yeniden deneme.
!!! warning
Bunların hiçbiri `stdio`'yu korumaz. Bir pipe'ın `Authorization` başlığı yoktur; bu yüzden orada
`token_verifier`'a hiç danışılmaz. Bir `stdio` sunucusunun güvenlik sınırı, onu başlatan süreçtir.
Aynısı testlerde kullandığınız bellek içi `Client(mcp)` için de geçerlidir: doğrudan sunucu nesnesine
bağlanır ve yetkilendirme dahil HTTP katmanını atlar.
## Çağıranın kimliği {#the-callers-identity}
Herhangi bir işleyicinin içinde **`get_access_token()`**, doğrulayıcınızın geçerli istek için döndürdüğü `AccessToken`'dır:
```python title="server.py" hl_lines="4 32-35"
--8<-- "docs_src/authorization/tutorial002.py"
```
* Araçlarda, kaynaklarda ve prompt'larda çalışır ve elden ele geçirilecek bir şey yoktur: auth middleware'i onu istek başına bir bağlam değişkeninde saklar.
* Geriye **doğrulayıcınızın oluşturduğu nesnenin aynısı** döner: `client_id`, `scopes`, `subject`, `expires_at` ve eklediğiniz ek `claims`. Araç başına kurallar için kanca budur: kapsamları okuyun ve reddedin.
* Kimliği doğrulanmış bir HTTP isteğinin dışında `None` döndürür. Bellek içinde ve `stdio` üzerinden her zaman `None`'dır.
`whoami` aracını `Authorization: Bearer alice-token` ile çağırın; model şunu okur:
```text
alice (scopes: notes:read)
```
## SDK'nın üstlenmediği yarı {#the-half-the-sdk-doesnt-do}
SDK size kaynak sunucusu yarısını verir: doğrula, duyur, reddet. Size bir giriş sayfası, bir onay ekranı ya da bir token vermez.
Üç tarafın birden hareketini izlemek için SDK deposundaki `examples/servers/simple-auth/` örneğini çalıştırın (küçük bir yetkilendirme sunucusu ve tam bu sayfadaki gibi kurulmuş bir kaynak sunucusu), ardından keşif ve token akışının tamamını görmek için `examples/clients/simple-auth-client/` istemcisini ona yönlendirin.
!!! info
İkinci bir kurucu argümanı daha var: `auth_server_provider=`. MCP sunucunuzun içine eksiksiz bir
yetkilendirme sunucusu gömer. MCP yetkilendirme spesifikasyonunun üzerine kurulduğu AS/RS ayrımından
daha eskidir. Yeni sunucular onu kullanmamalıdır.
Bir yetkilendirme sunucusu, kullanıcının onay ekranından tıklayarak geçmesi yerine kurumsal bir kimlik sağlayıcının imzalı beyanını da kabul edebilir; SDK bu alışverişin iki tarafını da destekler. Bu grant ve onu sunan istemci **[Kimlik beyanı](../client/identity-assertion.md)** sayfasında.
## Özet {#recap}
* Streamable HTTP üzerinden sunucunuz bir OAuth 2.1 **kaynak sunucusudur**: token'ları doğrular, asla vermez.
* `TokenVerifier` entegrasyon yüzeyinin tamamıdır: tek bir asenkron metot, token girer, `AccessToken | None` çıkar.
* `token_verifier=` ve `auth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...])` her zaman birlikte kullanılır.
* SDK, [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) Protected Resource Metadata belgesini `/.well-known/oauth-protected-resource/...` altında yayımlar ve kimliği doğrulanmamış istekleri, `WWW-Authenticate` başlığı ona işaret eden bir 401 ile yanıtlar. Keşif hikâyesinin tamamı bu.
* Herhangi bir işleyicide `get_access_token()`, kimin çağırdığını söyler.
* Yetkilendirme bir HTTP meselesidir. `stdio` ve bellek içi istemci onu hiç görmez.
İstemci yarısı (yetkilendirme sunucunuzu keşfetme ve token'ı sizin yerinize alma) **[OAuth istemcileri](../client/oauth-clients.md)** sayfasında. Kullanıcıdan kimlik istemek yerine bir kimliği *beyan eden* istemci ise **[Kimlik beyanı](../client/identity-assertion.md)** sayfasında.

180
i18n/tr/pages/run/deploy.md Normal file
View file

@ -0,0 +1,180 @@
---
translation:
sections: [28221886b198784f, f88ea1f1614f3a1d, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, e758745df6fb7b0a]
tool: 1
---
# Dağıtım ve ölçekleme {#deploy-scale}
Sunucunuz çalışıyor. Şimdi ona gerçek bir ana bilgisayar adı ve arkasında birden fazla worker gerekiyor.
Bunların neredeyse hiçbiri MCP'nin işi değil. ASGI sunucusunu, süreç yöneticisini, yük dengeleyiciyi siz getirirsiniz. Bu sayfada olan, gerçekten MCP'nin işi *olan* şeylerin kısa listesi: her dağıtımın önünde duran tek bir ayar ve "birden fazla worker" ifadesinin SDK'nın davranışını değiştirdiği iki yer.
## Her şeyden önce: Host izin listesi {#before-anything-else-the-host-allowlist}
`streamable_http_app()` hangi ana bilgisayar adının arkasında sunulacağını bilemez, bu yüzden en güvenli yanıtı varsayar: localhost. `transport_security=` verilmediğinde uygulama **DNS-rebinding korumasını** açar ve bir isteği yalnızca `Host` başlığı `127.0.0.1:<port>`, `localhost:<port>` veya `[::1]:<port>` ise kabul eder. `Origin` başlığı varsa, aynısının `http://` biçimi olmak zorundadır. Kendi makinenizde bu tam olarak doğru davranıştır: kötü niyetli bir web sayfasının, `127.0.0.1`'e yeniden bağladığı bir DNS adı üzerinden yerel sunucunuzu yönetmesini engeller.
Gerçek bir ana bilgisayar adının arkasına dağıtıldığında, aynı varsayılan siz aksini söyleyene kadar **her isteği** reddeder. Denetim, MCP'ye benzeyen herhangi bir şey çalışmadan önce yapılır; yani sizin yazdığınız hiçbir şeye danışılmaz bile:
```text
421 Misdirected Request Invalid Host header the Host is not in the allowlist
403 Forbidden Invalid Origin header the Origin is not in the allowlist
```
Çözüm `transport_security=`. Gerçekten sunduğunuz şeyi izin listesine alın:
```python title="server.py" hl_lines="2 13-17"
--8<-- "docs_src/deploy/tutorial001.py"
```
* `allowed_hosts` girdileri tam eşleşen dizgelerdir: `"mcp.example.com"` yalın bir `Host` başlığıyla, `"mcp.example.com:*"` ise herhangi bir portla eşleşir. İkisini de listeleyin.
* `allowed_origins` yalnızca tarayıcılar için önemlidir, çünkü başka hiçbir şey `Origin` göndermez. **[Mevcut bir uygulamaya ekleme](asgi.md)** sayfasındaki CORS yapılandırmasının sunucu tarafındaki ikizidir.
* `Host` başlığını zaten denetleyen bir ters vekil sunucunun arkasında, dürüst yapılandırma denetimi kapatmaktır: `TransportSecuritySettings(enable_dns_rebinding_protection=False)`.
* localhost dışında bir `host=` geçirmek (örneğin `host="mcp.example.com"`) o ana bilgisayar adını izin listesine **almaz**. Yalnızca localhost varsayılanının korumayı devreye sokmasını engeller; bu da her Host ve Origin'in kabul edilmesi demektir. Bunun yerine ne demek istediğinizi `transport_security=` ile söyleyin.
!!! check
`transport_security=security` argümanını silin ve uygulamayı yine de dağıtın. Başlar, `/mcp`
yönlendirilir ve her istek (düz bir `curl` dahil) şöyle döner:
```text
HTTP/1.1 421 Misdirected Request
Invalid Host header
```
Bu sözcükleri istemci tarafında bulamazsınız. `421`, bir JSON-RPC hatası değil, düz metin bir
HTTP yanıtıdır; bu yüzden MCP istemcisi genel bir aktarım hatası fırlatır. Beğenmediği ana
bilgisayar adı yalnızca **sunucunun** log'unda, tek bir uyarı olarak görünür. Yeni dağıtılmış ve
her bağlantıyı reddeden bir sunucu, aksi kanıtlanana kadar bir Host izin listesi sorunudur.
**[Sorun giderme](../troubleshooting.md)** de buradan başlar.
## Worker'lar ve kimin yapışkan olması gerektiği {#workers-and-who-has-to-be-sticky}
Ana bilgisayar adı yanıt vermeye başladıktan sonra, arkasına birden fazla worker koyun. Bunun için SDK'da bir ayar yoktur; bir Starlette uygulamasını, herhangi bir ASGI uygulamasını ölçeklediğiniz gibi ölçeklersiniz: nesneyi, fork etmeyi bilen bir şeye verirsiniz:
```console
uvicorn server:app --workers 4
```
Dört süreç, tek bir soket. Ve şimdi her dağıtımın yanıtlaması gereken soru: **bir isteğin, bir öncekini gören worker'a ulaşması gerekiyor mu?**
**2026-07-28** protokolünü konuşan bir istemci için, hayır. Modern bir istek, kendi içinde eksiksiz tek bir POST'tur: önünde `initialize` el sıkışması yok, yanıtta `Mcp-Session-Id` yok, ikinci bir isteğin geri *döneceği* hiçbir şey yok. Herhangi bir worker'a yönlendirin.
Bu, açtığınız bir kip değildir. `stateless_http=True` öyle olmalıymış gibi görünür, ancak aktarım `MCP-Protocol-Version` istek başlığına göre yönlendirme yapar, modern bir isteği modern işleyiciye verir ve **döner**. `stateless_http`'yi okuyan satır bu dönüşten *sonra* gelir. Mesele bayrağın 2026-07-28 yolunda yok sayılması değil; o satıra hiç ulaşılmamasıdır. `stateless_http` yalnızca **eski nesil** bacak için bir ayardır; modern yol ise yapısı gereği oturumsuzdur.
Spesifikasyonun 2025-11-25 veya daha eski bir sürümündeki eski nesil bir istemci için yanıt o bayrağa bağlıdır:
| İstemcinin protokol sürümü | Oturum | Yük dengeleyicinin yapması gereken |
| --- | --- | --- |
| **2026-07-28** | Yok. `Mcp-Session-Id` hiçbir zaman ayarlanmaz. | Hiçbir şey. Herhangi bir worker herhangi bir isteğe hizmet verir. |
| **2025-11-25 ve öncesi** (varsayılan) | `Mcp-Session-Id`, tek bir worker'ın belleğinde tutulur. | **Yapışkan oturumlar.** Farklı bir worker'a ulaşan bir devam isteği `404` *"Session not found"* alır. |
| **2025-11-25 ve öncesi**, `stateless_http=True` ile | Yok. | Hiçbir şey. Bedeli, sunucudan istemciye geri kanal (back-channel) (örnekleme (sampling), itmeli elicitation, `roots/list`) ve devam ettirilebilirliktir. |
Yapışkan oturumlar ve eski nesil bacağın bedeli kendi sayfasında: **[Eski nesil istemcilere hizmet verme](legacy-clients.md)**; iki neslin kendisi ise **[Protokol sürümleri](../protocol-versions.md)** sayfasında. Burada önemli olan yanıtın biçimi: *2026-07-28'de zaten durumsuzsunuz ve yapılandırılacak hiçbir şey yok.*
Sayfanın geri kalanı, durumsuz olmanın size **sağlamadığı** iki şey.
## Worker'lar arasında `requestState` {#requeststate-across-workers}
**[Çok turlu](../handlers/multi-round-trip.md)** (multi-round-trip) bir araç, istemcinin gidip alması gereken bir şeye (bir onay, bir seçim, bir kimlik bilgisi) ihtiyaç duyar; bu yüzden bir yanıt yerine bir soru döndürür ve yeniden denemede işini bitirir. İki tur arasında istemci, sunucunun bastığı opak bir `request_state` token'ı tutar. Yeniden denemede sunucunun o token'ı yeniden açması gerekir.
*Hangi anahtarla mühürlenmiş?* Varsayılan olarak, sunucunun oluşturulurken `os.urandom(32)` ile ürettiği bir anahtarla. `--workers 4` altında bu, dört süreçte dört oluşturma demektir: dört farklı anahtar, hiçbir yere yazılmamış, hiç paylaşılmamış, yeniden başlatmada kaybolan.
İşte hiçbir şey yapılandırmayan bir sunucuda, harekete geçmeden önce soran bir araç:
```python title="server.py" hl_lines="14 20"
--8<-- "docs_src/deploy/tutorial002.py"
```
İlk tur worker A'ya ulaşır. Worker A, `refund:120` değerini **kendi** anahtarıyla mühürler ve token'ı döndürür. İstemci soruyu bir insanın önüne koyar, evet yanıtını alır ve yeniden dener. Yeniden deneme yepyeni bir HTTP isteğidir.
!!! check
O yeniden denemenin worker B'ye ulaşmasına izin verin. B, kendisinin basmadığı bir token'ın
mührünü açmaya çalışır, açamaz ve turun tamamını reddeder. `refund` hiç çağrılmaz; istemci bir
JSON-RPC hatası alır:
```json
{
"code": -32602,
"message": "Invalid or expired requestState",
"data": {"reason": "invalid_request_state"}
}
```
Bu mesaj **sabittir**. Süresi dolmuş, kurcalanmış, farklı argümanlara karşı yeniden oynatılmış
ya da (gerçek bir dağıtımda açık ara en yaygın neden) kardeş bir worker tarafından mühürlenmiş
olsun: istemciye her seferinde aynı şey söylenir, böylece iletilen veri hangi denetimin başarısız
olduğunu asla açığa vurmaz. Gerçek neden, sunucunun log'unda tek bir `WARNING` satırıdır:
```text
requestState rejected on tools/call: unknown key
```
Tek worker'la çalışıp ikide *ara sıra* başarısız olmaya başlayan çok turlu bir araç budur. İki
turun yine de aynı sürece ulaşması gerekir; bu yüzden tam olarak yük dengeleyicinizin onları
ayırdığı sıklıkta başarısız olur.
İki tur iki bağımsız HTTP isteğidir ve onları birbirinden ayıran birçok sıradan şey vardır: istek başına dengeleyen bir vekil sunucu, arada kopan bir bağlantı, bir dağıtım ya da yeniden başlatma, `request_state`'i kalıcı olarak saklamış ve bambaşka bir süreçten devam eden bir istemci (**[Döngüyü kendiniz yürütme](../handlers/multi-round-trip.md#driving-the-loop-yourself)**). Bunların her biri "farklı bir worker" demektir.
Çözüm tek bir argüman. Ancak **iki** yarısı var.
```python title="server.py" hl_lines="1 12 14"
--8<-- "docs_src/deploy/tutorial003.py"
```
* **`keys=[...]`** herkesin bulduğu yarıdır. Her örneğe aynı gizli anahtarı (en az 32 bayt) verin; böylece her örnek, herhangi bir kardeşinin bastığı şeyin mührünü açabilir. `keys[0]` mühürler, listedeki her anahtar mühür açar; bu döndürme halkasıdır. Onu kesinti olmadan nasıl çevireceğiniz **[Anahtarları döndürme](../handlers/multi-round-trip.md#rotating-keys)** bölümünde.
* **Sunucunun adı** neredeyse kimsenin bulamadığı yarıdır ve anahtarı paylaştıktan sonra örnekler arası yeniden denemelerin hâlâ başarısız olmasının nedenidir. Her mühürlü token, sunucunun `name` değerini bir **audience claim** olarak taşır ve dönüşte katı biçimde denetlenir. Aynı koddan oluşturulmuş iki örneğin adı aynıdır ve bunu hiç fark etmezler. Onlara farklı adlar verin (`MCPServer(f"billing-{POD}")` iyi bir gözlemlenebilirlik alışkanlığı gibi okunur) ve her örnekler arası yeniden deneme, anahtar paylaşılmış olsun olmasın, tam olarak yukarıdaki gibi reddedilir. Log `unknown key` yerine `audience` der; istemci aradaki farkı anlayamaz.
Gizli anahtarı bir kez basın ve her örneğe aynı değeri verin. 32 bayttan az geçirirseniz SDK'nın kendi hata mesajının çalıştırmanızı söylediği komut budur:
```console
python -c "import secrets; print(secrets.token_hex(32))"
```
!!! warning "Aynı anahtarlar *ve* aynı ad"
Çok örnekli bir dağıtım ikisini de paylaşmak zorundadır. Örnek başına adlar sizin için
vazgeçilmezse, filoya bunun yerine tek bir açık audience verin: `RequestStateSecurity(keys=[...], audience="billing")`.
Böylece her örnek, adı ne olursa olsun `"billing"` altında basar ve kabul eder.
Mühürle ilgili geri kalan her şey **[`requestState`'i koruma](../handlers/multi-round-trip.md#protecting-requeststate)** bölümünde: neyi bağladığı, tur başına `ttl` (varsayılan olarak 600 saniye), kendi codec'inizi getirme, yapılandırılmamış varsayılanın `stdio` üzerinde neden tam olarak doğru olduğu. Bu sayfanın tüm katkısı iki maddelik bir denetim listesi: *aynı anahtarlar, aynı ad.*
!!! info
Hiç `InputRequiredResult` yazmamış olsanız bile bu yoldasınız. Parametreleri `Resolve(...)`
kullanan bir araç (**[Bağımlılıklar](../handlers/dependencies.md)**) çok turlu bir araçtır ve
SDK onun `request_state`'ini onun adına basar ve mühürler. Aynı varsayılan anahtar, worker'lar
arasında aynı başarısızlık, aynı çözüm.
## Replikalar arasında değişiklik bildirimleri {#change-notifications-across-replicas}
Bir istemcinin `subscriptions/listen` akışı uzun ömürlü tek bir yanıttır; bu yüzden tüm ömrü boyunca tek bir replikaya bağlı kalır. **Farklı** bir replikada yayımlanan bir `ctx.notify_resource_updated(...)` çağrısının ona ulaşması gerekir.
İkisi arasındaki bağlantı noktası `SubscriptionBus`'tır. Bir sunucuya hangi bus'ı verirseniz, her yayının gittiği ve her açık akışın dinlediği bus odur; bu yüzden her replikaya aynı bus'ı verin:
```python title="server.py" hl_lines="2 7 9"
--8<-- "docs_src/deploy/tutorial004.py"
```
Dağıtım (fan-out) tarafında hiçbir şey, bir akışın hangi sunucu nesnesine bağlı olduğuyla ilgilenmez. Tek bir `InMemorySubscriptionBus` tutan iki sunucu zaten böyle davranır: birinde bir listen akışı açın, diğerinde `edit_note`'u çağırın ve akış bundan haberdar olur. O bellek içi bus yalnızca tek bir süreç içindeki sunucu nesnelerini kapsar; bu da onu dağıtım değil, model yapar:
* Gerçek süreçler arasında **SDK size yardımcı olabilecek hiçbir bus sunmaz.** `SubscriptionBus`, kendi pub/sub altyapınız (Redis, NATS, zaten çalıştırdığınız her neyse) üzerinde gerçeklediğiniz ve `MCPServer(subscriptions=...)` olarak geçirdiğiniz iki metotlu bir `Protocol`'dür (`publish` ve `subscribe`). Taslak ve sözleşme **[Abonelikler](../handlers/subscriptions.md#scaling-past-one-process)** sayfasında.
* Bus dört küçük tipli olay taşır, asla JSON-RPC taşımaz. Onaylama, filtreleme ve akış yaşam döngüsü SDK'da kalır; bu yüzden bus'ınız protokolü bozamaz, yalnızca olayları süreçler arasında taşıyabilir.
* Akışlar devam ettirilebilir **değildir** ve olaylar yeniden **oynatılmaz**. Bir replikayı kaybetmek akışlarını düşürür; istemciler yeniden dinler ve yeniden getirir. Paylaşılacak bir olay deposu ve yapılandırılacak başka bir şey yoktur. Ölçeklemenin gerçekten yalnızca aynısının fazlası olduğu tek yer burası.
## SDK'nın size vermedikleri {#what-the-sdk-does-not-give-you}
Bir `MCPServer` bir uygulama sunucusu değil, bir protokol gerçeklemesidir. Bundan sonra aramaya çıkacağınız dağıtım ayarları bilerek eksiktir:
* **`workers=` yok.** `mcp.run("streamable-http")` tam olarak bir uvicorn süreci başlatır ve başlatacağı tek şey de odur. Çoklu süreç, `streamable_http_app()`'in ASGI'yi zaten neyle dağıtıyorsanız ona verilmesidir: `uvicorn --workers`, gunicorn, platformunuzun süreç yöneticisi. Bu sayfa bilerek onların hiçbiri için bir öğretici değildir; kendi belgeleri, buradaki bir kopyanın olacağından daha iyidir.
* **Sağlık denetimi rotası yok.** `@mcp.custom_route("/health", methods=["GET"])` yanıtın tamamıdır ve sunucunun geri kalanı kimlik doğrulamalı olsa bile bu rota asla kimlik doğrulaması yapmaz. Bu, bir canlılık yoklaması için doğru, özel olan herhangi bir şey için yanlıştır. **[Mevcut bir uygulamaya ekleme](asgi.md#custom-routes)** bir örnek gösterir.
* **Üretim ayarları nesnesi yok.** `MCPServer` üzerinde zaman aşımlarını, TLS'yi, zarif kapanmayı ya da bağlantı sınırlarını yazabileceğiniz bir yer yoktur, çünkü bunların hiçbiri onun işi değildir. ASGI sunucunuza aittirler ve onları orada yapılandırırsınız. Yapıcının *aldığı* bir avuç ayar **[Sunucunuzu çalıştırma](index.md)** sayfasında.
* **Sunulan bir `EventStore` yok, 2026-07-28'de buna gerek de yok.** Devam ettirilebilirlik, eski nesil durumlu bacağın bir özelliğidir; modern bir alışveriş tek bir POST, tek bir yanıt ve devam ettirilecek hiçbir şeydir.
## Özet {#recap}
* Varsayılan olarak uygulama yalnızca localhost'a gönderilen istekleri yanıtlar. `transport_security=TransportSecuritySettings(allowed_hosts=[...], allowed_origins=[...])` yayına çıkış kapısıdır: onu geçirene kadar gerçek bir ana bilgisayar adının arkasındaki her istek bir `421`'dir ve nedeni yalnızca sunucunun log'undadır.
* 2026-07-28'de oturum yoktur ve bir yük dengeleyicinin yapışacağı hiçbir şey yoktur. `stateless_http=True` yalnızca eski nesle ait bir ayardır, çünkü modern bir istek o bayrak hiç okunmadan yönlendirilir ve yanıtlanır.
* Varsayılan `requestState` anahtarı, süreç başına basılan `os.urandom(32)`'dir. Farklı bir worker'a ulaşan çok turlu bir yeniden deneme `-32602` *"Invalid or expired requestState"* ile başarısız olur.
* Çözüm `RequestStateSecurity(keys=[...])` **ve** her örnekte aynı sunucu adıdır. Ad, token'ın varsayılan audience claim'idir. Aynı anahtarlar, aynı ad.
* Değişiklik bildirimleri replikalar arasında paylaşılan tek bir `SubscriptionBus` üzerinden geçer. SDK'nın tek gerçeklemesi süreç içidir; kendi pub/sub'ınız üzerindeki iki metotlu `Protocol`'ü yazmak size düşer.
* `workers=` yok, sağlık rotası yok, üretim ayarları nesnesi yok. Kendi ASGI sunucunuzu getirin.
Gerçek bir ana bilgisayar adının önünde gereken diğer şey bir token: **[Yetkilendirme](authorization.md)**.

156
i18n/tr/pages/run/index.md Normal file
View file

@ -0,0 +1,156 @@
---
translation:
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
tool: 1
---
# Sunucunuzu çalıştırma {#running-your-server}
`mcp.run()` sunucuyu başlatır.
Vermeniz gereken tek karar **aktarım**: sunucunuzla istemcisi arasındaki baytların gerçekte nasıl taşındığı.
## Aktarım seçme {#pick-a-transport}
| Aktarım | Ne olduğu | Ne zaman |
|---|---|---|
| `stdio` | Host, dosyanızı bir alt süreç olarak başlatır ve onunla stdin ve stdout'u üzerinden konuşur. | Yerel sunucular. Varsayılan. |
| `streamable-http` | Bir portu dinleyen gerçek bir HTTP sunucusu. | Dağıttığınız her şey. |
| `sse` | Eski HTTP aktarımı. | Hiçbir zaman. |
!!! warning
SSE, 2025-03-26 protokol sürümünde yerini Streamable HTTP'ye bıraktı.
`mcp.run(transport="sse")` kendi `sse_path=` ve `message_path=` seçenekleriyle hâlâ çalışır,
ancak yalnızca henüz geçiş yapmamış istemciler için vardır. Üzerine yeni bir şey inşa etmeyin.
## `mcp.run()` {#mcprun}
```python title="server.py" hl_lines="12-13"
--8<-- "docs_src/run/tutorial001.py"
```
* `run()` senkrondur. Sunucunun ömrü boyunca bloke kalır.
* Argüman verilmezse aktarım `stdio` olur.
* `if __name__ == "__main__":` altında durur, çünkü sunucunuzu yükleyen her şey (`mcp dev`, `mcp run`, `mcp install`, testleriniz) bu dosyayı **içe aktarır**. Bu koruma, bir içe aktarmanın çalışan bir sunucuya dönüşmesini engeller.
### stdio {#stdio}
Yapılandırılacak hiçbir şey yok. Host, dosyanızı bir alt süreç olarak başlatır, istekleri stdin'ine yazar ve yanıtları stdout'undan okur.
Kendiniz çalıştırın, sonucunu görürsünüz:
```console
python server.py
```
Hiçbir şey yazdırmaz ve geri dönmez. Bir host'un ilk sözü söylemesini stdin'de bekliyordur.
Bu aynı zamanda stdout'un **iletişim hattının ta kendisi** olduğu anlamına gelir. Hizmet verirken SDK bu hattı özel bir dosya tanımlayıcısına taşır ve stdout'a *flush edilen* çıktıyı (miras aldığı stdout'a yazan bir alt süreç, flush edilmiş bir `print()`) akışı bozamayacağı stderr'e yönlendirir. Hizmet başlamadan *önce* stdout'a flush edilen çıktı (echo yapan bir sarmalayıcı betik, import sırasında tamponlanmadan yapılan bir print) yine hatta düşer; yorumlayıcı çıkışta boşaltana kadar tamponda kalan bir `print()` de öyle. Gerçekten istediğiniz çıktı için doğru araç `logging` modülüdür: işleyicisi her kaydı oluştuğu anda stderr'e flush eder. Ayrıntıların tamamı **[Log tutma](../handlers/logging.md)** sayfasında.
### Deneyin {#try-it}
```console
uv run mcp dev server.py
```
Inspector, gerçek bir host'un yaptığının aynısını yapar: `server.py` dosyasını bir alt süreç olarak başlatır ve ona stdio üzerinden bağlanır.
Ona hiç port vermediniz. Zaten yok.
## Streamable HTTP {#streamable-http}
Aynı sunucuyu bunun yerine bir porta koymak için aktarımı (ve seçeneklerini) `run()` içinde belirtin:
```python title="server.py" hl_lines="13"
--8<-- "docs_src/run/tutorial002.py"
```
Bu tek satır bir Starlette uygulaması kurar ve onu uvicorn ile sunar. İstemciler `http://127.0.0.1:3001/mcp` adresine bağlanır.
Her aktarımın kendi anahtar sözcük argümanları vardır ve hepsi `run()` üzerindedir:
* `host` / `port`: nerede dinleneceği. Varsayılanlar `127.0.0.1` ve `8000`.
* `streamable_http_path`: MCP endpoint'inin bulunduğu yol. Varsayılan `/mcp`.
* `json_response=True`: her POST'a SSE akışı yerine tek bir JSON gövdesiyle yanıt verir. Bu gövdede yanıttan başka hiçbir şeye yer yoktur; bu yüzden istek sırasında istemciye geri çağrı yapan bir araç (`ctx.elicit()`, örnekleme (sampling)) bu ayakta `NoBackChannelError` fırlatır ve sürmekte olan çağrıya bağlı bildirimler (`ctx.report_progress()` ile bildirilen ilerleme, çağrıya özel log mesajları) düşürülür; bağımsız `GET` akışı ilgisiz olanları taşımaya devam eder.
* `stateless_http=True`: istek başına yeni bir aktarım, oturum takibi yok.
* `max_request_body_size`: bayt cinsinden kabul edilen en büyük POST gövdesi. Varsayılan olarak 4 MiB;
daha büyük istekler, ayrıştırma veya oturum oluşturma öncesinde HTTP 413 alır. Bunu yalnızca meşru
MCP mesajları bu boyutu aştığında yükseltin.
* `event_store`, `retry_interval`, `transport_security`: kaldığı yerden devam edebilme ve DNS rebinding koruması. localhost dışında bir yere dağıtım yapana kadar bekleyebilirler; `transport_security` konusunu **[Dağıtım ve ölçekleme](deploy.md)** ele alır.
!!! warning
Aktarım seçenekleri `run()`'a gider, `MCPServer(...)`'a **değil**. Kurucu, sunucunuzun ne
*olduğunu* tanımlar: ad, sürüm, talimatlar. `run()` ise nasıl sunulduğunu tanımlar. Bunu
tersine çevirirseniz, daha MCP devreye bile girmeden Python yanıt verir:
```text
TypeError: MCPServer.__init__() got an unexpected keyword argument 'port'
```
`run()` kısa yoldur. Daha fazlasına ihtiyaç duyduğunuz an (sunucunuzun mevcut bir uygulamanın içine mount edilmesi, tek süreçte iki sunucu, tarayıcı istemcileri için CORS) ASGI uygulamasını kendiniz kurar ve herhangi bir ASGI sunucusuna teslim edersiniz. Bu da **[Mevcut bir uygulamaya ekleme](asgi.md)** sayfasının konusu.
## Sunucu ayarları {#server-settings}
Çalıştırmayla ilgili birkaç şey aktarımla ilgili değildir. Bunlar kurucu argümanlarıdır:
```python title="server.py" hl_lines="3"
--8<-- "docs_src/run/tutorial003.py"
```
* `log_level`: `MCPServer(...)` kurulduğu anda `logging.basicConfig()` fonksiyonuna verilir. Bu, **kök** logger'ı yapılandırır; dolayısıyla yalnızca SDK'nınkilerin değil, kendi logger'larınızın düzeyini de belirler. Varsayılan `"INFO"`.
* `debug`: HTTP aktarımlarının kurduğu Starlette uygulamasına iletilir. Varsayılan `False`.
Her ikisi de çalışma zamanında geri okuyabileceğiniz `mcp.settings` üzerine yerleşir.
## `mcp` komutu {#the-mcp-command}
`[cli]` ekstrası tüm bunların etrafına küçük bir komut satırı aracı kurar.
`mcp dev`, sunucunuzu **MCP Inspector** altında çalıştırır:
```console
uv run mcp dev server.py
uv run mcp dev server.py --with pandas --with numpy
uv run mcp dev server.py --with-editable .
```
`--with`, kurduğu ortama paket ekler; `--with-editable` kendi paketinizi o ortama kurar. `PATH` değişkeninizde `npx` bulunmalıdır: Inspector bir Node.js uygulamasıdır.
`mcp run` dosyayı içe aktarır, sunucu nesnesini (modül düzeyinde bir `mcp`, `server` veya `app`) bulur ve üzerinde `run()` çağırır:
```console
uv run mcp run server.py
uv run mcp run server.py:bookshop
```
`:` soneki, nesnenin adı `mcp`, `server` veya `app` olmadığında onu belirtir.
`if __name__ == "__main__":` bloğunuz burada hiç çalışmaz: `mcp run`, `run()`'ı kendisi çağırır ve ilettiği tek seçenek `--transport` seçeneğidir.
`mcp install` sunucuyu **Claude Desktop**'a kaydeder; böylece uygulama onu sizin için başlatır:
```console
uv run mcp install server.py --name "Bookshop"
uv run mcp install server.py -v API_KEY=abc123 -f .env
```
`-v KEY=VALUE` ve `-f .env`, ortam değişkenlerini bu kayda işler. Claude Desktop sunucunuzu kendi sürecinde başlatır. Kabuğunuzun ortamı orada yoktur.
Claude Desktop, `mcp install` komutunun bildiği tek host'tur. Diğer tüm host'lar (Claude Code, Cursor, VS Code) aynı başlatma komutunu kendi yapılandırma dosyalarında alır; her biri **[Gerçek bir host'a bağlanma](../get-started/real-host.md)** sayfasında var.
`mcp version` kurulu SDK sürümünü yazdırır.
!!! tip
`mcp dev` ve `mcp run` yalnızca `MCPServer`'ı anlar. Düşük seviyeli `Server` ile geliştiriyorsanız
onu kendiniz çalıştırırsınız. Bkz. **[Düşük seviyeli Server](../advanced/low-level-server.md)**.
## Özet {#recap}
* **Aktarım**, baytların sunucunuza nasıl ulaştığıdır: yerel bir alt süreç için `stdio`, bir port için `streamable-http`. SSE'nin yerini yenisi aldı.
* `mcp.run()` aktarımı seçer. Argümansız `stdio`'dur ve bloke kalır.
* Her aktarım seçeneği (`host`, `port`, `streamable_http_path`, ...) `run()`'a verilen bir argümandır, asla `MCPServer(...)`'a değil.
* `run()`'ı `if __name__ == "__main__":` altında tutun. Sunucunuzu yükleyen her şey önce dosyayı içe aktarır.
* `log_level=` ve `debug=` kurucu argümanlarıdır; `mcp.settings` üzerine yerleşirler.
* Inspector için `mcp dev`, bir dosyayı çalıştırmak için `mcp run`, Claude Desktop için `mcp install`, sürüm için `mcp version`.
* Aktarım, sunucunuzun ne *olduğunu* asla değiştirmez: bu sayfadaki üç dosya da birebir aynı aracı sunar.
Sınır `run()`'ın kendisi olduğunda (sunucunuz zaten var olan bir uygulamanın içindeyse) adres **[Mevcut bir uygulamaya ekleme](asgi.md)**. Gerçek bir ana bilgisayar adı ve birden fazla worker **[Dağıtım ve ölçekleme](deploy.md)** sayfasında. İstemcilerinizden bazıları hâlâ 2025-11-25 veya daha eski bir spesifikasyon sürümündeyse, iyi haber **[Eski nesil istemcilere hizmet verme](legacy-clients.md)** sayfasında.

View file

@ -0,0 +1,137 @@
---
translation:
sections: [3d1663c18edc824c, d4fd37009a13f03d, af9f398a5a8b679a, 470c2dd144294d69, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
tool: 1
---
# Eski nesil istemcilere hizmet verme {#serving-legacy-clients}
MCP'nin iki protokol nesli var: `2025-11-25` spesifikasyon sürümüne kadar uzanan `initialize` el sıkışması nesli ve modern nesil olan `2026-07-28`. Bu ayrımın kendisini anlatan sayfa **[Protokol sürümleri](../protocol-versions.md)**.
Bu sayfa o ayrımın sunucu tarafını ele alır ve yanıt tek bir cümleye sığar: **zaten dağıttığınız `streamable_http_app()` her ikisine de hizmet verir.**
SDK her isteği `MCP-Protocol-Version` başlığına göre yönlendirir. `2026-07-28` belirten bir istek modern işleyiciye gider. El sıkışması neslinden bir sürüm belirten ya da hiç başlık taşımayan bir istek (2026 öncesi bir istemcinin `initialize` isteği tam da böyle gelir), o istemcilerin beklediği aktarıma gider: `initialize` el sıkışması, oturumlar, hepsi. Bu, istek başına, kodunuzdan önce ve o tek uygulama üzerinde olur.
Yani eski nesil istemci, *ona göre* bir şey inşa ettiğiniz bir hedef değil. Zaten yazdığınız sunucuya *bağlanan* bir şey. Hiçbir şey yapılandırmazsınız.
!!! note
Kelimenin tam anlamıyla hiçbir şey. `legacy=` diye bir seçenek yok, sürüm izin listesi yok,
bir nesli reddetmenin ya da devre dışı bırakmanın yolu yok: ne `streamable_http_app()`
üzerinde, ne `run()` üzerinde, ne de oturum yöneticisinde. İki nesil de her zaman açık. O
imzada nesle özgü bir anahtara en yakın şey `stateless_http`, ve bu sayfanın büyük kısmı da
ondan ibaret.
## Tek işleyici, iki nesil {#one-handler-both-eras}
İşte kullanıcıya bir şey sorması gereken bir araç ve onu çağıran her iki nesilden istemci:
```python title="server.py" hl_lines="24 37-38"
--8<-- "docs_src/legacy_clients/tutorial001.py"
```
`reserve`, modelin sağlamadığı tek bir şeye ihtiyaç duyar: kaç kopya. Bir araç bunu `Annotated[..., Resolve(ask_quantity)]` ile bildirir (ayrıntıların tamamı **[Bağımlılıklar](../handlers/dependencies.md)** sayfasında). `reserve` içinde hiçbir şey bir sürüm adı vermez, bir yetenek kontrol etmez ya da dallanmaz.
İki istemci **aynı anda**, aynı `mcp` nesnesi üzerinde açıktır. `mode="legacy"`, `initialize` el sıkışmasını çalıştırır: 2026 öncesi bir istemcinin açtığı bağlantının ta kendisi. Diğeri varsayılanı alır ve `2026-07-28` sürümünde karar kılar.
```text
2025-11-25 {'result': "Reserved 2 of 'Dune'."}
2026-07-28 {'result': "Reserved 2 of 'Dune'."}
```
Aynı sunucu, aynı işleyici, aynı yanıt. Özelliğin tamamı bu.
*Nasıl* olduğu üzerinde durmaya değer, çünkü iki istemciye aynı soru bambaşka iki yoldan soruldu. `2026-07-28` bağlantısında sunucunun istek gönderebileceği bir kanal yoktur; bu yüzden `Resolve` soruyu araç sonucunun içinde döndürdü ve istemci çağrıyı yanıtla birlikte yeniden denedi (**[Çok turlu istekler (multi-round-trip)](../handlers/multi-round-trip.md)**). `2025-11-25` bağlantısında böyle bir şey yoktur; orada `Resolve`, çağrının ortasında canlı bir `elicitation/create` isteği gönderdi ve bekledi. İkisini de siz yazmadınız. `Resolve` bağlantının anlaşılan sürümünü okur ve seçer; araç gövdeniz her iki durumda da bir `AcceptedElicitation` görür.
!!! tip
Nesiller arası bu taşınabilirlik, `Resolve`'un üzerine inşa edilecek API olmasının
*nedenidir*. Eski kardeşi `ctx.elicit()`
(**[Elicitation (kullanıcıdan bilgi isteme)](../handlers/elicitation.md)**) yalnızca
`elicitation/create` gönderir; dolayısıyla yalnızca eski nesil bir bağlantıda çalışır.
`2026-07-28` bağlantısında çağrı başarısız olur. Bir araç hâlâ onu kullanıyorsa çözüm bir
sürüm kontrolü değil, yukarıda gördüğünüzdür.
## Eski nesil bir oturumun size maliyeti {#what-a-legacy-session-costs-you}
Yönlendirme bedava. Oturum değil.
`2026-07-28` bağlantısı **oturumsuzdur**: her istek tek başına durur ve modern işleyici asla `Mcp-Session-Id` vermez. Eski nesil bağlantı bunun tam tersidir. 2026 öncesi bir istemci `initialize` gönderdiği anda SDK bir `Mcp-Session-Id` üretir, onu bir yanıt başlığında döndürür ve istemcinin sonraki isteklerinin bulabilmesi için arkasında canlı bir kayıt tutar: anlaşılan sürüm, açık akışlar, oturumu yürüten bir arka plan görevi.
Bu kayıt **süreç içi, düz bir `dict`'tir**. Dağıtık bir oturum deposu yoktur ve bir tane takmanın yolu da yoktur.
Tek worker'da bu görünmez. İki worker'da ise sorunun tamamı budur: `Mcp-Session-Id` taşıyan ve onu üretmemiş bir worker'a düşen bir istek o dict'te hiçbir şey bulamaz ve yanıt araç sonucu değil, bir `404` (`Session not found`) olur. Yani birden fazla worker çalıştırdığınız anda **eski nesil istemciler yapışkan yönlendirmeye (sticky routing) ihtiyaç duyar**: bir oturumdaki her istek, onu başlatan sürece ulaşmak zorundadır. Modern istemcilerin buna hiç ihtiyacı olmaz; yapışacakları bir oturumları yoktur. Yapışkanlığı ve bunlardan birden fazlasını çalıştırmaya dair geri kalan her şeyi **[Dağıtım ve ölçekleme](deploy.md)** sayfası ele alır.
!!! warning
`event_store=` çözüm gibi görünür ama değildir. O bir oturum deposu değil,
**devam ettirilebilirliktir** (kaçırılan SSE olaylarını *aynı* oturuma yeniden bağlanan bir
istemciye yeniden oynatmak). Bir oturumu asla başka bir süreçten erişilebilir kılmaz.
## Tek ayar düğmesi: `stateless_http` {#the-one-knob-stateless_http}
Yapışkanlık ödemeyi reddettiğiniz bir bedelse, değiştirebileceğiniz tam olarak tek bir şey var.
```python title="server.py" hl_lines="28"
--8<-- "docs_src/legacy_clients/tutorial002.py"
```
Bu, sayfanın başındaki sunucuya tek bir anahtar sözcük eklenmiş hali. `stateless_http=True`, eski nesil kolun bunun yerine istek başına, kullan-at bir oturum kurmasını sağlar: `Mcp-Session-Id` verilmez, istekler arasında hiçbir şey hatırlanmaz; böylece herhangi bir worker herhangi bir isteğe hizmet verebilir ve yük dengeleyici canı ne isterse onu yapabilir.
Onunla ilgili iki şey, ne yaptığından daha önemli.
**Yalnızca eski nesil kola dokunur.** İstekler, `stateless_http` okunmadan *önce* sürüm başlığına göre yönlendirilir; bu yüzden modern yol onu hiç görmez. `2026-07-28` bağlantısı zaten oturumsuzdur ve her iki değerde de tıpatıp aynıdır.
**O kolda sunucudan istemciye giden her iki kanala da mal olur.** Tek bir `POST` boyunca yaşayan bir oturumun, sunucunun istek itebileceği bir akışı da bildirim itebileceği bağımsız bir akışı da yoktur. Sunucunun başlattığı her istek `NoBackChannelError` fırlatır: `ctx.elicit()`, emekliye ayrılmış örnekleme (sampling) ve kök dizinler (roots) çağrıları (**[Kullanım dışı özellikler](../deprecated.md)**) ve evet, *eski nesil* bir istemciye sorusunu soran `Resolve` da. Bildirimler bir hata bile almaz; sessizce düşürülür.
!!! note
`json_response=True` o düğme değildir ama aynı bedelin yarısını *her* eski nesil oturumda
öder: tek bir JSON gövdesiyle yanıtlanan bir `POST`'un istek kapsamlı kanal için akışı
yoktur; bu yüzden istek ortasındaki bir `ctx.elicit()` aynı `NoBackChannelError` istisnasını
fırlatır ve istekle ilişkili bildirimler düşürülür. Oturumun bağımsız akışına dokunulmaz:
ilgisiz bildirimler gelmeye devam eder.
!!! check
Yanlış olanı yapın. `reserve`, az önce iki istemciye de hizmet veren aracın ta kendisi. Onu
`stateless_http=True` ile dağıtın, aynı iki istemciyi HTTP üzerinden bağlayın ve her birinden
çağırın.
Modern istemci hâlâ `Reserved 2 of 'Dune'.` alır. Modern kol değişmedi.
Eski nesil istemcinin çağrısı, modelin okuyabileceği bir `is_error` sonucu olarak geri
dönmez. İsteğin tamamı, üst düzey bir protokol hatası olarak başarısız olur:
```text
mcp.shared.exceptions.MCPError: Cannot send 'elicitation/create': this transport context has no back-channel for server-initiated requests.
```
`Resolve` sizi kurtarmadı. `2025-11-25` bağlantısında `elicitation/create` göndermek
*zorundadır* ve ihtiyaç duyduğu kanal, `stateless_http=True`'nun elden çıkardığı şeyin ta
kendisidir. Nesiller arası taşınabilir kod, geri kanala (back-channel) ihtiyaç duymayan kod
demek değildir.
Yani bu gerçek bir ödünleşmedir ve yalnızca eski nesil kolda vardır: **oturumlu ve yapışkan, ya da durumsuz ve tek yönlü.** Araçlarınız hiçbir zaman istemciye geri çağrı yapmıyorsa `stateless_http=True` bedavadır ve almalısınız. Yapıyorlarsa oturumları koruyun ve yönlendirmeyi yapışkan tutun.
## Kodunuzun gerçekten çatallandığı yer {#where-your-code-actually-forks}
Neredeyse hiçbir yerde.
Araçlar, kaynaklar, prompt'lar, yapılandırılmış çıktı, ilerleme, hatalar: hiçbiri hangi neslin çağırdığını umursamaz. `initialize` el sıkışması, `Mcp-Session-Id`, bağımsız akış, bir oturumu bitiren `DELETE`: hepsinin sahibi SDK'dır ve bir işleyici bunların hiçbirini görmez. Etkileşimli girdi, nesillerin iletilen veride gerçekten ayrıştığı *tek* yerdir ve `Resolve` bunun sizin sorununuz olmaması için vardır: az önce tek bir aracın ikisine de hizmet verdiğini izlediniz.
Geriye tam olarak tek bir şey kalıyor, o da **değişiklik bildirimleri**; çünkü iki nesil farklı borulardan dinler:
* `2026-07-28` istemcisi bir `subscriptions/listen` akışı açar ve abonelik veri yolunu okur. `ctx.notify_resource_updated()` (ve `notify_tools_changed()`, `notify_prompts_changed()`, `notify_resources_changed()`) oraya, ve *yalnızca* oraya yayımlar. Bunun sayfası **[Abonelikler](../handlers/subscriptions.md)**.
* Eski nesil bir istemci, oturumunun açık tuttuğu bağımsız akışı okur. `ctx.session.send_resource_updated()` (ve `send_tool_list_changed()` ile benzerleri) isteği taşıyan *bağlantıya* yazar: eski nesil bir oturum için bu, onun bağımsız akışıdır. Modern bir bağlantıda bunun yeri yoktur: HTTP üzerinde böyle bir kanal yoktur, stdio üzerinde ise dört değişiklik bildirimi türü yalnızca `subscriptions/listen` akışlarında taşınır; bu yüzden modern bir bağlantıda bildirim sessizce düşürülür.
HTTP üzerinde iki çağrı da diğer neslin istemcilerine ulaşmaz. Herkese haber vermek için ikisini de çağırın:
```python title="server.py" hl_lines="19-20"
--8<-- "docs_src/legacy_clients/tutorial003.py"
```
İki satır, `if` yok, sürüm kontrolü yok, ve işiniz bitti. Eski nesil bir istemci var diye bir işleyicinin farklı yaptığı şeylerin listesinin tamamı bu.
## Özet {#recap}
* Tek bir `streamable_http_app()` iki protokol nesline de hizmet verir. SDK her isteği `MCP-Protocol-Version` başlığına göre yönlendirir; yapılandırılacak bir şey ve aranacak bir nesil düğmesi yoktur.
* Eski nesil bir istemci size bir oturuma mal olur: arkasında dağıtık bir depo olmayan, süreç içi bir `Mcp-Session-Id` kaydı. Birden fazla worker **yapışkan yönlendirme** demektir; aksi halde yanlış worker `404 Session not found` yanıtını verir. Çoklu worker'a dair ayrıntıların tamamı **[Dağıtım ve ölçekleme](deploy.md)** sayfasında.
* Tek düğme `stateless_http=True`'dur ve **yalnızca eski nesil kolu etkiler**. Eski nesil istemciler için bedava yük dengelemeyi, o koldaki sunucudan istemciye giden her iki kanal pahasına satın alır: sunucunun başlattığı istekler `NoBackChannelError` fırlatır (istemcide `is_error` sonucu değil, üst düzey bir hata) ve bildirimler düşürülür.
* `2026-07-28` bağlantısı her durumda oturumsuzdur. `stateless_http` ona hiç dokunmaz.
* İşleyici kodunuz nesle göre tam olarak tek bir yerde çatallanır: değişiklik bildirimleri. `ctx.notify_*` `subscriptions/listen` istemcilerine ulaşır; `ctx.session.send_*` eski nesil oturumlara ulaşır. İkisini de çağırın.
* Geri kalan her şey (`Resolve` aracılığıyla kullanıcıdan girdi istemek dahil) tasarımı gereği nesiller arası taşınabilirdir. Modern olanı bir kez yazın.

View file

@ -0,0 +1,85 @@
---
translation:
sections: [bc0227014724fa49, 15738c2f7fd67d86, a2c17bbe3f707e2f, d0d853376f162c06, b6368643fcc1c8d8, 902e33e17564a607]
tool: 1
---
# OpenTelemetry {#opentelemetry}
Sunucunuz zaten izleniyor. Hiçbir şey eklemeniz gerekmez.
Oluşturduğunuz her sunucu, işlediği her mesaj için bir [OpenTelemetry](https://opentelemetry.io/) span'ı üretir. Bunu siz yazmadınız, içe de aktarmıyorsunuz. `MCPServer(...)`'ı çağırdığınız anda oradadır.
```python title="server.py"
--8<-- "docs_src/opentelemetry/tutorial001.py"
```
Bu, eksiksiz ve izlenen bir sunucu. `search_books`'u çağırın, onun için bir span oluşturulur. Aynısı düşük seviyeli `Server` için de geçerlidir: izleme her ikisinde de bulunur.
## Elde ettikleriniz {#what-you-get}
Gelen her mesaj, adını yöntemden ve hedefinden alan bir `SERVER` span'ına dönüşür. Yani `search_books` için yapılan bir `tools/call`, `tools/call search_books` span'ıdır; yalın bir `tools/list` ise yalnızca `tools/list` olur.
Her span birkaç öznitelik taşır:
* `mcp.method.name` ve `mcp.protocol.version`, her span'da.
* `jsonrpc.request.id`, isteklerde (bildirimlerde yoktur).
* İstisna fırlatan bir işleyici span durumunu hata olarak ayarlar. `is_error=True` içeren bir araç sonucu da öyle.
Araç çağrılarını izlemek çok sık istenen bir şey olduğundan, `tools/call` span'ları OpenTelemetry'nin [GenAI anlamsal kurallarına](https://opentelemetry.io/docs/specs/semconv/gen-ai/) uyar:
* `gen_ai.operation.name`, `"execute_tool"` olarak ayarlanır.
* `gen_ai.tool.name`, çağrılan aracın adına ayarlanır.
Aynı mantıkla bir `prompts/get` span'ı `gen_ai.prompt.name` alır. Listeleme yöntemleri hiçbir `gen_ai.*` anahtarı taşımaz, çünkü adlandırılacak bir şey yoktur.
!!! tip
Bir izleme arayüzünün araç çağrılarınızı başka herhangi bir ajanınkileri grupladığı gibi gruplamasının nedeni bu GenAI öznitelikleridir. Bu gruplama size bedelsiz gelir; fazladan kod gerekmez.
## Siz isteyene kadar hiçbir maliyeti yok {#it-costs-nothing-until-you-want-it}
"Varsayılan olarak açık" tercihini rahat bir varsayılan yapan kısım burası.
SDK yalnızca OpenTelemetry'nin hafif yarısı olan `opentelemetry-api` paketine bağımlıdır. OpenTelemetry SDK'sı ve bir exporter kurulu değilken span oluşturmak etkisiz bir işlemdir. Yani sunucunuzun şu anda ürettiği span'ların size maliyeti neredeyse sıfırdır ve onları kimse toplamıyor.
Onları *görmek* istediğiniz gün diğer yarıyı kurar ve bir yere yönlendirirsiniz:
```console
uv add opentelemetry-sdk opentelemetry-exporter-otlp
```
Bir exporter'ı alışıldık OpenTelemetry yöntemiyle yapılandırın; SDK'nın sessizce oluşturduğu her span görünür hale gelir. Sunucu kodunuz değişmez. Tek bir satır bile.
!!! info
[Pydantic Logfire](https://logfire.pydantic.dev/) bu tür arka uçlardan biridir ve yapılandırmayı sizin yerinize yapar: `pip install logfire`, `logfire.configure()`, ardından MCP span'larınız canlı görünümde belirir. OpenTelemetry üzerine kuruludur, bu yüzden aşağıdaki her şey onun için de geçerlidir.
## Ağı aşan izler {#traces-that-cross-the-wire}
Bir iz en çok, bir isteği istemciden sunucunun içine kadar tek ve bağlantılı bir resimde takip ettiğinde işe yarar.
İstemci de sunucu da SDK'yı çalıştırıyorsa bu bağlantı otomatik kurulur. İstemci [W3C iz bağlamını](https://www.w3.org/TR/trace-context/) isteğe ekler, sunucu da onu geri okur; böylece sunucu span'ı aynı iz içinde istemci span'ının altına yerleşir. Bunun adı [SEP-414](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414) ve siz istemeden gelir.
Gelen mesaj iz bağlamı taşımıyorsa, örneğin SDK olmayan bir istemciden gelen bir istekte, sunucu span'ı yepyeni ve sahipsiz bir iz başlatmak yerine sunucuda o an geçerli olan span'ın altına bağlanır.
## Kapatma {#turning-it-off}
İzleme bir middleware'dir (ara katman); sunucunuzun listesindeki ilk middleware. Hiç span üretmeyen bir sunucuyu gerçekten istiyorsanız onu listeden çıkarın:
```python
from mcp.server._otel import OpenTelemetryMiddleware
mcp._lowlevel_server.middleware[:] = [
m for m in mcp._lowlevel_server.middleware if not isinstance(m, OpenTelemetryMiddleware)
]
```
!!! warning
Bu içe aktarmanın başında bir alt çizgi var ve bu bilerek böyle. Sınıf, tıpkı [`Server.middleware`](../advanced/middleware.md) gibi geçicidir; bu yüzden içe aktarma yolunun değişmesini beklemelisiniz. Buna neredeyse hiç ihtiyacınız olmaz: exporter kurulu değilken span'lar bedavadır, bu yüzden olağan yanıt onları açık bırakıp exporter kurmamaktır.
## Özet {#recap}
* Her `MCPServer` ve her düşük seviyeli `Server`, varsayılan olarak gelen mesaj başına bir `SERVER` span'ı üretir. Siz hiçbir şey yazmazsınız.
* Span'lar `mcp.method.name` ve `mcp.protocol.version` taşır; `tools/call` ve `prompts/get` ayrıca GenAI öznitelikleri taşır, böylece araç çağrılarınız başka herhangi bir ajanınkiler gibi gruplanır.
* Bir OpenTelemetry SDK'sı ve bir exporter kurana kadar hiçbir maliyeti yoktur; kurduğunuzda ise sunucunuzda hiçbir değişiklik olmadan görünür hale gelir.
* Her iki taraf da SDK'yı çalıştırdığında iz bağlamı istemciden sunucuya otomatik olarak yayılır.
Bir isteğin çalışıp çalışmayacağına karar veren şey ise **[Yetkilendirme](authorization.md)**.