Paginering og Store Resultatsæt i MCP
February 8, 2026 · View on GitHub
Når din MCP-server håndterer store datasæt - hvad enten det er tusindvis af filer, databaseposter eller søgeresultater - har du brug for paginering for effektivt at administrere hukommelsen og levere hurtige brugeroplevelser. Denne vejledning dækker, hvordan du implementerer og bruger paginering i MCP.
Hvorfor Paginering Er Vigtigt
Uden paginering kan store svar medføre:
- Hukommelsesudtømning - Indlæsning af millioner af poster på én gang
- Langsomme svartider - Brugere venter mens alle data indlæses
- Timeout-fejl - Anmodninger overskrider timeout-grænser
- Dårlig AI-præstation - LLM’er har svært ved massiv kontekst
MCP bruger cursor-baseret paginering for pålidelig og konsistent navigation gennem resultatsæt.
Hvordan MCP Paginering Fungerer
Cursor-konceptet
En cursor er en uigennemsigtig streng, der markerer din position i et resultatsæt. Tænk på den som et bogmærke i en lang bog.
sequenceDiagram
participant Client
participant Server
Client->>Server: værktøjer/liste (ingen cursor)
Server-->>Client: værktøjer [1-10], næsteCursor: "abc123"
Client->>Server: værktøjer/liste (cursor: "abc123")
Server-->>Client: værktøjer [11-20], næsteCursor: "def456"
Client->>Server: værktøjer/liste (cursor: "def456")
Server-->>Client: værktøjer [21-25], næsteCursor: null (slut)
Paginering i MCP-metoder
Disse MCP-metoder understøtter paginering:
| Metode | Returnerer | Cursor-understøttelse |
|---|---|---|
tools/list | Værktøjsdefinitioner | ✅ |
resources/list | Ressourcedefinitioner | ✅ |
prompts/list | Promptdefinitioner | ✅ |
resources/templates/list | Ressemplar skabeloner | ✅ |
Serverimplementering
Python (FastMCP)
from mcp.server import Server
from mcp.types import Tool, ListToolsResult
import math
app = Server("paginated-server")
# Simuleret stort datasæt
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 at 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 næste 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"
});
// Simuleret stort datasæt
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 cursor
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 næste 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() {
// Initialiser stort datasæt
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) {
// Dekod cursor
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 næste cursor
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
# Anvendelse
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;
}
// Brug
const tools = await getAllTools(client);
console.log(`Found ${tools.length} tools`);
Lazy loading-mønster
For meget store datasæt kan du indlæse sider efter 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 buffer hvis tilgængelig
if self.buffer:
return self.buffer.pop(0)
# Tjek om vi har gennemgået alle sider
if self.exhausted:
raise StopAsyncIteration
# Hent næste 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
# Brug - hukommelseseffektiv til store datasæt
async for tool in PaginatedToolIterator(session):
process_tool(tool)
Paginering for Ressourcer
Ressourcer har ofte brug for paginering til mapper eller store datasæt:
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))
# Dekod cursor (filindeks)
start_index = int(cursor) if cursor else 0
page_size = 20
end_index = min(start_index + page_size, len(all_files))
# Opret ressource liste for denne side
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 næste cursor
next_cursor = str(end_index) if end_index < len(all_files) else None
return ListResourcesResult(
resources=resources,
nextCursor=next_cursor
)
Cursor Designstrategier
Strategi 1: Indeksbaseret (Simpel)
# Cursor er bare indekset
cursor = "50" # Start ved element 50
Fordele: Simpel, tilstandsløs
Ulemper: Resultater kan flytte sig, hvis elementer tilføjes/fjernes
Strategi 2: ID-baseret (Stabil)
# Cursor er det sidst sete ID
cursor = "item_abc123" # Start efter dette element
Fordele: Stabil selvom elementer ændres
Ulemper: Kræver ordnede IDs
Strategi 3: Encoded State (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 indeholder flere tilstandsfelter
cursor = encode_cursor({
"offset": 50,
"filter": "active",
"sort": "name"
})
Fordele: Kan kode kompleks tilstand
Ulemper: Mere kompleks, større cursor-strenge
Bedste Praksis
1. Vælg passende sidestørrelser
# Overvej datastørrelsen
PAGE_SIZE_SMALL_ITEMS = 100 # Enkel metadata
PAGE_SIZE_MEDIUM_ITEMS = 20 # Rigere objekter
PAGE_SIZE_LARGE_ITEMS = 5 # Kompleks indhold
2. Håndter ugyldige cursors yndefuldt
@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 # Nulstil til begyndelsen
except (ValueError, TypeError):
start_index = 0 # Ugyldig markør, start forfra
# ...
3. Inkluder total antal (valgfrit)
return ListToolsResult(
tools=page_tools,
nextCursor=next_cursor,
# Nogle implementeringer inkluderer total for UI-fremdrift
_meta={"total": len(ALL_TOOLS)}
)
4. Test kanttilfælde
async def test_pagination():
# Tomt resultatsæt
result = await session.list_tools()
assert result.tools == []
assert result.nextCursor is None
# Enkeltside
result = await session.list_tools()
assert len(result.tools) <= PAGE_SIZE
# Ugyldig cursor
result = await session.list_tools(cursor="invalid")
assert result.tools # Skal returnere første side
Almindelige Faldgruber
❌ Returner alle resultater og paginer på klientsiden
# DÅRLIGT: Indlæser alt i hukommelsen
@app.list_tools()
async def list_tools() -> ListToolsResult:
all_tools = load_all_tools() # 1 million værktøjer!
return ListToolsResult(tools=all_tools)
✅ Paginér ved datakilden
# GODT: Indlæser kun det, der er nødvendigt
@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=...)
Hvad Kommer Nu
Yderligere Ressourcer
Ansvarsfraskrivelse: Dette dokument er oversat ved hjælp af AI-oversættelsestjenesten Co-op Translator. Selvom vi stræber efter nøjagtighed, opfordres du til at være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det oprindelige dokument på originalsproget bør betragtes som den autoritative kilde. For vigtig information anbefales professionel menneskelig oversættelse. Vi påtager os intet ansvar for eventuelle misforståelser eller fejltolkninger, der opstår som følge af brugen af denne oversættelse.