विषय पर बढ़ें

Progress

मशीनी अनुवाद

यह page अंग्रेज़ी documentation से अपने-आप अनुवादित किया गया है, और अंग्रेज़ी page ही प्रामाणिक version है। अगर कुछ गलत लगे, तो अनुवाद page बताता है कि इसकी सूचना कैसे दें।

जो tool तीस सेकंड लेता है और तीस सेकंड तक कुछ नहीं बोलता, वह टूटा हुआ लगता है।

Progress notifications इसे ठीक करते हैं। Tool बताता है कि काम कितना हो चुका है; client तय करता है कि उससे क्या दिखाए: bar, spinner, या log line।

Tool से report करें

एक Context parameter लें और report_progress call करें:

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bookshop")


@mcp.tool()
async def import_catalog(urls: list[str], ctx: Context) -> str:
    """Import book records from a list of catalog URLs."""
    for done, url in enumerate(urls, start=1):
        await ctx.report_progress(done, total=len(urls), message=f"Imported {url}")
    return f"Imported {len(urls)} records."

तीन arguments, और उनका मतलब आप तय करते हैं:

  • progress: आप कहाँ तक पहुँचे हैं। Spec की माँग है कि यह हर report के साथ बढ़े; कोई value न दोहराएँ, न पीछे जाएँ।
  • total: कुल कितना है, अगर आपको पता हो। Optional।
  • message: इसी चरण के बारे में एक human-readable line। Optional।

ctx अपने type hint की वजह से inject होता है और model इसे कभी नहीं देखता: import_catalog के input schema में सिर्फ़ एक property है, urlsContext page पूरी तरह उसी object के बारे में है; progress उन चीज़ों में से एक है जो वह आपको देता है।

Client से सुनें

Client हर call पर अलग से opt in करता है, call_tool को progress_callback= देकर:

client.py
import anyio
from mcp import Client

from server import mcp


async def show(progress: float, total: float | None, message: str | None) -> None:
    print(f"{message} ({progress}/{total})")


async def main() -> None:
    async with Client(mcp) as client:
        result = await client.call_tool(
            "import_catalog",
            {"urls": ["https://example.com/a.json", "https://example.com/b.json"]},
            progress_callback=show,
        )
    print(result.structured_content)


anyio.run(main)

Callback एक async function है जो ठीक वही लेता है जो server ने report किया: progress, total, message

Info

Client(mcp) सीधे server object से जुड़ता है, memory में, वही client जिस पर Testing page बना है। Client चाहे कोई भी transport इस्तेमाल करे, progress_callback parameter वही रहता है; जो timing आप अभी देखने वाले हैं वह in-memory connection की है। वह आपका callback inline चलाता है, इसलिए हर report call_tool के लौटने से पहले पहुँच जाती है। असली transport पर notifications और result में होड़ लगती है, और एक धीमा callback call_tool के लौटने के बाद भी चल रहा हो सकता है।

इसे आज़माएँ

client.py को server.py के बगल में रखें और चलाएँ:

python client.py
Imported https://example.com/a.json (1/2)
Imported https://example.com/b.json (2/2)
{'result': 'Imported 2 records.'}

Server पर हर await ctx.report_progress(...) client पर show का एक call बना, उसी क्रम में, और दोनों lines call_tool के लौटने से पहले print हुईं। Progress result में बंडल होकर नहीं आता; tool के काम करते रहने के दौरान ही stream होता है।

Warning

progress_callback call का है, Client का नहीं। इसके लिए कोई constructor argument नहीं है, क्योंकि अलग-अलग calls को अलग-अलग callbacks चाहिए: एक download bar चलाता है, अगला एक log line।

Check

अब progress_callback=show हटा दें और फिर से चलाएँ:

{'result': 'Imported 2 records.'}

कोई error नहीं, कोई warning नहीं, वही result। जब caller ने progress नहीं माँगा हो तब report_progress no-op है, इसलिए आप बिना शर्त report करें और कभी यह सोचने की ज़रूरत नहीं कि कोई सुन भी रहा है या नहीं।

जब total पता न हो

total तब के लिए है जब आपको denominator पता हो। अक्सर नहीं होता: आप कोई feed खाली कर रहे हैं, cursor पर चल रहे हैं, बिना length header वाली कोई चीज़ download कर रहे हैं।

इसे छोड़ दें:

server.py
from collections.abc import AsyncIterator

from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bookshop")


async def fetch_records(feed_url: str) -> AsyncIterator[str]:
    for title in ("Dune", "Neuromancer", "Hyperion"):
        yield f"{feed_url}#{title}"


@mcp.tool()
async def import_feed(feed_url: str, ctx: Context) -> str:
    """Import every record a catalog feed yields."""
    imported = 0
    async for record in fetch_records(feed_url):
        imported += 1
        await ctx.report_progress(imported, message=f"Imported {record}")
    return f"Imported {imported} records."

Callback को total=None मिलता है। Client अब भी activity दिखा सकता है ("3 imported so far..."), लेकिन percentage नहीं दिखा सकता। ज़्यादा सुंदर bar पाने के लिए कोई total न गढ़ें।

Tip

ज़रूरी नहीं कि progress किसी ख़ास चीज़ को गिने। Bytes, rows, pages: वह unit चुनें जिसे user पहचाने, और सिर्फ़ वही total वादा करें जिसे आप निभा सकें।

सारांश

  • Context लेने वाले किसी भी tool से await ctx.report_progress(progress, total=None, message=None)
  • Client call_tool को progress_callback= देता है: हर call पर, कभी Client पर नहीं।
  • Callback async (progress, total, message) -> None है और tool के चलते रहने के दौरान ही fire होता है।
  • Call पर callback न हो तो report_progress कुछ नहीं करता। बिना शर्त report करें।
  • जब total पता न हो तो उसे छोड़ दें; callback को None मिलता है।

Progress वह है जो चलता हुआ tool user को दिखाता है। जो lines वह आपके लिए, यानी server चलाने वाले व्यक्ति के लिए log करता है, वे एक अलग channel हैं: Logging