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
53 changed files with 89737 additions and 54 deletions

258
i18n/tr/glossary.json Normal file
View file

@ -0,0 +1,258 @@
{
"keep": [
"MCP",
"Model Context Protocol",
"MCPServer",
"FastMCP",
"ClientSession",
"Context",
"ctx",
"stdio",
"Streamable HTTP",
"SSE",
"JSON-RPC",
"JSON",
"OAuth",
"PKCE",
"JWT",
"CIMD",
"HTTP",
"HTTPS",
"TLS",
"CORS",
"URI",
"URL",
"ASGI",
"WebSocket",
"API",
"SDK",
"CLI",
"IDE",
"LLM",
"SEP",
"RFC",
"Python",
"TypeScript",
"Node.js",
"PyPI",
"Pydantic",
"Starlette",
"FastAPI",
"uvicorn",
"httpx",
"anyio",
"asyncio",
"trio",
"pytest",
"OpenTelemetry",
"Inspector",
"Claude",
"GitHub",
"VS Code",
"Windows",
"macOS",
"Linux",
"llms.txt",
"2026-07-28",
"2025-11-25",
"2025-06-18",
"2025-03-26"
],
"terms": [
{
"source": "tool",
"target": "araç",
"note": "MCP protocol noun (a server exposes tools): araç / araçlar, aracı in the accusative. Standard rendering. Wire identifiers such as `tools/call` and the `@mcp.tool()` decorator are code and stay Latin; the Inspector's **Tools** tab is a UI label and stays English."
},
{
"source": "resource",
"target": "kaynak",
"note": "MCP protocol noun (data a server exposes for reading), and also the general noun (a pool acquired in a lifespan is still kaynak). Standard rendering; context keeps it apart from kaynak kodu (source code). `resources/read` and `@mcp.resource()` are code."
},
{
"source": "prompt",
"target": "prompt",
"note": "The MCP feature (a reusable message template a server exposes) and the everyday LLM sense; kept in English as Turkish AI writing does, lower-case, suffixed with an apostrophe: prompt'u, prompt'a, prompt'lar. Not komut istemi (a shell prompt). `prompts/get` and `@mcp.prompt()` are code. Provisional pending native review; istem is the native alternative to weigh."
},
{
"source": "sampling",
"target": "örnekleme",
"note": "The (deprecated) client feature that lets a server borrow the client's model for a completion. Gloss the English on first use per page — örnekleme (sampling) — so the reader maps it to `sampling/createMessage`, which is code. Provisional pending native review."
},
{
"source": "roots",
"target": "kök dizinler",
"note": "The (deprecated) client feature listing the workspace directories a client exposes. Descriptive rendering with the English glossed on first use per page — kök dizinler (roots); a single root is kök dizin. `roots/list` and the `Root` type are code and stay Latin. Provisional pending native review; keeping roots in English is the open alternative."
},
{
"source": "elicitation",
"target": "elicitation",
"note": "The mechanism by which a server asks the user a question through the client mid-request. No Turkish term exists, so the English word is kept, lower-case, suffixed with an apostrophe (elicitation'ı, elicitation'a, elicitation'lar), and may carry a Turkish explanation on its first appearance per page — elicitation (kullanıcıdan bilgi isteme). `elicitation/create`, `ctx.elicit()` and the `Elicit` class are code. Provisional pending native review."
},
{
"source": "capability",
"target": "yetenek",
"note": "A negotiated protocol capability (what a client or server declared it supports): sunucu yetenekleri, \"capability negotiation\" → yetenek anlaşması. Not özellik, which is a feature. The `capabilities` field and keys such as `sampling.tools` stay Latin. Provisional pending native review."
},
{
"source": "transport",
"target": "aktarım",
"note": "The connection mechanism (\"every standard transport\" → tüm standart aktarımlar; \"transport layer\" → aktarım katmanı). Not taşıma or ulaşım, which are physical transportation. The transport names stdio, Streamable HTTP and SSE stay in English: stdio aktarımı, Streamable HTTP aktarımı. Provisional pending native review."
},
{
"source": "session",
"target": "oturum",
"note": "An MCP session (the negotiated connection state): oturum, oturum kimliği for \"session ID\". Standard rendering. `session` objects, `ClientSession` and `ServerSession` are code and stay Latin."
},
{
"source": "handler",
"target": "işleyici",
"note": "The tool, resource or prompt function you register, and request handlers generally (nav section \"Inside your handler\" → İşleyicinin içinde): işleyici / işleyiciler. Provisional pending native review; keeping handler in English is the alternative many Turkish developers use in speech."
},
{
"source": "dependency",
"target": "bağımlılık",
"note": "Both package dependencies and the SDK's parameter-injection feature (the \"Dependencies\" page → Bağımlılıklar; \"dependency injection\" → bağımlılık enjeksiyonu). Standard rendering. The `Resolve` marker class stays Latin."
},
{
"source": "resolver",
"target": "çözümleyici",
"note": "The plain function attached to a parameter with `Resolve(...)` that computes or asks for its value: çözümleyici, çözümleyici fonksiyon where the kind needs naming. Provisional pending native review; keeping resolver in English is the alternative. The `Resolve` class stays Latin."
},
{
"source": "client",
"target": "istemci",
"note": "An MCP client, and the client side of a connection: istemci. Standard rendering; müşteri is a customer and is only right where the English says customer. The `Client` class and the `mcp.client` module are code and stay Latin (`Client`'ı, `Client`'a)."
},
{
"source": "server",
"target": "sunucu",
"note": "An MCP server (the program you build): sunucu, MCP sunucusu. Standard rendering. The `MCPServer`, `Server` and `ServerSession` classes are code and stay Latin."
},
{
"source": "host",
"target": "host",
"note": "The MCP host — the application the user talks to (Claude Desktop, an IDE, an agent runtime) — kept in English as Turkish developers say it, lower-case, suffixed with an apostrophe: host'u, host'a, host'lar; host uygulama where the kind helps. ana bilgisayar is the network-machine sense (ana bilgisayar adı for a hostname is fine) and never names the MCP host. Provisional pending native review."
},
{
"source": "context",
"target": "bağlam",
"note": "The generic lower-case word (\"provide context to LLMs\" → LLM'lere bağlam sağlamak). The capitalised `Context` is the SDK object injected as `ctx`; it is on the keep list and stays Latin in prose (\"The Context\" page title → Context nesnesi). Standard rendering."
},
{
"source": "request",
"target": "istek",
"note": "A JSON-RPC or HTTP request (\"the initialize request\" → initialize isteği; HTTP isteği). Standard term; not talep, and not the English request in prose. `Request` types in code font stay Latin."
},
{
"source": "response",
"target": "yanıt",
"note": "A JSON-RPC or HTTP response (HTTP yanıtı, yanıt gövdesi). Pinned over cevap so one word is used throughout; never the English response in prose. `Response` types in code font stay Latin. Provisional pending native review."
},
{
"source": "notification",
"target": "bildirim",
"note": "A JSON-RPC notification (a message that expects no response): bildirim göndermek, ilerleme bildirimi. Standard term. Method strings such as `notifications/tools/list_changed` stay Latin."
},
{
"source": "callback",
"target": "callback",
"note": "Client callbacks and OAuth redirect callbacks alike (the \"Client callbacks\" page → İstemci callback'leri): kept in English, lower-case, suffixed with an apostrophe — callback'i, callback'e, callback'ler. Provisional pending native review; geri çağırma (işlevi) is the native alternative and may serve as a one-time gloss. Parameter names such as `sampling_callback` stay Latin."
},
{
"source": "decorator",
"target": "dekoratör",
"note": "The Python decorators the SDK is built on: dekoratör / dekoratörler. `@mcp.tool()` and its siblings are code and stay untouched. Standard rendering."
},
{
"source": "type hint",
"target": "tür ipucu",
"note": "Python type hints (\"from your type hints\" → tür ipuçlarınızdan; plural tür ipuçları). Pinned with tür, matching veri türü and dönüş türü; do not alternate with tip ipucu on the same site. Provisional pending native review."
},
{
"source": "exception",
"target": "istisna",
"note": "A raised Python exception: istisna; \"raises an exception\" → bir istisna fırlatır. Pinned over özel durum. Exception class names stay Latin. Provisional pending native review."
},
{
"source": "async",
"target": "asenkron",
"note": "The prose adjective (\"the async runtime\" → asenkron çalışma zamanı, \"an async callback\" → asenkron callback); pinned over eşzamansız. The `async` and `await` keywords in code font stay Latin. Provisional pending native review."
},
{
"source": "by default",
"target": "varsayılan olarak",
"note": "\"By default\" → varsayılan olarak; \"the default value\" → varsayılan değer; \"defaults to X\" → varsayılan olarak X. Standard rendering, pinned over öntanımlı."
},
{
"source": "authorization",
"target": "yetkilendirme",
"note": "Security sense: yetkilendirme (yetkilendirme sunucusu, yetkilendirme kodu), distinct from authentication → kimlik doğrulama. The `Authorization` header and code identifiers stay Latin. Standard rendering."
},
{
"source": "deploy",
"target": "dağıtım",
"note": "The noun is dağıtım and the verb dağıtmak (\"Deploy & scale\" → Dağıtım ve ölçekleme; \"deploy it behind a proxy\" → bir vekil sunucunun arkasına dağıtın). Never deploylamak; deploy etmek is speech, not documentation. Provisional pending native review."
},
{
"source": "lifespan",
"target": "lifespan",
"note": "The server's startup/shutdown scope (the \"Lifespan\" page, as in the ASGI lifespan); kept in English so the prose matches the `lifespan=` parameter, lower-case, suffixed with an apostrophe (lifespan'i, lifespan'e). May carry a Turkish explanation on first appearance per page — lifespan (yaşam döngüsü). Provisional pending native review; translating it as yaşam döngüsü throughout is the open alternative."
},
{
"source": "back-channel",
"target": "geri kanal",
"note": "This documentation's term for the server calling back into the client during a request, which exists only on legacy connections. Gloss the English on first use per page — geri kanal (back-channel) — so the reader can connect it to `NoBackChannelError`, which is code. Provisional pending native review."
},
{
"source": "multi-round-trip",
"target": "çok turlu",
"note": "The 2026-07-28 request pattern: \"Multi-round-trip requests\" → Çok turlu istekler, glossed on first use per page — çok turlu istekler (multi-round-trip). A single \"round trip\" is tur or gidiş-dönüş by context. The abbreviation MRTR stays Latin. Provisional coinage pending native review; çok gidiş-dönüşlü is the literal alternative."
},
{
"source": "deprecated",
"target": "kullanım dışı",
"note": "Advisory status: still works, scheduled for removal later — kullanım dışı / kullanım dışı bırakılmış (\"Deprecated features\" → Kullanım dışı özellikler; \"deprecation warning\" → kullanım dışı bırakma uyarısı; \"X is deprecated\" → X kullanım dışı bırakıldı). \"Removed\" is a different word (kaldırıldı); the corpus contrasts the two, so never render deprecated as kaldırıldı. The `MCPDeprecationWarning` class stays Latin. Provisional pending native review."
},
{
"source": "legacy",
"target": "eski nesil",
"note": "\"A legacy connection / client\" = one negotiated at spec version 2025-11-25 or earlier → eski nesil bağlantı, eski nesil istemci (the page \"Serving legacy clients\" → Eski nesil istemcilere hizmet verme). Pairs with \"era\" → nesil and keeps kullanım dışı free for \"deprecated\". Provisional pending native review."
},
{
"source": "era",
"target": "nesil",
"note": "\"Protocol era\" (\"a 2025-era client\", \"whatever era the client speaks\") → protokol nesli, 2025 neslinden bir istemci. Not the literal çağ or dönem. Provisional pending native review."
},
{
"source": "handshake",
"target": "el sıkışma",
"note": "The initialization handshake (\"the classic handshake\" → klasik el sıkışma; başlatma el sıkışması). el sıkışma is the standard Turkish networking term (as in TLS el sıkışması), so the literal word is correct here. Standard rendering."
},
{
"source": "middleware",
"target": "middleware",
"note": "Kept in English, lower-case in running text, suffixed with an apostrophe (middleware'i, middleware'ler); the \"Middleware\" page title stays Middleware. May carry the one-time explanation middleware (ara katman). Provisional pending native review; ara yazılım is the formal alternative."
},
{
"source": "Get started",
"target": "Başlarken",
"note": "The nav section that opens the guide, and the title of its index page. \"First steps\" is a separate page inside that section (İlk adımlar), so the two need distinct renderings or the sidebar shows the same title twice. Provisional pending native review."
},
{
"source": "First steps",
"target": "İlk adımlar",
"note": "The tutorial page inside the \"Get started\" section, spelled with the dotted capital İ; never reuse this rendering for the section itself (see that entry). Provisional pending native review."
},
{
"source": "Recap",
"target": "Özet",
"note": "Recurring section heading that closes most pages; one rendering everywhere, not Özet on some pages and Toparlayalım or Sonuç on others. Provisional pending native review."
},
{
"source": "Try it",
"target": "Deneyin",
"note": "Recurring section heading above a runnable example; one rendering everywhere, not Deneyin on some pages and Kendiniz deneyin or Deneme on others. Provisional pending native review."
}
]
}

