Files
2026-07-13 13:31:35 +08:00

13 KiB

Paginering en Grote Resultaatsets in MCP

Wanneer je MCP-server grote datasets verwerkt - of het nu duizenden bestanden, databasegegevens, of zoekresultaten zijn - heb je paginering nodig om het geheugen efficiënt te beheren en responsieve gebruikerservaringen te bieden. Deze gids behandelt hoe je paginering implementeert en gebruikt in MCP.

Waarom Paginering Belangrijk Is

Zonder paginering kunnen grote reacties leiden tot:

  • Geheugenuitputting - Het tegelijk laden van miljoenen records
  • Trage responstijden - Gebruikers wachten terwijl alle data wordt geladen
  • Timeoutfouten - Verzoeken overschrijden de time-out limieten
  • Slechte AI-prestaties - LLM's hebben moeite met enorme contexten

MCP gebruikt cursor-gebaseerde paginering voor betrouwbare, consistente navigatie door resultaatsets.


Hoe MCP Paginering Werkt

Het Cursor Concept

Een cursor is een ondoorzichtig tekenreeks dat je positie in een resultaatset markeert. Zie het als een bladwijzer in een lang boek.

sequenceDiagram
    participant Client
    participant Server
    
    Client->>Server: tools/list (geen cursor)
    Server-->>Client: tools [1-10], nextCursor: "abc123"
    
    Client->>Server: tools/list (cursor: "abc123")
    Server-->>Client: tools [11-20], nextCursor: "def456"
    
    Client->>Server: tools/list (cursor: "def456")
    Server-->>Client: tools [21-25], nextCursor: null (einde)

Paginering in MCP Methodes

Deze MCP-methodes ondersteunen paginering:

Methode Retourneert Cursor Ondersteuning
tools/list Tooldefinities
resources/list Resource-definities
prompts/list Prompt-definities
resources/templates/list Resource-templates

Serverimplementatie

Python (FastMCP)

from mcp.server import Server
from mcp.types import Tool, ListToolsResult
import math

app = Server("paginated-server")

