refactor: replace lowlevel Server decorators with on_* constructor kwargs (#1985)

This commit is contained in:
Max Isbey 2026-02-12 15:55:54 +00:00 • committed by GitHub
commit 0a22a9dc33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,17 +1,19 @@
"""Simple MCP server demonstrating pagination for tools, resources, and prompts.
This example shows how to use the paginated decorators to handle large lists
of items that need to be split across multiple pages.
This example shows how to implement pagination with the low-level server API
to handle large lists of items that need to be split across multiple pages.
"""
from typing import Any
from typing import TypeVar
import anyio
import click
from mcp import types
from mcp.server.lowlevel import Server
from mcp.server import Server, ServerRequestContext
from starlette.requests import Request
T = TypeVar("T")
# Sample data - in real scenarios, this might come from a database
SAMPLE_TOOLS = [
types.Tool(
@ -44,6 +46,102 @@ SAMPLE_PROMPTS = [
]
def _paginate(cursor: str | None, items: list[T], page_size: int) -> tuple[list[T], str | None]:
"""Helper to paginate a list of items given a cursor."""
if cursor is not None:
try:
start_idx = int(cursor)
except (ValueError, TypeError):
return [], None
else:
start_idx = 0
page = items[start_idx : start_idx + page_size]
next_cursor = str(start_idx + page_size) if start_idx + page_size < len(items) else None
return page, next_cursor
# Paginated list_tools - returns 5 tools per page
async def handle_list_tools(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
cursor = params.cursor if params is not None else None
page, next_cursor = _paginate(cursor, SAMPLE_TOOLS, page_size=5)
return types.ListToolsResult(tools=page, next_cursor=next_cursor)
# Paginated list_resources - returns 10 resources per page
async def handle_list_resources(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListResourcesResult:
cursor = params.cursor if params is not None else None
page, next_cursor = _paginate(cursor, SAMPLE_RESOURCES, page_size=10)
return types.ListResourcesResult(resources=page, next_cursor=next_cursor)
# Paginated list_prompts - returns 7 prompts per page
async def handle_list_prompts(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListPromptsResult:
cursor = params.cursor if params is not None else None
page, next_cursor = _paginate(cursor, SAMPLE_PROMPTS, page_size=7)
return types.ListPromptsResult(prompts=page, next_cursor=next_cursor)
async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
# Find the tool in our sample data
tool = next((t for t in SAMPLE_TOOLS if t.name == params.name), None)
if not tool:
raise ValueError(f"Unknown tool: {params.name}")
return types.CallToolResult(
content=[
types.TextContent(
type="text",
text=f"Called tool '{params.name}' with arguments: {params.arguments}",
)
]
)
async def handle_read_resource(
ctx: ServerRequestContext, params: types.ReadResourceRequestParams
) -> types.ReadResourceResult:
resource = next((r for r in SAMPLE_RESOURCES if r.uri == str(params.uri)), None)
if not resource:
raise ValueError(f"Unknown resource: {params.uri}")
return types.ReadResourceResult(
contents=[
types.TextResourceContents(
uri=str(params.uri),
text=f"Content of {resource.name}: This is sample content for the resource.",
mime_type="text/plain",
)
]
)
async def handle_get_prompt(ctx: ServerRequestContext, params: types.GetPromptRequestParams) -> types.GetPromptResult:
prompt = next((p for p in SAMPLE_PROMPTS if p.name == params.name), None)
if not prompt:
raise ValueError(f"Unknown prompt: {params.name}")
message_text = f"This is the prompt '{params.name}'"
if params.arguments:
message_text += f" with arguments: {params.arguments}"
return types.GetPromptResult(
description=prompt.description,
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(type="text", text=message_text),
)
],
)
@click.command()
@click.option("--port", default=8000, help="Port to listen on for SSE")
@click.option(
@ -53,142 +151,15 @@ SAMPLE_PROMPTS = [
help="Transport type",
)
def main(port: int, transport: str) -> int:
app = Server("mcp-simple-pagination")
# Paginated list_tools - returns 5 tools per page
@app.list_tools()
async def list_tools_paginated(request: types.ListToolsRequest) -> types.ListToolsResult:
page_size = 5
cursor = request.params.cursor if request.params is not None else None
if cursor is None:
# First page
start_idx = 0
else:
# Parse cursor to get the start index
try:
start_idx = int(cursor)
except (ValueError, TypeError):
# Invalid cursor, return empty
return types.ListToolsResult(tools=[], next_cursor=None)
# Get the page of tools
page_tools = SAMPLE_TOOLS[start_idx : start_idx + page_size]
# Determine if there are more pages
next_cursor = None
if start_idx + page_size < len(SAMPLE_TOOLS):
next_cursor = str(start_idx + page_size)
return types.ListToolsResult(tools=page_tools, next_cursor=next_cursor)
# Paginated list_resources - returns 10 resources per page
@app.list_resources()
async def list_resources_paginated(
request: types.ListResourcesRequest,
) -> types.ListResourcesResult:
page_size = 10
cursor = request.params.cursor if request.params is not None else None
if cursor is None:
# First page
start_idx = 0
else:
# Parse cursor to get the start index
try:
start_idx = int(cursor)
except (ValueError, TypeError):
# Invalid cursor, return empty
return types.ListResourcesResult(resources=[], next_cursor=None)
# Get the page of resources
page_resources = SAMPLE_RESOURCES[start_idx : start_idx + page_size]
# Determine if there are more pages
next_cursor = None
if start_idx + page_size < len(SAMPLE_RESOURCES):
next_cursor = str(start_idx + page_size)
return types.ListResourcesResult(resources=page_resources, next_cursor=next_cursor)
# Paginated list_prompts - returns 7 prompts per page
@app.list_prompts()
async def list_prompts_paginated(
request: types.ListPromptsRequest,
) -> types.ListPromptsResult:
page_size = 7
cursor = request.params.cursor if request.params is not None else None
if cursor is None:
# First page
start_idx = 0
else:
# Parse cursor to get the start index
try:
start_idx = int(cursor)
except (ValueError, TypeError):
# Invalid cursor, return empty
return types.ListPromptsResult(prompts=[], next_cursor=None)
# Get the page of prompts
page_prompts = SAMPLE_PROMPTS[start_idx : start_idx + page_size]
# Determine if there are more pages
next_cursor = None
if start_idx + page_size < len(SAMPLE_PROMPTS):
next_cursor = str(start_idx + page_size)
return types.ListPromptsResult(prompts=page_prompts, next_cursor=next_cursor)
# Implement call_tool handler
@app.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentBlock]:
# Find the tool in our sample data
tool = next((t for t in SAMPLE_TOOLS if t.name == name), None)
if not tool:
raise ValueError(f"Unknown tool: {name}")
# Simple mock response
return [
types.TextContent(
type="text",
text=f"Called tool '{name}' with arguments: {arguments}",
)
]
# Implement read_resource handler
@app.read_resource()
async def read_resource(uri: str) -> str:
# Find the resource in our sample data
resource = next((r for r in SAMPLE_RESOURCES if r.uri == uri), None)
if not resource:
raise ValueError(f"Unknown resource: {uri}")
# Return a simple string - the decorator will convert it to TextResourceContents
return f"Content of {resource.name}: This is sample content for the resource."
# Implement get_prompt handler
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None) -> types.GetPromptResult:
# Find the prompt in our sample data
prompt = next((p for p in SAMPLE_PROMPTS if p.name == name), None)
if not prompt:
raise ValueError(f"Unknown prompt: {name}")
# Simple mock response
message_text = f"This is the prompt '{name}'"
if arguments:
message_text += f" with arguments: {arguments}"
return types.GetPromptResult(
description=prompt.description,
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(type="text", text=message_text),
)
],
)
app = Server(
"mcp-simple-pagination",
on_list_tools=handle_list_tools,
on_list_resources=handle_list_resources,
on_list_prompts=handle_list_prompts,
on_call_tool=handle_call_tool,
on_read_resource=handle_read_resource,
on_get_prompt=handle_get_prompt,
)
if transport == "sse":
from mcp.server.sse import SseServerTransport