docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
6 changed files with 89737 additions and 54 deletions
158
i18n/hi/pages/advanced/apps.md
Normal file
158
i18n/hi/pages/advanced/apps.md
Normal 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
|
||||
```
|
||||
172
i18n/hi/pages/advanced/extensions.md
Normal file
172
i18n/hi/pages/advanced/extensions.md
Normal 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 **सब कुछ** जानता है जिसे ये दोनों छू सकते थे।
|
||||
34
i18n/hi/pages/advanced/index.md
Normal file
34
i18n/hi/pages/advanced/index.md
Normal 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 की ज़रूरत है या नहीं, तो नहीं है।
|
||||
207
i18n/hi/pages/advanced/low-level-server.md
Normal file
207
i18n/hi/pages/advanced/low-level-server.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
translation:
|
||||
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
|
||||
tool: 1
|
||||
---
|
||||
# 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)** है।
|
||||
121
i18n/hi/pages/advanced/middleware.md
Normal file
121
i18n/hi/pages/advanced/middleware.md
Normal 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
|
||||
को चलने दिया जाए भी या नहीं।
|
||||
85
i18n/hi/pages/advanced/pagination.md
Normal file
85
i18n/hi/pages/advanced/pagination.md
Normal 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)** में है।
|
||||
Loading…
Reference in a new issue