# Gesimuleerde grote dataset
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."""
    
    # Decodeer cursor om startindex te krijgen
    start_index = 0
    if cursor:
        try:
            start_index = int(cursor)
        except ValueError:
            start_index = 0
    
    # Haal pagina met resultaten op
    end_index = min(start_index + PAGE_SIZE, len(ALL_TOOLS))
    page_tools = ALL_TOOLS[start_index:end_index]
    
    # Bereken volgende cursor
    next_cursor = None
    if end_index < len(ALL_TOOLS):
        next_cursor = str(end_index)
    
    return ListToolsResult(
        tools=page_tools,
        nextCursor=next_cursor
    )

TypeScript

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"
});

// Gesimuleerde grote dataset
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) => {
  // Decodeer cursor
  let startIndex = 0;
  if (request.params?.cursor) {
    startIndex = parseInt(request.params.cursor, 10) || 0;
  }
  
  // Haal pagina resultaten op
  const endIndex = Math.min(startIndex + PAGE_SIZE, ALL_TOOLS.length);
  const pageTools = ALL_TOOLS.slice(startIndex, endIndex);
  
  // Bereken volgende cursor
  const nextCursor = endIndex < ALL_TOOLS.length ? String(endIndex) : undefined;
  
  return {
    tools: pageTools,
    nextCursor
  };
});

Java (Spring MCP)

@Service
public class PaginatedToolService {
    
    private static final int PAGE_SIZE = 10;
    private final List<Tool> allTools;
    
    public PaginatedToolService() {
        // Initialiseer grote dataset
        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) {
        // Decodeer cursor
        int startIndex = 0;
        if (cursor != null && !cursor.isEmpty()) {
            try {
                startIndex = Integer.parseInt(cursor);
            } catch (NumberFormatException e) {
                startIndex = 0;
            }
        }
        
        // Haal pagina met resultaten op
        int endIndex = Math.min(startIndex + PAGE_SIZE, allTools.size());
        List<Tool> pageTools = allTools.subList(startIndex, endIndex);
        
        // Bereken volgende cursor
        String nextCursor = endIndex < allTools.size() ? String.valueOf(endIndex) : null;
        
        return new ListToolsResult(pageTools, nextCursor);
    }
}

Clientimplementatie

Python Client

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

# Gebruik
async with client_session as session:
    tools = await get_all_tools(session)
    print(f"Found {len(tools)} tools")

TypeScript Client

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;
}

// Gebruik
const tools = await getAllTools(client);
console.log(`Found ${tools.length} tools`);

Lazy Loading Patroon

Voor zeer grote datasets, laad pagina's op aanvraag:

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):
        # Terugkeren uit buffer indien beschikbaar
        if self.buffer:
            return self.buffer.pop(0)
        
        # Controleren of we alle pagina's hebben uitgeput
        if self.exhausted:
            raise StopAsyncIteration
        
        # Volgende pagina ophalen
        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

# Gebruik - geheugen-efficiënt voor grote datasets
async for tool in PaginatedToolIterator(session):
    process_tool(tool)

Paginering voor Resources

Resources hebben vaak paginering nodig voor mappen of grote datasets:

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))
    
    # Decodeer cursor (bestandsindex)
    start_index = int(cursor) if cursor else 0
    page_size = 20
    end_index = min(start_index + page_size, len(all_files))
    
    # Maak een lijst met bronnen voor deze pagina
    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"
        ))
    
    # Bereken de volgende cursor
    next_cursor = str(end_index) if end_index < len(all_files) else None
    
    return ListResourcesResult(
        resources=resources,
        nextCursor=next_cursor
    )

Strategieën voor Cursorontwerp

Strategie 1: Index-Gebaseerd (Eenvoudig)

# Cursor is gewoon de index
cursor = "50"  # Begin bij item 50

Voordelen: Eenvoudig, stateloos
Nadelen: Resultaten kunnen verschuiven als items worden toegevoegd/verwijderd

Strategie 2: ID-Gebaseerd (Stabiel)

# Cursus is de laatst geziene ID
cursor = "item_abc123"  # Begin na dit item

Voordelen: Stabiel zelfs als items veranderen
Nadelen: Vereist geordende ID's

Strategie 3: Geëncodeerde Staat (Complex)

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())

# Cursor bevat meerdere statusvelden
cursor = encode_cursor({
    "offset": 50,
    "filter": "active",
    "sort": "name"
})

Voordelen: Kan complexe staat coderen
Nadelen: Complexer, grotere cursor-strings


Beste Praktijken

1. Kies Passende Paginagroottes

# Houd rekening met de gegevensgrootte
PAGE_SIZE_SMALL_ITEMS = 100   # Eenvoudige metadata
PAGE_SIZE_MEDIUM_ITEMS = 20   # Rijkere objecten
PAGE_SIZE_LARGE_ITEMS = 5     # Complexe inhoud

2. Ga Sluw om met Ongeldige Cursors

@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  # Terug naar begin
    except (ValueError, TypeError):
        start_index = 0  # Ongeldige cursor, opnieuw beginnen
    # ...

3. Voeg Totaaltelling toe (Optioneel)

return ListToolsResult(
    tools=page_tools,
    nextCursor=next_cursor,
    # Sommige implementaties bevatten totaal voor UI-voortgang
    _meta={"total": len(ALL_TOOLS)}
)

4. Test Randgevallen

async def test_pagination():
    # Lege resultaatset
    result = await session.list_tools()
    assert result.tools == []
    assert result.nextCursor is None
    
    # Enkele pagina
    result = await session.list_tools()
    assert len(result.tools) <= PAGE_SIZE
    
    # Ongeldige cursor
    result = await session.list_tools(cursor="invalid")
    assert result.tools  # Zou de eerste pagina moeten retourneren

Veelvoorkomende Valkuilen

Alle Resultaten Teruggeven en Daarna Pagineren aan de Clientzijde

# SLECHT: Laadt alles in het geheugen
@app.list_tools()
async def list_tools() -> ListToolsResult:
    all_tools = load_all_tools()  # 1 miljoen gereedschappen!
    return ListToolsResult(tools=all_tools)

Pagineer bij de Datasource

# GOED: Laadt alleen wat nodig is
@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=...)

Wat Nu?


Aanvullende Bronnen


Disclaimer: Dit document is vertaald met behulp van de AI-vertalingsservice Co-op Translator. Hoewel we streven naar nauwkeurigheid, kan het voorkomen dat automatische vertalingen fouten of onjuistheden bevatten. Het oorspronkelijke document in de oorspronkelijke taal geldt als de gezaghebbende bron. Voor cruciale informatie wordt professionele menselijke vertaling aanbevolen. Wij zijn niet aansprakelijk voor eventuele misverstanden of verkeerde interpretaties die voortkomen uit het gebruik van deze vertaling.