Amikor az MCP szervere nagy adatállományokat kezel - legyen szó akár több ezer fájl, adatbázis rekord vagy keresési találat listázásáról -, szükség van lapozásra a memória hatékony kezelése és a gyors válaszidők biztosítása érdekében. Ez az útmutató bemutatja, hogyan valósítható meg és használható a lapozás az MCP-ben.
Lapozás nélkül a nagy válaszok a következőket okozhatják:
- Memória kimerülése - Egyszerre több millió rekord betöltése
- Lassú válaszidő - A felhasználók megvárják az összes adat betöltését
- Időtúllépési hibák - A kérések túllépik az időkorlátokat
- Gyenge AI teljesítmény - A LLM-ek nehezen boldogulnak hatalmas kontextusokkal
Az MCP kurzor-alapú lapozást használ a megbízható és következetes eredményoldalazáshoz.
A kurzor egy átlátszatlan karakterlánc, amely az eredményhalmazon belüli helyzetedet jelöli. Olyan, mint egy könyvjelző egy hosszú könyvben.
sequenceDiagram
participant Client
participant Server
Client->>Server: eszközök/lista (nincs kurzor)
Server-->>Client: eszközök [1-10], következőKurzor: "abc123"
Client->>Server: eszközök/lista (kurzor: "abc123")
Server-->>Client: eszközök [11-20], következőKurzor: "def456"
Client->>Server: eszközök/lista (kurzor: "def456")
Server-->>Client: eszközök [21-25], következőKurzor: null (vég)
Ezek az MCP metódusok támogatják a lapozást:
| Metódus | Visszatérési érték | Kurzor támogatás |
|---|---|---|
tools/list |
Eszköz definíciók | ✅ |
resources/list |
Erőforrás definíciók | ✅ |
prompts/list |
Prompt definíciók | ✅ |
resources/templates/list |
Erőforrás sablonok | ✅ |
from mcp.server import Server
from mcp.types import Tool, ListToolsResult
import math
app = Server("paginated-server")
# Szimulált nagy adathalmaz
ALL_TOOLS = [
Tool(name=f"tool_{i}", description=f"Tool number {i}", inputSchema={})
for i in range(100)
]
PAGE_SIZE = 10
@app.list_tools()
async def list_tools(cursor: str | None = None) -> ListToolsResult:
"""List tools with pagination support."""
# Dekódolja a kurzort a kezdő index megszerzéséhez
start_index = 0
if cursor:
try:
start_index = int(cursor)
except ValueError:
start_index = 0
# Eredmények oldalának lekérése
end_index = min(start_index + PAGE_SIZE, len(ALL_TOOLS))
page_tools = ALL_TOOLS[start_index:end_index]
# Következő kurzor kiszámítása
next_cursor = None
if end_index < len(ALL_TOOLS):
next_cursor = str(end_index)
return ListToolsResult(
tools=page_tools,
nextCursor=next_cursor
)import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { ListToolsResultSchema } from "@modelcontextprotocol/sdk/types.js";
const server = new Server({
name: "paginated-server",
version: "1.0.0"
});
// Szimulált nagyméretű adatállomány
const ALL_TOOLS = Array.from({ length: 100 }, (_, i) => ({
name: `tool_${i}`,
description: `Tool number ${i}`,
inputSchema: { type: "object", properties: {} }
}));
const PAGE_SIZE = 10;
server.setRequestHandler(ListToolsResultSchema, async (request) => {
// Dekódolja a kurzort
let startIndex = 0;
if (request.params?.cursor) {
startIndex = parseInt(request.params.cursor, 10) || 0;
}
// Eredmények oldalának lekérése
const endIndex = Math.min(startIndex + PAGE_SIZE, ALL_TOOLS.length);
const pageTools = ALL_TOOLS.slice(startIndex, endIndex);
// Következő kurzor kiszámítása
const nextCursor = endIndex < ALL_TOOLS.length ? String(endIndex) : undefined;
return {
tools: pageTools,
nextCursor
};
});@Service
public class PaginatedToolService {
private static final int PAGE_SIZE = 10;
private final List<Tool> allTools;
public PaginatedToolService() {
// Nagy adatkészlet inicializálása
this.allTools = IntStream.range(0, 100)
.mapToObj(i -> new Tool("tool_" + i, "Tool number " + i, Map.of()))
.collect(Collectors.toList());
}
@McpMethod("tools/list")
public ListToolsResult listTools(@Param("cursor") String cursor) {
// Mutató dekódolása
int startIndex = 0;
if (cursor != null && !cursor.isEmpty()) {
try {
startIndex = Integer.parseInt(cursor);
} catch (NumberFormatException e) {
startIndex = 0;
}
}
// Eredmények oldalának lekérése
int endIndex = Math.min(startIndex + PAGE_SIZE, allTools.size());
List<Tool> pageTools = allTools.subList(startIndex, endIndex);
// Következő mutató kiszámítása
String nextCursor = endIndex < allTools.size() ? String.valueOf(endIndex) : null;
return new ListToolsResult(pageTools, nextCursor);
}
}from mcp import ClientSession
async def get_all_tools(session: ClientSession) -> list:
"""Fetch all tools using pagination."""
all_tools = []
cursor = None
while True:
result = await session.list_tools(cursor=cursor)
all_tools.extend(result.tools)
if result.nextCursor is None:
break
cursor = result.nextCursor
return all_tools
# Használat
async with client_session as session:
tools = await get_all_tools(session)
print(f"Found {len(tools)} tools")import { Client } from "@modelcontextprotocol/sdk/client/index.js";
async function getAllTools(client: Client): Promise<Tool[]> {
const allTools: Tool[] = [];
let cursor: string | undefined = undefined;
do {
const result = await client.listTools({ cursor });
allTools.push(...result.tools);
cursor = result.nextCursor;
} while (cursor);
return allTools;
}
// Használat
const tools = await getAllTools(client);
console.log(`Found ${tools.length} tools`);Nagyon nagy adatállományok esetén töltsd be az oldalakat igény szerint:
class PaginatedToolIterator:
"""Lazily iterate through paginated tools."""
def __init__(self, session: ClientSession):
self.session = session
self.cursor = None
self.buffer = []
self.exhausted = False
async def __anext__(self):
# Visszatérés a pufferből, ha elérhető
if self.buffer:
return self.buffer.pop(0)
# Ellenőrizze, hogy elfogytak-e az összes oldal
if self.exhausted:
raise StopAsyncIteration
# Következő oldal lekérése
result = await self.session.list_tools(cursor=self.cursor)
self.buffer = list(result.tools)
self.cursor = result.nextCursor
if self.cursor is None:
self.exhausted = True
if not self.buffer:
raise StopAsyncIteration
return self.buffer.pop(0)
def __aiter__(self):
return self
# Használat - memóriahatékony nagy adathalmazok esetén
async for tool in PaginatedToolIterator(session):
process_tool(tool)Az erőforrásoknál gyakran szükséges lapozás mappák vagy nagy adatállományok esetén:
from mcp.server import Server
from mcp.types import Resource, ListResourcesResult
import os
app = Server("file-server")
@app.list_resources()
async def list_resources(cursor: str | None = None) -> ListResourcesResult:
"""List files in directory with pagination."""
directory = "/data/files"
all_files = sorted(os.listdir(directory))
# Kurzor dekódolása (fájl index)
start_index = int(cursor) if cursor else 0
page_size = 20
end_index = min(start_index + page_size, len(all_files))
# Erőforrás lista létrehozása ehhez az oldalhoz
resources = []
for filename in all_files[start_index:end_index]:
filepath = os.path.join(directory, filename)
resources.append(Resource(
uri=f"file://{filepath}",
name=filename,
mimeType="application/octet-stream"
))
# Következő kurzor kiszámítása
next_cursor = str(end_index) if end_index < len(all_files) else None
return ListResourcesResult(
resources=resources,
nextCursor=next_cursor
)# A kurzor csak az index
cursor = "50" # Kezdje az 50. elemtőlElőnyök: Egyszerű, állapotmentes
Hátrányok: Az eredmények eltolódhatnak, ha elemek kerülnek be vagy ki
# A kurzor az utoljára látott azonosító
cursor = "item_abc123" # Ezt az elemet követően kezdődikElőnyök: Stabil, még ha az elemek változnak is
Hátrányok: Sorrendben lévő azonosítók szükségesek
import base64
import json
def encode_cursor(state: dict) -> str:
return base64.b64encode(json.dumps(state).encode()).decode()
def decode_cursor(cursor: str) -> dict:
return json.loads(base64.b64decode(cursor).decode())
# A kurzor több állapotmezőt tartalmaz
cursor = encode_cursor({
"offset": 50,
"filter": "active",
"sort": "name"
})Előnyök: Képes bonyolult állapotok kódolására
Hátrányok: Komplexebb, nagyobb kurzor karakterláncok
# Vegyük figyelembe az adatmennyiséget
PAGE_SIZE_SMALL_ITEMS = 100 # Egyszerű metaadatok
PAGE_SIZE_MEDIUM_ITEMS = 20 # Gazdagabb objektumok
PAGE_SIZE_LARGE_ITEMS = 5 # Összetett tartalom@app.list_tools()
async def list_tools(cursor: str | None = None) -> ListToolsResult:
try:
start_index = int(cursor) if cursor else 0
if start_index < 0 or start_index >= len(ALL_TOOLS):
start_index = 0 # Visszaállítás a kezdethez
except (ValueError, TypeError):
start_index = 0 # Érvénytelen kurzor, kezdjük elölről
# ...return ListToolsResult(
tools=page_tools,
nextCursor=next_cursor,
# Néhány megvalósítás tartalmazza az összesített értéket a felhasználói felület haladásához
_meta={"total": len(ALL_TOOLS)}
)async def test_pagination():
# Üres eredményhalmaz
result = await session.list_tools()
assert result.tools == []
assert result.nextCursor is None
# Egyetlen oldal
result = await session.list_tools()
assert len(result.tools) <= PAGE_SIZE
# Érvénytelen kurzor
result = await session.list_tools(cursor="invalid")
assert result.tools # Az első oldalt kell visszaadnia# ROSSZ: Minden betöltése a memóriába
@app.list_tools()
async def list_tools() -> ListToolsResult:
all_tools = load_all_tools() # 1 millió eszköz!
return ListToolsResult(tools=all_tools)# JÓ: Csak a szükséges dolgokat tölti be
@app.list_tools()
async def list_tools(cursor: str | None = None) -> ListToolsResult:
offset = int(cursor) if cursor else 0
tools = await db.query_tools(offset=offset, limit=PAGE_SIZE)
return ListToolsResult(tools=tools, nextCursor=...)Jogi nyilatkozat:
Ez a dokumentum az AI fordító szolgáltatás Co-op Translator használatával készült. Bár a pontosságra törekszünk, kérjük, vegye figyelembe, hogy az automatikus fordítások tartalmazhatnak hibákat vagy pontatlanságokat. Az eredeti, anyanyelvi dokumentum tekintendő hivatalos forrásnak. Kritikus információk esetén professzionális emberi fordítást javaslunk. Nem vállalunk felelősséget az ebből eredő félreértésekért vagy félreértelmezésekért.