170
i18n/tr/instructions.md Normal file
View file

@ -0,0 +1,170 @@
# Turkish (tr) — translation instructions
Target language: Turkish (Türkçe), directory and URL code `tr`, page language
tag `tr`. This file is sent verbatim with every translation request for this
language, on top of the shared rules in `../general-prompt.md`. The termbase
in `glossary.json` is sent alongside it and wins any terminology conflict with
this file.
## 1. Register
Write the clear, instructional Turkish of good developer documentation: polite
but not ceremonial, addressed to a colleague.
- The reader is siz, almost always left implicit. Steps and instructions take
the polite-plural imperative in -in / -ın / -un / -ün by vowel harmony
(çalıştırın, kurun, ekleyin, açın): "Install the SDK, then run the server" →
SDK'yı kurun, ardından sunucuyu çalıştırın. Never the over-formal -iniz
(çalıştırınız), never the bare sen imperative (çalıştır), never a mix.
- Statements about what code does use the aorist: "The SDK does the rest" →
Gerisini SDK halleder; "You can pass a schema" → Bir şema geçirebilirsiniz.
Not the bureaucratic -mektedir / -maktadır, not a needless -ecektir. "Your
server" is usually just sunucu; sunucunuz only where ownership is the point;
siz as an explicit subject only when the sentence contrasts actors.
- Headings, table headers and content-tab labels are noun phrases in sentence
case, typically the -ma / -me verbal noun, with no final punctuation:
"Running your server" → Sunucunuzu çalıştırma, "Handling errors" → Hataları
ele alma, "Inside your handler" → İşleyicinin içinde. Not an imperative
(Sunucuyu çalıştırın) and not a question unless the English heading is one.
- A first-person-plural aside (bir bakalım) is fine where the English says
"let's", not for plain instructions. One page, one register: drifting
between -in and -iniz, or into -mektedir, is wrong even if each sentence is
fine alone.
## 2. Voice
The English is warm, direct and confident: short sentences, second person, the
occasional one-line payoff ("That's the whole API."). Rewrite it as natural
Turkish, as if the page had been written in Turkish, keeping every claim exact.
- Follow Turkish word order; split a long English sentence into two rather
than mirroring its clause chain, and use everyday connectives (Ancak, Yani,
Bu yüzden) where they help. Never merge, drop or reorder the claims.
- Use concrete verbs (çalıştırın, geçirin, döndürür, bildirir, engeller) and
the active voice: "The tool is called by the model" → Aracı model çağırır,
not Araç model tarafından çağrılır. Keep the payoff lines short: "That's a
complete MCP server." → Bu, eksiksiz bir MCP sunucusu.
- Avoid officialese: -mektedir chains, gerçekleştirmek + noun (çalıştırma
işlemini gerçekleştirin → çalıştırın), söz konusu, işbu, tarafınızca, and
bulunmak as padding (yer almaktadır → var). Avoid word-for-word English too:
bir before every noun, o / onlar pronoun crutches, possessive chains
(sunucunuzun aracının şemasının), sahip olmak for every "has" (Sunucu üç
araca sahiptir → Sunucuda üç araç var).
- No hedging the English does not have ("don't" is kullanmayın, not
kaçınmanız iyi olabilir) — and no over-correction either: no sen, no chat
tone (hadi, süper, falan), no smileys, no Turkish verb endings on English
words (deploylamak — see §5).
Example — English: "You don't construct it and you don't configure it. You ask
for it."
- Not this (officialese): Söz konusu nesnenin oluşturulması ve yapılandırılması
tarafınızca gerçekleştirilmemektedir; yalnızca talep edilmesi gerekmektedir.
- Not this either (sen, chatty): Onu sen oluşturmuyorsun, ayarlamıyorsun da.
İstiyorsun, o kadar.
- This: Onu siz oluşturmazsınız, yapılandırmazsınız da. Yalnızca istersiniz.
## 3. Humour and idioms
- Translate the intent of a joke, aside or idiom, never its words: recast it
as a short, natural Turkish sentence in the same register, or keep it brief
where it carries nothing. Never drop the technical content around it.
- Recurring English tags get fixed renderings: "**[X](…)** has the whole
story" / "The whole story is in **[X](…)**" → Ayrıntıların tamamı
**[X](…)** sayfasında.; "That's the whole API." / "That's the whole
protocol." → API'nin tamamı bu. / Protokolün tamamı bu.; "That's it. It's
just Python." → Hepsi bu. Bildiğiniz Python.; "You get `3` back. ✨" →
Geriye `3` döner. ✨
- Idioms take the plain meaning, not the picture: "Out of the box the app
answers **only** requests addressed to localhost." → Varsayılan olarak
uygulama **yalnızca** localhost'a gönderilen istekleri yanıtlar. — not
kutudan çıktığı gibi; "under the hood" → arka planda, not kaputun altında;
"on the wire" → iletilen veride / ağ üzerinde, never kabloda.
- Keep an exclamation mark only where the English is a genuine exclamation of
encouragement — never after a warning or a step, never doubled, never in a
heading. Reproduce an emoji only where the English has one, in the same
place (two payoff lines end in ✨); never add one.
## 4. Typography
- Quotation marks are the double quotes the source uses ("…"), nested quotes
single ('…'); no «…», no „…“. When the English quotes a word the example
code prints or a UI label, it stays exactly as emitted: "Tools" sekmesi.
- Suffixes on Latin-script words. A proper name, keep-list term, acronym,
number, kept English word or inline code span takes its suffix after an
apostrophe, following vowel harmony for the word **as pronounced**:
- English words and names by their English sound: Python'ı, Python'da;
Claude'u, Claude'a; GitHub'ı; `Client`'ı, `Client`'a, `Client`'ta;
`Context`'i, `Context`'e; token'ı, token'lar; callback'i, callback'ler;
prompt'u, prompt'lar; localhost'a, localhost'ta.
- Acronyms letter by letter in Turkish: API'yi, API'ye, API'nin; SDK'yı,
SDK'nın, SDK'lar; MCP'yi, MCP'de; HTTP'nin; URL'yi, URL'ler; LLM'lere;
SSE'yi — except acronyms read as a word: JSON'u, JSON'a, JSON'da.
- After a voiceless final sound (p, ç, t, k, f, h, s, ş) the suffix
consonant hardens (`dict`'te, stdout'ta, `Client`'tan); a vowel-final word
takes the buffer letter (stdio'yu, stdio'da, anyio'nun).
- On a code span the apostrophe and suffix sit directly after the closing
backtick, never inside it, never after a space: `call_tool()`'u çağırın,
`ctx`'i isteyin. Suffixes stack the normal way: token'ları, prompt'larda.
- Never respell, re-case or hyphenate a term to suit the suffix. Where the
pronunciation is unclear (symbols, flags, paths, mixed digits), let a
Turkish noun carry the suffix: `--port` seçeneğini, `server.py` dosyasını,
`greeting://{name}` kaynağını, 8000 numaralı port.
- Dotted and dotless i. Turkish words follow Turkish casing — İstemci, İlk
adımlar; the capital of i is İ, the lowercase of I is ı. Words that stay in
English keep their letters untouched in every position: Inspector (never
İnspector), API (never APİ), `id`. Never re-case an English word or an
identifier yourself; a heading that starts with one leaves it as spelled.
- Sentence case everywhere: headings, admonition titles, tab labels and table
headers capitalise the first word and proper nouns only (Sunucunuzu
çalıştırma, not Sunucunuzu Çalıştırma); language names stay capitalised
(İngilizce).
- Digits stay ASCII. Protocol revision strings such as `2026-07-28` are
identifiers, copied byte for byte — never 28.07.2026, never 28 Temmuz 2026.
Version numbers, ports, status codes, RFC and SEP numbers are copied exactly.
- Prose quantities take the decimal comma only when nothing but the separator
changes (2.5 seconds → 2,5 saniye); when in doubt keep the number as written.
The percent sign precedes the number (%100); a unit follows a space (100 MB).
- e.g. → örneğin; i.e. → yani; etc. → vb.; "&" → ve. Emphasis lands on the
same words the source emphasises; a bolded "**not**" becomes a bolded değil
or negated verb (**does not** raise → hata **fırlatmaz**). Kept English words
are set in plain type, no italics or quotes. An em-dash aside usually becomes
a comma pair, parentheses or its own sentence; colons before lists stay.
## 5. Terminology pointer
The glossary (`glossary.json`) is injected separately and overrides this file
on every term it covers; each entry marks its choice as standard or provisional
and says whether it takes a first-use gloss. Its renderings assume:
- Identifiers stay in Latin script exactly as written: class, function,
method, parameter, module and header names, protocol method strings such as
`tools/call`, and everything in code font. So do the keep-list terms,
acronyms and product names, which drop the English plural "s" and take a
Turkish one where needed: "the SDKs" → SDK'lar.
- Two tracks, and the glossary decides per term. Translate where Turkish
developers use the Turkish word: sunucu, istemci, araç, kaynak, istek,
yanıt, bildirim, oturum, bağımlılık, işleyici, bağlam, şema, istisna,
yetkilendirme, kimlik doğrulama, varsayılan, sürüm, dağıtım. Keep the English
word — lower-case, plain type, suffixed with an apostrophe — where that is
what Turkish developers say: token, callback, middleware, endpoint, host,
prompt, lifespan, commit, log. Nouns are borrowed, verbs are not: commit
etmek, dağıtmak for "deploy" — never commitlemek, deploylamak.
- Text quoted from what the example code prints or displays — an output line,
a log message, an Inspector tab or button label — stays exactly as the code
emits it (usually English), in or out of code font; never translate it.
- First-use gloss, both ways, as the glossary marks it: a translated concept
carries the English once per page — örnekleme (sampling) — and a kept
English one may carry a Turkish explanation once — elicitation (kullanıcıdan
bilgi isteme). A glossary word used as an identifier in code font stays as
written: "the `sampling` capability" → `sampling` yeteneği.
- One rendering per term per page: the glossary target, every time. Do not
alternate yanıt and cevap, or istemci and client, for the same source term.
## 6. Provisional note
Every decision in this file, and every entry in `glossary.json`, is
provisional pending review by native Turkish-speaking developers. To propose a
change — a better rendering, a suffix rule that produces wrong forms, a term
that should switch tracks — edit this file or `glossary.json` in a pull
request; never edit the generated `pages/` or `notices.md`.

20
i18n/tr/notices.md Normal file
View file

@ -0,0 +1,20 @@
---
translation:
sections: [aff1b3e872b7876a, 4d80558ad052d586, 0bb81f1e62062d26, d5c35dcec50156bc]
tool: 1
---
# Çeviri bildirimleri {#translation-notices}
Çevrilmiş bir dokümantasyon sitesinin her sayfasının başında bu notlardan biri yer alır.
## Makine çevirisi {#translated}
Bu sayfa İngilizce dokümantasyondan otomatik olarak çevrildi; esas alınması gereken sürüm [İngilizce sayfadır](ENGLISH_PAGE). Yanlış görünen bir şey varsa, nasıl bildireceğinizi [Çeviriler](TRANSLATIONS_PAGE) sayfası açıklar.
## İngilizce sayfanın gerisinde kalmış çeviri {#outdated}
Bu çeviri yapıldıktan sonra İngilizce sayfa değişti; bu yüzden bazı bölümleri güncel olmayabilir. Şüpheye düştüğünüzde [İngilizce sayfayı](ENGLISH_PAGE) okuyun; çevrilmiş dokümantasyonun nasıl işlediğini [Çeviriler](TRANSLATIONS_PAGE) sayfası açıklar.
## İngilizce gösteriliyor {#english}
Bu sayfanın güncel bir çevirisi yok; bu yüzden onu İngilizce okuyorsunuz. Çevrilmiş dokümantasyonun nasıl işlediğini [Çeviriler](TRANSLATIONS_PAGE) sayfası açıklar.

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,122 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
tool: 1
---
# Middleware {#middleware}
**Middleware** (ara katman), sunucunun aldığı her mesajı saran tek bir asenkron fonksiyondur.
Onu `async (ctx, call_next)` biçiminde yazar ve `server.middleware` listesine eklersiniz. API'nin tamamı bu.
!!! warning
Middleware listesi kaynak kodda **geçici (provisional)** olarak işaretlidir: imzası ve anlamı
bir 2.x ara sürümünde değişebilir. Onu mesajları *gözlemlemek* (zamanlama, log tutma, izleme) ve
*reddetmek* için kullanın; sunucunuzun üzerinde durduğu temel haline getirmeyin.
`MCPServer` listeyi oluşturulurken alır (`MCPServer(name, middleware=[...])`) ve onu
`mcp.middleware` olarak sunar; alt düzey `Server` aynı listeyi `server.middleware` olarak sunar. Aşağıdaki
örnek alt düzey `Server`'ı kullanır; `Server(name, on_call_tool=...)` size yeniyse önce
**[Alt düzey Server](low-level-server.md)** sayfasını okuyun.
## Bir zamanlama middleware'i {#a-timing-middleware}
Bir sunucu, bir araç ve her mesajın ne kadar sürdüğünü loglayan bir middleware:
```python title="server.py" hl_lines="39-45 49"
--8<-- "docs_src/middleware/tutorial001.py"
```
* `ctx`, işleyicilerinizin aldığı `ServerRequestContext`'in aynısıdır. `ctx.method` ham
metot dizgesidir; `ctx.params` ise herhangi bir doğrulamadan **önceki** ham parametrelerdir.
* `call_next(ctx)` zincirin geri kalanını çalıştırır: doğrulama, işleyici araması, işleyiciniz.
Onun döndürdüğünü döndürürseniz yanıta dokunulmaz.
* `try`/`finally` bilinçli bir tercihtir: istisna fırlatan bir işleyicinin de süresi ölçülür, çünkü hata
middleware'inize `call_next`'ten çıkan istisna olarak ulaşır.
* `server.middleware.append(...)` onu kaydeder. Liste dıştan içe doğru çalışır, yani
`middleware[0]` ağ tarafına en yakın olandır.
### Deneyin {#try-it}
Bir istemci bağlayın, araçları listeleyin, birini çağırın. Logunuzda **üç** satır var:
```text
server/discover took 18.3 ms
tools/list took 0.1 ms
tools/call took 0.1 ms
```
İki çağrı yaptınız ve üç satır elde ettiniz. İlki `server/discover`: siz herhangi bir şey
istemeden önce, istemcinin bağlantıyı kurmak için gönderdiği istek.
İşin özü de bu. Middleware gelen **her** mesajı sarar:
* Bağlantı kurulumu: `server/discover` ya da eski nesil bir oturumda `initialize` ve
`notifications/initialized`.
* Her istek ve her bildirim. Bir bildirimde `ctx.request_id is None` olur,
`call_next(ctx)` `None` döndürür ve sizin döndürdüğünüz her şey atılır.
* Sunucunun işleyicisi olmayan bir metot bile: `call_next`,
`MCPError(-32601, "Method not found")` istisnasını istemciye giderken middleware'inizin *içinden* fırlatır.
## İçinde neler yapabilirsiniz {#what-you-can-do-inside-one}
Ne kadar tereddüt etmeniz gerektiğine göre artan sırayla:
* **Gözlemleyin.** Süresini ölçün, sayın, loglayın. Yukarıdaki örnek.
* **Reddedin.** `call_next(ctx)`'i çağırmak *yerine* bir `MCPError` fırlatın; o tek mesaj
bir JSON-RPC hatasıyla yanıtlanır. Bağlantı ayakta kalır; sonraki mesaj geçer. Bir sunucu
`subscriptions/listen`'ı çağıran başına böyle denetler:
Abonelikler sayfasındaki **[Kimin izleyebileceğine karar verme](../handlers/subscriptions.md#deciding-who-may-watch)** bölümü
bunu adım adım anlatır.
* **Yeniden yazın.** `ctx` bir dataclass'tır: `await call_next(dataclasses.replace(ctx, params=...))`
zincirin geri kalanına istemcinin gönderdiğinden farklı parametreler verir. Bunu `initialize`
için asla yapmayın: istemcinin geri aldığı sonuç sizin yeniden yazdığınız parametrelerden oluşturulur, ancak
sunucu bağlantı durumunu ağdan gelen özgün parametrelere göre kaydeder. İki taraf
el sıkışmayı neyi müzakere ettikleri konusunda anlaşamadan bitirebilir.
* **Yanıtlayın.** `call_next(ctx)`'i çağırmadan bir sonuç döndürün; bu sonuç istemciye sizin
yanıtınız olarak gider. `call_next` size tamamlanmış iletim biçimini verir ve işlem hattı döndürdüğünüzü
asla yamalamaz; bu yüzden zarfın tamamı sizindir: 2026 neslinden bir bağlantıda buna
`serverInfo` `_meta` damgası da dahildir. SDK bu damgayı işleyici sonuçlarına ekler, sizinkilere eklemez.
!!! check
`initialize`, middleware'in sardığı şeylerden biridir ve onun için elinizdeki *tek* kanca
budur. Onu `add_request_handler` ile devralmaya çalışırsanız SDK reddeder:
```text
ValueError: 'initialize' is handled by the server runner and cannot be overridden;
use Server.middleware to observe or wrap initialization
```
!!! warning
`initialize` satır içinde ele alınır: middleware zinciriniz dönene kadar sunucu başka gelen
mesaj okumaz. Bu yüzden `initialize`'ı işlerken sunucudan istemciye bir isteği (`ctx.session.send_request(...)`,
bir elicitation) beklemek **bağlantıyı kilitler**: beklediğiniz
yanıt asla okunamaz. Gönderip unutulan bildirimlerde sorun yoktur.
## Varsayılan olarak açık gelen tek middleware {#the-one-middleware-that-ships-on-by-default}
SDK tam olarak bir middleware ile gelir ve o zaten sunucunuzun listesindedir: her mesaj için
bir OpenTelemetry span'i yayan middleware. Onu siz eklemezsiniz ve çoğu zaman
aklınıza bile gelmez. Bir exporter kurana kadar hiçbir şey yapmaz ve kendi sayfası vardır:
**[OpenTelemetry](../run/opentelemetry.md)**.
!!! info
ASGI middleware'i yazdıysanız bu yapıyı zaten biliyorsunuz. Starlette'in
`(scope, receive, send)` üçlüsü `(ctx, call_next)` oldu ve aktarımdan *sonra*, ham
HTTP isteği yerine çözülmüş mesaj üzerinde çalışır. İkisi birlikte kullanılabilir: `streamable_http_app()`
üzerindeki Starlette middleware'i HTTP'yi görür; bu ise MCP'yi görür.
## Özet {#recap}
* Bir middleware `async (ctx, call_next) -> result` biçimindedir; `MCPServer(middleware=[...])` olarak geçirilir (ya da
`mcp.middleware` listesine eklenir), alt düzey `Server`'da ise `server.middleware` listesine eklenir.
* Gelen **her** mesajı sarar (`server/discover`, `initialize`, istekler, bildirimler,
bilinmeyen metotlar) ve dıştan içe doğru çalışır.
* Bir bildirimi bir istekten `ctx.request_id is None` ile ayırt edersiniz.
* Tek bir mesajı reddetmek için `call_next`'i çağırmak yerine istisna fırlatın; bağlantı ayakta kalır.
* SDK'nın kendi OpenTelemetry izlemesi de bir middleware'dir ve zaten listededir. Bkz.
**[OpenTelemetry](../run/opentelemetry.md)**.
* Yüzeyin tamamı geçicidir. Onunla gözlemleyin; üzerine inşa etmeyin.
Bir isteği saran her şey bu kadar. İsteğin çalışıp çalışmayacağına karar veren ise
**[Yetkilendirme](../run/authorization.md)**.

View file

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

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

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

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

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

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

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

View 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)**.

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

View file

@ -0,0 +1,96 @@
---
translation:
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
tool: 1
---
# Kullanım dışı özellikler {#deprecated-features}
2026-07-28 spesifikasyonu beş şeyi emekliye ayırıyor. SDK hâlâ hepsini uygular ve artık her biri bir **kullanım dışı bırakma uyarısı** taşır.
Aşağıdaki tablo kullanım dışı bırakılan her özelliği, neden gittiğini ve yerine neyin üzerine inşa etmeniz gerektiğini gösterir.
## Neler kullanım dışı {#what-is-deprecated}
| Kullanım dışı | Neden | Bunun yerine ne yaparsınız |
|---|---|---|
| **Kök dizinler (roots)**: `ctx.session.list_roots()`, `client.send_roots_list_changed()`, `Client(...)`'a geçirdiğiniz `list_roots_callback=` | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) bu yeteneği emekliye ayırıyor. | Yolları sıradan araç argümanları veya kaynak URI'leri olarak alın ya da bir `InputRequiredResult` içine bir `ListRootsRequest` gömün (bkz. **[Çok turlu istekler (multi-round-trip)](handlers/multi-round-trip.md)**). |
| **Sunucunun başlattığı örnekleme (sampling)**: `ctx.session.create_message()`, `Client(...)`'a geçirdiğiniz `sampling_callback=` | SEP-2577 bu yeteneği emekliye ayırıyor. | `InputRequiredResult` döndürün ve çağrıyı istemcinin yeniden denemesine bırakın (bkz. **[Çok turlu istekler](handlers/multi-round-trip.md)**). |
| **Protokol üzerinden log tutma**: `ctx.log()`, `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`, `ctx.session.send_log_message()`, `client.set_logging_level()` | SEP-2577 bu yeteneği emekliye ayırıyor. Protokol içinde yerine geçen bir şey yok. | stderr'e yazan sıradan `import logging` (bkz. **[Log tutma](handlers/logging.md)**). |
| **`ping`**: `client.send_ping()` | Yalnızca kullanım dışı bırakılmadı, protokolden **kaldırıldı**. 2026-07-28 sürümünde `ping` yöntemi yok. | Hiçbir şey. Yalnızca `mode="legacy"` bağlantısında çalışır. |
| **İstemciden sunucuya ilerleme**: `client.send_progress_notification()` | 2026-07-28 ilerlemeyi yalnızca sunucudan istemciye yönlü yapar. | Gönderecek bir şey yok. İlerlemeyi *sunucunuz* `ctx.report_progress()` ile bildirir (bkz. **[İlerleme](handlers/progress.md)**). |
Bu tablodan üç şey çıkar:
* Kök dizinler, örnekleme ve log tutma bir arada gider. Tek bir öneri, **SEP-2577**, üç yeteneği birden kullanım dışı bırakır.
* Örnekleme ve kök dizinler daha derin bir sorunu paylaşır: bunlar bir **sunucunun** **istemciye** **istek** gönderdiği yerlerdir. 2026-07-28 sürümünün **[Çok turlu istekler](handlers/multi-round-trip.md)** ile değiştirdiği şey tam da bu yöndür. Giden, bağımsız RPC yöntemleridir (`sampling/createMessage`, `roots/list` ve push tarzı `elicitation/create`); `CreateMessageRequest` / `ListRootsRequest` / `ElicitRequest` yük türleri `InputRequiredResult.input_requests` içine gömülü olarak yaşamaya devam eder ve istemcide aynı callback'lere ulaşır.
* `ping` diğerlerinden ayrılır. Protokol onu kullanım dışı bırakmaz, kaldırır. SDK yöntemi yine de uyarır (mesajı *deprecated* değil *removed* der) ve modern bir bağlantıda çağrıldığında *"Method not found"* yanıtı gelir.
## Kullanım dışı bırakma tavsiye niteliğindedir {#deprecated-is-advisory}
Bugün hiçbir şey bozulmaz.
Yukarıdaki her yöntem, **2025-11-25 veya daha eski** bir sürümle anlaşmış her oturumda çalışmaya devam eder. İstemcide `mode="legacy"` sabitleyin, 2026 öncesi davranışın aynısını elde edersiniz. İletilen veride hiçbir değişiklik yoktur ve yetenek anlaşması aynıdır.
Değişen şey, her biri ilk kez çalıştığında görünür bir uyarı almanızdır:
```text
MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
```
`MCPDeprecationWarning`, `DeprecationWarning`'in **değil**, `UserWarning`'in alt sınıfıdır. Bu kasıtlıdır: Python'ın varsayılan filtresi `DeprecationWarning`'i yalnızca doğrudan `__main__` olarak çalıştırılan kodda gösterir; kütüphaneler bir şeyleri böyle kullanım dışı bırakır ve iki yıl boyunca kimse fark etmez. Bu uyarı ise her yerde, `-W` bayrağı olmadan görünür.
!!! warning
"Tavsiye niteliği" iletilen veriye gelince biter. Örnekleme ve kök dizinler sunucudan
istemciye giden *isteklerdir* ve 2026-07-28 oturumunda bunları taşıyacak bir kanal yoktur.
Modern bir bağlantıda bir aracın içinde `ctx.session.create_message()`'ı çağırın: uyarı
yine tetiklenir, ardından gönderim bir hatayla başarısız olur:
```text
Cannot send 'sampling/createMessage': this transport context has no back-channel
for server-initiated requests.
```
Bu sırayla iki sinyal. `MCPDeprecationWarning`, yöntemi çağırdığınız anda, her
bağlantıda tetiklenir. Hata ise SDK ardından göndermeyi denediğinde geri dönen şeydir.
Bu ikisi yalnızca, istemcisi eşleşen callback'i kaydetmiş bir `mode="legacy"`
bağlantısında uçtan uca çalışır.
## Uyarıyı susturma {#silencing-the-warning}
Yeni kodda susturmayın.
Ancak bakımını yaptığınız ve gerçekten 2026 öncesi istemcilere hizmet veren bir sunucunun sessiz bir log'a sonuna kadar hakkı vardır. Kategoriyi, ilk kullanım dışı çağrı çalışmadan önce filtreleyin:
```python
import warnings
from mcp import MCPDeprecationWarning
warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
```
API'nin tamamı bu. Yöntem başına bir anahtar yok, zaten istemezsiniz de: tek kategori olmasının anlamı, tek satırın onu susturması ve tek satırın geri getirmesidir.
!!! check
Filtreyi ters yönde çalıştırın, bedava bir regresyon testi elde edersiniz. pytest
yapılandırmanızdaki `filterwarnings` ayarına `"error::mcp.MCPDeprecationWarning"`
ekleyin; kullanım dışı çağrı uyarmak yerine **istisna fırlatır**. Hâlâ `ctx.info()`'yu
çağıran `old_log` adlı bir araç artık geçmez ve şunu bildirmeye başlar:
```text
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
```
Tek satır pytest yapılandırmasıyla, kullanım dışı bir çağrı bir testi başarısız kılmadan
kod tabanınıza bir daha asla sızamaz.
## Özet {#recap}
* 2026-07-28 spesifikasyonu **kök dizinleri**, sunucunun başlattığı **örneklemeyi** ve protokol üzerinden **log tutmayı** kullanım dışı bırakır (hepsi [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), **ilerlemeyi** sunucudan istemciye yönle sınırlar ve **`ping`**'i kaldırır.
* Yerine geçenler sütunu sizi ileriye yönlendirir: örnekleme ve kök dizinler için **[Çok turlu istekler](handlers/multi-round-trip.md)**, log tutma için **[Log tutma](handlers/logging.md)**, ilerleme için **[İlerleme](handlers/progress.md)**. `ping` için hiçbir şey gerekmez.
* Kullanım dışı bırakma tavsiye niteliğindedir: iletilen veride değişiklik yok, her şey 2026 öncesi oturumlarda çalışmaya devam eder ve görünür bir `MCPDeprecationWarning` alırsınız (bir `UserWarning`, dolayısıyla varsayılan olarak açık).
* Örnekleme ve kök dizinler ayrıca, 2026-07-28 oturumunda bulunmayan bir geri kanala (back-channel) ihtiyaç duyar. Modern bir bağlantıda önce uyarır, sonra istisna fırlatırlar.
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` tüm kategoriyi susturur; pytest'te `"error::mcp.MCPDeprecationWarning"` bunu bir test hatasına dönüştürür.
* Yeni kod bunların hiçbiri üzerine kurulmamalıdır.
Bu belgelerdeki diğer tüm sayfalar güncel API'yi anlatır.

View file

@ -0,0 +1,144 @@
---
translation:
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
tool: 1
---
# İlk adımlar {#first-steps}
**[Giriş sayfası](../index.md)** hızlı ilerler: bir sunucu yazın, çalıştırın, bir araç çağırın.
Bu sayfa ise ağırdan alır: bir sunucunun sunabileceği üç şeyin hepsini ele alır ve yol boyunca her şeye bir ad verir.
## Host, istemci ve sunucu {#host-client-and-server}
Bundan sonraki her sayfada göreceğiniz üç sözcük:
* **Host**, LLM uygulamasıdır: Claude, bir IDE, bir ajan çalışma zamanı. Kullanıcının konuştuğu şey odur.
* **İstemci**, host'un içinde yaşar ve MCP konuşur. Host, bağlandığı her sunucu için bir istemci çalıştırır.
* **Sunucu**, bu SDK ile sizin oluşturduğunuz şeydir. İstemcilere bir şeyler sunar. Modelle hiçbir zaman doğrudan konuşmaz.
Sunucuyu siz yazarsınız. Host'lar başkasının ürünüdür. SDK size bir de `Client` verir. Onu sunucularınızı test etmek için kullanırsınız; bu sayfanın ilerisinde karşınıza çıkar.
## Üç temel öğe {#the-three-primitives}
Bir sunucu tam olarak üç tür şey sunar. Onları birbirinden ayıran, **kullanılmalarına kimin karar verdiğidir**:
| Temel öğe | Kontrol eden | Nedir | Örnek |
|----------------|-----------------|---------------------------------------------------------------|-----------------------------------------------|
| **Araçlar** | Model | Modelin bir eylemde bulunmak için çağırdığı fonksiyon | Bir API çağrısı, bir veritabanı yazma işlemi |
| **Kaynaklar** | Uygulama | Host'un modelin bağlamına yüklediği veri | Bir dosyanın içeriği, bir API yanıtı |
| **Prompt'lar** | Kullanıcı | Kullanıcının adıyla çağırdığı, yeniden kullanılabilir mesaj şablonu | Bir slash komutu, bir menü girdisi |
"Kontrol eden", bu ayrımın özüdür. Bir araç, **model** onu çağırmaya karar verdiği için çalışır. Bir kaynak, **uygulama** modelin ona ihtiyacı olduğuna karar verdiği için eklenir. Bir prompt, **kullanıcı** onu seçtiği için çalışır.
!!! info
Daha önce bir web API'si geliştirdiyseniz sezginin çoğu zaten sizde var: **kaynak** bir `GET`'tir
(veri yükler, hiçbir şeyi değiştirmez), **araç** ise bir `POST`'tur (iş yapar ve yan etkileri
olabilir). **Prompt**'un HTTP'de karşılığı yoktur; kullanıcının adıyla çalıştırdığı kayıtlı bir
sorguya daha yakındır.
## Tek sunucu, üçü birden {#one-server-all-three}
```python title="server.py" hl_lines="6 12 18"
--8<-- "docs_src/first_steps/tutorial001.py"
```
Üç sade fonksiyon, üç dekoratör. Her dekoratör kaydın tamamıdır:
* `@mcp.tool()`, `add`'i bir **araç** yapar.
* `@mcp.resource("greeting://{name}")`, `greeting`'i bir **kaynak şablonu** yapar: URI içindeki `{name}`, fonksiyonun parametresidir.
* `@mcp.prompt()`, `summarize`'ı bir **prompt** yapar. Döndürdüğü dize bir kullanıcı mesajına dönüşür.
Geri kalan her şeyi (adı, açıklamayı, argüman şemasını) SDK fonksiyonun kendisinden okur: adından, docstring'inden, tür ipuçlarından. Hiçbirini ayrıca bildirmediniz.
!!! tip
SDK'nın iki yarısının iki ayrı import yolu vardır: `from mcp import Client` ve
`from mcp.server import MCPServer`. `from mcp import MCPServer` diye bir şey yoktur.
### Deneyin {#try-it}
MCP Inspector ile çalıştırın:
```console
uv run mcp dev server.py
```
Yazdırdığı URL'yi açın. Inspector'da her temel öğe için bir sekme var; sırayla üzerinden geçin.
**Tools.** Tek bir girdi: `add`, açıklaması *Add two numbers.* Formda `a` için zorunlu bir tamsayı alanı, `b` için de bir tane daha var. Doldurun, çağırın; sonuç `3`. Inspector bu formu `a: int, b: int` ifadesinden oluşturdu. Diğer tüm istemciler de öyle yapar.
**Resources.** *Resources* listesi boş. `greeting`, **Resource Templates** altında; çünkü `greeting://{name}` bir parametre içerir: biri bir `name` verene kadar listelenecek tek bir kaynak yoktur. Ona `World` verin ve okuyun:
```text
Hello, World!
```
**Prompts.** Tek bir girdi: tek bir zorunlu `text` argümanı olan `summarize`. Biraz metinle getirin; `role: user` taşıyan ve içeriği işlenmiş dizeniz olan tek bir mesaj alırsınız. Bir prompt'un hepsi budur: mesaj oluşturan bir fonksiyon.
Inspector sunucunuzu **stdio** üzerinden çalıştırdı; bu, bir MCP sunucusunun konuşabileceği aktarımlardan biridir. Henüz bir tane seçmiyorsunuz; bunun sayfası **[Sunucunuzu çalıştırma](../run/index.md)**.
## Yetenekler {#capabilities}
Inspector'da üç sekme gördünüz. Üç tane olduğunu nereden bildi?
Bir istemci bağlandığında sunucu **yeteneklerini** beyan eder: hangi istek ailelerini yanıtlayacağını. İstemci, neyi isteyeceğine karar vermek için bu beyanı kullanır. Bunu siz hiç yazmadınız; `MCPServer` sizin yerinize beyan eder.
Kendiniz bakın. SDK'nın `Client`'ı sunucu nesnesini doğrudan kabul eder ve ona **bellek içinde** bağlanır (alt süreç yok, port yok):
```python
import asyncio
from mcp import Client
from server import mcp
async def main() -> None:
async with Client(mcp) as client:
print(client.server_capabilities.model_dump(exclude_none=True))
asyncio.run(main())
```
```text
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
```
Bu sözlük, sunucunuzun beyan ettiği **yeteneklerdir**. Bağlanan her istemcinin öğrendiği ilk şey budur:
| Yetenek | İstemci artık şunları çağırabilir |
|-------------|----------------------------------------------------------------|
| `tools` | `tools/list`, `tools/call` |
| `resources` | `resources/list`, `resources/templates/list`, `resources/read` |
| `prompts` | `prompts/list`, `prompts/get` |
`MCPServer` üç temel öğenin hepsini sunar; bu yüzden üçü de her zaman beyan edilir.
Orada ne olmadığına dikkat edin. `completions` (kaynak şablonları ve prompt'lar için argüman otomatik tamamlama) sizin yazacağınız bir işleyici gerektirir; bu sunucuda yok, dolayısıyla yetenek de yok ve uslu bir istemci sormaz. İsteğe bağlı her şey için kural budur: şeyi kaydedin, yetenek belirir; **[Tamamlamalar](../servers/completions.md)** bunu kanıtlar.
!!! info
`Client(mcp)`, bu belgelerdeki her örneğin test edildiği aynı bellek içi istemcidir;
sizinkileri de böyle test edeceksiniz. Kendine ait koca bir sayfası var: **[Test etme](testing.md)**.
## Yazmadıklarınız {#what-you-did-not-write}
Bu sayfaya dönüp bir bakın. Üç küçük Python fonksiyonu yazdınız. Şunları **yazmadınız**:
* Bir JSON Schema. `a: int, b: int`, `add` şemasının *ta kendisidir*.
* Bir istek işleyici. `tools/list`, `resources/read`, `prompts/get`: hepsi sizin yerinize sunulur.
* Bir yetenek beyanı. `MCPServer` onu sizin yerinize yaptı.
* Tek satır protokol. Sürüm anlaşması, JSON-RPC çerçevelemesi, yetenek değiş tokuşu: hepsi `mcp dev` ve `Client(mcp)` içinde oldu ve siz hiçbirini görmediniz.
SDK'nın bütün meselesi bu oran.
## Özet {#recap}
* **Host** LLM uygulamasıdır, **istemci** onun MCP konuşan yarısıdır, **sunucu** ise sizin oluşturduğunuz şeydir.
* Araçları **model**, kaynakları **uygulama**, prompt'ları **kullanıcı** kontrol eder.
* Her temel öğe için bir dekoratör: `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. Ad, açıklama ve şema fonksiyondan gelir.
* İçinde `{param}` olan bir URI, somut kaynaklardan ayrı listelenen bir kaynak **şablonu** oluşturur.
* Sunucunun **yetenekleri** sizin yerinize beyan edilir ve bir istemci yalnızca sunucunun beyan ettiklerini ister.
* `Client(mcp)` sunucu nesnesine bellek içinde bağlanır: ilk günden test düzeneğiniz.
Sırada **[Gerçek bir host'a bağlanma](real-host.md)** var: bu sunucu, gerçekten, Claude Desktop'ın ya da bir IDE'nin içinde. Ardından **[Test etme](testing.md)**: bir sayfa, bir bellek içi istemci ve çalışıp çalışmadığını bir daha asla tahmin etmek zorunda kalmazsınız. Ondan sonra her temel öğenin kendi sayfası var; modelin yönettiğiyle başlıyoruz: **[Araçlar](../servers/tools.md)**.

View file

@ -0,0 +1,57 @@
---
translation:
sections: [ed4a756b4c53c585, 97e2fb315b7fe398, 4d04f1c6f4bf6c1d, 577d73078fc62baf]
tool: 1
---
# Başlarken {#get-started}
MCP'de ya da bu SDK'da yeni misiniz? Buradan başlayın. Bu sayfalar sizi sıfırdan
çalışan, test edilmiş bir sunucuya götürür: [SDK'yı kurun](installation.md),
[ilk sunucunuzu](first-steps.md) yazın, [onu gerçek bir host'a bağlayın](real-host.md) ve
bellek içi bir istemciyle [test edin](testing.md).
## Kodu çalıştırma {#run-the-code}
Kod bloklarının tamamı doğrudan kopyalanıp kullanılabilir: hepsi eksiksiz, çalışan dosyalardır.
Takip etmek için bir bloğu `server.py` dosyasına yapıştırın ve MCP Inspector'da açın:
```console
uv run mcp dev server.py
```
Kodu yazmanız (ya da kopyalamanız), düzenlemeniz ve yerelde çalıştırmanız **ŞİDDETLE önerilir**. Asıl meseleyi kendi editörünüzde kullanırken görürsünüz: ne kadar az kod yazdığınızı, otomatik tamamlamayı, daha hiçbir şeyi çalıştırmadan hataları yakalayan tür denetimlerini.
## Tahmin yürütmeyeceksiniz {#you-will-not-be-guessing}
Bu belgelerdeki her örnek, SDK'nın kendi deposunda [`docs_src/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/docs_src) altında duran eksiksiz bir dosyadır ve her biri SDK'nın test paketi tarafından **bellek içi bir istemci** aracılığıyla çalıştırılır:
```python
import pytest
from mcp import Client
from server import mcp
@pytest.mark.anyio
async def test_add() -> None:
async with Client(mcp) as client:
result = await client.call_tool("add", {"a": 1, "b": 2})
assert result.structured_content == {"result": 3}
```
Alt süreç yok, port yok, aktarım yok. `Client(mcp)` sunucu nesnesine doğrudan bağlanır.
SDK'daki bir değişiklik bu sayfalardan birindeki örneği bozarsa, sayfadan önce CI kırmızıya döner. Burada okuduğunuz kod, çalışan kodun ta kendisidir.
Bunu [Test etme](testing.md) sayfasında kendiniz de kullanacaksınız; kendi sunucularınızı da böyle test edersiniz.
## Bundan sonra nereye {#where-to-go-next}
Bir sunucuyu çalıştırdıktan sonra bu belgelerin geri kalanı bir kurs değil, bir başvuru kaynağıdır.
Her sayfa kendi başına ayakta durur; bu yüzden doğrudan ihtiyacınız olana atlayın:
* Bir sunucunun ne sunduğu (araçlar, kaynaklar, prompt'lar) **[Sunucular](../servers/index.md)** bölümünde.
* Kaydettiğiniz fonksiyonların içinde nelerin kullanılabildiği **[İşleyicinin içinde](../handlers/index.md)** bölümünde.
* Sunucuyu istemcilerin önüne çıkarma (stdio, HTTP, mevcut FastAPI uygulamanız) **[Sunucunuzu çalıştırma](../run/index.md)** bölümünde.
* Diğer tarafı, yani MCP sunucularını *kullanan* bir uygulamayı oluşturma **[İstemciler](../client/index.md)** bölümünde.

Some files were not shown because too many files have changed in this diff Show more