Low-Level Server API
Relevant source files
The following files were used as context for generating this wiki page:
The Low-Level Server API provides the foundational components for building custom Model Context Protocol (MCP) servers. It is designed for developers who require fine-grained control over request handling, session management, and transport integration. The core of this API is the mcp.server.Server class, which serves as a base for implementing the server's logic by registering specific handlers for different MCP methods.
This API is distinct from the higher-level FastMCP API, which offers a more declarative and simplified way to create servers. The low-level API is ideal for complex use cases, integration with existing web frameworks, or when custom behavior for handling resources, tools, and prompts is necessary.
Core Concepts
The Server Class
The central component of the low-level API is the mcp.server.Server class. To create a custom server, you must inherit from this class and implement handlers for the MCP capabilities you wish to support.
The Server class is initialized with a server name and provides a set of decorators to register handler functions for various MCP requests. It is transport-agnostic, meaning the core server logic is decoupled from the communication protocol (e.g., SSE, StreamableHTTP).
Sources: src/mcp/server/lowlevel/server.py:17-21, tests/shared/test_streamable_http.py:121-123
Request Handlers
Handlers are Python functions that contain the logic for responding to specific MCP requests. They are registered using decorators on an instance of a Server subclass. Each decorator corresponds to an MCP method.
The following diagram illustrates the relationship between a custom server implementation, the base Server class, and the handler decorators.
This diagram shows a custom ServerTest class inheriting from the base Server class and implementing its logic via handlers.
Sources: tests/shared/test_streamable_http.py:121-230, src/mcp/server/lowlevel/server.py:23-112
The available handler decorators are:
| Decorator | MCP Method | Purpose |
|---|---|---|
@server.list_tools() | tools/list | Returns a list of available tools. |
@server.call_tool() | tools/call | Executes a specific tool with given arguments. |
@server.list_resources() | resources/list | Returns a list of available resources. |
@server.read_resource() | resources/read | Reads the content of a specific resource. |
@server.list_prompts() | prompts/list | Returns a list of available prompts. |
@server.get_prompt() | prompts/get | Renders a specific prompt with given arguments. |
@server.complete() | complete | Provides completion suggestions for a resource or prompt argument. |
@server.create_message() | messages/create | Handles sampling requests from the client (e.g., for LLM interaction). |
@server.elicit() | elicit | Handles elicitation requests for user input. |
Sources: src/mcp/server/lowlevel/server.py:23-112, tests/shared/test_streamable_http.py:124-230
Request Context
Handlers can optionally receive a RequestContext object, which provides access to the current session, the original request, and methods for sending server-initiated notifications. To receive the context, the handler function must include a parameter type-hinted as mcp.server.fastmcp.Context (or RequestContext from mcp.shared.context).
The RequestContext is essential for:
- Accessing the
request_idof the current operation. - Sending progress updates, log messages, or other notifications via
ctx.session. - Accessing the underlying transport request object (e.g., a Starlette
Request).
Sources: tests/shared/test_streamable_http.py:190-192, tests/server/fastmcp/test_server.py:596-601
Architecture and Transport
The low-level Server is designed to be integrated with a transport layer that handles the specifics of communication. The StreamableHTTPServerTransport is a key component that adapts the server for HTTP-based communication.
The following diagram shows how a client request flows through the transport layer to the server.
This flowchart depicts the request flow from a client through the StreamableHTTP transport and session manager to the custom server's handler.
Sources: tests/shared/test_streamable_http.py:255-271, tests/shared/test_streamable_http.py:441-447
Request Lifecycle
The sequence diagram below details the interactions for a tools/call request.
This sequence diagram illustrates the step-by-step process of a tools/call request being handled by the server.
Sources: tests/shared/test_streamable_http.py:171-230, tests/shared/test_streamable_http.py:265-271
Defining a Custom Server
To build a server, create a class that inherits from mcp.server.Server and use decorators to register your handler functions.
Basic Server Example
Here is an example of a simple server that implements a sum tool and a get_weather tool.
# Sourced from examples/snippets/servers/lowlevel/basic.py
from mcp.server import Server
from mcp.types import Tool, TextContent
# 1. Create a server instance
server = Server(name="Tool Example")
# 2. Define and register the list_tools handler
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="sum",
description="Adds two numbers",
inputSchema={"type": "object", "properties": {"a": {"type": "integer"}, "b": {"type": "integer"}}},
),
Tool(
name="get_weather",
description="Gets the weather for a city",
inputSchema={"type": "object", "properties": {"city": {"type": "string"}}},
),
]
# 3. Define and register the call_tool handler
@server.call_tool()
async def call_tool(name: str, args: dict) -> list[TextContent]:
if name == "sum":
result = args["a"] + args["b"]
return [TextContent(type="text", text=str(result))]
elif name == "get_weather":
city = args["city"]
return [TextContent(type="text", text=f"Weather in {city}: 22degreesC")]
else:
raise ValueError(f"Unknown tool: {name}")
Sources: examples/snippets/servers/lowlevel/basic.py:4-32
Server with Structured Output
The low-level API also supports structured output, where a tool can return a Pydantic BaseModel instance. The server automatically serializes this to JSON for the structuredContent field in the response.
# Sourced from examples/servers/structured_output_lowlevel.py
import pydantic
from mcp.server import Server
from mcp.types import Tool, TextContent
# Define the structured output model
class Weather(pydantic.BaseModel):
temperature: float
condition: str
humidity: int
wind_speed: float
server = Server(name="Structured Output Example")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="get_weather",
description="Gets the weather for a city",
inputSchema={"type": "object", "properties": {"city": {"type": "string"}}},
# Define the output schema for the client
outputSchema=Weather.model_json_schema(),
)
]
@server.call_tool()
async def call_tool(name: str, args: dict) -> Weather:
if name == "get_weather":
# Return a Pydantic model instance directly
return Weather(
temperature=72.5,
condition="sunny",
humidity=45,
wind_speed=5.2,
)
raise ValueError(f"Unknown tool: {name}")
In this example, the call_tool handler returns a Weather object directly. The Server handles the conversion to the appropriate CallToolResult format, populating both the content (as a JSON string) and structuredContent fields.
Sources: examples/servers/structured_output_lowlevel.py:5-40
Conclusion
The Low-Level Server API provides a powerful and flexible way to create MCP servers in Python. By subclassing mcp.server.Server and implementing handlers with decorators, developers can gain full control over the server's behavior, manage state, and integrate with various transport protocols. While it requires more boilerplate code than the high-level FastMCP API, it is the ideal choice for building complex, high-performance, and deeply customized MCP applications.
Sources: src/mcp/server/lowlevel/server.py, tests/shared/test_streamable_http.py