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

View file

@ -0,0 +1,158 @@
---
translation:
sections: [0355618e5f4d5fe4, 1821eaf50f2d0b64, 82e0b28ebd3abf5a, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
**MCP App** ऐसा tool है जिसका एक चेहरा है: अपने data के साथ-साथ tool एक HTML document की ओर इशारा करता है, जिसे host interactive surface के रूप में render करता है।
दो हिस्से, हमेशा दो हिस्से:
1. **एक tool**, जो काम करता है और data लौटाता है, किसी भी दूसरे tool की तरह।
2. **एक `ui://` resource**, जिसमें वह HTML है जो host उसके लिए दिखाता है।
tool में resource का `_meta.ui.resourceUri` reference होता है। host उसे `resources/read` से fetch करता है, **sandboxed iframe** में render करता है, और tool का result `postMessage` के ज़रिए उस iframe में भेजता है। आपका server कभी कोई `ui/*` message न भेजता है, न पाता है: वह traffic host और iframe के बीच का है। आप एक tool और एक HTML document serve करते हैं; दिखाने का सारा काम host करता है।
SDK इसे built-in `Apps` extension (`io.modelcontextprotocol/ui`) के रूप में देता है। अगर [Extensions](extensions.md) आपके लिए नए हैं, तो पहले उस page पर एक नज़र डाल लें। एक मिनट लगेगा, फिर वापस आएँ।
## चेहरे वाली घड़ी {#a-clock-with-a-face}
```python title="server.py" hl_lines="19 22 30 32"
--8<-- "docs_src/apps/tutorial001.py"
```
चार कदम:
* `Apps()`: एक instance में आपके UI-bound tools और उनके resources रहते हैं।
* `@apps.tool(resource_uri="ui://clock/app.html")`: एक साधारण tool, साथ में
`_meta.ui.resourceUri` की मुहर। जो कुछ `@mcp.tool()` लेता है (name, title,
description, ...) वह सब यहाँ भी चलता है।
* `apps.add_html_resource("ui://clock/app.html", CLOCK_HTML)`: उससे मेल खाता
resource, जो `text/html;profile=mcp-app` के रूप में serve होता है। ठीक यही MIME type
host को बताता है "यह app है, इसे render करें"।
* `MCPServer("clock", extensions=[apps])`: opt in करें। server अब
`capabilities.extensions` के तहत `io.modelcontextprotocol/ui` advertise करता है।
HTML खुद host के `postMessage` को सुनता है और result दिखाता है। असली
apps के लिए अपने HTML के अंदर official [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps)
browser SDK इस्तेमाल करें। यह आपको raw message events की जगह `ontoolresult`, `callServerTool`,
`getHostContext` और `onhostcontextchanged` देता है।
## Graceful degradation {#graceful-degradation}
हर client apps render नहीं करता। इसका आपके लिए क्या मतलब है, spec साफ़-साफ़ कहता है:
> UI उपलब्ध होने पर भी tools का एक सार्थक `content` array लौटाना **अनिवार्य (MUST)** है।
model `content` पढ़ता है; iframe इंसानों के लिए है। UI-capable host भी text result
model को देता है, और text-only client को **सिर्फ़** वही मिलता है। इसलिए मानक
pattern है: एक tool, दो जवाब। `get_time` को फिर से देखें:
```python title="server.py" hl_lines="23-27"
--8<-- "docs_src/apps/tutorial001.py"
```
`client_supports_apps(ctx)` तभी `True` होता है जब client ने
`io.modelcontextprotocol/ui` extension declare किया हो **और** अपनी `mimeTypes`
settings में `text/html;profile=mcp-app` सूचीबद्ध किया हो। यह field ज़रूरी है,
इसलिए जो client इसे छोड़ देता है वह गिना नहीं जाता। इसी file में `main()` ठीक यही
declare करता है: negotiation का client वाला आधा हिस्सा, और rich जवाब वापस आता है।
!!! warning
कभी भी `"[Rendered UI]"` जैसा placeholder अकेले content के रूप में न लौटाएँ।
अगर fallback text बेकार है, तो tool हर text-only client के लिए और खुद model
के लिए बेकार है। वह वाक्य लिखें।
## iframe को lock करना {#locking-the-iframe-down}
सुरक्षा metadata resource वाले हिस्से पर रहता है: iframe क्या load कर सकता है, उसे
कौन-सी browser permissions चाहिए, वह किस तरह frame होना चाहेगा:
```python title="server.py" hl_lines="9 19-22"
--8<-- "docs_src/apps/tutorial002.py"
```
`csp` और `permissions` **host से की गई requests** हैं, server का व्यवहार नहीं। host
इन्हीं से iframe की Content-Security-Policy और Permissions-Policy बनाता है, और
मना भी कर सकता है। अनुमति मिल ही गई, यह मानने के बजाय अपने JS में feature-detect करें।
`ResourceCsp`, एक-एक field करके (Python नाम, wire key, host उसके साथ क्या करता है):
| Python | Wire (`_meta.ui.csp`) | क्या नियंत्रित करता है |
|---|---|---|
| `connect_domains` | `connectDomains` | `connect-src`: `fetch`/XHR कहाँ जा सकते हैं |
| `resource_domains` | `resourceDomains` | `img-src`, `style-src`, ...: static assets |
| `frame_domains` | `frameDomains` | `frame-src`: nested iframes |
| `base_uri_domains` | `baseUriDomains` | `base-uri`: `<base>` किस ओर इशारा कर सकता है |
`ResourcePermissions`: हर field iframe के लिए एक browser permission माँगता है।
| Python | Wire (`_meta.ui.permissions`) |
|---|---|
| `camera` | `camera` |
| `microphone` | `microphone` |
| `geolocation` | `geolocation` |
| `clipboard_write` | `clipboardWrite` |
!!! note
CSP और permissions **resource** पर रहते हैं, tool पर कभी नहीं। spec के tool
metadata में इनके लिए कोई जगह नहीं है, और hosts वहाँ इन्हें अनदेखा करते हैं। SDK इस
गलती को लिखना ही नामुमकिन बना देता है: `@apps.tool()` में `csp` parameter है ही नहीं।
### Visibility {#visibility}
tool पर `visibility=["app"]` कहता है "यह iframe के लिए है, model के लिए नहीं":
* `"model"`: model इसे call कर सकता है।
* `"app"`: iframe इसे call कर सकता है (`callServerTool` के ज़रिए)।
* छोड़ दिया जाए: दोनों, जो default है।
Filtering **host का** काम है। आपका server app-only tools को `tools/list` में किसी भी
दूसरे tool की तरह सूचीबद्ध करता है; host उन्हें model से छिपाता है। server-side filter न करें।
## वे नियम जो SDK लागू करता है {#the-rules-the-sdk-enforces}
ये सब startup पर ही fail होते हैं, production में नहीं:
* जो `resource_uri` या resource URI `ui://...` नहीं है, वह decoration/registration
के समय `ValueError` है।
* ऐसे URI से बँधा tool जिसका **कोई मेल खाता registered resource नहीं** है, तब `ValueError`
है जब `MCPServer(extensions=[apps])` extension को consume करता है। ऐसा tool जो HTML
advertise करे पर `resources/read` पर 404 दे, misconfiguration है, इसलिए server
construct होने से मना कर देता है।
* `@apps.tool()` पर `meta={"ui": ...}` `ValueError` है। `_meta["ui"]` decorator का
है; अपनी बात `resource_uri=` और `visibility=` से कहें। बाकी `meta=` keys
साथ में आराम से merge हो जाती हैं।
आज न TypeScript ext-apps SDK इनमें से कुछ पकड़ता है, न FastMCP; हम चाहेंगे कि
आपको यह किसी host से पहले पता चल जाए।
## Inline HTML से आगे {#beyond-inline-html}
`add_html_resource` आम मामले को संभालता है: HTML की एक string। बाकी किसी भी चीज़ के लिए,
disk पर रखा HTML हो या generate किया गया content, resource खुद बनाएँ और सौंप दें:
```python title="server.py" hl_lines="12 18"
--8<-- "docs_src/apps/tutorial003.py"
```
जब resource कोई MIME type साफ़ तौर पर set नहीं करता, तो `add_resource`
`text/html;profile=mcp-app` MIME type भर देता है, और साफ़ तौर पर दिए गए बेमेल type को
reject कर देता है: किसी और MIME type वाला `ui://` resource ऐसा resource है जिसे कोई host render नहीं करेगा।
!!! tip
क्या आप ऐसे pre-GA host के लिए बना रहे हैं जो अब भी deprecated flat
`_meta["ui/resourceUri"]` key पढ़ता है? इसे खुद merge करें:
`@apps.tool(resource_uri="ui://x", meta={"ui/resourceUri": "ui://x"})`।
nested `ui` object ही spec वाला आकार है; flat key हटने वाली है।
## इसे चलता देखें {#see-it-run}
`examples/stories/` में `apps` story यही page एक चलाने लायक जोड़ी के रूप में है: UI-bound
clock tool वाला एक server, और एक client जो Apps negotiate करता है, tool का
`_meta.ui.resourceUri` पढ़ता है, HTML fetch करता है और tool को call करता है।
```bash
uv run python -m stories.apps.client
```

View file

@ -0,0 +1,172 @@
---
translation:
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
tool: 1
---
# Extensions {#extensions}
**extension** एक identifier के पीछे रखा गया MCP behaviour का opt-in bundle है।
server पर यह tools, resources और नए request methods जोड़ सकता है, और `tools/call` को wrap कर सकता है। client पर यह `tools/call` के अतिरिक्त result shapes claim कर सकता है और vendor notifications observe कर सकता है। हर पक्ष अपने-अपने `capabilities.extensions` के तहत advertise करता है, और जिसने इसे नहीं माँगा उसके लिए कुछ नहीं बदलता। यही contract है ([SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)), और इसका एक सुनहरा नियम है: **extensions default रूप से बंद रहते हैं**।
## extension इस्तेमाल करना {#using-an-extension}
construction के समय instances पास करें:
```python title="server.py"
--8<-- "docs_src/extensions/tutorial001.py"
```
हो गया। अब server `capabilities.extensions` के तहत `io.modelcontextprotocol/ui` advertise करता है और extension जो कुछ जोड़ता है वह सब serve करता है।
`Apps` built-in reference extension है, और इसका अपना अलग page है: **[MCP Apps](apps.md)**।
!!! note
extensions construction के समय ही तय हो जाते हैं। बाद में call करने के लिए कोई `add_extension` नहीं है: जब clients server से जुड़े हों, तब उसका capability map बदलना नहीं चाहिए।
capability map `server/discover` के साथ जाता है, जो **2026-07-28** का रास्ता है। legacy `initialize` handshake में इसे रखने की कोई जगह नहीं है, इसलिए legacy client को extension दिखता ही नहीं। इसे ध्यान में रखकर design करें: extension server को **बढ़ाता** है, server को इस्तेमाल करने का यही एकमात्र तरीका नहीं होना चाहिए।
## अपना extension लिखना {#writing-your-own}
`Extension` को subclass करें और सिर्फ़ वही override करें जिसकी ज़रूरत हो। हर method का default है।
### Identifier {#the-identifier}
```python
--8<-- "docs_src/extensions/tutorial002.py"
```
identifier एक `vendor-prefix/name` string है जो spec की `_meta` key grammar का पालन करती है: dot से अलग किए गए labels (हर label अक्षर से शुरू होता है, अक्षर या अंक पर खत्म होता है), फिर एक slash, फिर name। यह **class define होते ही** validate होता है, इसलिए typo पकड़ने के लिए server के boot होने का इंतज़ार नहीं करना पड़ता:
```text
TypeError: Stamps.identifier must be a `vendor-prefix/name` string
(reverse-DNS prefix required), got 'stamps'
```
prefix के रूप में ऐसा domain इस्तेमाल करें जो आपके नियंत्रण में हो। `io.modelcontextprotocol/*` उन extensions के लिए है जिन्हें खुद MCP project specify करता है।
### tools जोड़ना {#contributing-tools}
सबसे छोटा काम का extension एक tool और एक settings map है:
```python title="server.py" hl_lines="17 19-20 22-23 26"
--8<-- "docs_src/extensions/tutorial003.py"
```
* `tools()` `ToolBinding`s लौटाता है। server हर एक को ठीक वैसे ही register करता है जैसे आपने खुद `mcp.add_tool(...)` call किया हो: वही schema generation, वही `Context` injection, सब कुछ वही।
* `settings()` वह value है जो `capabilities.extensions["com.example/stamps"]` पर advertise होती है। बिना settings के extension advertise करने के लिए `{}` (default) लौटाएँ।
* extension को server कभी नहीं मिलता। यह अपने योगदान data के रूप में declare करता है; `MCPServer` उन्हें consume करता है। mutate करने के लिए कोई `self.server` नहीं है।
और `main()` इसका सबूत है, सीधे `mcp` से जुड़ा एक in-memory client:
```python title="server.py" hl_lines="29-34"
--8<-- "docs_src/extensions/tutorial003.py"
```
### अपने methods serve करना {#serving-your-own-methods}
extension **नए request methods** register कर सकता है: उसके अपने verbs, जो spec के verbs के साथ-साथ serve होते हैं:
```python title="server.py" hl_lines="16-22 31 40-48"
--8<-- "docs_src/extensions/tutorial004.py"
```
* `SearchParams` `RequestParams` को subclass करता है, इसलिए 2026 का `_meta` envelope एक समान तरीके से parse होता है और आपके handler को validated params मिलते हैं, कच्चा dict कभी नहीं। जो client के नियंत्रण में है उसकी सीमा बाँधें: `Field(ge=1, le=100)` किसी बेतुके `limit` को तभी reject कर देता है, इससे पहले कि आपका code उसके लिए कुछ allocate करे।
* `require_client_extension(ctx, EXTENSION_ID)` ही gate है: जिस client ने extension declare नहीं किया उसे `-32021` (missing required client capability) error मिलता है, साथ में वह machine-readable `requiredCapabilities` payload जो spec माँगता है।
* `protocol_versions=frozenset({"2026-07-28"})` method को एक wire version पर pin कर देता है। किसी भी दूसरे version पर client को `METHOD_NOT_FOUND` मिलता है, ठीक वैसे जैसे method वहाँ मौजूद ही न हो। उस client के लिए, वह है भी नहीं।
methods **सख़्ती से additive** हैं। SDK इसे construction के समय लागू करता है, runtime पर नहीं:
* spec में define किए गए method (`tools/list`, `completion/complete`, ...) के लिए `MethodBinding` बनाते ही `ValueError` raise होता है। core verbs server के हैं।
* एक ही method को bind करने वाले दो extensions हों, तो दूसरा register होते ही raise होता है। plugins एक-दूसरे को last-write-wins से ही खराब करते हैं; हम ऐसा नहीं करते।
* खाली `protocol_versions` set भी raise करता है: जो method कभी serve ही नहीं हो सकता वह bug है, configuration नहीं।
### Client side {#the-client-side}
उसी file का `main()` ही client की पूरी कहानी है, उसके दोनों हिस्से:
```python title="server.py" hl_lines="54-58"
--8<-- "docs_src/extensions/tutorial004.py"
```
* `Client(..., extensions=[advertise(EXTENSION_ID)])` extension declare करता है। ये declarations `ClientCapabilities.extensions` बन जाती हैं: 2026-07-28 connection पर यह map हर request के `_meta` envelope में जाता है, इसलिए server इसे **हर** request पर देखता है; legacy connection पर यह `initialize` handshake के साथ जाता है। server code को फ़र्क नहीं पड़ता कि कौन सा: `require_client_extension(ctx, ...)` और `ctx.session.check_client_capability(...)` दोनों रास्तों पर सही स्रोत पढ़ते हैं।
* vendor methods एक परत नीचे `client.session.send_request(...)` पर उतरते हैं; `Client` सिर्फ़ spec verbs के लिए first-class methods जोड़ता है। `send_request` कोई भी `Request` subclass स्वीकार करता है, इसलिए vendor request जैसी है वैसी ही चली जाती है।
### `tools/call` को intercept करना {#intercepting-toolscall}
यह इकलौता interceptive hook है। tool call को observe, short-circuit या veto करने के लिए `intercept_tool_call` override करें:
```python title="server.py" hl_lines="17-24"
--8<-- "docs_src/extensions/tutorial005.py"
```
* `params` validated `CallToolRequestParams` है: आपको `params.name` और `params.arguments` बिना raw JSON छुए मिलते हैं। यही तय करता है कि कौन सा tool call चलेगा: `call_next` से rewritten context पास करने से वह बदलता है जो handler `ctx` पर देखता है, tool invocation नहीं। wire-level request rewriting [Middleware](middleware.md) का काम है।
* `call_next(ctx)` chain का बाकी हिस्सा चलाता है और handler का result लौटाता है। इसे बिना बदले लौटाएँ (observe), कुछ और लौटाएँ (replace), या `MCPError` raise करें (refuse)। आप जो भी लौटाते हैं वह किसी भी handler result की तरह serialize होता है, 2026 पीढ़ी के `serverInfo` identity stamp समेत, इसलिए short-circuit करने वाला interceptor कभी anonymous या off-schema response नहीं बनाता।
* कई extensions होने पर interceptors registration के क्रम में nest होते हैं: `extensions=[...]` में पहला extension सबसे बाहर होता है।
* default implementation pass-through है, और जिस server के extensions इस hook को कभी override नहीं करते, उसका bare `tools/call` handler अनछुआ रहता है। जो आप इस्तेमाल नहीं करते उसकी कीमत नहीं चुकाते।
hook `tools/call` को wrap करता है, और कुछ नहीं। हर message से जुड़ी बातों के लिए [Middleware](middleware.md) इस्तेमाल करें। वह इसी के लिए है।
## client extension इस्तेमाल करना {#using-a-client-extension}
**client extension** वही contract है, इस्तेमाल करने वाले पक्ष से: एक identifier के पीछे client-side behaviour का bundle। instances को `Client(extensions=[...])` में पास करें और tools सामान्य तरीके से call करें:
```python title="client.py" hl_lines="66-68"
--8<-- "docs_src/extensions/tutorial006.py"
```
`call_tool("buy", ...)` हर दूसरे call की तरह सादा `CallToolResult` लौटाता है। extension ने जो बदला: server अब `buy` का जवाब final result के बजाय `receipt` **result shape** से दे सकता है, और `call_tool` के लौटने से पहले `Receipts` उसे पूरा कर देता है (यहाँ follow-up call से receipt redeem करके)। call site में कुछ नहीं हिलता।
extension हटा दें तो इनमें से कुछ भी मौजूद नहीं: server का gate उस client को मना कर देता है जिसने इसे declare नहीं किया (error -32021), और gate छोड़ने वाले server से आया claimed shape validation में fail होता है, ठीक वैसे जैसे spec अनजान `resultType` के लिए माँगता है। default रूप से बंद, wire के दोनों सिरों पर।
**बिना** किसी client-side behaviour के identifier advertise करने के लिए (server capability पर gate लगाता है, client कुछ नहीं करता, जैसे ऊपर वाले search client में), `advertise()` इस्तेमाल करें:
```python
from mcp.client import advertise
client = Client(mcp, extensions=[advertise("com.example/search")])
```
## client extension लिखना {#writing-a-client-extension}
`ClientExtension` को subclass करें और सिर्फ़ वही override करें जिसकी ज़रूरत हो। योगदान के तीन प्रकार, हर एक का default: `settings()`, `claims()` और `notifications()`।
```python title="client.py" hl_lines="17-18 43-44 46-47"
--8<-- "docs_src/extensions/tutorial006.py"
```
* identifier वही grammar मानता है जो server का, और class define होते ही validate होता है।
* `claims()` `ResultClaim`s लौटाता है: एक wire tag, उसे parse करने वाला model, और उसे पूरा करने वाला resolver। model के लिए `result_type: Literal["receipt"]` से tag pin करना ज़रूरी है और वह verb के core result types को subclass नहीं कर सकता; दोनों बातें claim बनते समय enforce होती हैं। `receipt_token` जैसे vendor fields wire पर जैसे हैं वैसे जाते हैं: substituted shape client तक हू-ब-हू पहुँचता है।
* resolver को parsed model और एक `ClaimContext` मिलता है; `ctx.session` वही public handle है जो `client.session`, इसलिए follow-ups साधारण session calls हैं। यह verb का सामान्य `CallToolResult` लौटाता है।
* `settings()` वह value है जो `ClientCapabilities.extensions[identifier]` पर advertise होती है, और `Client` बनते समय एक बार पढ़ी जाती है।
`notifications()` observe करने के लिए vendor server notifications declare करता है:
```python
def notifications(self) -> Sequence[NotificationBinding[Any]]:
return [NotificationBinding(method="notifications/receipts", params_type=ReceiptEvent, handler=self.on_receipt)]
```
handler को validated params एक-एक करके, dispatch के क्रम में मिलते हैं। यह observe करता है; veto या reply नहीं कर सकता।
दो शांत नियम। claims सिर्फ़ 2026-07-28 connections पर सक्रिय रहते हैं, और capability advertisement उन्हीं के पीछे चलता है: legacy connection पर claims गायब हो जाते हैं और identifier भी उनके साथ advertisement से हट जाता है, इसलिए client कभी ऐसा extension advertise नहीं करता जिसके shapes वह खुद reject कर देता। और जब claimed shape resolver के बजाय आपको खुद चाहिए, तो `client.session.call_tool(..., allow_claimed=True)` call करें; उस flag के बिना, session-tier caller तक पहुँचने वाला claimed shape `UnexpectedClaimedResult` raise करता है।
### Extension verbs {#extension-verbs}
extension के अपने request methods को client-side registration की ज़रूरत नहीं। vendor request type `mcp.types.Request` को subclass करता है और `client.session.send_request` से जाता है, जैसा [अपने methods serve करना](#serving-your-own-methods) में है। एक बात और: जब किसी params key का `Mcp-Name` header में जाना ज़रूरी हो (tasks जैसे extension specs अपने verbs के लिए यह माँगते हैं), तो request type `name_param` declare करता है:
```python title="client.py" hl_lines="22-25 46-47"
--8<-- "docs_src/extensions/tutorial007.py"
```
session हर send path पर `params["jobId"]` को `Mcp-Name` में mirror करता है, और value न होने पर ज़रूरी header चुपचाप छोड़ने के बजाय साफ़ तौर पर fail होता है।
## extension क्या नहीं कर सकता {#what-an-extension-cannot-do}
योगदान की surface जानबूझकर **बंद** रखी गई है। server पर: settings, tools, resources, methods, एक `tools/call` interceptor। client पर: settings, result claims, notification bindings। extension ये नहीं कर सकता:
* **host के अंदर पहुँचना।** यह data declare करता है; इसके पास server या client का कोई reference नहीं होता।
* **core behaviour बदलना।** spec methods और core result tags construction के समय reject हो जाते हैं (`initialize` को runner ने पूरी तरह reserve कर रखा है); core vocabulary से ढकी notification binding इसके बजाय warning के साथ चुप हो जाती है।
* **देर से register करना।** `MCPServer(...)` या `Client(...)` के लौटने के बाद extension set जैसा है वैसा ही रहता है।
अगर आप इन दीवारों से लड़ रहे हैं, तो आप extension नहीं लिख रहे। आप fork लिख रहे हैं। ये दीवारें ही feature हैं: `extensions=[Apps(), Stamps()]` पढ़ने वाला user **सब कुछ** जानता है जिसे ये दोनों छू सकते थे।

View file

@ -0,0 +1,34 @@
---
translation:
sections: [ca6988b7503cd2d3]
tool: 1
---
# Advanced {#advanced}
एक साधारण server या client को जो कुछ चाहिए, उस सबकी विषय के हिसाब से जगह ऊपर के sections में है।
यह section उन रास्तों के लिए है जिनकी ज़रूरत तब पड़ती है जब `MCPServer` की convenience
layer आड़े आने लगे:
* **[Low-level Server](low-level-server.md)**: वह class जिस पर `MCPServer` बना है।
हाथ से लिखे schemas, `on_*` handlers, आपके लिए कुछ भी check नहीं होता, और आपके अपने custom JSON-RPC
methods।
* **[Pagination](pagination.md)** और **[Middleware](middleware.md)**: दो चीज़ें जो आप
**सिर्फ़** low-level `Server` पर ही कर सकते हैं।
* **[Extensions](extensions.md)** और **[MCP Apps](apps.md)**: protocol की
extension surface। extension packages को server में जोड़ें, या अपना खुद का लिखें।
कुछ चीज़ें जिन्हें आप शायद यहाँ ढूँढें, असल में वहीं रखी गई हैं जहाँ उनका इस्तेमाल
होता है:
* **Authorization**, **[अपना server चलाना](../run/index.md)** के अंतर्गत है, क्योंकि server
को वहीं सुरक्षित किया जाता है जहाँ उसे deploy किया जाता है।
* **OAuth**, **identity assertion**, **एक से ज़्यादा servers** से जुड़ना, और
response **cache**, ये सब **[Clients](../client/index.md)** के अंतर्गत हैं।
* **Multi-round-trip requests** और **Subscriptions**,
**[आपके handler के अंदर](../handlers/index.md)** के अंतर्गत हैं, क्योंकि दोनों ही ऐसे काम हैं जो
handler **करता** है।
* **URI templates**, **[Servers](../servers/index.md)** के अंतर्गत है, Resources के बगल में।
* **[Protocol versions](../protocol-versions.md)** और
**[Deprecated features](../deprecated.md)**, दोनों का अपना-अपना top-level page है।
अगर आपको पक्का नहीं पता कि इस section की ज़रूरत है या नहीं, तो नहीं है।

View file

@ -0,0 +1,207 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
tool: 1
---
# Low-level Server {#the-low-level-server}
`@mcp.tool()` एक layer है। इसके नीचे एक दूसरी server class है, `Server`, जो raw MCP बोलती है: आप इसे protocol objects देते हैं और यह उन्हें बिना बदले wire पर रख देती है।
`MCPServer` इसी के ऊपर बना है। नीचे आप तब उतरते हैं जब convenience layer रास्ते में आने लगे:
* आपको **हूबहू** कोई schema भेजना है (file से load किया हुआ, database से generate किया हुआ), न कि Python signature से निकाला गया।
* आपको result पर पूरा नियंत्रण चाहिए: `_meta`, `is_error`, `structured_content` की हर key।
* आपको ऐसा method handle करना है जिसे MCP define नहीं करता।
बाकी सब के लिए `MCPServer` पर ही रहें।
## वही tool, हाथ से {#the-same-tool-by-hand}
यह वही `search_books` tool है जिसे **[Tools](../servers/tools.md)** `@mcp.tool()` की नौ lines में लिखता है, बस sugar हटाकर:
```python title="server.py" hl_lines="22 26 32"
--8<-- "docs_src/lowlevel/tutorial001.py"
```
तीन चीज़ें बदलीं, और पूरा low-level API बस यही है:
* **Handlers constructor parameters हैं।** `on_list_tools=` और `on_call_tool=` `Server(...)` में जाते हैं। यहाँ नीचे कोई decorator नहीं है, और हर handler का आकार एक ही है: `async (ctx, params) -> result`।
* **Input schema आप लिखते हैं।** `Tool.input_schema` एक सादा JSON Schema `dict` है। कोई इसे type hints से नहीं निकालता, क्योंकि निकालने के लिए type hints हैं ही नहीं।
* **Result आप बनाते हैं।** `CallToolResult(content=[TextContent(...)])`, हाथ से। न कुछ wrap होता है, न convert, न return annotation से अनुमान लगाया जाता है।
`params` parse की हुई request है: `CallToolRequestParams` आपको `.name` और `.arguments` देता है। `ctx` एक `ServerRequestContext` है: client से वापस बात करने के लिए `ctx.session`, `ctx.lifespan_context`, `ctx.request_id`, और `ctx.meta`, यानी request का आने वाला `_meta`।
!!! info
अगर आपने FastAPI इस्तेमाल किया है, तो यह रिश्ता आप पहले से जानते हैं। `MCPServer` decorators और type hints वाली layer है; `Server` उसके नीचे का Starlette है। ये प्रतिद्वंद्वी नहीं हैं: `MCPServer` एक `Server` बनाता है और उस पर ठीक ऐसे ही handlers register करता है।
### इसे आज़माएँ {#try-it}
इसके लिए कोई Inspector नहीं है: `mcp dev` और `mcp run` सिर्फ़ `MCPServer` स्वीकार करते हैं। In-memory `Client` को कोई फ़र्क नहीं पड़ता; वह low-level `Server` को ठीक वैसे ही लेता है जैसे `MCPServer` को:
```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)]
```
वही text जो `@mcp.tool()` वाले version ने दिया था। दो असली अंतर:
* `result.structured_content` `None` है। High-level server आपके लिए `-> str` को `{"result": ...}` में wrap कर देता है; यहाँ जो आपने नहीं बनाया, उसे कोई नहीं बनाता।
* `list_tools` वही schema लौटाता है जो **आपने** type किया, अक्षर-दर-अक्षर। High-level version में हर property पर `"title": "Query"` था और root पर `"title": "search_booksArguments"`: Pydantic की देन। यहाँ नीचे, अगर कुछ wire पर है, तो उसे वहाँ आपने रखा है।
## आपके लिए कुछ जाँचा नहीं जाता {#nothing-is-checked-for-you}
`MCPServer` गलत argument को आपका function चलने से पहले ही ठुकरा देता है, call को अपने generate किए schema से validate करके (**[Tools](../servers/tools.md)**)।
`Server` ऐसा नहीं करता। आपका `input_schema` client को **advertise** होता है; `params.arguments` पर कभी **लागू** नहीं होता।
!!! check
`search_books` को बिना `limit` के call करें और आपका `args["limit"]` `KeyError` raise करता है। Client को दिखता है:
```text
MCPError: Internal server error
```
एक JSON-RPC error, code `-32603`, जान-बूझकर generic message के साथ: SDK आपका traceback किसी remote caller को leak नहीं करेगा। Model को कभी पता नहीं चलता कि उसने क्या गलत किया, इसलिए वह दोबारा कोशिश नहीं कर सकता। (Test में `raise_exceptions=True` इसके बजाय असली exception सामने लाता है; देखें **[Testing](../get-started/testing.md)**।)
यह बात हर जगह लागू होती है। Low-level handler से raise हुआ exception **हमेशा** protocol error होता है, कभी `is_error=True` वाला tool result नहीं। अगर आप चाहते हैं कि model failure पढ़े और संभल जाए, तो `params.arguments` खुद validate करें और `CallToolResult(content=[TextContent(...)], is_error=True)` लौटाएँ। Failure के ये दो प्रकार **[Errors संभालना](../servers/handling-errors.md)** का विषय हैं।
## दो tools, एक handler {#two-tools-one-handler}
`on_call_tool` server के हर tool के लिए अकेला entry point है। Routing आप `params.name` पर करते हैं:
```python title="server.py" hl_lines="38-43"
--8<-- "docs_src/lowlevel/tutorial002.py"
```
* `list_tools` दोनों advertise करता है। `call_tool` नाम के आधार पर dispatch करता है।
* `else` branch मायने रखती है: `Server` किसी ऐसे नाम के लिए आई `tools/call` को भी, जिसे आपने कभी list नहीं किया, सीधे आपके handler में भेज देगा। वहाँ raise करने से call ऊपर वाले `-32603` में ही बदल जाती है।
## Structured output, हाथ से {#structured-output-by-hand}
`Tool` पर `output_schema` declare करें और result पर `structured_content` रखें। दोनों आपके हैं:
```python title="server.py" hl_lines="19-23 36"
--8<-- "docs_src/lowlevel/tutorial003.py"
```
इसे call करें और result में दोनों रूप आते हैं:
```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` block server की पहचान की मुहर है: SDK इसे 2026 पीढ़ी के हर result में जोड़ता है, constructor से लिए `version` के साथ (जो server कोई version set नहीं करता वह खाली string बताता है)। जिस server को अपनी पहचान नहीं बतानी है, वह middleware से यह key हटा सकता है, क्योंकि middleware जो results लौटाता है उनका मालिक वही है।
Server इन दोनों fields की कभी तुलना नहीं करता। इस SDK का `Client` करता है: ऐसा `structured_content` लौटाएँ जो आपके declare किए `output_schema` पर खरा न उतरे, और `call_tool` एक `RuntimeError` raise करता है जो `Invalid structured content returned by tool search_books` से शुरू होता है और आगे `jsonschema` की failure उद्धृत करता है। Schema का वादा करना सस्ता है; उसे निभाना आपकी ज़िम्मेदारी है। Return types और schemas की पूरी सीढ़ी **[Structured Output](../servers/structured-output.md)** में है।
## `_meta`: application के लिए, model के लिए नहीं {#\_meta-for-the-application-not-the-model}
`content` जवाब का वह हिस्सा है जिसे model पढ़ता है। `structured_content` वही जवाब typed data के रूप में है। `_meta` तीसरा channel है: ऐसा data जो result के साथ **client application** के लिए चलता है, जवाब का हिस्सा बने बिना।
इसे record IDs, trace IDs, ऐसी किसी भी चीज़ के लिए इस्तेमाल करें जिसकी ज़रूरत आपके UI को है और आपके prompt को नहीं:
```python title="server.py" hl_lines="37"
--8<-- "docs_src/lowlevel/tutorial004.py"
```
* आप इसे `_meta=` के रूप में बनाते हैं, जो wire वाला नाम है। Client इसे `result.meta` के रूप में वापस पढ़ता है।
* अपनी keys को namespace दें (`bookshop/record_ids`)। `io.modelcontextprotocol/*` keys protocol के लिए reserved हैं।
!!! warning
`_meta` आपके और client application के बीच की convention है, इसकी guarantee नहीं कि model तक क्या
पहुँचता है। क्या render करना है यह host तय करता है। Tool result के किसी भी हिस्से में कभी कोई secret न रखें।
## Capabilities आपके handlers से तय होती हैं {#capabilities-follow-your-handlers}
`Server` ठीक उन्हीं method families को advertise करता है जिनके लिए आपने उसे handlers दिए। ऊपर वाला `Bookshop` `on_list_tools` और `on_call_tool` pass करता है और कुछ नहीं, इसलिए इससे जुड़ने वाला client देखता है:
```json
{"tools": {"listChanged": false}}
```
न `resources`, न `prompts`: उनके पीछे कुछ है ही नहीं। `on_list_prompts` pass करें और `prompts` दिखने लगता है; `on_completion` pass करें और `completions` दिखने लगता है।
`MCPServer` हमेशा tools, resources और prompts advertise करता है, चाहे आपने कोई register किया हो या नहीं, क्योंकि उसके managers हमेशा मौजूद रहते हैं। यहाँ नीचे constructor call **ही** declaration है।
## Lifespan generic {#the-lifespan-generic}
`Server` उस type में generic है जो उसका lifespan yield करता है। इसे एक बार annotate करें और object जहाँ भी सामने आता है, typed होता है:
```python title="server.py" hl_lines="24-26 44-45 50"
--8<-- "docs_src/lowlevel/tutorial005.py"
```
* Lifespan एक `Callable[[Server[Catalog]], AbstractAsyncContextManager[Catalog]]` है; `async` generator पर `@asynccontextmanager` आपको ठीक यही देता है।
* यह जो भी `yield` करता है वह `ctx.lifespan_context` बन जाता है, और चूँकि handlers `ServerRequestContext[Catalog]` से annotate हैं, `.search(...)` autocomplete होता है और type-check होता है।
* Server शुरू होने पर इसमें एक बार enter किया जाता है और रुकने पर एक बार exit। Startup, teardown, और इसी विचार का `MCPServer` वाला version **[Lifespan](../handlers/lifespan.md)** में हैं।
`lifespan=` के बिना `ctx.lifespan_context` एक खाली `dict` है।
## आपका अपना method {#a-method-of-your-own}
Constructor उन methods को cover करता है जिन्हें MCP define करता है। बाकी सब `add_request_handler` cover करता है:
```python title="server.py" hl_lines="35-36 39-40 43-44 48"
--8<-- "docs_src/lowlevel/tutorial006.py"
```
* पहला argument method string है। Notifications के लिए इसका जुड़वाँ है, `add_notification_handler`।
* `params_type` वह model है जिससे आने वाले `params` आपका handler चलने से **पहले** validate होते हैं, इसलिए custom methods को वह validation **मिलती** है जो tools को नहीं मिलती। `RequestParams` को subclass करें ताकि `_meta` field हर दूसरे method की तरह parse हो।
* Handler `BaseModel`, `dict`, या `None` लौटाता है। SDK इसे JSON-RPC result में serialise कर देता है।
एक बात साफ़-साफ़: high-level `Client` के पास सिर्फ़ उन्हीं methods के लिए verbs हैं जिन्हें MCP define करता है, इसलिए कोई `client.reindex()` नहीं है। Vendor method ऐसे peer के लिए है जो पहले से जानता है कि यह मौजूद है: ऐसा client जो आप खुद ship करते हैं, या आपकी कोई दूसरी service जो JSON-RPC बोलती है।
एक method जिस पर आप दावा नहीं कर सकते:
```text
ValueError: 'initialize' is handled by the server runner and cannot be overridden;
use Server.middleware to observe or wrap initialization
```
Handshake runner का है। `server/discover`, `ping`, और बाकी हर built-in को आप बदल सकते हैं।
!!! tip
उस error में जिस `Server.middleware` का ज़िक्र है, वह **हर** आने वाले message को wrap करता है, `initialize` समेत। अगर आप किसी नए method का जवाब देने के बजाय traffic देखना या फिर से लिखना चाहते हैं, तो **[Middleware](middleware.md)** से शुरू करें।
## बाकी handlers {#the-other-handlers}
इनमें से हर एक ऐसा विचार है जिसकी शब्दावली अब आपके पास है; हर एक का अपना page है।
* `on_call_tool`, `on_get_prompt`, और `on_read_resource` अपने सामान्य result के बजाय `InputRequiredResult` लौटा सकते हैं, ताकि call रुक जाए और client से input माँगा जाए; देखें **[Multi-round-trip requests](../handlers/multi-round-trip.md)**। इस tier के मुताबिक, आपके लिए कुछ install नहीं होता: जहाँ `MCPServer` default रूप से `requestState` को seal करता है, वहीं यहाँ आपका set किया `request_state` ठीक वैसे ही wire पार करता है जैसा लिखा गया, जब तक आप `server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[...]), default_audience=server.name))` से opt in न करें: एक line (दोनों नाम `mcp.server.request_state` से import होते हैं) और ठीक वही sealing और verification मिलती है जो `MCPServer` करता है (**[`requestState` की सुरक्षा](../handlers/multi-round-trip.md#protecting-requeststate)**)।
* `on_list_resources`, `on_read_resource`, `on_list_prompts`, `on_get_prompt`, `on_completion` बाकी primitives के लिए वही `(ctx, params) -> result` आकार हैं।
* `on_subscriptions_listen` 2026-07-28 की `subscriptions/listen` stream serve करता है। `SubscriptionBus` के ऊपर बना `ListenHandler` pass करें और अपने बाकी handlers से bus पर events publish करें; पूरी रचना के लिए देखें **[Subscriptions](../handlers/subscriptions.md)**।
* `server.streamable_http_app()` वही Starlette app लौटाता है जो `MCPServer` का लौटाता है; इसे वैसे ही deploy करें जैसे **[अपना server चलाना](../run/index.md)** किसी भी दूसरे ASGI app को deploy करता है। यहाँ नीचे कोई `server.run(transport=...)` नहीं है: `server.run(read_stream, write_stream, server.create_initialization_options())` streams की एक जोड़ी पर एक connection चलाता है, और पूरी जानकारी बस वही एक line है।
## सारांश {#recap}
* Low-level `Server` अपने handlers `on_*` **constructor parameters** के रूप में लेता है; हर handler `async (ctx, params) -> result` है।
* `input_schema` dict आप लिखते हैं और `CallToolResult` आप बनाते हैं। आपके लिए न कुछ derive होता है, न wrap, न validate।
* Handler में exception `-32603` protocol error है। जिस tool error को model पढ़ सके, वह `is_error=True` वाला `CallToolResult` है जिसे **आप** लौटाते हैं।
* Result पर `_meta` client application के नाम है, model के नहीं।
* `Server[T]` उस चीज़ में generic है जो उसका lifespan yield करता है; `ctx.lifespan_context` एक typed `T` है।
* `add_request_handler(method, params_type, handler)` कोई भी method serve करता है। `initialize` reserved है।
* `Server` जो capabilities advertise करता है, वे इससे निकलती हैं कि आपने कौन से handlers register किए।
`Client(server)` ने दोनों servers के साथ एक जैसा बर्ताव किया क्योंकि वे एक ही protocol **हैं**, और यही असली बात है। इससे नीचे की अगली layer कोई class है ही नहीं: वह **[Middleware](middleware.md)** है।

View file

@ -0,0 +1,121 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
tool: 1
---
# Middleware {#middleware}
**middleware** एक async function है जो server को मिलने वाले हर message को wrap करता है।
इसे आप `async (ctx, call_next)` के रूप में लिखते हैं और `server.middleware` में append करते हैं। पूरा API बस इतना ही है।
!!! warning
middleware list source में **provisional** के रूप में चिह्नित है: इसका signature और semantics
किसी 2.x minor release में बदल सकते हैं। इसका इस्तेमाल messages को **देखने** (timing, logging, tracing) और
**अस्वीकार करने** के लिए करें; इसे वह नींव न बनाएँ जिस पर आपका server खड़ा हो।
`MCPServer` यह list construction के समय लेता है (`MCPServer(name, middleware=[...])`) और इसे
`mcp.middleware` के रूप में उपलब्ध कराता है; low-level `Server` वही list `server.middleware` के रूप में देता है। नीचे दिया गया
उदाहरण low-level `Server` इस्तेमाल करता है; अगर `Server(name, on_call_tool=...)` आपके लिए नया है, तो पहले
**[Low-level Server](low-level-server.md)** पढ़ें।
## Timing middleware {#a-timing-middleware}
एक server, एक tool, एक middleware जो log करता है कि हर message में कितना समय लगा:
```python title="server.py" hl_lines="39-45 49"
--8<-- "docs_src/middleware/tutorial001.py"
```
* `ctx` वही `ServerRequestContext` है जो आपके handlers को मिलता है। `ctx.method` raw
method string है; `ctx.params` raw params हैं, किसी भी validation से **पहले**।
* `call_next(ctx)` बाकी chain चलाता है: validation, handler lookup, आपका handler।
जो उसने लौटाया वही लौटा दें, तो response जस का तस रहता है।
* `try`/`finally` जानबूझकर है: जो handler raise करता है उसका समय भी मापा जाता है, क्योंकि failure
आपके middleware तक `call_next` से निकले exception के रूप में पहुँचती है।
* `server.middleware.append(...)` इसे register करता है। list outermost-first चलती है, इसलिए
`middleware[0]` वह है जो wire के सबसे नज़दीक है।
### इसे आज़माएँ {#try-it}
client connect करें, tools की सूची लें, एक को call करें। आपके log में **तीन** lines हैं:
```text
server/discover took 18.3 ms
tools/list took 0.1 ms
tools/call took 0.1 ms
```
आपने दो calls किए और तीन lines मिलीं। पहली `server/discover` है: वह request जो
client ने connection तैयार करने के लिए भेजी, आपके कुछ माँगने से पहले।
यही असली बात है। middleware **हर** inbound message को wrap करता है:
* connection setup: `server/discover`, या legacy session पर `initialize` और `notifications/initialized`।
* हर request और हर notification। notification के लिए `ctx.request_id is None` होता है,
`call_next(ctx)` `None` लौटाता है, और आप जो भी लौटाएँ वह फेंक दिया जाता है।
* वह method भी जिसके लिए server के पास कोई handler नहीं है: `call_next`
`MCPError(-32601, "Method not found")` को client की ओर जाते हुए आपके middleware के **बीच से** raise करता है।
## इसके अंदर आप क्या कर सकते हैं {#what-you-can-do-inside-one}
इस क्रम में कि आपको कितना हिचकना चाहिए, कम से ज़्यादा की ओर:
* **देखें (Observe)।** समय मापें, गिनें, log करें। ऊपर वाला उदाहरण।
* **अस्वीकार करें (Refuse)।** `call_next(ctx)` call करने के **बजाय** `MCPError` raise करें और उस एक message का
जवाब JSON-RPC error से दिया जाता है। connection बना रहता है; अगला message निकल जाता है। इसी तरह
server हर caller के लिए `subscriptions/listen` को gate करता है:
Subscriptions page पर **[यह तय करना कि कौन देख सकता है](../handlers/subscriptions.md#deciding-who-may-watch)**
इसे चरण दर चरण समझाता है।
* **फिर से लिखें (Rewrite)।** `ctx` dataclass है: `await call_next(dataclasses.replace(ctx, params=...))`
बाकी chain को client के भेजे params से अलग params देता है। `initialize` के साथ ऐसा कभी न करें:
client को जो result वापस मिलता है वह आपके बदले हुए params से बनता है, लेकिन
server अपनी connection state मूल wire params से commit करता है। दोनों पक्ष
handshake इस असहमति के साथ पूरा कर सकते हैं कि उन्होंने क्या negotiate किया।
* **जवाब दें (Answer)।** `call_next(ctx)` call किए बिना result लौटाएँ और वह आपके response के रूप में client को
जाता है। `call_next` आपको तैयार wire form देता है, और pipeline आप जो लौटाते हैं उसे कभी patch नहीं करता,
इसलिए पूरा envelope आपका है: 2026 पीढ़ी के connection पर इसमें
`serverInfo` का `_meta` stamp शामिल है, जिसे SDK handler results में जोड़ता है पर आपके results में नहीं।
!!! check
`initialize` उन चीज़ों में से एक है जिन्हें middleware wrap करता है, और इसके लिए आपको मिलने वाला यह **एकमात्र** hook है।
`add_request_handler` से इसे अपने हाथ में लेने की कोशिश करें तो SDK मना कर देता है:
```text
ValueError: 'initialize' is handled by the server runner and cannot be overridden;
use Server.middleware to observe or wrap initialization
```
!!! warning
`initialize` inline संभाला जाता है: जब तक आपकी middleware chain लौट नहीं आती, server आगे कोई inbound
message नहीं पढ़ता। इसलिए `initialize` संभालते समय server-to-client request (`ctx.session.send_request(...)`,
कोई elicitation) को await करना **connection को deadlock कर देता है**: जिस
response का आप इंतज़ार कर रहे हैं वह कभी पढ़ा ही नहीं जा सकता। fire-and-forget notifications ठीक हैं।
## वह एक middleware जो default रूप से चालू आता है {#the-one-middleware-that-ships-on-by-default}
SDK ठीक एक middleware साथ देता है, और वह पहले से आपके server की list में है: वह जो
हर message के लिए OpenTelemetry span emit करता है। आप इसे append नहीं करते, और ज़्यादातर समय
इसके बारे में सोचते भी नहीं। जब तक आप कोई exporter install नहीं करते यह no-op है, और इसका अपना page है:
**[OpenTelemetry](../run/opentelemetry.md)**।
!!! info
अगर आपने ASGI middleware लिखा है, तो यह आकार आप पहले से जानते हैं। Starlette का
`(scope, receive, send)` यहाँ `(ctx, call_next)` बन गया, और यह transport के **बाद** चलता है,
raw HTTP request की जगह decoded message पर। दोनों साथ मिलकर काम करते हैं: `streamable_http_app()` पर
Starlette middleware HTTP देखता है; यह MCP देखता है।
## सारांश {#recap}
* middleware `async (ctx, call_next) -> result` है, जिसे `MCPServer(middleware=[...])` के रूप में पास किया जाता है (या
`mcp.middleware` में append किया जाता है), और low-level `Server` पर `server.middleware` में append किया जाता है।
* यह **हर** inbound message को wrap करता है (`server/discover`, `initialize`, requests, notifications,
अनजान methods) और outermost-first चलता है।
* `ctx.request_id is None` से आप notification और request में फ़र्क करते हैं।
* एक message को अस्वीकार करने के लिए `call_next` call करने के बजाय raise करें; connection बचा रहता है।
* SDK का अपना OpenTelemetry tracing भी एक middleware है, जो पहले से list में है। देखें
**[OpenTelemetry](../run/opentelemetry.md)**।
* पूरा surface provisional है। इससे देखें; इस पर निर्माण न करें।
request को wrap करने वाली हर चीज़ बस इतनी ही है। **[Authorization](../run/authorization.md)** वह है जो तय करता है कि request
को चलने दिया जाए भी या नहीं।

View file

@ -0,0 +1,85 @@
---
translation:
sections: [a9aba7a026c7bd85, ed32bda7ba9ae33a, 7e64cc5646abb91f, 22a0129ee78b3c63, d875373c06d8d2f9]
tool: 1
---
# Pagination {#pagination}
ज़्यादातर servers को इसकी ज़रूरत कभी नहीं पड़ती।
`MCPServer` हर `list_*` request का जवाब अपने पास मौजूद सब कुछ देकर करता है, एक ही page में, `next_cursor=None` के साथ। कुछ दर्जन tools, resources या prompts के लिए यही सही जवाब है और configure करने को कुछ नहीं है।
Pagination उस server के लिए है जिसकी resource list असल में एक database है: हज़ारों rows जिन्हें वह एक ही response में serialize करने से मना करता है। Protocol का जवाब है **cursor**: server एक page के साथ एक opaque token लौटाता है, और client अगला page पाने के लिए वही token वापस भेजता है।
`@mcp.resource()` में इसके लिए कोई hook नहीं है। Paging करने के लिए आप list handler ख़ुद लिखते हैं, **[low-level Server](low-level-server.md)** पर।
## Paging करने वाला server {#a-server-that-pages}
```python title="server.py" hl_lines="12 15-16"
--8<-- "docs_src/pagination/tutorial001.py"
```
* Low-level `Server` पर handlers constructor arguments होते हैं, decorators नहीं। `on_list_resources` हर `resources/list` request का जवाब देता है; जोड़ने का पूरा काम बस इतना ही है।
* हर paged handler का type `params: PaginatedRequestParams | None` होता है, और उदाहरण दोनों स्वीकार करता है। लेकिन किसी connection पर SDK आपको कभी `None` नहीं देता (बिना `params` member वाली request handler तक default values वाले model के रूप में पहुँचती है), इसलिए जो संकेत मायने रखता है वह है `params.cursor is None`: **शुरू से शुरू करें**।
* Cursor **क्या** है, यह आप तय करते हैं। यहाँ यह string के रूप में लिखा गया offset है। Timestamp, primary key, base64 blob: कुछ भी जिसे आप बाहर भेजते समय बना सकें और वापस आने पर पहचान सकें।
* `next_cursor=None` से आप कहते हैं "वह आख़िरी page था"। कोई count नहीं, कोई total नहीं, कोई `has_more` नहीं। `None` ही पूरा संकेत है।
!!! tip
10 का `PAGE_SIZE` उदाहरण को पढ़ने लायक बनाता है। अपना page size हर endpoint के हिसाब से चुनें:
एक-line वाले resources की list 500 का page झेल सकती है; भारी-भरकम prompt templates की list नहीं।
इसमें client की कोई राय नहीं चलती, और यह जानबूझकर ऐसा है।
### इसे आज़माएँ {#try-it}
`Client(server)` memory में low-level `Server` से ठीक वैसे ही जुड़ता है जैसे `MCPServer` से।
बिना arguments के `list_resources()` call करें। आपको दस resources मिलते हैं, `book-1` से `book-10` तक, और `next_cursor` string `"10"` है।
इसे `list_resources(cursor="10")` से वापस दें, तो पहला resource `book-11` है और नया `next_cursor` `"20"` है।
दसवाँ page `next_cursor` को `None` पर set करके लौटता है। हो गया।
## Client loop {#the-client-loop}
`Client` का हर `list_*` method (`list_tools`, `list_resources`, `list_resource_templates`, `list_prompts`) `cursor=` keyword लेता है। Paged list को पूरा खींचना एक `while True` है:
```python title="client.py" hl_lines="26-32"
--8<-- "docs_src/pagination/tutorial002.py"
```
* `cursor` `None` से शुरू होता है, इसलिए पहली request में कोई cursor नहीं जाता।
* `next_cursor` देखने से **पहले** extend करें: आख़िरी page में भी resources होते हैं।
* `next_cursor is None` ही बाहर निकलने का रास्ता है। बाकी कुछ भी सीधे `cursor=` में वापस जाता है, बिना छेड़े।
इसका `main()` चलाएँ और यह `100 resources` print करता है: दस-दस के दस pages, एक ऐसे loop से जुड़े हुए जिसे कभी पता ही नहीं था कि दस pages थे।
यह वही loop है जो **[The Client](../client/index.md)** हर `list_*` verb के लिए दिखाता है, और paging न करने वाले server पर इसकी कोई क़ीमत नहीं: पहले ही response में `next_cursor` `None` होता है और loop एक बार चलता है।
## तीन नियम {#the-three-rules}
**Cursors opaque होते हैं।** Client को कभी किसी cursor को parse करना, बनाना या अंदाज़ा लगाना नहीं चाहिए। Cursor का एकमात्र वैध स्रोत पिछले page का `next_cursor` है, जस का तस।
**Page size server चुनता है।** Protocol में कोई `limit=` नहीं है। अलग page size चाहिए तो server बदलें।
**Paging को नज़रअंदाज़ करने वाला client भी काम करता है।** वह एक बार `list_resources()` call करता है, पहले दस पाता है, और जिस `next_cursor` को उसने फेंक दिया उस पर कभी ध्यान नहीं देता। कुछ टूटता नहीं; उसे बस कम दिखता है।
!!! check
Opaque का मतलब opaque। कोई cursor गढ़ लें (`list_resources(cursor="page-2")`) तो
protocol आपके लिए कुछ नहीं कर सकता। यह server `int("page-2")` आज़माता है, handler raise करता है,
और client के पास जो लौटता है वह है:
```text
MCPError(-32603, 'Internal server error', None)
```
जो cursor आपको server से नहीं मिला, वह bug है, feature request नहीं।
## सारांश {#recap}
* `MCPServer` सब कुछ एक page में लौटाता है। Pagination opt-in है, और opt in आप low-level `Server` पर करते हैं।
* `on_list_resources` (और `on_list_tools`, `on_list_prompts`, `on_list_resource_templates`) को `PaginatedRequestParams | None` मिलता है; पहले page के लिए `params.cursor` `None` होता है।
* आप एक page और `next_cursor` लौटाते हैं: कोई भी string जिसे आप बाद में पहचान लें, या `None` जब कुछ बचा न हो।
* Client loop: `cursor=` दें, जमा करें, `next_cursor is None` होने तक दोहराएँ।
* Cursors opaque होते हैं, page size server का है, और paging न करने वाले client को भी पहला page मिलता है।
हाथ से लिखी `Server` API का बाकी हिस्सा (`on_call_tool`, `input_schema` dicts, `_meta`) **[The low-level Server](low-level-server.md)** में है।

View file

@ -0,0 +1,122 @@
---
translation:
sections: [9e7b9a1710e5aeba, b74ca4c1d2ddddee, fa8714e61bf90c5a, 04db67a886b7271c, 857690fb8f876800]
tool: 1
---
# Caching hints {#caching-hints}
2026-07-28 protocol पर server `tools/list`, `prompts/list`, `resources/list`, `resources/templates/list`, `resources/read` और `server/discover` के लिए जो भी result लौटाता है, उसमें दो fields होते हैं: `ttlMs`, यानी client कितने milliseconds तक उस result को fresh मान सकता है, और `cacheScope`, यानी cache किया गया result users के बीच share किया जा सकता है (`"public"`) या किसी एक authorization context का है (`"private"`)।
server खुद कुछ भी cache नहीं करता। ये fields एक **घोषणा** हैं: "यह tool list सबके लिए एक जैसी है और अगले एक मिनट तक नहीं बदलेगी।" इसके बाद client (या आपके आगे लगा कोई gateway) round trip छोड़ सकता है। hints मानना या न मानना client की मर्ज़ी है; उन्हें भेजना server का काम है, और SDK यह आपके लिए कर देता है।
बिना कुछ configure किए हर result `ttlMs: 0, cacheScope: "private"` कहता है: तुरंत stale, कभी share नहीं। यह हमेशा सुरक्षित है और हमेशा spec के अनुरूप। अगर आपकी lists सच में स्थिर हैं और सभी callers के लिए एक जैसी हैं, तो construction के समय ही यह बता दें:
```python title="server.py" hl_lines="5-8"
--8<-- "docs_src/caching/tutorial001.py"
```
* map की keys **method name** हैं, और सिर्फ़ वही छह cacheable methods वैध keys हैं। parameter का type `Mapping[CacheableMethod, CacheHint]` है, इसलिए editor keys को autocomplete करता है और चलाने से पहले ही typo पकड़ लेता है; जो कुछ type checker से बच निकलता है, वह construction के समय raise होता है।
* जिस method का आप ज़िक्र नहीं करते, उसके defaults बने रहते हैं। map overrides का समूह है, manifest नहीं।
* `CacheHint(ttl_ms=5_000)` ने `scope` को unset छोड़ा, इसलिए वह `"private"` ही रहता है: हर caller के लिए पाँच second की freshness। scope और TTL अलग-अलग फ़ैसले हैं।
* `"server/discover"` भी वैध key है, क्योंकि discovery result किसी भी list की तरह cacheable है।
!!! warning
`cacheScope: "public"` का मतलब है कि आपका cache किया गया response **किसी को भी** दिया जा
सकता है। shared gateway बेझिझक एक user का result दूसरे को थमा देगा, भले ही request
authenticated रही हो। किसी result को `"public"` तभी mark करें जब वह हर caller के लिए एक जैसा
हो, और `cacheScope` को कभी access control की तरह इस्तेमाल न करें: यह label है, ताला नहीं।
## Per-handler override {#per-handler-override}
low-level `Server` पर handlers अपने results खुद बनाते हैं, और `ttl_ms` / `cache_scope` result models पर बस fields हैं। जो handler इन्हें explicitly set करता है, वह constructor map पर हमेशा भारी पड़ता है, field दर field:
```python title="server.py" hl_lines="10 16"
--8<-- "docs_src/caching/tutorial002.py"
```
handler ने `ttl_ms=1_000` कहा और scope के बारे में कुछ नहीं। wire पर: `ttlMs: 1000` (handler वाला, map का `60_000` नहीं) और `cacheScope: "public"` (map वाला, क्योंकि handler ने इसे unset छोड़ा)। explicit, configured पर भारी पड़ता है, और configured, default पर। यह हर field पर अलग से लागू होता है, इसलिए handler एक field को पक्का कर सकता है और दूसरे को server-wide policy पर छोड़ सकता है।
यही उन dynamic मामलों का रास्ता भी है जिन्हें constructor जान नहीं सकता: जो handler `resources/read` को हर user के हिसाब से filter करता है, वह बाकी तरह से public server में किसी एक URI के लिए `cache_scope="private"` लौटा सकता है।
paginated lists पर एक सावधानी: protocol की माँग है कि एक list के **हर page पर वही `cacheScope`** हो। constructor map यह अपने आप पूरा करता है, क्योंकि उसकी keys method हैं, page नहीं। लेकिन जो handler scope को खुद override करता है, उस consistency की ज़िम्मेदारी उसी की है: इसे **हर** page पर override करें, सिर्फ़ cursor मौजूद होने पर नहीं, वरना पहले page और दूसरे page में मेल नहीं रहेगा।
## client को क्या दिखता है {#what-the-client-sees}
2026-07-28 session पर `Client` आपके लिए hints का पालन करता है: इसमें built-in response cache है, जो default रूप से चालू रहता है। जो result `ttlMs` के साथ आता है, वह store हो जाता है, और उस TTL के भीतर वैसा ही call cache से serve होता है, बिना round trip के। जिस result में **कोई** hint नहीं होता, वह cache नहीं होता: बिना hint वाले results को `CacheConfig.default_ttl_ms` मिलता है, जिसका default `0` है (तुरंत stale), इसलिए जो server कुछ भी declare नहीं करता, उसे ठीक वैसा ही call-दर-call traffic दिखता है जैसा हमेशा दिखता था।
```python title="client.py" hl_lines="33 35 38"
--8<-- "docs_src/caching/tutorial003.py"
```
चार calls, तीन fetches। दूसरे call को fresh entry मिली और वह server तक पहुँचा ही नहीं; (inject की गई) clock को TTL से आगे बढ़ाने पर तीसरे ने फिर से fetch किया; चौथे ने `cache_mode="refresh"` कहा। यह kwarg पाँचों caching verbs पर मौजूद है (`list_tools`, `list_prompts`, `list_resources`, `list_resource_templates`, `read_resource`):
* `"use"` (default) fresh entry हो तो उसे serve करता है, और न हो तो fetch करके store करता है।
* `"refresh"` कभी serve नहीं करता: यह fetch करता है और result store करता है, जो कुछ cache में था उसे बदलते हुए।
* `"bypass"` cache को छुए बिना round trip करता है: न read, न write।
एक नियम `"use"` से ऊपर है: **`meta` वाले calls हमेशा server तक पहुँचते हैं।** जिस request में `meta` set हो (progress token, tracing fields), उसे wire request की उम्मीद होती है, इसलिए `cache_mode="use"` में उसे `"refresh"` माना जाता है: cache read छोड़ दिया जाता है, और fetch किया गया result फिर भी cache की entry की जगह ले लेता है। `"bypass"` और explicit `"refresh"` हमेशा की तरह ही बर्ताव करते हैं।
caching पूरी तरह बंद करने के लिए `Client(server, cache=None)` से construct करें: हर call फिर से round trip है, और `cache_mode`, भले ही अब भी स्वीकार होता है, कुछ नहीं करता।
scope का पालन भी अपने आप होता है: `"private"` entries cache के *partition* (नीचे देखें) से बँधी होती हैं, जबकि `"public"` वाली चाहें तो ज़्यादा व्यापक sharing चुन सकती हैं। और जिन entries का नाम notifications लेते हैं, ठीक उनके लिए **notifications TTL पर भारी पड़ते हैं**: `list_changed` notification मेल खाती cached listing को evict कर देता है, और `resources/updated` ठीक उसी URI के तहत store किए गए cached read को evict करता है, चाहे वे कितने भी fresh रहे हों। 2026-07-28 connection पर ये notifications `subscriptions/listen` stream पर आते हैं जिसे आप `client.listen(...)` से खोलते हैं, और eviction आपके watcher को event दिखने से पहले पूरा हो जाता है; **[Subscriptions](subscriptions.md)** वही page है।
`resources/updated` पर एक सावधानी: eviction सिर्फ़ exact URI पर होता है। store contract में कोई enumerate या scan operation नहीं है (reference TypeScript implementation की तरह ही), इसलिए *sub*-resource URI वाला notification उसके parent के cached read को evict नहीं करता। अगर आपका server sub-resources का संकेत इसी तरह देता है, तो parent को `cache_mode="refresh"` से फिर fetch करें।
### इसे configure करना: `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`: entries कहाँ रहती हैं। default हर client के लिए नया in-memory store है; clients या processes के बीच cache share करना हो तो अपना `ResponseCacheStore` implementation (जैसे Redis-backed) pass करें। contract types (`ResponseCacheStore`, `CacheKey`, `CacheEntry`, और default `InMemoryResponseCacheStore`) `mcp.client` से import किए जा सकते हैं। एक lookup एक के बाद एक ज़्यादा से ज़्यादा दो store `get` जारी कर सकता है (पहले private arm, फिर public), इसलिए remote store की latency की उम्मीदें उसी हिसाब से तय करें। custom store के लिए explicit `partition` **ज़रूरी** है।
* `partition`: authorization-context label, जो shared store के भीतर एक principal की `"private"` entries को किसी दूसरे को serve होने से रोकता है।
* `target_id`: explicit server identity, custom transports और in-process servers के लिए (नीचे देखें)।
* `default_ttl_ms`: उन results पर लागू TTL जिनमें `ttlMs` hint नहीं है। default `0` बिना hint वाले results को uncached छोड़ देता है।
* `share_public`: server ने जिन entries को `"public"` बताया, उन्हें partitions के पार serve करना (नीचे देखें)। default रूप से बंद।
* `clock`: wall-clock source, epoch seconds में। ऊपर के उदाहरण की तरह एक inject करें, और expiry tests में sleep की ज़रूरत नहीं रहती।
!!! warning "Partition = verified principal"
`partition` किसी **verified credential** से निकालें, जैसे validate किए गए token का subject। इसे request में आए data से कभी न निकालें, और server URL से भी कभी नहीं (server identity key की एक अलग axis है)। SDK एक library है जिसका अपना कोई authentication नहीं: trust anchor वही है जो `CacheConfig` construct करता है, यानी deployment, tenant नहीं। multi-tenant gateway हर authenticated principal के लिए एक अलग `CacheConfig` बनाता है।
partition `Client` के पूरे जीवनकाल के लिए स्थिर भी रहता है। अगर connection का authorization context session के बीच बदलता है (जैसे किसी दूसरे principal के रूप में re-authentication), तो cache उसके साथ नहीं बदलता; नए principal के लिए नया `Client` construct करें।
cache keys में **server की identity** भी होती है: वह URL string जिसे आपने dial किया, जिसमें से `user:pass@` userinfo हटा दी जाती है और बाकी byte-दर-byte वैसी ही रहती है। न case folding, न query reordering, न trailing slash की सफ़ाई। कम normalize करने से सिर्फ़ sharing घटती है, जबकि ज़्यादा normalize करने से दो tenants (`?tenant=a` बनाम `?tenant=b`) आपस में मिल सकते हैं, इसलिए ऊपरी तौर पर अलग URL बस entries share नहीं करते। जब कोई URL नहीं होता (in-process server, या `Transport` instance), तो client को उसकी जगह हर instance के लिए एक random identity मिलती है; server को नाम देने के लिए `CacheConfig.target_id` set करें (custom store के साथ यह ज़रूरी है, और construction यह बता देता है)। identity key material में जाने से पहले sha256-hash की जाती है, इसलिए जिस URL की query string में secrets हों, वह store keys में कभी नहीं दिखता। pre-hash रूप को खुद भी log न करें।
!!! warning "`share_public` server पर भरोसा करता है, पूरे fleet में"
default रूप से `"public"` entries भी अपने partition के भीतर ही रहती हैं। `share_public=True` उन entries को, जिन्हें server ने `cacheScope: "public"` mark किया, store इस्तेमाल करने वाले **हर** partition को serve करता है, और उन सबकी ओर से server के वर्गीकरण पर भरोसा करता है। जो server per-tenant data पर `"public"` की मुहर लगा देता है (bug से या बदनीयती से), वह फिर एक tenant का response बाकियों को leak कर देता है। यह flag जान-बूझकर सिर्फ़ constructor स्तर पर है: per-call `cache_mode` caching को सीमित कर सकता है, लेकिन per-call कोई भी चीज़ sharing को बढ़ा नहीं सकती।
### cache क्या कभी नहीं करता {#what-the-cache-never-does}
* **Session-tier calls इसे bypass करते हैं।** `client.session.list_tools()` और उसके साथी हमेशा round trip करते हैं; cache `Client` verbs पर रहता है।
* **`server/discover` इससे बाहर रहता है।** discover result एक बार, connect के समय, दिया जाता है और response cache में कभी नहीं जाता, भले ही उसमें `ttlMs` हो। अगर reconnect probe से बचने के लिए आप उसे खुद persist करते हैं ([`prior_discover`](../protocol-versions.md#reconnecting-with-prior_discover)), तो उसकी freshness का हिसाब आपका है: `DiscoverResult` में ठीक इसी काम के लिए `ttl_ms` और `cache_scope` पहले से parse किए हुए मौजूद हैं।
* **Continuation pages कभी cache नहीं होते।** सिर्फ़ बिना cursor वाले calls हिस्सा लेते हैं। expired cursor के कारण reject हुआ continuation page cached listing को *evict* ज़रूर करता है, क्योंकि listing उसके नीचे बदल गई।
* **Multi-round-trip reads कभी cache नहीं होते।** `input_responses`/`request_state` से seed किया गया `read_resource`, या ऐसा जो input rounds से होकर resolve होता है, कभी cache में नहीं जाता (spec का MUST)।
* **Notification eviction को notifications चाहिए।** eviction उतना ही अच्छा है जितनी transport की delivery, और आधुनिक in-process path (default `mode="auto"` के साथ `Client(server)`) आज standalone notifications deliver नहीं करता।
* **Eviction eventual है, तात्कालिक नहीं।** wire-path notifications spawn किए गए tasks से dispatch होते हैं, इसलिए किसी notification के आने से race कर रहे call को pre-eviction entry एक बार और serve हो सकती है; यह window dispatch latency से सीमित है, और eviction फिर भी हो ही जाता है।
* **कोई stale-if-error नहीं।** expired entry कभी इसलिए serve नहीं होती कि refetch fail हो गया; error आगे propagate होता है।
* **कोई early re-fetch नहीं।** store की गई entry तब तक serve होती है जब तक उसका TTL expire न हो जाए, और उसके बाद का अगला call round trip की कीमत चुकाता है; background में कुछ refresh नहीं होता।
* **कोई coalescing नहीं।** दो concurrent एक जैसे calls दो fetches हैं।
* **24 घंटे से ज़्यादा का TTL नहीं।** इससे बड़ा `ttlMs`, चाहे server ने भेजा हो या configure किया गया हो, store करते समय घटा दिया जाता है (`mcp.client.caching.MAX_TTL_MS`), जिससे यह सीमित रहता है कि कोई भी entry, hint चाहे कितना भी उदार हो, कितनी देर serve हो सकती है।
* **shared store** पर clients आपस में race करते हैं। जब किसी eviction ने चल रहे fetch को पीछे छोड़ दिया हो तो हर client अपना write छोड़ देता है, लेकिन कोई *co-tenant* client अब भी ऐसी entry वापस लिख सकता है जिसे किसी ऐसे eviction ने हटा दिया था जो उसने कभी देखा ही नहीं; और race का यह हिसाब-किताब भी खुद सीमित है: 4096 tracked keys के बाद सबसे पुरानी key का guard सबसे पहले हटता है। दोनों windows स्वीकार्य हैं, और ऊपर बताई गई TTL cap उन्हें बंद कर देती है।
* **protocol की अलग-अलग पीढ़ियों के बीच serve नहीं किया जाता।** entries negotiated protocol version तक सीमित हैं: shared persistent store पर कोई session कभी ऐसी entry serve नहीं करता जो किसी दूसरे negotiated version के तहत लिखी गई हो (वही listing पीढ़ी के हिसाब से सच में अलग होती है, क्योंकि SDK पुराने sessions के लिए 2026 वाले fields हटा देता है)। eviction भी इसी तरह सिर्फ़ मौजूदा पीढ़ी की entries को छूता है; दूसरी पीढ़ी की entries बस TTL से अपने आप पुरानी होकर हट जाती हैं।
### hints खुद पढ़ना {#reading-the-hints-yourself}
hints हर cacheable result पर सादे fields के रूप में भी मौजूद हैं (`result.ttl_ms` और `result.cache_scope`, पहले से parse किए हुए), अगर आप built-in cache के ऊपर (या उसकी जगह) अपना हिसाब-किताब रखना चाहें।
किसी **पुराने server** (pre-2026 protocol) के सामने ये fields wire पर होते ही नहीं, और models अपने conservative defaults दिखाते हैं: `ttl_ms == 0` और `cache_scope == "private"`, stale और unshared, जो कुछ भी declare न करने वाले server के लिए सही मान्यता है। cache legacy session के साथ भी यही करता है: वहाँ hints कभी देखे ही नहीं जाते (wire पर चाहे जो keys आएँ), सिर्फ़ `default_ttl_ms` लागू होता है, और उसका default `0` कुछ भी cache नहीं करता, इसलिए pre-2026 connection ठीक वैसे ही बर्ताव करता है जैसे cache के आने से पहले करता था। अगर आपको "server ने 0 कहा" और "server ने कुछ नहीं कहा" में फ़र्क करना हो, तो `"ttl_ms" in result.model_fields_set` जाँचें: यह तभी set होता है जब field सच में आया हो।
## पुराने clients {#older-clients}
pre-2026 protocol versions वाले clients को इनमें से कोई भी field कभी नहीं दिखता; SDK उन connections के लिए serialization के समय इन्हें हटा देता है। hints एक बार configure करें; version के हिसाब से अलग कुछ लिखने को नहीं है।
## सारांश {#recap}
* छह methods में `ttlMs`/`cacheScope` होते हैं; SDK इनका default `0`/`"private"` रखता है, stale और unshared, हमेशा सुरक्षित।
* construction के समय `cache_hints={method: CacheHint(...)}` (`MCPServer` और `Server` दोनों में) हर method के लिए server-wide values set करता है।
* जो handler अपने result पर ये fields set करता है, वह map को override करता है, field दर field।
* `"public"` एक वादा है कि result हर caller के लिए एक जैसा है। यह access control नहीं है।
* `Client` hints का पालन अपने आप करता है: उसका response cache default रूप से चालू है, दोबारा fetch करने की बजाय fresh entries serve करता है, और जो servers (या sessions) कोई hint नहीं देते उनके लिए कुछ भी cache नहीं करता।
* हर call पर, `cache_mode="refresh"` दोबारा fetch करता है और `"bypass"` cache को छोड़ देता है; construction के समय `cache=None` इसे पूरी तरह बंद कर देता है।

View file

@ -0,0 +1,154 @@
---
translation:
sections: [adf3c545b5be46b6, 916cd3ab1c03f461, e9be7a8d0eb0a456, 565890a636288ecf, 6af7e49db9129ec3, 06b0238c174186af, 90c6043be435fcb0]
tool: 1
---
# Client callbacks {#client-callbacks}
MCP में लगभग हर request एक ही दिशा में जाती है: client से server की ओर।
server भी **client** से चीज़ें माँग सकता है: user से कोई सवाल पूछना, user के model से sampling करना, user के workspace folders की सूची लेना। इन requests का जवाब आप `Client(...)` को **callbacks** देकर देते हैं।
## पूछने वाला server {#a-server-that-asks}
यह एक ऐसा server है जिसका tool अपने आप पूरा नहीं हो सकता:
```python title="server.py" hl_lines="16"
--8<-- "docs_src/client_callbacks/tutorial001.py"
```
* `ctx.elicit(...)` **client को** `elicitation/create` request भेजता है और इंतज़ार करता है।
* जब तक कोई (form में कोई व्यक्ति, या आपका code) `name` नहीं देता, tool लौटता नहीं।
यह server वाला आधा हिस्सा है, और इसकी पूरी जानकारी **[Elicitation](../handlers/elicitation.md)** page में है। यह page wire का दूसरा सिरा है।
## Elicitation callback {#the-elicitation-callback}
```python title="client.py" hl_lines="6-10 16-17"
--8<-- "docs_src/client_callbacks/tutorial002.py"
```
* elicitation callback का रूप `async (context, params) -> ElicitResult` है।
* `params.message` सवाल है। `params.requested_schema` उस जवाब का JSON Schema है जो server चाहता है। असली client इससे form बनाकर दिखाता है; यह वाला अपने आप भर देता है।
* आप `ElicitResult(action="accept", content={...})` लौटाते हैं, या `action="decline"`, या `action="cancel"`। इनके अलावा सिर्फ़ एक विकल्प है `ErrorData(...)`, जो request को ठुकरा देता है और पूरा call fail कर देता है।
* `context` एक `ClientRequestContext` है: चालू `session`, server का `request_id`, और उसके साथ लगाया गया कोई भी `meta`।
!!! tip
`params` elicitation के दोनों modes का union है। यहाँ `params.mode` का मान `"form"` है; `"url"` request
में schema की जगह `params.url` आता है। एक ही callback दोनों को संभालता है; `params.mode` पर branch करें।
पूरा pattern **[Elicitation](../handlers/elicitation.md)** में दिखाया गया है।
### इसे आज़माएँ {#try-it}
`issue_card` call करें और दोनों सिरों को देखें।
आपके callback को server का सवाल मिलता है, पहले से parse किया हुआ:
```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'}
```
वह जवाब देता है, tool के अंदर `ctx.elicit(...)` आगे बढ़ता है, और tool पूरा हो जाता है:
```python
result.content # [TextContent(type='text', text='Card issued to Ada Lovelace.')]
```
आपकी ओर से एक `tools/call`, server की ओर से वापस एक `elicitation/create`, जिसका जवाब आपके function ने दिया, और यह सब एक ही tool call के अंदर।
!!! info
`Client(...)` call पर `mode="legacy"` असल में काम कर रहा है। default रूप से `Client(...)` modern
protocol path negotiate करता है, और उस path में server-से-client requests के लिए कोई back-channel नहीं है:
आपका callback चलने से पहले ही `ctx.elicit` fail हो जाता है। यह transport तय नहीं करता; negotiated
protocol तय करता है, in-memory में भी और URL पर भी। जब भी आपके client को ऐसी किसी request का जवाब देना हो,
`mode="legacy"` तय करें; इस page के पीछे का हर test यही करता है। पूरी जानकारी **[Protocol versions](../protocol-versions.md)** में है।
2026-07-28 session पर callback बेकार नहीं होता, उसे input अलग तरीके से मिलता है: जब कोई tool
`ElicitRequest` वाला `InputRequiredResult` लौटाता है, तो `Client` उस entry को उसी
`elicitation_callback` को भेज देता है और आपके लिए call दोबारा करता है। वह flow **[Multi-round-trip requests](../handlers/multi-round-trip.md)** है।
## callback ही capability है {#a-callback-is-a-capability}
आपने server को कभी नहीं बताया कि आपका client elicitation requests का जवाब दे सकता है। SDK ने बताया।
जब client जुड़ता है तो वह अपनी `capabilities` घोषित करता है, जो server की capabilities का ठीक उल्टा रूप है। वह object आप नहीं लिखते। **callback register करना ही घोषणा है।**
| आप देते हैं | client घोषित करता है |
| --- | --- |
| `elicitation_callback=` | `"elicitation": {"form": {}, "url": {}}` |
| `sampling_callback=` | `"sampling": {}` |
| `list_roots_callback=` | `"roots": {"listChanged": true}` |
| इनमें से कोई नहीं | `{}` |
sampling की sub-capabilities ही एकमात्र बारीकी हैं: जब आपका sampler `tools` / `tool_choice` parameters संभालता हो, तो `sampling_callback` के साथ `sampling_capabilities=SamplingCapability(tools=SamplingToolsCapability())` दें। servers को `sampling.tools` घोषित दिखनी चाहिए, तभी वे इन्हें भेज सकते हैं।
`logging_callback` और `message_handler` इस table में नहीं हैं। वे notifications संभालते हैं, और notifications को किसी capability की ज़रूरत नहीं।
server इस घोषणा को `ctx.session.check_client_capability(...)` से पढ़ता है। ऐसा करने वाला एक tool जोड़ें:
```python title="server.py" hl_lines="23-31"
--8<-- "docs_src/client_callbacks/tutorial003.py"
```
सिर्फ़ `elicitation_callback` के साथ जुड़ें और इसे call करें:
```python
result.structured_content # {'result': ['elicitation']}
```
तीनों callbacks दें तो आपको `['elicitation', 'sampling', 'roots']` मिलता है। कोई न दें तो `[]` मिलता है।
!!! check
अब गलत काम करें: `elicitation_callback` के **बिना** जुड़ें और फिर भी `issue_card` call करें।
server की `elicitation/create` request फिर भी आपके client तक पहुँचती है, और SDK आपकी ओर से उसका
जवाब देता है, error के साथ, क्योंकि आपने कभी कहा ही नहीं कि आप इसे संभाल सकते हैं। वह error पूरे call को
डुबो देता है। `call_tool` कोई `is_error` result नहीं लौटाता; वह raise करता है:
```text
MCPError: Elicitation not supported
```
यह protocol error है (`-32600`, **invalid request**), tool error नहीं: model के पढ़ने और दोबारा
कोशिश करने के लिए इसमें कुछ नहीं है। इसीलिए `client_features` रखना फ़ायदेमंद है: अच्छे ढंग से बना server
पूछने से पहले जाँच लेता है।
## Deprecated जोड़ी {#the-deprecated-pair}
`sampling_callback` `sampling/createMessage` का जवाब देता है: server **आपके** model से कुछ complete करने को कहता है। `list_roots_callback` `roots/list` का जवाब देता है: server पूछता है कि वह किन directories में काम कर सकता है।
दोनों काम करते हैं। दोनों ऊपर वाले नियम का पालन करते हैं। और दोनों ऐसे RPCs को serve करते हैं जिन्हें **2026-07-28 spec हटा देता है**: modern server request के बीच में आपके client को वापस call नहीं करता, वह request को tool result के हिस्से के रूप में आपको वापस सौंप देता है (**[Multi-round-trip requests](../handlers/multi-round-trip.md)**)। callbacks खुद बेकार नहीं हुए हैं। जब किसी `InputRequiredResult` में `CreateMessageRequest` या `ListRootsRequest` होता है, तो `Client` का auto-loop उसे उसी `sampling_callback` या `list_roots_callback` को भेज देता है जो आपने यहाँ register किया था। पूरी सूची **[Deprecated features](../deprecated.md)** में है।
जो servers अभी आगे नहीं बढ़े हैं, उनसे बात करने के लिए आपको ये callbacks अब भी चाहिए। signatures:
```python title="client.py"
--8<-- "docs_src/client_callbacks/tutorial004.py"
```
* sampling callback को पूरा `CreateMessageRequestParams` (`messages`, `model_preferences`, `max_tokens`) मिलता है और वह `CreateMessageResult` लौटाता है। model **आप** चलाते हैं, जैसे चाहें; SDK सिर्फ़ request पहुँचाता है।
* roots callback कोई params नहीं लेता और `ListRootsResult` लौटाता है।
* दोनों में से कोई भी, मना करने के लिए, इसकी जगह `ErrorData(...)` लौटा सकता है।
इन्हें `Client(...)` को ठीक वैसे ही दें जैसे `elicitation_callback` को।
## Notification callbacks {#the-notification-callbacks}
दो और। इनमें से कोई कुछ घोषित नहीं करता।
`logging_callback` को server की भेजी हुई `notifications/message` मिलती है, `LoggingMessageNotificationParams` (`level`, `logger`, `data`) के रूप में। protocol logging खुद 2026-07-28 spec में deprecated है (इसकी जगह क्या करना है, यह **[Logging](../handlers/logging.md)** में है), इसलिए यह callback उन servers के लिए है जो इसे अब भी emit करते हैं। 2026 पीढ़ी के connection पर अकेला callback आपको कुछ नहीं दिलाता, क्योंकि 2026 servers log messages सिर्फ़ उन्हीं requests को भेजते हैं जो इसके लिए opt in करती हैं: हर request पर वह opt-in लगाने और उस level व उससे ऊपर के messages पाने के लिए `Client(...)` को `log_level="info"` (या कोई और level) दें। 2026 से पहले के servers इसे नज़रअंदाज़ करते हैं और अपना `logging/setLevel` वाला व्यवहार बनाए रखते हैं।
`message_handler` सब कुछ पकड़ने वाला है: session जो भी server notification सामने लाता है, वह इस तक पहुँचती है (उसके खास callback के अलावा), और stream-backed transport पर हर transport-level `Exception` भी। दो कभी नहीं पहुँचते: `notifications/cancelled` को SDK सामने लाने के बजाय खुद लागू करता है, और चालू `listen()` stream की subscription acknowledgment उसी stream में खप जाती है। parameter को `IncomingMessage` (`ServerNotification | Exception`, `mcp.client` से export किया हुआ) से annotate करें। जानने लायक एक ही pattern है `if isinstance(message, Exception): raise message`, ताकि टूटा हुआ connection चुपचाप गायब होने के बजाय ज़ोर से fail हो।
## सारांश {#recap}
* server client को requests भेज सकता है। आप उनका जवाब `Client(...)` को दिए गए callbacks से देते हैं।
* elicitation callback मौजूदा वाला है: `async (context, params) -> ElicitResult`, form और URL mode दोनों के लिए एक ही function।
* **callback register करना ही capability घोषित करना है।** इसके बिना SDK आपकी ओर से server की request ठुकरा देता है और पूरा call `MCPError` के साथ fail हो जाता है।
* server पूछने से पहले `ctx.session.check_client_capability(...)` से पता कर लेता है।
* `sampling_callback` और `list_roots_callback` इसी तरह काम करते हैं लेकिन deprecated features को serve करते हैं; modern servers इनकी जगह multi-round-trip requests इस्तेमाल करते हैं।
* `logging_callback` और `message_handler` को notifications मिलती हैं। वे कुछ घोषित नहीं करते।
`Client(...)` का पहला argument transport object है। हर प्रकार की जानकारी **[Client transports](transports.md)** में है।

View file

@ -0,0 +1,151 @@
---
translation:
sections: [a91322c46111d16d, 8e6fd6d6f59bb568, e7828fd2729b2c9d, a03ec26bfc678b65, 1034c653c0bcf1b0]
tool: 1
---
# Identity assertion {#identity-assertion}
साधारण OAuth provider (**[OAuth clients](oauth-clients.md)**) MCP server से एक सवाल पूछकर शुरू करता है: **आप किस authorization server पर भरोसा करते हैं?** जवाब जिधर इशारा करे, वह उधर चला जाता है, और फिर या तो कोई इंसान sign in करता है या उसकी जगह कोई pre-shared secret काम आता है।
Enterprise नहीं चाहता कि इनमें से कोई भी बात हर server के हिसाब से अलग तय हो। वह पहले से एक identity provider चलाता है (Okta, Microsoft Entra ID, या आपका अपना); user आज सुबह ही उसमें sign in कर चुका है; और यही वह एक जगह है जहाँ security team तय करना चाहती है कि कौन कहाँ तक पहुँच सकता है। [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990), यानी **Enterprise-Managed Authorization** extension, यह फ़ैसला वहीं ले जाता है। IdP एक short-lived JWT sign करता है, **Identity Assertion JWT Authorization Grant**, यानी **ID-JAG**: यह बयान कि **यह user**, **इस client** के ज़रिए, **इस MCP server** तक पहुँच सकता है। Client इसे देकर बदले में साधारण access token ले लेता है। न browser, न consent screen, न dynamic registration।
यह page उसी लेन-देन के दोनों सिरों के बारे में है। MCP server खुद कभी नहीं बदलता: वह अब भी **[Authorization](../run/authorization.md)** वाला resource server ही है, जो भी token सामने आए उसे जाँचता है।
## दो token requests {#two-token-requests}
यहाँ दो अलग-अलग authorities काम कर रही हैं, और उन्हें अलग-अलग नाम से पहचान लेना ही इस page को समझने का ज़्यादातर हिस्सा है। **Enterprise IdP** आपके organization का identity provider है: उसे पता है कि employee कौन है, policy वहीं रहती है, और ID-JAG वही जारी करता है। SDK उससे कभी बात नहीं करता। **MCP authorization server** वही पक्ष है जो **[Authorization](../run/authorization.md)** में था: MCP server के metadata में नामित issuer, वह चीज़ जो वे tokens बनाती है जिन्हें वह MCP server स्वीकार करता है। साधारण OAuth flow में ये दोनों भूमिकाएँ आमतौर पर एक ही system निभाता है। यहाँ ये दो हैं, और पूरा grant बस इतना है कि दूसरा पहले पर भरोसा करने को राज़ी हो।
Client इनमें से हर एक को एक token request भेजता है।
1. **Enterprise IdP को।** Client user के sign-in (उनका OpenID Connect ID token) के बदले ID-JAG लेता है। यह [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token exchange है, यह पूरी तरह आपके IdP का API है, और **SDK इसे नहीं करता**। यह आप करते हैं, एक async callback के अंदर। Policy का फ़ैसला भी यहीं होता है: जो IdP मना कर दे वह ID-JAG जारी ही नहीं करता, और पेश करने को कुछ बचता ही नहीं।
2. **MCP authorization server को।** Client ID-JAG को [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523) `jwt-bearer` grant के तहत पेश करता है (`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, ID-JAG `assertion` के रूप में) और access token पाता है। **यही वह request है जो SDK भेजता है**, और इसे स्वीकार करना ही वह एक चीज़ है जो यह page authorization server में जोड़ता है।
नीचे सब कुछ दूसरी request के बारे में है: उसे भेजने वाला client और उसका जवाब देने वाला authorization server।
## Client {#the-client}
**`IdentityAssertionOAuthProvider`** `mcp.client.auth.extensions.identity_assertion` में रहता है। **[OAuth clients](oauth-clients.md)** के हर provider की तरह यह भी `httpx2.Auth` है: एक बनाएँ, उसे `auth=` पर रखें, और `httpx2.AsyncClient` transport को सौंप दें।
```python title="client.py" hl_lines="49-50 53-61"
--8<-- "docs_src/identity_assertion/tutorial001.py"
```
इसे नीचे से पढ़ें।
* `main()` वही standard OAuth-client वाला `main()` है (**[OAuth clients](oauth-clients.md)**), पंक्ति-दर-पंक्ति बिना बदलाव। यही बात है: एक बार provider बन जाए तो आगे किसी को पता नहीं चलता कि token किस grant से आया।
* Provider वह लेता है जो बाकी providers discover नहीं कर सकते: `client_id` और `client_secret` जो किसी ने authorization server के साथ **पहले से register** कर रखे हैं, उस authorization server का `issuer`, और `assertion_provider`, एक async callback जो माँगने पर ताज़ा ID-JAG लौटाता है।
* `storage` वही `TokenStorage` protocol है। सिर्फ़ दो token methods ही कभी call होते हैं; यहाँ dynamic registration नहीं है, इसलिए याद रखने को कोई `client_info` नहीं है।
### Assertion provider {#the-assertion-provider}
`fetch_id_jag(audience, resource)` ही वह इकलौता code है जो आप लिखते हैं। यह हर token exchange पर एक बार await होता है, construction के समय कभी नहीं, और सिर्फ़ तब **जब** authorization server का metadata fetch और validate हो चुका हो, इसलिए गलत configure किया गया issuer कभी assertion leak नहीं करवाता। इसके दो arguments उन claims में से दो हैं जिनके साथ ID-JAG बनना ज़रूरी है: `audience` authorization server का issuer है (ID-JAG का `aud`) और `resource` MCP server का canonical identifier है (ID-JAG का `resource`)। तीसरा वह है जो आपके पास पहले से है: ID-JAG के `client_id` claim में वही `client_id` होना चाहिए जो आपने provider को दिया, वरना authorization server exchange से मना कर देता है।
उसके ऊपर वाला `idp_issue_id_jag` **आपका code नहीं है**। वह identity provider की जगह खड़ा है, और assertion को उसी process में sign करता है ताकि file पूरी रहे और आप ID-JAG में जाने वाला हर claim पढ़ सकें। असली `fetch_id_jag` इसकी जगह पिछले section की पहली token request भेजता है: आपके IdP के सामने [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token exchange, जिसे Identity Assertion JWT Authorization Grant draft परिभाषित करता है और जिसे [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) profile करता है। Sign in किए हुए user का ID token `subject_token` के रूप में जाता है, `requested_token_type` ID-JAG का अपना URN है (`urn:ietf:params:oauth:token-type:id-jag`), `audience` और `resource` जस के तस आगे जाते हैं, और response में ID-JAG आता है। अपने IdP की documentation में इन्हीं नामों के साथ यही exchange ढूँढें।
!!! tip
हर exchange के लिए ताज़ा ID-JAG माँगा जाता है, और यही मक़सद है: यह एक बार इस्तेमाल होने वाला,
कुछ मिनट जीने वाला grant है, और इस page का authorization server एक ही ID-JAG को दो बार स्वीकार
करने से मना कर देता है। इसे cache न करें। इसके बदले जो access token मिलता है, दोबारा इस्तेमाल वही होता है।
### Issuer configuration है {#the-issuer-is-configuration}
उलटफेर यहाँ है। `OAuthClientProvider` resource server से पूछता है कि कौन सा authorization server इस्तेमाल करे, और जवाब जिधर इशारा करे उधर चला जाता है। यह provider ऐसा करने से मना करता है: `issuer` ज़रूरी है, [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) metadata उसी issuer के अपने well-known path से fetch होता है, token endpoint उसी issuer के origin पर होना चाहिए, और resource server से कभी कुछ नहीं पूछा जाता।
Extension इसकी माँग नहीं करता; यह जान-बूझकर चुना गया ज़्यादा सख़्त रास्ता है। इस client के पास चुराने लायक दो चीज़ें हैं, एक pre-registered secret और एक audience-bound assertion, और जो client किसी compromised MCP server को खुद को हमलावर के authorization server की तरफ़ मोड़ने दे, वह दोनों उसी को post कर देगा। Construction के समय issuer को pin कर देने से वह बातचीत ही ख़त्म हो जाती है।
!!! warning
Configure किए गए `issuer` की तुलना metadata document के `issuer` field से RFC 8414 §3.3 के
simple string comparison से होती है: एक-एक character, आख़िरी slash समेत, बिना किसी normalization के।
इसका अंदाज़ा न लगाएँ। अपने authorization server से `/.well-known/oauth-authorization-server` fetch करें
और जो `issuer` value वह लौटाए उसे copy करें। इस page के authorization server के लिए वह
`https://auth.example.com/` है, slash के साथ, क्योंकि उसका issuer pydantic URL object से बना था।
Mismatch होने पर flow एक भी credential या assertion भेजे जाने से पहले `OAuthFlowError: Authorization server metadata issuer
mismatch` पर रुक जाता है।
### Confidential client {#a-confidential-client}
`client_secret` ज़रूरी है; इसके बिना constructor `ValueError` raise करता है। [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) के नीचे वाला IETF profile इस grant को confidential clients के लिए आरक्षित रखता है, SEP-990 client से authenticate करने की माँग करता है, और यह SDK shared secret पर ज़ोर देकर दोनों लागू करता है। `token_endpoint_auth_method` तय करता है कि यह कहाँ से होकर जाए: `client_secret_post` (default, form body में) या `client_secret_basic` (HTTP Basic header)। Profile `private_key_jwt` की भी इजाज़त देता है; यह provider उसे support नहीं करता।
!!! tip
`client_secret` को environment या किसी secret manager से पढ़ें, source control से कभी नहीं।
### Provider आपके लिए क्या करता है {#what-the-provider-does-for-you}
पहली request बिना authentication के जाती है, और server का `401` flow शुरू करता है।
1. **Discovery।** यह configure किए गए issuer के [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) well-known path से authorization server metadata fetch करता है, जाँचता है कि document का `issuer` मेल खाता है, और जाँचता है कि token endpoint issuer के origin पर है।
2. **Assertion।** यह आपके `assertion_provider` को await करता है।
3. **Exchange।** यह token endpoint पर `jwt-bearer` grant POST करता है, `OAuthToken` store करता है, और आपकी मूल request `Authorization: Bearer ...` के साथ दोबारा भेजता है।
जिस `403` का `WWW-Authenticate` `insufficient_scope` बताता है, वह चरण 2 और 3 को आपके `scope` और challenge किए गए scope के union के साथ दोबारा चलाता है। (`scope` हमेशा सिर्फ़ एक माँग है; इस page का authorization server वही देता है जो ID-JAG कहता है, उससे ज़्यादा कुछ नहीं।) इसमें कहीं कोई refresh token नहीं है: access token expire होने पर अगला `401` ताज़ा ID-JAG बनवाता है और फिर exchange करता है, और **यही** वह lever है जो IdP के हाथ में है। नाकामियाँ **[OAuth clients](oauth-clients.md)** के बाकी हिस्से वाले वही दो exceptions हैं: discovery और validation के लिए `OAuthFlowError`, और जब token endpoint मना करे तब उसका subclass `OAuthTokenError`।
## Authorization server {#the-authorization-server}
ज़्यादातर बार आप यहीं रुक जाते हैं। MCP authorization server किसी और का product है, ID-JAGs स्वीकार करना उसकी configuration में चालू करने की चीज़ है, और [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) का SDK वाला आधा हिस्सा ऊपर का client है।
SDK खुद authorization server भी **बन** सकता है: `create_auth_routes` authorization server के routes एक list के रूप में लौटाता है जिसे कोई भी Starlette app mount कर सकता है; repository में `examples/servers/simple-auth/` इसी तरह एक चलाता है। SEP-990 उस surface में एक flag और एक method जोड़ता है:
```python title="auth_server.py" hl_lines="48-50 105-107"
--8<-- "docs_src/identity_assertion/tutorial002.py"
```
* `identity_assertion_enabled=True` सब कुछ gate करता है। बंद होने पर, जो default है, `/token` इस grant का जवाब `unsupported_grant_type` से देता है चाहे आपने hook implement किया हो, और metadata में इसका ज़िक्र नहीं होता। चालू होने पर metadata में `jwt-bearer` grant type जुड़ जाता है और `authorization_grant_profiles_supported` में `urn:ietf:params:oauth:grant-profile:id-jag` सूचीबद्ध हो जाता है; यही वह field है जिससे extension support का ऐलान करता है। (इस SDK का client इसे कभी नहीं पढ़ता: वह एक issuer के लिए provision किया गया है और सीधे माँग लेता है।)
* **`exchange_identity_assertion`** ही hook है। इसके चलने से पहले SDK client को authenticate कर चुका होता है, public clients को मना कर चुका होता है, और उन clients को मना कर चुका होता है जिनके registration में यह grant सूचीबद्ध नहीं है। आपको `IdentityAssertionParams` मिलता है (कच्चा `assertion`, माँगे गए `scopes` और `resource`) और आप सादा `OAuthToken` लौटाते हैं।
* Dynamic client registration इस grant को बिना शर्त मना करता है, इसलिए यहाँ `get_client` हाथ से provision किया गया client देता है। ID-JAG client खुद को register करके अस्तित्व में नहीं ला सकता।
* आधी class इनकारों से भरी है। `OAuthAuthorizationServerProvider` **पूरा** authorization server है, इसलिए वह authorization-code flow भी माँगता है; जो server users को sign in भी कराता है वह उन्हें सच में implement करता है, और इस वाले में ठीक एक ही दरवाज़ा है।
!!! warning
SDK assertion को कभी decode नहीं करता: सिर्फ़ आपके deployment को पता है कि वह किस IdP पर भरोसा करता है
और वह IdP कौन सी keys publish करता है, इसलिए `exchange_identity_assertion` के अंदर की हर चीज़ पर पूरा भार टिका है।
Signature को IdP की published keys (उसकी JWKS; यहाँ वाला shared secret demo का है) से verify करें,
और [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523) §3 के मुताबिक `iss` और `exp` भी। JWT header का `typ`
`oauth-id-jag+jwt` होना ज़रूरी करें; यह profile का बचाव है ताकि कोई और JWT grant बनाकर replay न किया जा सके।
`aud` आपका अपना issuer हो, यह ज़रूरी करें। ID-JAG का `client_id` claim उसी client के बराबर हो जिसे
handler ने authenticate किया, और उसका `resource` claim किसी ऐसे resource का नाम ले जिसे आप सच में serve करते हैं,
यह ज़रूरी करें। `jti` को assertion के `exp` तक track करें ताकि वह एक ही बार स्वीकार हो। और दिए गए scopes,
और सबसे बढ़कर जारी किए गए token का `resource`, validated ID-JAG से लें, request से कभी नहीं:
`params.resource` वही है जो client ने type किया। Processing के पूरे नियम
[Enterprise-Managed Authorization specification](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization) में हैं।
ख़राब assertion को `TokenError("invalid_grant", ...)` से reject करें। इस flow का दूसरा error code `invalid_target` है: जो ID-JAG किसी ऐसे resource का नाम ले जिसे आप serve नहीं करते, उसे इसी से मना किया जाता है, और यही इस server को किसी और के resource के लिए tokens बनाने से रोकता है। और दिए गए scopes ID-JAG के `scope` claim से आते हैं (जिस assertion में यह न हो उसे भी मना किया जाता है); आपका server शायद इसकी जगह user के groups map करे।
और ध्यान दें कि लौटाए गए `OAuthToken` में क्या नहीं है: refresh token। IdP यह तय करके कि अगला ID-JAG जारी करना है या नहीं, तय करता है कि इस user की पहुँच कब तक बनी रहे। यहाँ बना refresh token वह फ़ैसला चुपचाप वापस सौंप देता।
!!! info
जो server अब भी `auth_server_provider=` से अपना authorization server embed करता है, वह
`AuthSettings(identity_assertion_enabled=True)` के ज़रिए इसी code तक पहुँचता है। **[Authorization](../run/authorization.md)** समझाता है कि नए
servers को वहाँ से शुरू क्यों नहीं करना चाहिए।
!!! check
इस page की दोनों files को आपस में जोड़ दें और पूरा grant बस एक `POST /token` है:
```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`, न `/register`, न protected-resource-metadata fetch। Wire पर सिर्फ़ ये requests हैं:
वह जिस पर `401` आया, well-known fetch, यह exchange, और फिर bearer लगा हुआ साधारण
MCP traffic। और जो `sub` आपके validator ने ID-JAG से पढ़ा, tool के अंदर
`get_access_token().subject` ठीक वही बताता है।
### इसे आज़माएँ {#try-it}
SDK repository में `examples/stories/identity_assertion/` यही page असल में चलता हुआ है: वही `exchange_identity_assertion` validator, उसके tokens पर gate किया गया MCP server, एक stand-in IdP, और client, सब एक self-checking program में। `uv run python -m stories.identity_assertion.client --http` पूरा exchange चलाता है और assert करता है कि जिस user का नाम IdP ने लिया, tool को वही user दिखता है।
## सारांश {#recap}
* [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) यह फ़ैसला end user के बजाय enterprise identity provider को करने देता है कि client किन MCP servers तक पहुँच सकता है। IdP उस फ़ैसले को sign करके **ID-JAG** में डाल देता है।
* ID-JAG हासिल करना **आपके IdP** के सामने [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token exchange है, और SDK इसे नहीं करता। उसे MCP authorization server के सामने पेश करना [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523) `jwt-bearer` grant है, और SDK उसके दोनों पक्ष करता है।
* `IdentityAssertionOAuthProvider` एक और `httpx2.Auth` है: pre-registered confidential client, pin किया गया `issuer`, और एक `assertion_provider(audience, resource)` callback। न browser, न registration, न refresh token।
* Authorization server कभी resource server से discover नहीं होता। `issuer` को ठीक उसी string पर configure करें जो उसका metadata document देता है; तुलना एक-एक character की होती है।
* Server की तरफ़, `identity_assertion_enabled=True` और `exchange_identity_assertion`। SDK client को authenticate करता है और grant को gate करता है; ID-JAG validate करना पूरी तरह आपका काम है, और जारी किया गया token ID-JAG के `resource` से बँधा होता है, request के नहीं।
इकलौता पक्ष जिसे इस page ने कभी नहीं छुआ, वह MCP server है। अभी-अभी बनाए गए token के साथ वह जो करता है, वह **[Authorization](../run/authorization.md)** में पहले से कर ही रहा था।

View file

@ -0,0 +1,217 @@
---
translation:
sections: [ebef1e7a0df854f4, a4c687d3d627d516, 8e79141fc2985342, b345dd05b9c3c7ab, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
tool: 1
---
# Client {#the-client}
**`Client`** वह ज़रिया है जिससे Python program किसी MCP server से बात करता है।
यह एक object है जिसका एक ही lifecycle है: इसे बनाएँ, `async with` में enter करें, methods call करें। protocol का हर verb (tools की सूची लेना, किसी tool को call करना, resource पढ़ना, prompt render करना) इस पर एक `async` method है जो typed result लौटाता है।
## आपका पहला client {#your-first-client}
```python title="client.py" hl_lines="14-18"
--8<-- "docs_src/client/tutorial001.py"
```
ऊपर वाला server सिर्फ़ इसलिए है ताकि connect करने के लिए कुछ हो। client वे पाँच highlighted lines हैं।
* `Client(mcp)` को **server object ही** दिया गया है। यही in-memory transport है: न subprocess, न port, न HTTP। इस page का हर उदाहरण, और आपका लिखा हर test, इसी तरह connect करता है।
* `async with` ही **lifecycle** है। इसमें enter करते ही connect और negotiate होता है; बाहर निकलते ही disconnect। कोई `connect()` / `close()` जोड़ी नहीं है, और block खत्म होने के बाद `Client` दोबारा इस्तेमाल नहीं हो सकता।
* block के अंदर connection की जानकारी पहले से सादी properties के रूप में मौजूद है।
### `Client` को क्या दे सकते हैं {#what-you-can-pass-to-client}
`Client` एक positional argument लेता है और उसके type से transport तय करता है:
* `MCPServer` (या low-level `Server`) instance: **in-process** connect होता है।
* URL string (`Client("http://localhost:8000/mcp")`): Streamable HTTP, production वाला रास्ता।
* **transport**: कोई भी चीज़ जिसे आप `async with ... as (read, write)` कर सकें, जैसे subprocess को wrap करने वाला `stdio_client(...)`।
इस page की बाकी हर चीज़ तीनों में एक जैसी है। Headers, subprocesses, timeouts और `Transport` protocol का अपना अलग page है: **[Client transports](transports.md)**।
### connected client पर क्या है {#whats-on-a-connected-client}
चार read-only properties, जो block में enter करते ही भर जाती हैं:
* `client.server_info`: server की पहचान, या `None` अगर 2026 पीढ़ी का server इसे report नहीं करता (python-sdk servers default रूप से करते हैं)। यहाँ `server_info.name` `"Bookshop"` है, और `server_info.version` वही है जो server report करता है।
* `client.server_capabilities`: server क्या कर सकता है (`tools`, `resources`, `prompts`, `completions`, ...)। जो capability server के पास नहीं है वह `None` होती है।
* `client.protocol_version`: वह protocol version जिस पर दोनों पक्ष सहमत हुए। यहाँ यह `"2026-07-28"` है।
* `client.instructions`: server की `instructions=` string, या `None` अगर उसने कोई set नहीं की।
आपने कोई protocol version नहीं चुना। default रूप से `Client` server को probe करता है और पुराने servers पर पुराने classic handshake पर लौट आता है, इसलिए एक ही client किसी भी पीढ़ी के server के साथ काम करता है। जब इसे नियंत्रित करने की ज़रूरत हो, पूरी जानकारी **[Protocol versions](../protocol-versions.md)** में है।
!!! tip
`client.session` अंदर का `ClientSession` है, low-level escape hatch।
इस page की किसी भी चीज़ के लिए आपको इसकी ज़रूरत नहीं पड़ेगी।
## tools की सूची लेना {#listing-tools}
```python title="client.py" hl_lines="15-20"
--8<-- "docs_src/client/tutorial002.py"
```
`list_tools()` एक `ListToolsResult` लौटाता है; tools `.tools` में हैं। हर एक वह पूरी definition है जो host किसी model को देगा:
```python
tool.name # 'search_books'
tool.title # 'Search the catalog'
tool.description # 'Search the catalog by title or author.'
```
और `tool.input_schema` वह JSON Schema है जो server ने function के type hints से निकाला:
```json
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"},
"limit": {"default": 10, "title": "Limit", "type": "integer"}
},
"required": ["query"],
"title": "search_booksArguments"
}
```
UI को argument form दिखाने के लिए, और model को valid arguments बनाने के लिए, जो कुछ चाहिए वह सब इसी schema में है।
!!! tip
`title` optional है, इसलिए किसी इंसान को tools दिखाने वाले UI को चुनना पड़ता है: `title` हो तो वही,
नहीं तो `name`। `from mcp.shared.metadata_utils import get_display_name` ठीक यही करता है,
tools, resources, resource templates और prompts के लिए।
## tool call करना {#calling-a-tool}
`call_tool(name, arguments)` tool चलाता है और आपको `CallToolResult` वापस देता है।
```python title="client.py" hl_lines="26-33"
--8<-- "docs_src/client/tutorial003.py"
```
server का `lookup_book` एक Pydantic `Book` लौटाता है। client को यह दिखता है:
```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
```
एक return value, पढ़ने की तीन चीज़ें। हर एक को पढ़ने वाला अलग है।
### `content`: जो model पढ़ता है {#content-what-the-model-reads}
`content` **content blocks** की एक `list` है, और content block एक union है: `TextContent`, `ImageContent`, `AudioContent`, `ResourceLink`, या `EmbeddedResource`। एक tool कई blocks लौटा सकता है, अलग-अलग तरह के।
इसीलिए `main` `block.text` को छूने से पहले `isinstance(block, TextContent)` से narrow करता है। ध्यान दें कि `isinstance` के बाहर कहीं `.text` नहीं है: type checker इसकी अनुमति नहीं देगा, क्योंकि `ImageContent` में `.data` है, `.text` नहीं। tool आपको क्या भेज सकता है, इस बारे में union ईमानदार है; आपका code भी होना चाहिए।
### `structured_content`: जो आपका application पढ़ता है {#structured_content-what-your-application-reads}
`structured_content` tool की return value JSON के रूप में है, जो tool के declared `output_schema` से मेल खाती है। न string parsing, न अंदाज़ा।
जब दोनों मौजूद हों तो वे जानबूझकर एक ही बात दो बार कहते हैं: `content` model के लिए है, `structured_content` code के लिए। structured वाला हिस्सा कहाँ से आता है, और उसे कैसे नियंत्रित करें, यह **[Structured output](../servers/structured-output.md)** page पर है।
### `is_error`: tool fail हुआ या नहीं {#is_error-whether-the-tool-failed}
जो tool raise करता है वह आपके client में raise **नहीं** होता। वह `is_error=True` के साथ एक साधारण result के रूप में लौटता है।
!!! check
`lookup_book` से `"Solaris"` माँगें (ऐसा title जो catalog में नहीं है) और function
`ValueError` raise करता है। call फिर भी सामान्य रूप से लौटता है:
```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
```
exception का message `content` में पहुँचा, जहाँ **model** उसे पढ़कर दोबारा कोशिश कर सकता है। यह
जानबूझकर है: tool error बातचीत का हिस्सा है, crash नहीं। `structured_content` पर भरोसा करने से
पहले हमेशा `is_error` देखें।
!!! warning
`is_error=True` सिर्फ़ आपके अपने `raise` तक सीमित नहीं है। ऐसा tool माँगें जो server के पास है ही नहीं
(`call_tool("does_not_exist", {})`) और कुछ raise नहीं होता। आपको वही shape वापस मिलता है,
`is_error=True` और `content` में `Unknown tool: does_not_exist`। `Client` का कोई method
`MCPError` तभी raise करता है जब server result की जगह JSON-RPC **error** से जवाब दे, और
server कब क्या भेजता है यह **[errors संभालना](../servers/handling-errors.md)** में बताया गया है।
## Resources {#resources}
resource verbs जोड़ियों में आते हैं: सूची लेने के दो तरीके, पढ़ने का एक।
```python title="client.py" hl_lines="22-31"
--8<-- "docs_src/client/tutorial004.py"
```
* `list_resources()` **concrete** resources लौटाता है, जिनका URI तय है। यहाँ: `['catalog://genres']`।
* `list_resource_templates()` **parameterised** वाले लौटाता है। यहाँ: `['catalog://genres/{genre}']`। ये दो अलग सूचियाँ हैं क्योंकि template तब तक पढ़ा नहीं जा सकता जब तक आप उसे भर न दें।
* `read_resource(uri)` एक सादा `str` URI लेता है और दोनों पर काम करता है: `"catalog://genres/poetry"` दें और server उसे template से match कर लेता है।
`read_resource` `contents` लौटाता है, `TextResourceContents` या `BlobResourceContents` की सूची। वही तरीका जो tool content का है: `isinstance` से narrow करें, फिर `.text` (या `.blob`) पढ़ें।
client को यह भी बताया जा सकता है कि कोई resource कब बदला। 2025 पीढ़ी के connections पर यह `subscribe_resource(uri)` / `unsubscribe_resource(uri)` है - methods की ऐसी जोड़ी जिसे `MCPServer` implement नहीं करता, इसलिए 2026-07-28 wire पर (जहाँ ये verbs अब मौजूद नहीं हैं) request का जवाब `-32601`, *Method not found* आता है। 2026 में इसकी जगह `subscriptions/listen` stream है, जिसे `MCPServer` serve **करता है** - वहाँ `server_capabilities.resources.subscribe` `True` है - और उसे `client.listen(...)` से consume करना इस section का **[Subscriptions](subscriptions.md)** page है।
## Prompts {#prompts}
```python title="client.py" hl_lines="15-20"
--8<-- "docs_src/client/tutorial005.py"
```
`list_prompts()` बताता है कि server क्या देता है और हर prompt को क्या चाहिए:
```python
prompt.name # 'recommend'
prompt.title # 'Recommend a book'
prompt.arguments # [PromptArgument(name='genre', required=True)]
```
`get_prompt(name, arguments)` उसे render करता है। arguments dict `str -> str` है: prompt arguments हमेशा strings होते हैं। result `messages` है, `PromptMessage` की सूची, जिनमें हर एक का `role` और एक `content` block है:
```python
message.role # 'user'
message.content # TextContent(type='text', text='Recommend one poetry book from the catalog and say why.')
```
host ये messages सीधे model को दे देता है। पूरा feature बस इतना ही है।
## Completions {#completions}
जिस server में completion handler हो वह user के type करते-करते prompt और resource-template arguments autocomplete कर सकता है।
```python title="client.py" hl_lines="27-31"
--8<-- "docs_src/client/tutorial006.py"
```
* `ref` बताता है कि आप **कौन-सा** prompt या template भर रहे हैं: `PromptReference` या `ResourceTemplateReference`।
* `argument` `{"name": ..., "value": ...}` है: argument और user ने अब तक जो type किया है।
जवाब `result.completion.values` में है। `"p"` type करें और server `['poetry']` लौटाता है। server वाला पक्ष, और handler पहले से भरे **बाकी** arguments का इस्तेमाल अपने सुझाव कम करने के लिए कैसे करता है, यह **[Completions](../servers/completions.md)** page पर है।
## Pagination {#pagination}
हर `list_*` method एक `cursor=` keyword लेता है और हर result में `next_cursor` होता है। जब `next_cursor` `None` हो, आपके पास सब कुछ है।
```python title="client.py" hl_lines="22-30"
--8<-- "docs_src/client/tutorial007.py"
```
यह loop हर server के साथ सही है। `MCPServer` सब कुछ एक ही page में लौटाता है, इसलिए `next_cursor` `None` होता है और loop एक बार चलता है, यही वजह है कि ज़्यादातर code इसे कभी लिखता ही नहीं। जो servers सच में page करते हैं, और cursors जिन नियमों का पालन करते हैं, वे **[Pagination](../advanced/pagination.md)** में हैं।
## tests में {#in-tests}
बिना process और बिना port वाला `Client(mcp)` अपने आप में server के लिए test harness है।
इसी के लिए एक constructor flag बना है: `Client(mcp, raise_exceptions=True)`। इसका असर सिर्फ़ in-memory connections पर होता है, और **[Testing](../get-started/testing.md)** वह page है जो इसे समझाता है और इसके चारों ओर पूरा pattern बनाता है।
## सारांश {#recap}
* `Client(x)` server object से in-memory connect होता है, URL string से Streamable HTTP पर, और बाकी किसी भी चीज़ से transport के ज़रिए।
* `async with` ही पूरा lifecycle है। इसके अंदर `server_capabilities` और `protocol_version` पहले से भरे होते हैं; server दे तो `server_info` और `instructions` भी।
* `list_tools()` आपको हर tool का `name`, `title`, `description` और `input_schema` देता है।
* `call_tool()` model के लिए `content`, आपके code के लिए `structured_content`, और `is_error` लौटाता है। raise करने वाला tool एक result है, exception नहीं।
* `content` block types का union है; पढ़ने से पहले `isinstance` से narrow करें।
* `list_resources` / `list_resource_templates` / `read_resource`, `list_prompts` / `get_prompt`, और `complete` बाकी verbs पूरे करते हैं।
* हर `list_*` `cursor=` लेता है; `next_cursor` के `None` होने तक loop करें।
server *client* से जो चीज़ें माँग सकता है, और आप उनका जवाब कैसे देते हैं, वह **[Client callbacks](callbacks.md)** है।

View file

@ -0,0 +1,152 @@
---
translation:
sections: [c6899d3892bd9fa0, 79372cff3cc48a88, 63878d29e87c3e73, 13175843d3588af4, e7e2b9fd516f77de, 758f06399b513c1f, a05d7278487d610b]
tool: 1
---
# OAuth clients {#oauth-clients}
कुछ MCP servers सुरक्षित होते हैं। उन्हें बिना token के request भेजें तो जवाब `401 Unauthorized` आता है।
token पाने का तरीका **`OAuthClientProvider`** है। यह कोई MCP object है ही नहीं। यह `httpx2.Auth` है, "हर request के साथ कुछ करो" वाला httpx2 का standard hook। इसे `httpx2.AsyncClient` पर लगाएँ, वह client Streamable HTTP transport को दें, और इसके बारे में सोचना बंद कर दें।
यह page client वाला हिस्सा है। अपने server से token की माँग करवाना **[Authorization](../run/authorization.md)** में है।
## Provider {#the-provider}
```python title="client.py" hl_lines="44-54"
--8<-- "docs_src/oauth_clients/tutorial001.py"
```
आप इसे चार चीज़ें देते हैं:
* `server_url`: वह MCP endpoint जिससे आप जुड़ रहे हैं। बाकी सब कुछ provider इसी से खोज लेता है।
* `client_metadata`: वही जो आप किसी authorization server के "register an application" form में भरते।
* `storage`: जहाँ दो runs के बीच tokens रहते हैं।
* `redirect_handler` और `callback_handler`: वे दो पल जिनमें कोई इंसान शामिल होता है।
file में और कहीं OAuth का ज़िक्र नहीं है। `main()` को कभी कोई token दिखता ही नहीं।
### Client metadata {#client-metadata}
`OAuthClientMetadata` असली [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) registration document है, Pydantic model के रूप में।
आप तीन fields भरते हैं। बाकी defaults भर देते हैं: `grant_types` पहले से `["authorization_code", "refresh_token"]` है और `response_types` पहले से `["code"]` है, ठीक वही flow जो यह provider चलाता है।
!!! check
चूँकि यह Pydantic model है, यह **network पर एक भी byte जाने से पहले** validate करता है।
`redirect_uris` छोड़ दें तो construction वहीं के वहीं `ValidationError` के साथ fail हो जाता है,
जो field का नाम बताता है:
```text
redirect_uris
Field required [type=missing, input_value={'client_name': 'Bookshop Agent'}, input_type=dict]
```
न कोई browser खुला, न authorization server पर कोई अधूरी registration पीछे छूटी।
### Token storage {#token-storage}
**`TokenStorage`** चार async methods वाला `Protocol` है। आपको किसी से inherit नहीं करना; methods लिख दें और कोई भी class token store बन जाती है:
* `get_tokens` / `set_tokens` `OAuthToken` रखते हैं: access token, refresh token, expiry, scope।
* `get_client_info` / `set_client_info` वह `OAuthClientInformationFull` रखते हैं जो authorization server ने तब जारी किया था जब provider ने आपको register किया, आपके `client_id` समेत।
ऊपर वाला in-memory version काम करता है। यह process के ख़त्म होते ही सब कुछ भूल भी जाता है, इसलिए अगला run पूरी प्रक्रिया फिर से दोहराता है। इसे किसी file या अपने platform के keyring में persist करें और अगला run चुपचाप चल जाता है।
!!! tip
सिर्फ़ tokens नहीं, `client_info` भी store करें। provider पहली बार तब dynamically register करता है
जब उसे कोई stored `client_info` नहीं मिलता। इसे फेंक दें तो हर run पर एक नई registration बनती है।
### दो handlers {#the-two-handlers}
authorization code flow को इंसान की ज़रूरत ठीक एक बार पड़ती है: किसी को sign in करके "allow" पर click करना होता है।
* **`redirect_handler`** को पूरी तरह बने हुए authorization URL के साथ await किया जाता है। `client_id`, `redirect_uri`, `state` और PKCE challenge उसमें पहले से मौजूद हैं। आपका काम बस इतना है कि browser को वहाँ पहुँचाएँ। desktop app `webbrowser.open` call करता है; यह file उसे print कर देती है।
* उसके बाद **`callback_handler`** await होता है। यह तब तक इंतज़ार करता है जब तक user वापस आपके `redirect_uri` पर नहीं पहुँच जाता, और उस redirect के query parameters को `AuthorizationCodeResult` के रूप में लौटाता है।
असली client `input()` call करने के बजाय redirect URI पर एक छोटा local HTTP server चलाता है। आकार बिल्कुल वही है: redirect हों, और `code`, `state` व `iss` वापस दें।
!!! warning
`state` और `iss` को ठीक वैसे ही आगे दें जैसे वे आए थे। provider `state` की तुलना उससे करता है
जो उसने खुद बनाया था, और `iss` की तुलना खोजे गए issuer से, और मेल न खाने पर मना कर देता है।
यही CSRF और server-mix-up से बचाव हैं।
### `Client` में {#into-the-client}
`main()` देखें। provider **httpx2 client** पर जाता है, httpx2 client `streamable_http_client(url, http_client=...)` में जाता है, और वह transport `Client` में जाता है।
`streamable_http_client` में `auth=` keyword नहीं है। HTTP स्तर की हर चीज़ (auth, headers, timeouts, proxies) उस `httpx2.AsyncClient` पर होती है जो आप लाते हैं। यह layering **[Client transports](transports.md)** में है।
## Provider आपके लिए क्या करता है {#what-the-provider-does-for-you}
जब `Client` पहली बार request भेजता है, server `401` लौटाता है। provider कमान संभाल लेता है:
1. **Discovery.** यह `WWW-Authenticate` header पढ़ता है, `/.well-known/oauth-protected-resource` से server का Protected Resource Metadata लाता है, पता करता है कि कौन सा authorization server इस resource की रक्षा करता है, और **उस** server का metadata लाता है।
2. **Registration.** storage में कुछ नहीं है? यह आपके `OAuthClientMetadata` के साथ आपको dynamically register करता है और नतीजा store कर लेता है।
3. **Authorization.** यह PKCE pair और `state` बनाता है, authorization URL तैयार करता है, आपके `redirect_handler` को await करता है, फिर code के लिए आपके `callback_handler` को await करता है।
4. **Exchange.** यह code के बदले `OAuthToken` लेता है, उसे store करता है, और आपकी मूल request को `Authorization: Bearer ...` के साथ दोबारा भेजता है।
उसके बाद यह शांत रहता है। tokens storage से आते हैं, expire हुआ access token refresh token से refresh हो जाता है, और सिर्फ़ तब जब इनमें से कुछ काम नहीं करता, यह flow फिर से चलाता है।
आपने इसमें से कुछ नहीं लिखा। दो keyword arguments बचते हैं (`client_metadata_url` और `validate_resource_url`), और इस file को दोनों में से किसी की ज़रूरत नहीं। `client_metadata_url` जानने लायक है; इसका अपना section नीचे है।
### इसे आज़माएँ {#try-it}
इन docs के ज़्यादातर उदाहरण आप in-memory `Client(server)` से जाँच सकते हैं। यह नहीं: इस flow का पूरा मतलब ही HTTP `401` है, और in-memory client व उसके server के बीच कोई HTTP होता ही नहीं।
repository में live version मौजूद है। `examples/servers/simple-auth/` एक standalone authorization server और एक protected MCP server चलाता है; `examples/clients/simple-auth-client/` इसी page का client है, छोटी CLI में बढ़ा हुआ। उसकी README में दो commands हैं: servers शुरू करें, client को उनके सामने चलाएँ, और चारों चरण अपनी आँखों के सामने होते देखें।
## Client ID Metadata Documents {#client-id-metadata-documents}
spec का 2026-07-28 revision dynamic client registration को **Client ID Metadata Documents** (CIMD) के पक्ष में deprecated कर देता है। जिस भी authorization server से मिले उस पर नई registration POST करने के बजाय, आपका client अपने बारे में एक JSON document किसी स्थिर HTTPS URL पर publish करता है, और वही URL उसका `client_id` **है**। authorization server वह document लाता है; provider उसे कभी छूता तक नहीं।
SDK पहले से इसे समझता है: provider बनाते समय URL को `client_metadata_url=` के रूप में दें। जब authorization server का metadata `client_id_metadata_document_supported: true` बताता है, provider `/register` request को पूरी तरह छोड़ देता है: URL `client_id` बनकर flow में जाता है, और कोई `client_secret` नहीं होता। जब server इसे नहीं बताता (ज़्यादातर अभी नहीं बताते), या आप URL देते ही नहीं, तो provider **चुपचाप** dynamic registration पर लौट आता है, और ऊपर बताया सब कुछ ठीक वैसे ही काम करता है। stored `client_info` अब भी दोनों पर भारी पड़ती है।
URL HTTPS होना चाहिए और उसका path root न हो; इसके अलावा कुछ भी construction पर `ValueError` है, किसी network गतिविधि से पहले। साथ आने वाला `examples/clients/simple-auth-client/` इसे `MCP_CLIENT_METADATA_URL` environment variable के रूप में लेता है।
## Machine to machine {#machine-to-machine}
रात को चलने वाला job, CI का कोई step, कोई दूसरी service। न browser है, न "allow" पर click करने वाला कोई। यही **client credentials** grant है: आपके पास पहले से `client_id` और `client_secret` हैं, और token endpoint ही पूरा flow है।
`ClientCredentialsOAuthProvider` वही `httpx2.Auth` है, बस इंसान के बिना:
```python title="client.py" hl_lines="4 27-33"
--8<-- "docs_src/oauth_clients/tutorial002.py"
```
क्या बदला:
* न `OAuthClientMetadata`, न handlers। आप `client_id` और `client_secret` देते हैं; provider उनके इर्द-गिर्द एक न्यूनतम `client_credentials` registration बनाता है और dynamic registration पूरी तरह छोड़ देता है।
* `scope` space से अलग की गई string है, OAuth का wire format।
* आगे का सब कुछ बिल्कुल वही है: वही `TokenStorage`, वही `httpx2.AsyncClient(auth=...)`, वही `streamable_http_client`।
default रूप से secret token request पर HTTP Basic auth के रूप में जाता है (`client_secret_basic`)। उसे form body में डालने के लिए `token_endpoint_auth_method="client_secret_post"` दें। कुछ authorization servers दोनों में से सिर्फ़ एक ही स्वीकार करते हैं।
!!! tip
`client_secret` को environment या किसी secret manager से पढ़ें, source control से कभी नहीं।
!!! info
एक और provider `mcp.client.auth.extensions.client_credentials` में रहता है:
**`PrivateKeyJWTOAuthProvider`**, उन clients के लिए जो shared secret के बजाय JWT से
authenticate करते हैं (`private_key_jwt`, key-pair और workload-identity वाला रूप)। यह उसी
pattern पर चलता है: एक बनाएँ, `auth=` पर लगाएँ। उसी module में
`SignedJWTParameters` और `static_assertion_provider` भी हैं, दो helpers जो इसका assertion बनाते हैं।
बिना इंसान वाली एक और स्थिति है: client किसी enterprise का है जिसका identity provider, न कि user, तय करता है कि वह किन MCP servers तक पहुँच सकता है। वह अलग grant है, अपने trust model और अपने page के साथ, **[Identity assertion](identity-assertion.md)**।
## जब यह fail होता है {#when-it-fails}
जब OAuth flow में गड़बड़ होती है, provider `mcp.client.auth` से `OAuthFlowError` raise करता है। इसके दो subclasses हैं। `OAuthRegistrationError` का मतलब है कि registration से ऐसा client नहीं मिला जिसे आप इस्तेमाल कर सकें: authorization server ने आपको register करने से मना कर दिया, या register तो किया लेकिन ऐसे credentials के साथ जो यह flow इस्तेमाल नहीं कर सकता (उदाहरण के लिए कोई authentication method जिसे यह implement नहीं करता)। `OAuthTokenError` का मतलब है कि token नहीं मिल सका: token endpoint ने मना कर दिया, या किसी stored client record में ऐसा authentication method है जिसे यह client लागू नहीं कर सकता, जिसकी report token request बनाते समय होती है, भेजी नहीं जाती। एक `except OAuthFlowError:` discovery, registration, authorization और exchange, सबको cover करता है।
हर चीज़ flow error नहीं होती। network अब भी fail हो सकता है; वे साधारण `httpx2` exceptions हैं और बिना छुए आगे निकल जाते हैं।
## सारांश {#recap}
* `OAuthClientProvider` `httpx2.Auth` है। इसे `httpx2.AsyncClient` पर लगाएँ, उसे `streamable_http_client(url, http_client=...)` को दें, और `Client` को कभी पता नहीं चलता कि OAuth हुआ।
* आप चार चीज़ें देते हैं: server URL, एक `OAuthClientMetadata`, एक `TokenStorage`, और redirect/callback handler की जोड़ी।
* `TokenStorage` `Protocol` है: चार async methods, कोई base class नहीं। tokens के साथ-साथ `client_info` भी persist करें।
* discovery, registration (dynamic, या **Client ID Metadata Document** के ज़रिए), PKCE, `state` और `iss` की जाँच, और token refresh provider का काम हैं, आपका नहीं।
* `ClientCredentialsOAuthProvider` बिना इंसान वाला version है: `client_id` + `client_secret`, न handlers, न browser।
* हर OAuth failure `OAuthFlowError` है; `OAuthRegistrationError` और `OAuthTokenError` इसके subclasses हैं।
इस handshake का दूसरा हिस्सा, अपने **server** से token की माँग करवाना, **[Authorization](../run/authorization.md)** में है।

View file

@ -0,0 +1,87 @@
---
translation:
sections: [09c857a25a9dc37a, 43bc6a76a243a50e, 0a716022a88768df, 4b7f78042bfcfff7, c112662e61b03315, 58974ba1f489a8b4, d18adbdbb835ea73]
tool: 1
---
# Session groups {#session-groups}
`Client` सिर्फ़ एक server से जुड़ता है। असली applications को अक्सर कई servers चाहिए होते हैं (search server, database server, कोई internal API) और फिर हर एक के लिए अलग connection और अलग tools की सूची संभालनी पड़ती है।
**`ClientSessionGroup`** एक अकेला object है जिसमें कई connections रहते हैं और जो उन सबकी expose की गई हर चीज़ को एक ही view में मिला देता है।
## दो servers {#two-servers}
दो साधारण servers से शुरू करें। इनका आपस में कोई लेना-देना नहीं है, इसलिए स्वाभाविक रूप से दोनों ने अपने tool का नाम `search` रखा:
```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"
```
## एक group {#one-group}
`ClientSessionGroup` बनाएँ और हर server के लिए एक बार **`connect_to_server`** call करें:
```python title="client.py" hl_lines="10-12"
--8<-- "docs_src/session_groups/tutorial003.py"
```
* `connect_to_server` transport parameters लेता है, server object नहीं: subprocess शुरू करने के लिए `StdioServerParameters` (`mcp` से), या पहले से किसी URL पर सुन रहे server के लिए `StreamableHttpParameters` / `SseServerParameters` (`mcp.client.session_group` से)।
* `group.tools` हर जुड़े हुए server के tools का `dict[str, Tool]` है। `group.resources` और `group.prompts` का आकार भी यही है।
* `group.call_tool(name, arguments)` नाम खोजता है, वह session ढूँढता है जिसका यह tool है, और call आगे भेज देता है। आपको कभी बताना नहीं पड़ता कि कौन सा server।
!!! check
`client.py` को दोनों servers के साथ रखें और चलाएँ। दूसरा `connect_to_server` मना कर देता है:
```text
mcp.shared.exceptions.MCPError: {'search'} already exist in group tools.
```
यह `MCPError` है, जो दूसरे server से कुछ भी register होने से पहले raise होता है। नाम **पूरे**
group में unique होना ज़रूरी है, और जिन दो servers पर आपका नियंत्रण नहीं है वे कभी न कभी टकराएँगे ही।
## `component_name_hook` {#component_name_hook}
इसे servers पर नहीं, group पर ठीक किया जाता है। `(name, server_info)` लेने वाला function pass करें, और group उसे हर उस नाम पर चलाता है जिसे वह register करता है:
```python title="client.py" hl_lines="7-8 15"
--8<-- "docs_src/session_groups/tutorial004.py"
```
इसे फिर चलाएँ। `print(sorted(group.tools))` अब दोनों दिखाता है:
```text
['Library.search', 'Web.search']
```
* **key** आपकी है। `by_server` ने इसे `server_info.name` से बनाया, यानी वह नाम जिससे हर `MCPServer(...)` बनाया गया था।
* अंदर का `Tool` जस का तस है: `group.tools["Web.search"].name` अब भी `"search"` है, और `call_tool` wire पर यही नाम भेजता है। prefix आपके process से बाहर कभी नहीं जाता।
* बात सिर्फ़ tools की नहीं है। library का `hours` resource `Library.hours` नाम से register होता है।
!!! tip
hook **हर** server के **हर** नाम पर चलता है, सिर्फ़ टकराव पर नहीं: सिर्फ़-टकराव-पर-prefix
जैसा कोई mode नहीं है। एक scheme चुनें और उसे हर जगह लागू होने दें।
## servers जोड़ना और हटाना {#adding-and-removing-servers}
`connect_to_server` वह `ClientSession` लौटाता है जो उसने खोला। अगर कभी उस server को हटाना हो तो इसे संभालकर रखें: `await group.disconnect_from_server(session)` उसके tools, resources और prompts group से हटा देता है।
अगर आपके पास पहले से जुड़ा हुआ `ClientSession` है (`Client.session` ऐसा ही एक है), तो नया transport खोलने के बजाय उसे `await group.connect_with_session(server_info, session)` को सौंप दें। यह उसी तरह aggregate करता है। group कभी ऐसा session बंद नहीं करता जो उसने खुद नहीं खोला। `server_info` component prefixes के लिए server का नाम देता है; 2026 पीढ़ी के connection पर `client.server_info` `None` हो सकता है (identity वैकल्पिक है), इसलिए उस स्थिति में अपना `Implementation(name=..., version=...)` pass करें।
## Classic handshake {#the-classic-handshake}
`ClientSessionGroup` `Client` पर नहीं, `ClientSession` पर बना है। हर `connect_to_server` classic `initialize` handshake चलाता है। यह **[Protocol versions](../protocol-versions.md)** में बताया गया `server/discover` probe कभी नहीं भेजता। हर MCP server वह handshake समझता है, इसलिए इससे किसी के साथ भी compatibility नहीं खोती; इसका मतलब बस इतना है कि group ऐसे server तक भी पुराने, धीमे रास्ते से पहुँचता है जो बेहतर कर सकता था।
## सारांश {#recap}
* `ClientSessionGroup` कई server connections रखता है और उनके tools, resources और prompts को एक-एक `dict` में मिला देता है।
* हर server के लिए `connect_to_server(params)`। यह transport parameters लेता है, कभी वह server object या URL नहीं जो `Client` लेता है।
* `group.call_tool(name, arguments)` आपके लिए call को उस server तक पहुँचाता है जिसका वह tool है।
* नाम पूरे group में unique होने ज़रूरी हैं; `search` tool वाले दो servers अपने आप साथ नहीं रह सकते।
* `component_name_hook=` हर register किए गए नाम को फिर से लिखता है। dict key बदलती है, wire पर जाने वाला नाम नहीं।
* `connect_with_session` पहले से आपके पास मौजूद session जोड़ता है; `disconnect_from_server` एक session हटाता है।
group कौन सा handshake बोलता है (और `Client` किस तेज़ handshake को तरजीह देता है), यही **[Protocol versions](../protocol-versions.md)** का विषय है।

View file

@ -0,0 +1,91 @@
---
translation:
sections: [8f9558e57f29eee1, a88c587739e0465c, 46ebfd5b325ed041, 4d10b00b57ce4bd9, 2cdb0edd1f59b3e2]
tool: 1
---
# Subscriptions {#subscriptions}
server का catalog स्थिर नहीं होता। tools runtime पर आते हैं, और किसी resource URI के पीछे का content बदलता रहता है। client को इसकी ख़बर `client.listen(...)` से मिलती है: एक `subscriptions/listen` request, जिसका response ही stream **है**। यह खुला रहता है और वे change notifications लाता है जो client ने माँगे थे।
यह page client वाला सिरा है: stream खोलना, उस पर अपने main flow के साथ-साथ नज़र रखना, और उसके ख़त्म होने को संभालना। बदलाव publish करना, filter करना और method को serve करना server की तरफ़ की कहानी है, जो **आपके handler के अंदर** section के **[Subscriptions](../handlers/subscriptions.md)** page में बताई गई है। यहाँ के उदाहरण वहीं बनाए गए sprint-board server से बात करते हैं।
## stream पर नज़र रखना {#watching-the-stream}
subscription बस एक context manager है। इसमें enter करते ही request भेजी जाती है, जिसमें आपके keyword arguments subscription filter बनते हैं, और server के acknowledgment का इंतज़ार होता है, इसलिए block शुरू होने तक stream live हो चुका होता है।
```python title="client.py" hl_lines="15 18 28"
--8<-- "docs_src/subscriptions/tutorial003.py"
```
iterate करने पर चार typed events मिलते हैं: `ToolsListChanged`, `PromptsListChanged`, `ResourcesListChanged`, और `ResourceUpdated(uri=...)`।
event यह बताता है कि **क्या** बदला, कभी यह नहीं कि **कैसे**। इसीलिए `follow_board` `read_resource` और `list_tools` को call करता है: event दोबारा fetch करने का इशारा है। कौन-सा resource बदला, यह मान लेने के बजाय `event.uri` पढ़ें: filter में कई URI हो सकते हैं, और server उनमें से किसी एक के sub-resource पर बदलाव की ख़बर दे सकता है।
consume होने का इंतज़ार कर रहे duplicate events मिलकर एक हो जाते हैं, और दोबारा fetch करने पर आपको फिर भी मौजूदा state ही मिलता है। सिर्फ़ एक जैसे events ही मिलते हैं: अलग-अलग URI के दो `ResourceUpdated` दो events हैं।
handle की दो और properties:
* `sub.honored` वह filter है जिसे server ने acknowledge किया: एक `SubscriptionFilter` जिसमें आपके दिए fields हैं, जिन्हें attributes की तरह पढ़ा जाता है (`sub.honored.prompts_list_changed`)। `MCPServer` आपके माँगे हर kind को honor करता है, इसलिए वह आपकी request ज्यों की त्यों वापस लौटा देता है। कम kinds support करने वाला server कम acknowledge करता है, और honor किया गया kind फिर भी शायद कभी fire न हो। server पूरी request को acknowledge करने के बजाय उसे ठुकरा भी सकता है (server page पर [कौन देख सकता है, यह तय करना](../handlers/subscriptions.md#deciding-who-may-watch) देखें), जो request के error के रूप में सामने आता है।
* `sub.subscription_id` listen request की id है, वही जो इस stream के हर frame पर लगी होती है। एक साथ कई subscriptions खुले हो सकते हैं, और हर एक अपनी id से demultiplex होता है।
## बिना block किए नज़र रखना {#watching-without-blocking}
`follow_board` तब तक चलता है जब तक server stream बंद न कर दे, जो शायद कभी न हो, इसलिए अकेले चलाने पर यह आपके पूरे program पर क़ब्ज़ा कर लेता है। असली clients को watcher main flow के **साथ-साथ** चाहिए: agent tools call करता रहे और watcher cache या UI को ताज़ा रखे।
पहले subscription खोलें, फिर watcher शुरू करें और अपने काम में लग जाएँ।
=== "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` और `read_board` import करता है, जिसे यह repo
`tutorial003.py` नाम से रखता है। अगर आप rendered files को साथ-साथ `client.py` और `app.py` नाम से save करते हैं,
तो इसके बजाय `from client import BOARD, read_board` लिखें। नीचे दिया `watch.py` उदाहरण
भी `read_board` को इसी तरह import करता है।
असली बात क्रम की है। कुछ भी replay नहीं होता, इसलिए stream बनने से पहले publish हुआ event छूट जाता है। `client.listen(...)` में enter करना acknowledgment का इंतज़ार करता है, इसलिए उस पल के बाद का हर बदलाव आपके watcher तक पहुँचता है, और block के अंदर लिया गया snapshot एक भी बदलाव नहीं चूक सकता।
खुले stream के साथ-साथ उसी client पर requests बेरोक चलती हैं, चाहे watcher task से हों या किसी और से। चूँकि consume न हुए **duplicate** events मिलकर एक हो जाते हैं, इसलिए व्यस्त main flow में तीन के बजाय शायद एक ही refetch हो। अलग-अलग events नहीं मिलते: कई URI वाला filter हर URI के लिए एक pending event queue में रखता है।
नज़र रखना बंद करने के लिए block से बाहर निकलें: कोई `unsubscribe` call नहीं है। block वाले task को cancel करने से यह अपने आप हो जाता है, और SDK listen request को उसी तरह cancel करता है जैसा transport चाहता है: Streamable HTTP पर, उस request का stream बंद करके। app के पूरे जीवनकाल तक चलने वाला watcher कभी अपने आप नहीं लौटता, इसलिए shutdown पर उसे, या उसके task group के scope को, cancel करें।
## streams का ख़त्म होना {#streams-end}
stream दो में से किसी एक तरह से ख़त्म होता है, और दोनों साधारण control flow हैं। server का graceful close `async for` को ख़त्म कर देता है; अचानक टूटने पर `SubscriptionLost` raise होता है।
यह फ़र्क़ सिर्फ़ diagnosis के काम का है, आगे क्या करना है उसमें कोई फ़र्क़ नहीं: stream जा चुका है, कुछ replay नहीं हुआ, और जिस watcher को अब भी परवाह है वह दोबारा listen करता है और दोबारा fetch करता है।
```python title="watch.py" hl_lines="16 20"
--8<-- "docs_src/subscriptions/tutorial005.py"
```
servers अपने कारणों से streams को gracefully बंद करते हैं, जिनमें ऐसे subscriber को हटाना भी शामिल है जिसका backlog बहुत बड़ा हो गया हो, इसलिए साफ़ अंत नज़र रखना बंद करने का इशारा नहीं है। दोबारा listen करने से पहले back off करें।
`SubscriptionLost` का एक local कारण भी है। client ज़्यादा से ज़्यादा 1024 बिना consume हुए events रखता है, और जो consumer इतना पीछे रह जाए वह बिना सीमा के बढ़ते जाने के बजाय subscription खो देता है। `async for` की body छोटी रखें और धीमा काम कहीं और करें।
`keep_following` सिर्फ़ `SubscriptionLost` को catch करता है। `listen()` में enter करने पर `MCPError` (connection fail हुआ, या server यह method serve नहीं करता), `TimeoutError` (कोई acknowledgment नहीं आया), और `ListenNotSupportedError` (2026 से पहले का connection) भी raise हो सकते हैं। तय करें कि इनमें से किन पर आपका watcher retry करे: आख़िरी वाला कभी ठीक नहीं होता।
## सारांश {#recap}
* `async with client.listen(...)` में enter करें; enter करना acknowledgment का इंतज़ार करता है, इसलिए उसके बाद publish हुआ कुछ भी नहीं छूटता।
* `async for event in sub` से iterate करें। events दोबारा fetch करने के इशारे हैं, payload कभी नहीं।
* subscription खोलें, फिर watcher को task के रूप में चलाएँ, और tool calls उसके साथ-साथ चलते रहते हैं।
* साफ़ अंत loop को रोक देता है; टूटने पर `SubscriptionLost` raise होता है। दोनों ही हालात में: दोबारा listen करें, दोबारा fetch करें, पहले back off करें।
* block से बाहर निकलना ही unsubscribe है।
इन events को publish करना, filter को सीमित करना, और एक process से आगे scale करना server की कहानी है: **[Subscriptions](../handlers/subscriptions.md)**। यही events client-side cache को भी सही बनाए रखते हैं, और अगला page **[Caching](caching.md)** है।

View file

@ -0,0 +1,128 @@
---
translation:
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
tool: 1
---
# Client transports {#client-transports}
हर `Client` अपने server से एक **transport** के ज़रिए बात करता है: वही चीज़ जो असल में messages ले जाती है।
आप कभी transport को अलग से configure नहीं करते। `Client` सिर्फ़ एक positional argument लेता है और उसके type से transport तय कर लेता है।
हर transport का **server** वाला पक्ष (`mcp.run()` क्या करता है और आप क्या deploy करते हैं) **[अपना server चलाना](../run/index.md)** में है।
## Memory में {#in-memory}
server object ही पास करें:
```python title="client.py" hl_lines="14"
--8<-- "docs_src/client_transports/tutorial001.py"
```
न कोई subprocess, न कोई port, न wire पर कोई bytes। client और server एक ही process में दो objects हैं, और call फिर भी असली protocol layer से होकर जाती है: `search_books` ठीक वैसे ही list, validate और invoke होता है जैसे HTTP पर होता।
इससे यह एक साथ दो काम करता है:
* **Test harness।** इस documentation का हर उदाहरण इसी तरीके से चलाया जाता है, और **[Testing](../get-started/testing.md)** page पूरा pattern इसी के इर्द-गिर्द बनाता है।
* **Embedding API।** जो application खुद server बनाता है, उसे उसके tools call करने के लिए network hop की ज़रूरत नहीं।
## Streamable HTTP {#streamable-http}
URL string पास करें और आपको **Streamable HTTP** मिलता है, वह transport जिसके पीछे आप deploy करते हैं:
```python title="client.py" hl_lines="5"
--8<-- "docs_src/client_transports/tutorial002.py"
```
पूरा production client बस इतना ही है। `Client` आपके लिए URL को `streamable_http_client(...)` में लपेट देता है, एक `httpx2.AsyncClient` के ऊपर जो MCP की ज़रूरत के हिसाब से configure किया गया है: `follow_redirects=True`, connect/write/pool के लिए 30 सेकंड का timeout, और 300 सेकंड का read timeout, क्योंकि server response stream को खुला रख सकता है।
!!! check
जो `Client` आपने बनाया है वह connected **नहीं** है। बनाने से सिर्फ़ transport चुना जाता है;
उसे खोलता `async with` है। enter करने से पहले connection तक पहुँचने की कोशिश करें तो SDK साफ़ बता देता है:
```text
RuntimeError: Client must be used within an async context manager
```
जब आपने `Client("http://...")` लिखा, तब न कुछ resolve हुआ, न fetch, न spawn। वह line मुफ़्त है।
### अपना `httpx2.AsyncClient` लाएँ {#bring-your-own-httpx2asyncclient}
जैसे ही आपको `Authorization` header, cookie, proxy, mTLS या कोई अलग timeout चाहिए, `httpx2.AsyncClient` खुद बनाएँ और उसे `streamable_http_client` को दें:
```python title="client.py" hl_lines="8-14"
--8<-- "docs_src/client_transports/tutorial003.py"
```
दो बातें ध्यान देने लायक हैं:
* `httpx2.AsyncClient` आपका है, इसलिए उसे enter और exit भी **आप** ही करते हैं। SDK कभी ऐसे client को बंद नहीं करता जो उसने नहीं बनाया।
* `streamable_http_client(url, http_client=...)` एक transport लौटाता है, और `Client(transport)` उसे किसी भी दूसरी चीज़ की तरह स्वीकार करता है।
TLS पर एक बात: `httpx2` certificates को operating system के trust store (
[`truststore`](https://pypi.org/project/truststore/) के ज़रिए) से verify करता है, किसी bundled CA list से नहीं। ऐसे environment में जहाँ
काम का system CA store न हो (कुछ minimal containers), standard `SSL_CERT_FILE`/`SSL_CERT_DIR`
environment variables set करें या अपने `httpx2.AsyncClient` को explicit `verify=ssl_context` पास करें
(पृष्ठभूमि
[`httpx` and `httpx-sse` replaced by `httpx2`](../migration.md#httpx-and-httpx-sse-replaced-by-httpx2) में है)।
!!! warning
`streamable_http_client` पहले `headers=` और `timeout=` सीधे लेता था। अब नहीं लेता:
इसके parameters सिर्फ़ `url`, `http_client` और `terminate_on_close` हैं। आदत से `headers=`
लिख दें तो यह मिलता है:
```text
TypeError: streamable_http_client() got an unexpected keyword argument 'headers'
```
HTTP से जुड़ी हर चीज़ अब उसी एक `httpx2.AsyncClient` पर रहती है जो आप पास करते हैं।
!!! info
`httpx2` जाना-पहचाना `httpx` API ही रखता है, इसलिए अगर आप `httpx` जानते हैं तो यहाँ auth,
proxies, event hooks, retries और connection limits कैसे करने हैं, यह आप पहले से जानते हैं। SDK न ऊपर से कुछ जोड़ता है, न कुछ
हटाता है। OAuth भी यहीं जुड़ता है:
`httpx2.AsyncClient(auth=OAuthClientProvider(...))`। वह पूरा flow **[OAuth clients](oauth-clients.md)** में है।
## stdio {#stdio}
**stdio** server एक subprocess है। client उसे launch करता है, उसके stdin पर JSON-RPC लिखता है और उसके stdout से JSON-RPC पढ़ता है। desktop host आपकी machine पर server इसी तरह चलाता है: host यही code **है**, बस ऊपर एक UI के साथ, और **[असली host से जुड़ें](../get-started/real-host.md)** यही रिश्ता host की तरफ़ से, एक config file के रूप में दिखाता है।
process को `StdioServerParameters` से बताएँ, `stdio_client` से उसे transport में बदलें, और **वही** `Client` को दें:
```python title="client.py" hl_lines="4-8 12"
--8<-- "docs_src/client_transports/tutorial004.py"
```
`Client` अकेले parameters object को स्वीकार नहीं करता। `StdioServerParameters` configuration है; `stdio_client(server)` वह transport है जो उससे process spawn करना जानता है। हमेशा wrap करें।
`async with` block से बाहर निकलने पर subprocess भी बंद हो जाता है: stdin बंद, इंतज़ार, और अटका रहे तो kill। आपको उसे खुद कभी साफ़ नहीं करना पड़ता।
!!! warning
child आपका environment inherit **नहीं** करता। उसे एक minimal allow-list मिलती है (POSIX पर `HOME`, `LOGNAME`,
`PATH`, `SHELL`, `TERM` और `USER`) ताकि ऐसे process में कुछ भी संवेदनशील leak न हो जिसे शायद
आपने लिखा ही न हो।
जिस server को API key चाहिए, उसे वह वहाँ नहीं मिलेगी। उसे `env=` से explicitly पास करें; वे
variables allow-list के ऊपर merge हो जाते हैं। ऊपर `BOOKSHOP_API_KEY` यही कर रहा है।
## SSE {#sse}
`mcp.client.sse` का `sse_client(url)` वह HTTP transport है जिसकी जगह Streamable HTTP ने ली। जो server अब भी इसे बोलता है, उससे बात करने के लिए इसे उसी तरह wrap करें, `Client(sse_client("http://localhost:8000/sse"))`, और इस पर कुछ नया न बनाएँ।
## `Transport` protocol {#the-transport-protocol}
`Client` के लिए ऊपर की सभी चीज़ें एक ही हैं।
**transport** कोई भी async context manager है जो message streams का `(read, write)` जोड़ा yield करता है: औपचारिक रूप से, `mcp.client` का `Transport` protocol। `Client` अपने argument को type से resolve करता है: server object in-process जुड़ता है, `str` `streamable_http_client(url)` बन जाता है, और बाकी सब कुछ सीधे transport के रूप में enter किया जाता है। यही आख़िरी नियम वजह है कि `stdio_client(...)`, `streamable_http_client(...)` और `sse_client(...)` सब उसी एक slot में बैठते हैं, और यही वजह है कि आप अपना खुद का भी लिख सकते हैं।
## सारांश {#recap}
* `Client(mcp)` (server object) memory में जुड़ता है। इसे tests और embedding के लिए इस्तेमाल करें।
* `Client("http://.../mcp")` (URL) Streamable HTTP पर जुड़ता है, जो production transport है।
* Headers, auth, proxies और timeouts उस `httpx2.AsyncClient` पर होने चाहिए जो आप `streamable_http_client(url, http_client=...)` को पास करते हैं। कोई `headers=` keyword नहीं है।
* stdio है `Client(stdio_client(StdioServerParameters(...)))`, अकेला parameters object कभी नहीं।
* subprocess को allow-list वाला environment मिलता है, आपका नहीं; `env=` उसमें जोड़ता है।
* transport वह हर चीज़ है जिस पर आप `async with x as (read, write)` कर सकें। जो कुछ server object या URL नहीं है, `Client` उसे सीधे उसी protocol को सौंप देता है।
* `Client` बनाने से transport चुना जाता है। `async with` उसे खोलता है।
transport खुल जाने के बाद दोनों पक्षों को protocol version पर सहमत होना होता है। आम तौर पर आपको इस बारे में सोचना ही नहीं पड़ता; जब पड़े, तो **[Protocol versions](../protocol-versions.md)** वह page है।

View file

@ -0,0 +1,96 @@
---
translation:
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
tool: 1
---
# Deprecated features {#deprecated-features}
2026-07-28 spec पाँच चीज़ों को retire करता है। SDK अब भी इनमें से हर एक को implement करता है, और अब हर एक पर **deprecation warning** लगी है।
नीचे दी गई table हर deprecated feature का नाम, उसके हटने की वजह, और उसकी जगह किस पर build करना है, यह बताती है।
## क्या deprecated है {#what-is-deprecated}
| Deprecated | क्यों | इसके बजाय क्या करें |
|---|---|---|
| **Roots**: `ctx.session.list_roots()`, `client.send_roots_list_changed()`, `Client(...)` को दिया जाने वाला `list_roots_callback=` | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) इस capability को retire करता है। | paths को साधारण tool arguments या resource URIs के रूप में लें, या `InputRequiredResult` में `ListRootsRequest` embed करें (**[Multi-round-trip requests](handlers/multi-round-trip.md)** देखें)। |
| **Server-initiated sampling**: `ctx.session.create_message()`, `Client(...)` को दिया जाने वाला `sampling_callback=` | SEP-2577 इस capability को retire करता है। | `InputRequiredResult` लौटाएँ और client को call retry करने दें (**[Multi-round-trip requests](handlers/multi-round-trip.md)** देखें)। |
| **Protocol logging**: `ctx.log()`, `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`, `ctx.session.send_log_message()`, `client.set_logging_level()` | SEP-2577 इस capability को retire करता है। protocol के अंदर इसकी जगह कुछ नहीं लेता। | stderr पर साधारण `import logging` (**[Logging](handlers/logging.md)** देखें)। |
| **`ping`**: `client.send_ping()` | protocol से **हटा दिया गया**, सिर्फ़ deprecated नहीं। 2026-07-28 में कोई `ping` method नहीं है। | कुछ नहीं। यह सिर्फ़ `mode="legacy"` connection पर काम करता है। |
| **Client->server progress**: `client.send_progress_notification()` | 2026-07-28 progress को सिर्फ़ server->client बनाता है। | भेजने को कुछ नहीं। आपका *server* `ctx.report_progress()` से progress report करता है (**[Progress](handlers/progress.md)** देखें)। |
इस table से तीन बातें निकलती हैं:
* roots, sampling और logging साथ-साथ जाते हैं। एक ही proposal, **SEP-2577**, तीनों capabilities को एक साथ deprecate करता है।
* sampling और roots की एक गहरी साझा समस्या है: ये वे जगहें हैं जहाँ **server** **client** को **request** भेजता है। यही वह पूरी दिशा है जिसे 2026-07-28 **[Multi-round-trip requests](handlers/multi-round-trip.md)** से बदलता है। जो गए हैं वे standalone RPC methods हैं (`sampling/createMessage`, `roots/list`, और push-style `elicitation/create`); `CreateMessageRequest` / `ListRootsRequest` / `ElicitRequest` payload types बचे रहते हैं, `InputRequiredResult.input_requests` में embed होकर, और client पर वे उन्हीं callbacks तक पहुँचते हैं।
* `ping` बाकियों से अलग है। protocol इसे deprecate नहीं करता, हटा देता है। SDK method अब भी warn करता है (उसका message *removed* कहता है, *deprecated* नहीं) और modern connection पर इसे call करने पर जवाब *"Method not found"* आता है।
## Deprecated होना बस सलाह भर है {#deprecated-is-advisory}
आज कुछ नहीं टूटता।
ऊपर का हर method ऐसे किसी भी session पर काम करता रहता है जिसने **2025-11-25 या उससे पहले** का version negotiate किया हो। client पर `mode="legacy"` pin करें और आपको ठीक 2026 से पहले वाला व्यवहार मिलता है। wire में कोई बदलाव नहीं है और capability negotiation जस का तस है।
बदलता यह है कि हर एक के पहली बार चलने पर आपको साफ़ दिखने वाली warning मिलती है:
```text
MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
```
`MCPDeprecationWarning` `UserWarning` का subclass है, `DeprecationWarning` का **नहीं**। यह जानबूझकर है: Python का default filter `DeprecationWarning` को सिर्फ़ उसी code में दिखाता है जो सीधे `__main__` के रूप में चलता है, और इसी तरह libraries चीज़ें deprecate करती हैं और दो साल तक किसी को पता नहीं चलता। यह वाली हर जगह दिखती है, बिना किसी `-W` flag के।
!!! warning
"बस सलाह" वाली बात wire पर आकर खत्म हो जाती है। sampling और roots server-से-client
*requests* हैं, और 2026-07-28 session के पास इन्हें ले जाने का कोई channel नहीं है। modern
connection पर tool के अंदर `ctx.session.create_message()` call करें तो warning फिर भी
fire होती है, और फिर send एक error के साथ fail हो जाता है:
```text
Cannot send 'sampling/createMessage': this transport context has no back-channel
for server-initiated requests.
```
दो संकेत, इसी क्रम में। `MCPDeprecationWarning` उसी पल fire होती है जब आप method call
करते हैं, किसी भी connection पर। error वह है जो तब वापस आता है जब SDK उसे भेजने की
कोशिश करता है। ये दोनों end-to-end सिर्फ़ ऐसे `mode="legacy"` connection पर काम करते हैं
जिसके client ने matching callback register किया हो।
## warning को चुप कराना {#silencing-the-warning}
नए code में ऐसा न करें।
लेकिन जिस server की आप देखरेख करते हैं और जो सच में 2026 से पहले के clients को serve करता है, उसे शांत log का पूरा हक है। पहला deprecated call चलने से पहले इस category को filter करें:
```python
import warnings
from mcp import MCPDeprecationWarning
warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
```
पूरा API बस इतना ही है। हर method के लिए अलग switch नहीं है, और आपको चाहिए भी नहीं: एक category का मतलब ही यह है कि एक line उसे चुप कराती है और एक line उसे वापस ले आती है।
!!! check
filter को उल्टा चलाएँ और आपको मुफ़्त में regression test मिलता है। अपनी pytest
configuration की `filterwarnings` setting में `"error::mcp.MCPDeprecationWarning"`
जोड़ें और deprecated call warn करने के बजाय **raise** करता है। `old_log` नाम का tool
जो अब भी `ctx.info()` call करता है, pass होना बंद कर देता है और यह report करने लगता है:
```text
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
```
pytest configuration की एक line, और कोई deprecated call बिना test fail किए आपके
codebase में चुपके से वापस नहीं आ सकता।
## सारांश {#recap}
* 2026-07-28 spec **roots**, server-initiated **sampling**, और protocol **logging** को deprecate करता है (तीनों [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), **progress** को server-से-client तक सीमित करता है, और **`ping`** को हटा देता है।
* replacement वाला column आपको आगे का रास्ता दिखाता है: sampling और roots के लिए **[Multi-round-trip requests](handlers/multi-round-trip.md)**, logging के लिए **[Logging](handlers/logging.md)**, progress के लिए **[Progress](handlers/progress.md)**। `ping` को कुछ भी नहीं चाहिए।
* Deprecated होना बस सलाह भर है: wire में कोई बदलाव नहीं, 2026 से पहले के sessions पर सब कुछ काम करता रहता है, और आपको साफ़ दिखने वाली `MCPDeprecationWarning` मिलती है (यह `UserWarning` है, इसलिए default रूप से चालू है)।
* sampling और roots को इसके अलावा back-channel चाहिए जो 2026-07-28 session के पास नहीं है। modern connection पर ये warn करते हैं और फिर raise करते हैं।
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` पूरी category को चुप कराता है; pytest में `"error::mcp.MCPDeprecationWarning"` इसे test failure में बदल देता है।
* नया code इनमें से किसी पर भी नहीं बनना चाहिए।
इन docs का बाकी हर page मौजूदा API सिखाता है।

View file

@ -0,0 +1,144 @@
---
translation:
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
tool: 1
---
# पहले कदम {#first-steps}
**[landing page](../index.md)** तेज़ी से चलता है: server लिखें, उसे चलाएँ, tool call करें।
यह page आराम से चलता है: वे तीनों चीज़ें जो server expose कर सकता है, और रास्ते में हर चीज़ का नाम भी।
## host, client और server {#host-client-and-server}
तीन शब्द जो यहाँ से आगे हर page पर दिखेंगे:
* **host** LLM application है: Claude, कोई IDE, कोई agent runtime। यह वह चीज़ है जिससे user बात करता है।
* **client** host के अंदर रहता है और MCP बोलता है। host जितने servers से जुड़ा है, हर एक के लिए एक client चलाता है।
* **server** वह है जो आप इस SDK से बनाते हैं। यह clients को चीज़ें expose करता है। यह model से सीधे कभी बात नहीं करता।
server आप लिखते हैं। hosts किसी और का product हैं। SDK आपको एक `Client` भी देता है। इससे आप अपने servers test करेंगे, और यह इसी page पर आगे दिखता है।
## तीन primitives {#the-three-primitives}
server ठीक तीन तरह की चीज़ें expose करता है। इन्हें अलग करने वाली बात यह है कि **इन्हें इस्तेमाल करने का फ़ैसला कौन करता है**:
| Primitive | नियंत्रण किसका | यह क्या है | उदाहरण |
|---------------|-----------------|-----------------------------------------------------|------------------------------------|
| **Tools** | model | ऐसा function जिसे model कोई काम करने के लिए call करता है | API call, database write |
| **Resources** | application | data जिसे host model के context में load करता है | किसी file की सामग्री, API response |
| **Prompts** | user | दोबारा इस्तेमाल होने वाला message template जिसे user नाम से चलाता है | slash command, menu entry |
"नियंत्रण किसका" ही इस बँटवारे का पूरा मतलब है। tool इसलिए चलता है क्योंकि **model** ने उसे call करने का फ़ैसला किया। resource इसलिए जुड़ता है क्योंकि **application** ने तय किया कि model को उसकी ज़रूरत है। prompt इसलिए चलता है क्योंकि **user** ने उसे चुना।
!!! info
अगर आपने web API बनाया है तो ज़्यादातर समझ आपके पास पहले से है: **resource** एक `GET` है
(data load करता है और कुछ बदलता नहीं) और **tool** एक `POST` है (काम करता है और उसके
side effects हो सकते हैं)। **prompt** का HTTP में कोई जोड़ीदार नहीं; यह उस saved query के
ज़्यादा करीब है जिसे user नाम से चलाता है।
## एक server, तीनों चीज़ें {#one-server-all-three}
```python title="server.py" hl_lines="6 12 18"
--8<-- "docs_src/first_steps/tutorial001.py"
```
तीन सादे functions, तीन decorators। हर decorator ही पूरा registration है:
* `@mcp.tool()` `add` को **tool** बनाता है।
* `@mcp.resource("greeting://{name}")` `greeting` को **resource template** बनाता है: URI में `{name}` function का parameter है।
* `@mcp.prompt()` `summarize` को **prompt** बनाता है। यह जो string लौटाता है, वही user message बन जाती है।
बाकी सब कुछ (नाम, description, argument schema) SDK function से ही पढ़ लेता है: उसका नाम, उसका docstring, उसके type hints। आपने इनमें से कुछ भी अलग से declare नहीं किया।
!!! tip
SDK के दो हिस्सों के दो import paths हैं: `from mcp import Client` और
`from mcp.server import MCPServer`। `from mcp import MCPServer` जैसा कुछ नहीं है।
### इसे आज़माएँ {#try-it}
इसे MCP Inspector से चलाएँ:
```console
uv run mcp dev server.py
```
जो URL यह print करता है उसे खोलें। Inspector में हर primitive के लिए एक tab है; उन्हें क्रम से देखें।
**Tools.** एक entry: `add`, जिसका description है *Add two numbers.* form में `a` के लिए एक ज़रूरी integer field है और `b` के लिए एक और। उन्हें भरें, call करें, और result `3` है। Inspector ने वह form `a: int, b: int` से बनाया। बाकी हर client भी यही करता है।
**Resources.** *Resources* सूची खाली है। `greeting` **Resource Templates** के नीचे है, क्योंकि `greeting://{name}` में parameter है: जब तक कोई `name` न दे, सूची में दिखाने के लिए कोई एक resource है ही नहीं। इसे `World` दें और पढ़ें:
```text
Hello, World!
```
**Prompts.** एक entry: `summarize`, एक ही ज़रूरी `text` argument के साथ। कुछ text देकर इसे get करें और आपको एक message मिलता है जिसमें `role: user` है और content के रूप में आपकी render की हुई string। prompt बस इतना ही है: messages बनाने वाला function।
Inspector ने आपका server **stdio** पर चलाया, जो उन transports में से एक है जो MCP server बोल सकता है। अभी आपको कोई चुनना नहीं है; उसके लिए **[अपना server चलाना](../run/index.md)** page है।
## Capabilities {#capabilities}
Inspector में आपने तीन tabs देखे। उसे कैसे पता चला कि तीन हैं?
जब client जुड़ता है, server अपनी **capabilities** declare करता है: requests के कौन-से परिवारों का वह जवाब देगा। client उसी declaration से तय करता है कि माँगे भी तो क्या। आपने यह कभी नहीं लिखा; `MCPServer` आपके लिए इसे declare करता है।
खुद देखें। SDK का `Client` सीधे server object लेता है और उससे **in memory** जुड़ता है (न subprocess, न port):
```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}}
```
वह dictionary आपके server की declared **capabilities** है। हर जुड़ने वाला client सबसे पहले यही जानता है:
| Capability | client अब ये call कर सकता है |
|-------------|------------------------------------------------------------|
| `tools` | `tools/list`, `tools/call` |
| `resources` | `resources/list`, `resources/templates/list`, `resources/read` |
| `prompts` | `prompts/list`, `prompts/get` |
`MCPServer` तीनों primitives serve करता है, इसलिए तीनों हमेशा declare होती हैं।
ध्यान दें कि क्या नहीं है। `completions` (resource templates और prompts के लिए argument autocomplete) को आपका लिखा handler चाहिए, इस server में वह नहीं है, इसलिए capability मौजूद नहीं है और सलीके वाला client पूछेगा ही नहीं। हर optional चीज़ का यही नियम है: चीज़ register करें और capability आ जाती है; **[Completions](../servers/completions.md)** इसे साबित करता है।
!!! info
`Client(mcp)` वही in-memory client है जिससे इन docs का हर उदाहरण test होता है, और
इसी से आप अपने servers test करेंगे। इसे पूरा एक page मिलता है: **[Testing](testing.md)**।
## जो आपने नहीं लिखा {#what-you-did-not-write}
इस page पर पीछे मुड़कर देखें। आपने तीन छोटे Python functions लिखे। आपने ये **नहीं** लिखे:
* JSON Schema। `a: int, b: int` **ही** `add` का schema है।
* request handler। `tools/list`, `resources/read`, `prompts/get`: सब आपके लिए serve होते हैं।
* capability declaration। `MCPServer` ने आपके लिए बना दिया।
* protocol की एक भी line। version negotiation, JSON-RPC framing, capability exchange: यह सब `mcp dev` और `Client(mcp)` के अंदर हुआ, और आपने कभी देखा ही नहीं।
यही अनुपात SDK का पूरा मतलब है।
## सारांश {#recap}
* **host** LLM app है, **client** उसका MCP बोलने वाला हिस्सा है, **server** वह है जो आप बनाते हैं।
* tools पर **model** का नियंत्रण है, resources पर **application** का, prompts पर **user** का।
* हर primitive के लिए एक decorator: `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`। नाम, description और schema function से आते हैं।
* `{param}` वाला URI resource **template** बनाता है, जो concrete resources से अलग सूची में दिखता है।
* server की **capabilities** आपके लिए declare हो जाती हैं, और client वही माँगता है जो server declare करता है।
* `Client(mcp)` server object से in memory जुड़ता है: पहले दिन से आपका test harness।
आगे है **[असली host से जुड़ें](real-host.md)**: यही server Claude Desktop या किसी IDE के अंदर, सच में। फिर **[Testing](testing.md)**: एक page, एक in-memory client, और आपको कभी अंदाज़ा नहीं लगाना पड़ेगा कि यह काम करता है या नहीं। उसके बाद हर primitive को अपना page मिलता है, शुरुआत उससे जिसे model चलाता है: **[Tools](../servers/tools.md)**।

View file

@ -0,0 +1,57 @@
---
translation:
sections: [ed4a756b4c53c585, 97e2fb315b7fe398, 4d04f1c6f4bf6c1d, 577d73078fc62baf]
tool: 1
---
# शुरू करें {#get-started}
MCP में नए हैं, या इस SDK में? यहीं से शुरू करें। ये pages आपको शून्य से एक चालू,
test किए हुए server तक ले जाते हैं: [SDK install करें](installation.md), अपना
[पहला server](first-steps.md) बनाएँ, [उसे असली host से जोड़ें](real-host.md), और
in-memory client से [उसे test करें](testing.md)।
## code चलाएँ {#run-the-code}
सभी code blocks सीधे copy करके इस्तेमाल किए जा सकते हैं: ये पूरी, काम करने वाली files हैं।
साथ-साथ चलने के लिए, किसी block को `server.py` में paste करें और उसे MCP Inspector में खोलें:
```console
uv run mcp dev server.py
```
**पुरज़ोर सलाह** है कि code खुद लिखें (या copy करें), उसमें बदलाव करें और उसे locally चलाएँ। अपने editor में इस्तेमाल करने पर ही असली बात समझ आती है: कितना कम लिखना पड़ता है, autocompletion, और कुछ भी चलाने से पहले गलतियाँ पकड़ लेने वाले type checks।
## आपको अंदाज़ा नहीं लगाना पड़ेगा {#you-will-not-be-guessing}
इन docs का हर उदाहरण SDK की अपनी repository में [`docs_src/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/docs_src) के नीचे एक पूरी file है, और SDK का test suite हर एक को **in-memory client** के ज़रिए चलाकर परखता है:
```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}
```
न subprocess, न port, न transport। `Client(mcp)` सीधे server object से जुड़ता है।
अगर SDK में कोई बदलाव इन pages के किसी उदाहरण को तोड़ता है, तो page से पहले CI लाल हो जाता है। जो code आप यहाँ पढ़ते हैं, वही code चलता है।
[Testing](testing.md) में आप इसे खुद इस्तेमाल करेंगे; अपने servers भी इसी तरह test किए जाते हैं।
## आगे कहाँ जाएँ {#where-to-go-next}
एक बार server चल जाए, तो बाकी docs course नहीं, reference हैं।
हर page अपने आप में पूरा है, इसलिए सीधे वहीं जाएँ जिसकी ज़रूरत है:
* server क्या expose करता है (tools, resources, prompts), यह **[Servers](../servers/index.md)** में है।
* आपके register किए functions के अंदर क्या-क्या उपलब्ध है, यह **[आपके handler के अंदर](../handlers/index.md)** में है।
* इसे clients के सामने लाना (stdio, HTTP, आपका मौजूदा FastAPI app) **[अपना server चलाना](../run/index.md)** में है।
* दूसरा पक्ष बनाना, यानी ऐसा application जो MCP servers **इस्तेमाल करता है**, **[Clients](../client/index.md)** में है।

View file

@ -0,0 +1,47 @@
---
translation:
sections: [6e2f9bab94d5ed36, 8cf653388f69e28b, 6fd9ea2f65de0df6]
tool: 1
---
# Installation {#installation}
Python SDK PyPI पर [`mcp`](https://pypi.org/project/mcp/) नाम से उपलब्ध है। इसके लिए **Python 3.10+** ज़रूरी है।
ये docs **v2** का वर्णन करते हैं, जो मौजूदा stable release line है:
=== "uv"
```bash
uv add "mcp[cli]"
```
=== "pip"
```bash
pip install "mcp[cli]"
```
!!! note "v1 से आ रहे हैं?"
v2 एक major version है जिसमें breaking changes हैं; **[Migration Guide](../migration.md)**
में हर एक बदलाव की जानकारी है। अगर आपका **package** `mcp` पर निर्भर है और अभी migrate करने के लिए
तैयार नहीं है, तो `<2` की upper bound रखें (उदाहरण के लिए `mcp>=1.28,<2`), ताकि बिना pin किया गया resolve 1.x line पर ही रहे।
## क्या-क्या install होता है {#what-gets-installed}
SDK इस्तेमाल करने के लिए यह सब जानना ज़रूरी नहीं है, लेकिन अगर आप सोच रहे हैं कि हर dependency किस काम की है:
* `mcp-types`: हर protocol type (requests, results, content blocks) अपने अलग package के रूप में, जिसका version SDK के साथ कदम मिलाकर चलता है। जो code `mcp` पर निर्भर है, वह इसे `mcp.types` alias के ज़रिए import करता है (इन docs में हर `from mcp.types import ...`); `mcp_types` को सीधे सिर्फ़ उसी project में import करें जो SDK के बिना `mcp-types` install करता है।
* [`anyio`](https://anyio.readthedocs.io/): async runtime। पूरा SDK anyio के आधार पर लिखा गया है, इसलिए यह `asyncio` या `trio` दोनों में से किसी पर भी चलता है।
* [`pydantic`](https://docs.pydantic.dev/): हर `mcp.types` model इसी पर बना है, साथ ही पूरा schema generation और validation भी।
* [`httpx2`](https://pypi.org/project/httpx2/): Streamable HTTP और SSE **client** transports के पीछे का HTTP client, जिसमें server-sent events का support पहले से मौजूद है।
* [`starlette`](https://www.starlette.io/), [`uvicorn`](https://www.uvicorn.org/), [`sse-starlette`](https://pypi.org/project/sse-starlette/), और [`python-multipart`](https://pypi.org/project/python-multipart/): HTTP **server** transports।
* [`jsonschema`](https://pypi.org/project/jsonschema/): tool के structured output को उसके घोषित output schema के अनुसार validate करता है।
* [`pyjwt[crypto]`](https://pyjwt.readthedocs.io/): authorization के लिए OAuth token संभालना।
* [`opentelemetry-api`](https://opentelemetry-python.readthedocs.io/): सिर्फ़ हल्का-सा API, ताकि SDK के tracing middleware की कोई लागत न हो, जब तक आप खुद OpenTelemetry SDK और exporter install न करें।
* [`typing-extensions`](https://typing-extensions.readthedocs.io/) और [`typing-inspection`](https://pypi.org/project/typing-inspection/): Python 3.10 पर आधुनिक typing features।
* [`pywin32`](https://pypi.org/project/pywin32/): सिर्फ़ Windows पर, `stdio` subprocess management के लिए इस्तेमाल होता है।
## Optional extras {#optional-extras}
* `mcp[cli]`, `mcp` command-line tool (`mcp dev`, `mcp run`, `mcp install`) के लिए [`typer`](https://typer.tiangolo.com/) और [`python-dotenv`](https://pypi.org/project/python-dotenv/) जोड़ता है। development के दौरान आपको यह चाहिए होगा; deploy किए गए server में शायद इसकी ज़रूरत न पड़े।
* `mcp[rich]` बेहतर server logs के लिए [`rich`](https://rich.readthedocs.io/) जोड़ता है।

View file

@ -0,0 +1,182 @@
---
translation:
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
tool: 1
---
# असली host से connect करना {#connect-to-a-real-host}
**host** वह application है जिसके अंदर आपका server आखिरकार चलता है: Claude Desktop, Claude Code, कोई IDE। user इसी host से बात करता है। इसके अंदर एक MCP **client** आपके server को child process के रूप में launch करता है और उसी process के stdin और stdout पर उससे बात करता है।
यानी host से connect करना बस एक काम है: आप उसे **वह command बताते हैं जो आपका server शुरू करता है**। इस page पर जो कुछ है (दो CLI commands, तीन JSON files), वह उसी एक command को रखने की अलग-अलग जगहें हैं।
## एक server, हर host {#one-server-every-host}
```python title="server.py" hl_lines="3 33-34"
--8<-- "docs_src/real_host/tutorial001.py"
```
दो tools और एक resource, एक ही file में। इस file की तीन बातें नीचे के हर host के लिए मायने रखती हैं:
* बिना arguments के `mcp.run()` एक **stdio** server शुरू करता है: यह block होता है, stdin पर protocol messages पढ़ता है और stdout पर लिखता है। इस page का हर host यही transport बोलता है। host आपकी file को child process के रूप में शुरू करता है और उन दोनों pipes का मालिक होता है, इसीलिए connect करना हमेशा बस "यह रहा command" ही होता है। आप कभी port नहीं चुनते, और किसी port पर कुछ listen नहीं करता।
* `run()` `if __name__ == "__main__":` के नीचे है। नीचे की हर चीज़ इस file को execute करने के बजाय **import** करती है, इसलिए बिना guard वाला `run()` module के load होते ही server शुरू कर देता।
* server object module-level global है जिसका नाम `mcp` है। `mcp run` इसी नाम को ढूँढता है (`server` और `app` भी चलते हैं)। कोई और नाम रखें तो उसे साफ़-साफ़ बताना होगा: `mcp run server.py:bookshop`।
इस page पर Python की यह आख़िरी line है। यहाँ से नीचे सब host configuration है।
## Launch command {#the-launch-command}
नीचे के हर host को यही एक command मिलता है:
```bash
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
सबके लिए एक ही command, क्योंकि `uv run --with` उसी वक़्त SDK को एक नए environment में resolve कर देता है: यह किसी भी directory से चलता है और इसे न कोई project चाहिए, न activate करने के लिए कोई virtual environment। यहाँ यह बात कहीं और से ज़्यादा मायने रखती है, क्योंकि host आपके server को आपके shell से नहीं, बल्कि **अपनी** working directory से, लगभग खाली environment के साथ launch करता है।
यही वह command है जो `mcp install` आपके लिए Claude Desktop के config में लिखता है (नीचे देखें), इसलिए जो आप हाथ से लिखते हैं और जो tool बनाता है, दोनों मेल खाते हैं, सिवाय उस exact version pin के जो tool जोड़ता है।
!!! tip "अगर host को `uv` न मिले"
host आपके server को बहुत छोटे `PATH` के साथ spawn करता है, और हो सकता है `uv` उसमें न हो। सिर्फ़
`uv` की जगह `which uv` (macOS/Linux) या `where uv` (Windows) से मिला absolute path लिखें। `mcp install`
ठीक यही लिखता है।
!!! note "यह page local setup की बात है"
यहाँ की हर चीज़ आपके server को उसी machine पर चलाती है जिस पर host है: host आपकी
file को stdio पर launch करता है। निजी या एक ही machine वाले tool के लिए यह बिल्कुल सही है। जिन
लोगों के पास आपकी file **नहीं** है, उन्हें server देने के लिए आप command नहीं, **URL** देते हैं: वही
`mcp` object, Streamable HTTP पर serve किया हुआ। **[अपना server चलाना](../run/index.md)**
वह फ़ैसला एक table में रखता है, और **[Deploy और scale](../run/deploy.md)** वहाँ से
असली hostname तक का रास्ता है।
और host किसी application से ज़्यादा कुछ नहीं जिसके अंदर MCP client हो, इसलिए आपका अपना
Python भी host की भूमिका निभा सकता है: **[Client transports](../client/transports.md)** इसी
file को `stdio_client(...)` से subprocess के रूप में launch करता है, और **[Testing](testing.md)**
बिना किसी process के, memory में ही उससे connect करता है।
## Claude Desktop {#claude-desktop}
वह इकलौता host जिसे SDK आपके लिए configure कर सकता है:
```bash
uv run mcp install server.py
```
बस इतना ही। `mcp install` server का नाम पढ़ने के लिए file को import करता है, Claude Desktop की config file ढूँढता है और उसमें launch command लिख देता है। साथ ही यह आपके path को absolute बना देता है, ताकि आपको न करना पड़े।
इसमें कोई रहस्य नहीं है। यह रही वह entry जो यह लिखता है:
```json
{
"mcpServers": {
"Bookshop": {
"command": "/absolute/path/to/uv",
"args": [
"run",
"--frozen",
"--with",
"mcp[cli]==2.0.0",
"mcp",
"run",
"/absolute/path/to/server.py"
]
}
}
}
```
यह ऊपर वाले section का launch command ही है, तीन चीज़ें जोड़कर: `uv` का absolute path, `--frozen` ताकि `uv` आस-पास पड़ी किसी lockfile को कभी दोबारा न लिखे, और आपके install किए हुए `mcp` version का exact pin। यह `claude_desktop_config.json` में जाता है, जो यहाँ रहती है:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
यह file आप हाथ से भी लिख सकते हैं। `mcp install` इसलिए है ताकि ऐसा करते समय आप वह जानी-पहचानी गलती (relative path) न करें।
Claude Desktop को पूरी तरह quit करें (सिर्फ़ उसकी window नहीं) और दोबारा खोलें।
!!! warning
अगर Claude Desktop की config **directory** अभी मौजूद नहीं है तो `mcp install` `Claude app not found` के साथ
fail होता है। Claude Desktop install करें और एक बार चलाएँ: directory उसी से बनती है।
!!! tip
Claude Desktop आपके server को अपने process में शुरू करता है, इसलिए आपके shell के environment variables
वहाँ नहीं होते। `uv run mcp install server.py -v API_KEY=abc123` (या `-f .env`) उन्हें entry के
`env` field में दर्ज कर देता है। `--name` entry का नाम override करता है; default server का `name` है।
## Claude Code {#claude-code}
edit करने के लिए कोई file नहीं है। server को `claude` CLI से register करें; `--` के बाद जो कुछ है वही launch command है।
```bash
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
यह पक्का करने के लिए कि `bookshop` connected है और उसके tools सूची में दिख रहे हैं, Claude Code session के अंदर `/mcp` चलाएँ।
## Cursor {#cursor}
अपने project root में `.cursor/mcp.json` बनाएँ।
```json
{
"mcpServers": {
"bookshop": {
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
```
वही `command` और `args`, उसी `mcpServers` key के नीचे जो Claude Desktop इस्तेमाल करता है। server दोनों tools के साथ Cursor की MCP settings में दिखता है।
## VS Code {#vs-code}
अपने project root में `.vscode/mcp.json` बनाएँ।
```json
{
"servers": {
"bookshop": {
"type": "stdio",
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
```
Cursor की file से दो फ़र्क़ हैं, और बस यही दो: wrapper key `mcpServers` नहीं, `servers` है, और हर entry अपना `type` बताती है। trust prompt को confirm करें, फिर Command Palette में **MCP: List Servers** `bookshop` को चलता हुआ दिखाता है।
!!! note
आपको VS Code 1.99 या उससे नया चाहिए, जिसमें **GitHub Copilot** extension signed in हो (Copilot Free
काफ़ी है), और Copilot Chat **Agent** mode में होना चाहिए, क्योंकि कोई और mode tools को call नहीं करता।
## यह दिख नहीं रहा {#it-doesnt-show-up}
किसी भी host config को छूने से पहले, launch command खुद चलाएँ:
```bash
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
कुछ print नहीं होता, और यह लौटता नहीं। यह चुप्पी सही है: stdio server इंतज़ार कर रहा है कि host पहले stdin पर बोले (रोकने के लिए `Ctrl-C`)। traceback या तुरंत exit ही असली bug है, और अब आप उसे host के ज़रिए अंदाज़ा लगाने के बजाय सीधे पढ़ सकते हैं।
जब वह command बैठकर इंतज़ार करने लगे, तो बाकी समस्या लगभग हमेशा इन तीन में से एक होती है:
* **Relative path।** host आपके server को **अपनी** working directory से launch करता है, उस directory से नहीं जहाँ से आपने register किया था। जहाँ `/absolute/path/to/server.py` चाहिए वहाँ `server.py` लिखना सबसे आम गड़बड़ी है। अगर host को `uv` भी न मिले, तो वह path भी absolute होना चाहिए।
* **host अब भी पुराने config पर चल रहा है।** hosts अपना config launch के समय पढ़ते हैं। ख़ासकर Claude Desktop को **पूरी तरह quit** करना (सिर्फ़ window बंद करना नहीं) और दोबारा खोलना पड़ता है, तभी `claude_desktop_config.json` में किया गया बदलाव लागू होता है।
* **divert की गई window के बाहर कुछ stdout तक पहुँच गया।** stdio पर stdout **ही** protocol है। serve करते समय SDK flush हुए भटके हुए output को stderr की ओर मोड़ देता है, लेकिन उससे पहले stdout पर flush हुआ output (कोई wrapper script जो echo करती है, unbuffered process में import के समय चला `print()`), या interpreter exit पर खाली होने वाला buffered `print()`, host को corrupt message थमा देता है और host connection छोड़ देता है। default `logging` configuration से log करें, जिसका stderr handler हर record को flush करता है; custom handlers को भी stdout से बचना ही है। पूरी जानकारी **[Logging](../handlers/logging.md)** में है।
Claude Desktop हर server का अलग log रखता है: `mcp-server-<NAME>.log` आपके server का stderr है, connections के लिए `mcp.log` के बगल में, macOS पर `~/Library/Logs/Claude` में और Windows पर `%APPDATA%\Claude\logs` में।
इन तीनों के आगे कुछ भी हो, तो **[Troubleshooting](../troubleshooting.md)** वाला page देखें।
## सारांश {#recap}
* **host** (Claude Desktop, कोई IDE) एक MCP client चलाता है जो आपके server को stdio पर child process के रूप में launch करता है। connect करने का मतलब है उसे एक launch command देना।
* वह command है `uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py`: कोई venv activate नहीं करना, किसी भी directory से चलता है।
* **Claude Desktop** वह इकलौता host है जिसे `mcp install` आपके लिए configure करता है। यह वही command (साथ में `uv` का absolute path, `--frozen`, और आपके install किए हुए version का exact pin) `claude_desktop_config.json` में लिख देता है, ताकि आपको कभी न लिखना पड़े।
* **Claude Code** के लिए `claude mcp add bookshop -- <launch command>`। **Cursor** के लिए `mcpServers` के नीचे `.cursor/mcp.json`। **VS Code** के लिए `servers` के नीचे `.vscode/mcp.json`, हर entry में एक `type`।
* हर जगह absolute paths, config edit करने के बाद host को restart करें, और SDK के अलावा किसी को stdout पर न लिखने दें।
इस page का हर host उसी एक file से, उसी एक command से connect हुआ। वह file क्या **expose** कर सकती है, यही बाकी docs हैं: **[Tools](../servers/tools.md)**, **[Resources](../servers/resources.md)**, और stdio के अलावा हर transport **[अपना server चलाना](../run/index.md)** में।

View file

@ -0,0 +1,105 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
tool: 1
---
# Testing {#testing}
Python SDK में `Client` class आती है जिसके साथ **in-memory transport** मिलता है: इसे अपना server object दें और यह उससे सीधे जुड़ जाता है।
कोई subprocess नहीं। कोई port नहीं। कोई transport ही नहीं। यह वही विचार है जो FastAPI के `TestClient` का है।
## Basic usage {#basic-usage}
मान लेते हैं कि आपके पास एक ही tool वाला सीधा-सादा server है:
```python title="server.py"
--8<-- "docs_src/testing/tutorial001.py"
```
नीचे दिया गया test चलाने के लिए दो अतिरिक्त (development) dependencies चाहिए:
=== "uv"
```bash
uv add --dev pytest inline-snapshot
```
=== "pip"
```bash
pip install pytest inline-snapshot
```
!!! info
ये docs मानकर चलते हैं कि आप [`pytest`](https://docs.pytest.org/en/stable/) पहले से जानते हैं।
नीचे दिया गया test पूरे result object पर एक ही line में assert करने के लिए
[`inline-snapshot`](https://15r10nk.github.io/inline-snapshot/latest/) का इस्तेमाल करता है। यह test के
output को उसी `snapshot(...)` literal के रूप में record करता है जो आपको दिख रहा है। अगर आप इसे इस्तेमाल
नहीं करना चाहते, तो import हटा दें और किसी भी दूसरे test की तरह उन्हीं fields पर assert करें जो आपके
लिए मायने रखती हैं (`result.content[0].text == "3"`)।
अब test:
```python title="test_server.py"
import pytest
from inline_snapshot import snapshot
from mcp import Client
from mcp.types import CallToolResult, TextContent
from server import mcp
@pytest.fixture
def anyio_backend(): # (1)!
return "asyncio"
@pytest.fixture
async def client(): # (2)!
async with Client(mcp, raise_exceptions=True) as c:
yield c
@pytest.mark.anyio
async def test_call_add_tool(client: Client):
result = await client.call_tool("add", {"a": 1, "b": 2})
# Drop the server identity stamp in `_meta`; it is not what this test is about.
result.meta = None
assert result == snapshot(
CallToolResult(
content=[TextContent(type="text", text="3")],
structured_content={"result": 3},
)
)
```
1. अगर आप `trio` इस्तेमाल कर रहे हैं, तो इसकी जगह `"trio"` लौटाएँ। विस्तार से जानने के लिए [anyio documentation](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on) देखें।
2. यह fixture एक जुड़ा हुआ client yield करता है। हर test जो `client` लेता है, उसे उसी server से नया in-memory connection मिलता है।
हो गया! अब आप और scenarios को cover करने के लिए अपने tests बढ़ा सकते हैं।
## `raise_exceptions=True` क्यों? {#why-raise_exceptionstrue}
दो अलग-अलग चीज़ें गड़बड़ हो सकती हैं, और यह flag उनमें से सिर्फ़ एक को छूता है।
**आपके tools** में से किसी के अंदर हुआ exception protocol failure नहीं है। वह `is_error=True` वाला सामान्य result बन जाता है, और model उसका message पढ़ता है। `raise_exceptions` इसमें कुछ नहीं बदलता: इसके साथ या इसके बिना, `call_tool` वही `is_error=True` वाला result लौटाता है। इस पर एक पूरा page है:
**[Errors संभालना](../servers/handling-errors.md)**।
Tool body के **बाहर** की failure अलग है। `Client(mcp)` जो connection देता है, उस पर server इसे client तक पहुँचने से पहले एक सामान्य `"Internal server error"` में sanitise कर देता है। किसी अनपेक्षित crash की बारीकियाँ remote caller तक कभी leak नहीं होनी चाहिए। Test में आप ठीक यही **नहीं** चाहते, और `raise_exceptions=True` यही बदलता है: आपके test को sanitise किया हुआ message नहीं, बल्कि असली message दिखता है।
Tests में इसे चालू रहने दें। Production code में इसका कोई मतलब नहीं है।
## Default रूप से in-process {#in-process-by-default}
!!! note
`Client(mcp)` in-process जुड़ता है और default रूप से **पीढ़ी-निरपेक्ष** है: यह server को probe करता है और
सही protocol path चुनता है। अगर आपका test legacy-विशेष semantics (sampling या elicitation push,
`message_handler`) को परखता है, तो `mode="legacy"` pin करें, और वहाँ `raise_exceptions=True` हटा दें:
legacy connection पहले से ही कभी sanitise नहीं करता, और यह flag failure को आपके test में नहीं, बल्कि
server task के अंदर दोबारा raise करता है।
यही एक line वह वजह भी है कि ये docs आपसे वादा कर सकते हैं कि इनके उदाहरण काम करते हैं: हर example file को SDK का अपना test suite चलाकर परखता है, और उनमें से लगभग सभी को ठीक इसी client के ज़रिए। आप वही tool इस्तेमाल कर रहे हैं जो SDK खुद पर इस्तेमाल करता है।
आपके पास एक चलता हुआ, tested server है। इसे किसी असली application (Claude Desktop, कोई IDE) के अंदर रखना **[असली host से जुड़ें](real-host.md)** में है; इसे serve करने का हर दूसरा तरीका **[अपना server चलाना](../run/index.md)** में है।

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