Pagination
मशीनी अनुवाद
यह page अंग्रेज़ी documentation से अपने-आप अनुवादित किया गया है, और अंग्रेज़ी page ही प्रामाणिक version है। अगर कुछ गलत लगे, तो अनुवाद page बताता है कि इसकी सूचना कैसे दें।
ज़्यादातर 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 पर।
Paging करने वाला server
from typing import Any
from mcp.server import Server, ServerRequestContext
from mcp.types import ListResourcesResult, PaginatedRequestParams, Resource
BOOKS = [f"book-{n}" for n in range(1, 101)]
PAGE_SIZE = 10
async def list_books(ctx: ServerRequestContext[Any], params: PaginatedRequestParams | None) -> ListResourcesResult:
start = 0 if params is None or params.cursor is None else int(params.cursor)
end = start + PAGE_SIZE
page = [Resource(uri=f"books://catalog/{name}", name=name) for name in BOOKS[start:end]]
next_cursor = str(end) if end < len(BOOKS) else None
return ListResourcesResult(resources=page, next_cursor=next_cursor)
server = Server("Bookshop", on_list_resources=list_books)
app = server.streamable_http_app()
- Low-level
Serverपर handlers constructor arguments होते हैं, decorators नहीं।on_list_resourcesहरresources/listrequest का जवाब देता है; जोड़ने का पूरा काम बस इतना ही है। - हर paged handler का type
params: PaginatedRequestParams | Noneहोता है, और उदाहरण दोनों स्वीकार करता है। लेकिन किसी connection पर SDK आपको कभीNoneनहीं देता (बिनाparamsmember वाली 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 की कोई राय नहीं चलती, और यह जानबूझकर ऐसा है।
इसे आज़माएँ
mcp run सिर्फ़ MCPServer स्वीकार करता है, इसलिए इसे आप ख़ुद serve करते हैं। server.py की आख़िरी line Server से एक साधारण ASGI app बनाती है, और uvicorn उसी को चलाता है:
uvicorn server:app --port 8000
किसी भी client (The Client, या Inspector) को http://localhost:8000/mcp की ओर point करें और बिना 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
Client का हर list_* method (list_tools, list_resources, list_resource_templates, list_prompts) cursor= keyword लेता है। Paged list को पूरा खींचना एक while True है:
import anyio
from mcp import Client
from mcp.types import Resource
async def list_all_resources(client: Client) -> list[Resource]:
resources: list[Resource] = []
cursor: str | None = None
while True:
page = await client.list_resources(cursor=cursor)
resources.extend(page.resources)
if page.next_cursor is None:
break
cursor = page.next_cursor
return resources
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
resources = await list_all_resources(client)
print(f"{len(resources)} resources")
if __name__ == "__main__":
anyio.run(main)
cursorNoneसे शुरू होता है, इसलिए पहली request में कोई cursor नहीं जाता।next_cursorदेखने से पहले extend करें: आख़िरी page में भी resources होते हैं।next_cursor is Noneही बाहर निकलने का रास्ता है। बाकी कुछ भी सीधेcursor=में वापस जाता है, बिना छेड़े।
uvicorn अब भी server.py serve कर रहा हो, तब दूसरे terminal में python client.py चलाएँ। यह 100 resources print करता है: दस-दस के दस pages, एक ऐसे loop से जुड़े हुए जिसे कभी पता ही नहीं था कि दस pages थे।
यह वही loop है जो The Client हर list_* verb के लिए दिखाता है, और paging न करने वाले server पर इसकी कोई क़ीमत नहीं: पहले ही response में next_cursor None होता है और loop एक बार चलता है।
तीन नियम
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 के पास जो लौटता है वह है:
MCPError(-32603, 'Internal server error', None)
जो cursor आपको server से नहीं मिला, वह bug है, feature request नहीं।
सारांश
MCPServerसब कुछ एक page में लौटाता है। Pagination opt-in है, और opt in आप low-levelServerपर करते हैं।on_list_resources(औरon_list_tools,on_list_prompts,on_list_resource_templates) कोPaginatedRequestParams | Noneमिलता है; पहले page के लिएparams.cursorNoneहोता है।- आप एक 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 में है।