Dependencies
मशीनी अनुवाद
यह page अंग्रेज़ी documentation से अपने-आप अनुवादित किया गया है, और अंग्रेज़ी page ही प्रामाणिक version है। अगर कुछ गलत लगे, तो अनुवाद page बताता है कि इसकी सूचना कैसे दें।
tool के arguments model से आते हैं। कुछ values कभी वहाँ से नहीं आनी चाहिए: आपके records से निकाली गई कीमत, ऐसी confirmation जो सिर्फ़ कोई इंसान दे सकता है, कोई भी ऐसी चीज़ जिसे model गढ़कर गलत कर सकता है।
Dependencies वे parameters हैं जिन्हें आपके अपने functions भरते हैं। आप parameter को annotate करते हैं, function का नाम देते हैं, और tool चलने से पहले SDK उसे call करता है।
एक declare करें
parameter के type को Annotated[...] में लपेटें और Resolve(fn) जोड़ें:
from typing import Annotated
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver import Resolve
mcp = MCPServer("Bookshop")
INVENTORY = {"Dune": 7, "Neuromancer": 0}
class Stock(BaseModel):
title: str
copies: int
async def check_stock(title: str) -> Stock:
return Stock(title=title, copies=INVENTORY.get(title, 0))
@mcp.tool()
async def reserve_book(title: str, stock: Annotated[Stock, Resolve(check_stock)]) -> str:
"""Reserve a copy of a book."""
if stock.copies == 0:
return f"{title!r} is out of stock."
return f"Reserved {title!r} ({stock.copies - 1} copies left)."
check_stockresolver है: सादा function, जिसे SDKreserve_bookसे पहले चलाता है और जिसकी return valuestockargument बन जाती है।- इसका
titleparameter tool का अपनाtitleargument ही है, जिसका मिलान नाम से होता है। resolver को ठीक वही validated value दिखती है जो tool body को दिखेगी। - tool body ऐसे
Stockसे शुरू होती है जो पहले से मौजूद है। tool में कोई lookup code नहीं, कोई "अगर यह न मिले तो" वाली भूमिका नहीं।
Info
अगर आपने FastAPI इस्तेमाल किया है, तो यह Depends है। वही तरीका, वही वजह: function बताता है
कि उसे क्या चाहिए, framework वह देता है, और सारी wiring type annotation में रहती है।
model को नहीं दिखता
यह रहा वह input schema जो tools/list reserve_book के लिए बताता है:
{
"type": "object",
"properties": {
"title": {"title": "Title", "type": "string"}
},
"required": ["title"],
"title": "reserve_bookArguments"
}
एक ही property। Context के Context की तरह, resolve किया गया parameter आपके और SDK के बीच का अनुबंध है: stock schema में नहीं है, model को इसके बारे में कभी नहीं बताया जाता, और जो client फिर भी stock value भेजता है उसे अनदेखा कर दिया जाता है। resolver की value ही वह अकेली value है जो आपके tool को मिल सकती है।
आखिरी बात ही असली बात है। जो parameter model दे ही नहीं सकता, उसे model गलत भी नहीं कर सकता।
इसे आज़माएँ
server को MCP Inspector के साथ चलाएँ:
uv run mcp dev server.py
reserve_book के form में सिर्फ़ एक title field है। stock उस पर कहीं नहीं है। इसे Dune के साथ call करें:
Reserved 'Dune' (6 copies left).
tool body ने खुद कुछ भी नहीं खोजा: पहले check_stock चला, और उसका लौटाया Stock argument बनकर आया। Neuromancer आज़माएँ और वही resolver tool को शून्य थमा देता है।
Tip
आप tool body में सीधे check_stock(title) call भी कर सकते हैं। इसे dependency तब declare करें
जब value एक helper call से ज़्यादा की हकदार हो: stock की ज़रूरत वाला हर tool वही parameter
declare करता है, और चाहे कितने भी tools इसे declare करें, SDK resolver को प्रति call ज़्यादा से
ज़्यादा एक बार चलाता है। अगले sections बाकी जोड़ते हैं: एक-दूसरे पर निर्भर resolvers, और user से
पूछने वाले resolvers।
Dependencies की dependencies
resolver उसी annotation से अपनी खुद की dependencies declare कर सकता है:
from typing import Annotated
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver import Resolve
mcp = MCPServer("Bookshop")
INVENTORY = {"Dune": 7, "Neuromancer": 0}
class Stock(BaseModel):
title: str
copies: int
async def check_stock(title: str) -> Stock:
return Stock(title=title, copies=INVENTORY.get(title, 0))
async def estimate_delivery(stock: Annotated[Stock, Resolve(check_stock)]) -> str:
return "tomorrow" if stock.copies > 0 else "in 2-3 weeks"
@mcp.tool()
async def order_book(
title: str,
stock: Annotated[Stock, Resolve(check_stock)],
delivery: Annotated[str, Resolve(estimate_delivery)],
) -> str:
"""Order a book from the shop."""
if stock.copies == 0:
return f"{title!r} is on backorder; it would arrive {delivery}."
return f"Ordered {title!r}; it arrives {delivery}."
estimate_deliverycheck_stockपर निर्भर है। SDK graph को क्रम से चलाता है: पहले stock, फिर estimate, फिर tool।stockऔरdeliveryदोनों को आखिरकारcheck_stockचाहिए, लेकिन यह प्रति call एक बार चलता है। एक inventory lookup, दो consumers।- register करने को कुछ नहीं है। annotations ही graph हैं।
Check
"प्रति call एक बार" पर आँख मूँदकर भरोसा न करें। check_stock में एक print डालें और Inspector से
order_book call करें: प्रति call एक line। दो consumers, एक lookup।
SDK graph का विश्लेषण तब करता है जब tool register होता है, न कि जब उसे call किया जाता है। ऐसा parameter जिसे वह वर्गीकृत न कर सके - न Context, न Resolve(...), न किसी tool argument का नाम - और resolvers का कोई cycle, दोनों startup पर InvalidSignature raise करते हैं। server किसी भी client के जुड़ने से पहले ही fail हो जाता है, और error में गड़बड़ी वाले parameter या resolver का नाम होता है।
resolver के parameters ठीक tool के parameters की तरह resolve होते हैं: कोई और Resolve(...), नाम से tool के अपने arguments, या Context - ctx.headers, lifespan object, सब कुछ।
Warning
HTTP transports पर Context में ctx.headers शामिल होते हैं। headers client का भेजा हुआ input हैं,
किसी भी tool argument की तरह: locale या feature flag के लिए ठीक, पहचान के लिए कभी नहीं। caller कौन
है, यह आपकी authorization layer (Authorization) से आता है, किसी ऐसे header से नहीं जिसे कोई भी set कर सकता है।
Tip
प्रति call एक बार का मतलब ठीक यही है: अगला tools/call check_stock को फिर से चलाता है। ऐसा resource
जिसे एक request से ज़्यादा जीना चाहिए - database pool, HTTP client - उसकी जगह Lifespan में है, और
resolver उस तक ctx.request_context.lifespan_context के ज़रिए पहुँच सकता है।
तभी पूछें जब ज़रूरी हो
resolver को जवाब पता हो, यह ज़रूरी नहीं। वह Elicit(message, Model) लौटा सकता है और SDK user से पूछ लेता है - यानी Elicitation की machinery, जो आपके लिए चलाई जाती है:
from typing import Annotated
from pydantic import BaseModel, Field
from mcp.server import MCPServer
from mcp.server.mcpserver import Elicit, Resolve
mcp = MCPServer("Bookshop")
INVENTORY = {"Dune": 7, "Neuromancer": 0}
class Stock(BaseModel):
title: str
copies: int
class Backorder(BaseModel):
confirm: bool = Field(description="Order anyway and wait?")
async def check_stock(title: str) -> Stock:
return Stock(title=title, copies=INVENTORY.get(title, 0))
async def confirm_backorder(
title: str,
stock: Annotated[Stock, Resolve(check_stock)],
) -> Backorder | Elicit[Backorder]:
if stock.copies > 0:
return Backorder(confirm=True) # in stock: nothing to ask
return Elicit(f"{title!r} is out of stock (2-3 weeks). Order anyway?", Backorder)
@mcp.tool()
async def order_book(
title: str,
stock: Annotated[Stock, Resolve(check_stock)],
backorder: Annotated[Backorder, Resolve(confirm_backorder)],
) -> str:
"""Order a book from the shop."""
if not backorder.confirm:
return "No order placed."
if stock.copies == 0:
return f"Backordered {title!r}; it ships in 2-3 weeks."
return f"Ordered {title!r}."
- stock में हो:
confirm_backorderसीधेBackorderलौटाता है। कोई सवाल नहीं, कोई round-trip नहीं। user को तभी टोका जाता है जब उसका जवाब मायने रखता हो। - stock में न हो: SDK elicitation भेजता है, जवाब को
Backorderके हिसाब से validate करता है, और उसे inject कर देता है। आपका resolver protocol को कभी छूता तक नहीं। - tool
backorder.confirmको किसी भी दूसरे argument की तरह पढ़ता है। नहीं कहना भी एक जवाब है: elicitationconfirm=Falseके साथ accept होता है, tool चलता है, और कोई order नहीं दिया जाता। पूछना tool body की plumbing नहीं, एक precondition बन गया।
और अगर user जवाब ही न दे - सवाल decline कर दे, या cancel कर दे?
Check
Neuromancer के लिए order_book चलाएँ और सवाल decline करें। annotation
Annotated[Backorder, Resolve(...)] के रूप में लिखी हो तो tool body कभी नहीं चलती; call ऐसे error
result के साथ fail होता है जिसे model पढ़ सकता है:
Error executing tool order_book: Resolver for parameter 'backorder' could not resolve: elicitation was decline
precondition के लिए यही सही default है: जवाब नहीं, तो order नहीं। जब decline होना ऐसा नतीजा हो जिसे आपका tool खुद संभालना चाहे - backorder छोड़ दे पर फिर भी कोई दूसरा title सुझाए - तो इसके बजाय ElicitationResult[Backorder] annotate करें और tool को पूरा accept/decline/cancel नतीजा मिलता है जिस पर वह branch कर सके। Elicitation वह रूप दिखाता है, और पूछने के बारे में बाकी सब भी: schema के नियम, तीनों जवाब, बातचीत का client वाला पक्ष।
Info
framework सवाल का transport negotiate हुए protocol version से चुनता है; ऊपर का code दोनों पर एक जैसा
है। 2026-07-28 और उसके बाद सवाल एक multi-round-trip tools/call के भीतर जाता है - server उसे
लौटाता है, client का elicitation_callback उसका जवाब देता है, और Client आपके लिए call को फिर से
आज़माता है (Multi-round-trip requests)। 2025-11-25 और उससे पहले यह call के
बीच में एक synchronous elicitation request होती है। हर सवाल प्रति call ठीक एक बार पूछा जाता है - यह
गारंटी सवाल के बारे में है, resolver के बारे में नहीं। multi-round-trip रूप में, जब भी call किसी सवाल के
बाद फिर से शुरू होता है, कोई भी resolver दोबारा चल सकता है, इसलिए return Elicit(...) से पहले का code
उन हर rounds पर चलता है; फिर दर्ज किया गया जवाब दोहराए गए सवाल को user से दोबारा पूछे बिना पूरा कर
देता है। दर्ज जवाब सिर्फ़ तभी देखा जाता है जब resolver पूछता है; जो resolver पूछे बिना जवाब दे देता
है, जैसे check_stock, वह हमेशा अपनी खुद की गणना की गई value देता है। चूँकि हर जवाब वापस उसके सवाल
से मिलाया जाता है, elicit करने वाले resolver को अपना सवाल tool के arguments और पहले के जवाबों से
deterministic ढंग से बनाना होगा। प्रति call बनने वाली value (default_factory id, timestamp) हर round
पर फिर से बनती है और ऐसे सवाल में नहीं आनी चाहिए जिससे जवाब को बँधना है। ऐसे अस्थिर data से बना सवाल
हर दर्ज जवाब को बासी दिखा देता है, इसलिए server उसे हर round पर फिर से पूछता है, जब तक client की
round limit call को खत्म नहीं कर देती।
user से नहीं, client से पूछें
Elicitation उन तीन सवालों में से एक है जो resolver पूछ सकता है, और multi-round-trip flow इनके अलावा कोई और सवाल नहीं होने देता। बाकी दो user के बजाय client के पास जाते हैं: client के ज़रिए LLM call चलाने के लिए Sample(...) लौटाएँ (एक sampling/createMessage request), या client के मौजूदा roots लाने के लिए ListRoots()। दोनों में से किसी का accept/decline नतीजा नहीं होता; consumer सीधे result type annotate करता है, CreateMessageResult (जब request में tools या tool_choice हो तो CreateMessageResultWithTools) या ListRootsResult:
from typing import Annotated
from mcp.server import MCPServer
from mcp.server.mcpserver import Resolve, Sample
from mcp.types import CreateMessageResult, SamplingMessage, TextContent
mcp = MCPServer("Bookshop")
def suggest_title(genre: str) -> Sample:
prompt = f"Suggest one {genre} book title. Answer with the title only."
return Sample(
[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
max_tokens=50,
)
@mcp.tool()
async def recommend_book(
genre: str,
suggestion: Annotated[CreateMessageResult, Resolve(suggest_title)],
) -> str:
"""Recommend a book in the given genre."""
title = suggestion.content.text if suggestion.content.type == "text" else "the classics"
return f"Today's {genre} pick: {title}"
- framework इन्हें ठीक
Elicitकी तरह route करता है: 2026-07-28 पर multi-round-triptools/callके भीतर, 2025-11-25 पर standalone server->client request के ज़रिए। declare न की गई capability call को-32021protocol error के साथ मना कर देती है (sampling,roots, form-modeelicitation; जब request मेंtoolsयाtool_choiceहो तोsampling.tools)। - ऊपर वाला info box सवालों के बारे में जो कुछ कहता है, वह बिना बदलाव लागू होता है:
Samplerequest का मिलान उसके दर्ज result से उसके हूबहू rendering से होता है, इसलिए उसे tool के arguments और पहले के जवाबों से deterministic ढंग से बनाएँ; तब client LLM call की कीमत प्रति tool call एक बार चुकाता है, प्रति round एक बार नहीं। दर्ज result बाकी call भरrequest_stateमें साथ चलता है, इसलिए बहुत बड़ा completion बचे हुए हर round-trip को भारी बना देता है। - standalone sampling और roots features 2026-07-28 पर deprecated हैं (SEP-2577)। जिन नए servers को client के model की ज़रूरत है वे इसी carrier के ज़रिए पूछते हैं; जिन्हें नहीं है उन्हें सीधे किसी LLM provider से integrate करना चाहिए।
"none"के अलावाinclude_contextकी values खुद deprecated हैं; उनसे बचें।
सारांश
- tool parameter पर
Annotated[T, Resolve(fn)]: SDKfnचलाता है और उसकी return value inject करता है। - resolve किया गया parameter model को नहीं दिखता और कोई client उसे भेज नहीं सकता। जो values model को गढ़नी नहीं चाहिए - कीमतें, पहचान, अनुमतियाँ - उनकी जगह यहीं है।
- resolver के parameters उसी तरह resolve होते हैं:
Context, कोई औरResolve(...), या नाम से कोई tool argument। graph हर resolver को प्रति round ज़्यादा से ज़्यादा एक बार चलाता है, चाहे उसके कितने भी consumers हों; हर सवाल ठीक एक बार पूछा जाता है, और call के किसी सवाल के बाद फिर से शुरू होने पर कोई भी resolver दोबारा चल सकता है। - खराब graphs registration के समय
InvalidSignatureके साथ fail होते हैं, call के बीच में नहीं। - user से पूछने के लिए
Elicit(message, Model)लौटाएँ, सिर्फ़ तब जब ज़रूरी हो। बिना wrap की annotations decline पर abort करती हैं;ElicitationResult[T]tool को branch करने देती है। - client से LLM completion या roots की सूची माँगने के लिए
Sample(...)याListRoots()लौटाएँ; सादा result inject हो जाता है।
server startup पर एक बार जो state बनाता है, और handler उस तक कैसे पहुँचता है, वह Lifespan page है।