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

12 KiB
Raw Permalink Blame History

Paginering og store resultatsamlinger i MCP

Når din MCP-server håndterer store datasett enten det er tusenvis av filer, databaseoppføringer eller søkeresultater trenger du paginering for å administrere minnet effektivt og gi responsive brukeropplevelser. Denne guiden dekker hvordan man implementerer og bruker paginering i MCP.

Hvorfor paginering er viktig

Uten paginering kan store svar forårsake:

  • Minnemangel Laste millioner av oppføringer samtidig
  • Sakte responstider Brukere må vente mens alle data lastes
  • Timeout-feil Forespørsler overskrider tidsgrense
  • Dårlig AI-ytelse LLM-er sliter med enorm kontekst

MCP bruker kursorbasert paginering for pålitelig og konsistent gjennomgang av resultatsamlinger.


Hvordan MCP-paginering fungerer

Kursor-konseptet

En kursor er en ugjennomsiktig streng som markerer din posisjon i en resultatsamling. Tenk på det som et bokmerke i en lang bok.

sequenceDiagram
    participant Client
    participant Server
    
    Client->>Server: tools/list (ingen markør)
    Server-->>Client: verktøy [1-10], nesteMarkør: "abc123"
    
    Client->>Server: tools/list (markør: "abc123")
    Server-->>Client: verktøy [11-20], nesteMarkør: "def456"
    
    Client->>Server: tools/list (markør: "def456")
    Server-->>Client: verktøy [21-25], nesteMarkør: null (slutt)

Paginering i MCP-metoder

Disse MCP-metodene støtter paginering:

Metode Returnerer Støtte for kursor
tools/list Verktøydefinisjoner
resources/list Ressursdefinisjoner
prompts/list Prompt-definisjoner
resources/templates/list Ressursmaler

Serverimplementering

Python (FastMCP)

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

app = Server("paginated-server")

# Simulert stort datasett
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."""
    
    # Dekod cursor for å få startindeks
    start_index = 0
    if cursor:
        try:
            start_index = int(cursor)
        except ValueError:
            start_index = 0
    
    # Hent side med resultater
    end_index = min(start_index + PAGE_SIZE, len(ALL_TOOLS))
    page_tools = ALL_TOOLS[start_index:end_index]
    
    # Beregn neste 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"
});

// Simulert stort datasett
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) => {
  // Dekod pekeren
  let startIndex = 0;
  if (request.params?.cursor) {
    startIndex = parseInt(request.params.cursor, 10) || 0;
  }
  
  // Hent side med resultater
  const endIndex = Math.min(startIndex + PAGE_SIZE, ALL_TOOLS.length);
  const pageTools = ALL_TOOLS.slice(startIndex, endIndex);
  
  // Beregn neste peker
  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() {
        // Initialiser stort datasett
        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) {
        // Dekode markør
        int startIndex = 0;
        if (cursor != null && !cursor.isEmpty()) {
            try {
                startIndex = Integer.parseInt(cursor);
            } catch (NumberFormatException e) {
                startIndex = 0;
            }
        }
        
        // Hent side med resultater
        int endIndex = Math.min(startIndex + PAGE_SIZE, allTools.size());
        List<Tool> pageTools = allTools.subList(startIndex, endIndex);
        
        // Beregn neste markør
        String nextCursor = endIndex < allTools.size() ? String.valueOf(endIndex) : null;
        
        return new ListToolsResult(pageTools, nextCursor);
    }
}

Klientimplementering

Python-klient

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

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

TypeScript-klient

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

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

Lazy Loading-mønster

For veldig store datasett, last sider etter behov:

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):
        # Returner fra bufferen hvis tilgjengelig
        if self.buffer:
            return self.buffer.pop(0)
        
        # Sjekk om vi har gått gjennom alle sider
        if self.exhausted:
            raise StopAsyncIteration
        
        # Hent neste side
        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

# Bruk - minneeffektiv for store datasett
async for tool in PaginatedToolIterator(session):
    process_tool(tool)

Paginering for ressurser

Ressurser trenger ofte paginering for kataloger eller store datasett:

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))
    
    # Dekode markør (filindeks)
    start_index = int(cursor) if cursor else 0
    page_size = 20
    end_index = min(start_index + page_size, len(all_files))
    
    # Opprett ressursliste for denne siden
    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"
        ))
    
    # Beregn neste markør
    next_cursor = str(end_index) if end_index < len(all_files) else None
    
    return ListResourcesResult(
        resources=resources,
        nextCursor=next_cursor
    )

Kursor designstrategier

Strategi 1: Indeksbasert (Enkel)

# Markøren er bare indeksen
cursor = "50"  # Start ved element 50

Fordeler: Enkel, stateless
Ulemper: Resultater kan skifte hvis elementer legges til/fjernes

Strategi 2: ID-basert (Stabil)

# Markør er den sist sette ID-en
cursor = "item_abc123"  # Start etter dette elementet

Fordeler: Stabil selv om elementer endres
Ulemper: Krever ordnede ID-er

Strategi 3: Kodet tilstand (Kompleks)

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

# Markøren inneholder flere tilstands felt
cursor = encode_cursor({
    "offset": 50,
    "filter": "active",
    "sort": "name"
})

Fordeler: Kan kode kompleks tilstand
Ulemper: Mer kompleks, større kursorstrenger


Beste praksis

1. Velg passende sidestørrelser

# Vurder datastørrelsen
PAGE_SIZE_SMALL_ITEMS = 100   # Enkel metadata
PAGE_SIZE_MEDIUM_ITEMS = 20   # Rikere objekter
PAGE_SIZE_LARGE_ITEMS = 5     # Komplekst innhold

2. Håndter ugyldige kursorer grasiøst

@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  # Tilbakestill til begynnelsen
    except (ValueError, TypeError):
        start_index = 0  # Ugyldig markør, start på nytt
    # ...

3. Inkluder totalantall (valgfritt)

return ListToolsResult(
    tools=page_tools,
    nextCursor=next_cursor,
    # Noen implementasjoner inkluderer total for UI-fremdrift
    _meta={"total": len(ALL_TOOLS)}
)

4. Test nykkepunkter

async def test_pagination():
    # Tomt resultatssett
    result = await session.list_tools()
    assert result.tools == []
    assert result.nextCursor is None
    
    # Enkel side
    result = await session.list_tools()
    assert len(result.tools) <= PAGE_SIZE
    
    # Ugyldig peker
    result = await session.list_tools(cursor="invalid")
    assert result.tools  # Skal returnere første side

Vanlige feller

Returnere alle resultater og så paginere på klienten

# DÅRLIG: Laster alt inn i minnet
@app.list_tools()
async def list_tools() -> ListToolsResult:
    all_tools = load_all_tools()  # 1 million verktøy!
    return ListToolsResult(tools=all_tools)

Paginere ved datakilden

# BRA: Laster kun det som trengs
@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=...)

Hva er neste


Ekstra ressurser


Ansvarsfraskrivelse: Dette dokumentet er oversatt ved hjelp av AI-oversettelsestjenesten Co-op Translator. Selv om vi streber etter nøyaktighet, vennligst vær oppmerksom på at automatiserte oversettelser kan inneholde feil eller unøyaktigheter. Det opprinnelige dokumentet på sitt originale språk skal anses som den autoritative kilden. For kritisk informasjon anbefales profesjonell menneskelig oversettelse. Vi er ikke ansvarlige for misforståelser eller feiltolkninger som oppstår ved bruk av denne oversettelsen.