Low-Level Server

Purpose and Scope

Use the low-level Server API when you want the MCP protocol layer directly instead of the convenience behavior supplied by McpServer. The documentation defines Server as the layer under McpServer: it routes each JSON-RPC request to a handler registered for a method string, and nothing more. That makes it useful for framework authors, gateways, protocol experiments, compatibility work, and tests that need to observe the exact request and result shape. It is not the easiest path for normal tools, resources, prompts, or completions because you must declare capabilities, write list responses, dispatch calls, and validate inputs yourself.

Sources: docs/advanced/low-level-server.md

The main tradeoff is control versus safety. McpServer helpers such as registerTool accept a schema, derive the model-visible JSON Schema, validate incoming arguments before the handler runs, and infer handler argument types. With Server, the example writes the tool catalog by hand and casts request arguments inside the handler. That is appropriate when you deliberately need raw JSON-RPC method handling, but it also means client-visible behavior is exactly what your handler returns or throws. A bad argument can become a protocol error instead of a structured tool error if you do not validate before using it.

Sources: docs/advanced/low-level-server.md, docs/get-started/first-server.md

Relevant Source Files

  • docs/advanced/low-level-server.md — Primary guide for the low-level Server API, manual tools/list and tools/call handlers, capability declaration, fromJsonSchema validation, and the relationship to McpServer.
  • docs/clients/server-requests.md — Shows the same setRequestHandler style from the client side, including capability declaration and server-originated request methods such as elicitation/create and sampling/createMessage.
  • docs/get-started/first-server.md — Provides the high-level McpServer contrast: registerTool, Zod schema validation, typed handler arguments, content blocks, isError results, and stdio serving.
  • docs/.vitepress/theme/index.ts — Confirms the v2 documentation site theme composition, including the shared VitePress layout extension used to render this documentation family.
  • docs/v1/.vitepress/theme/index.ts — Shows the v1 documentation theme reusing the shared custom CSS, which helps distinguish current v2 low-level guidance from archived v1 documentation presentation.
  • docs/servers/completion.md — Demonstrates a high-level server feature where completable registers completion/complete and advertises completions automatically, a useful contrast with Server manual capability work.

When to Choose Server Instead of McpServer

Choose Server when the method string is the unit of your design. In the low-level guide, the tool list is produced by registering a handler for tools/list, and all tool invocations share one handler for tools/call. The SDK does not infer that tools exist from those handlers. You must declare the tools capability in the constructor options before registering tool handlers, and the response to tools/list is exactly the array you write. This is ideal when you are implementing an adapter that already has its own registry or when you want a deliberately minimal protocol surface.

Sources: docs/advanced/low-level-server.md

Prefer McpServer for application servers where the domain behavior matters more than protocol plumbing. The first-server tutorial builds a weather server with McpServer, registerTool, and a Zod input schema. In that flow, the developer writes one schema and one handler, while the SDK handles argument validation and tool advertisement. A rejected input such as an overlong state code fails before the handler runs, producing a model-readable validation failure. That default behavior is usually what product integrations want because it keeps handler code focused on business logic and makes invalid input predictable.

Sources: docs/get-started/first-server.md

The distinction also appears in completion support. The completion guide shows completable wrapping a prompt argument and notes that the first completable field registers the completion/complete handler and advertises the completions capability automatically. A low-level Server implementation of the same capability would need to manage the method registration and capability advertisement explicitly. That does not make Server inferior; it makes responsibilities visible. Use it when you need to decide exactly which methods exist, what they return, and how much validation or compatibility behavior to layer on top.

Sources: docs/servers/completion.md, docs/advanced/low-level-server.md

Core API Reference

ComponentDocumented contractResponsibility at low level
Server constructornew Server(serverInfo, options) with capabilities such as toolsDeclare every capability before method handlers rely on it
setRequestHandlerRegisters an async handler for a JSON-RPC method stringRoute requests such as tools/list or tools/call yourself
tools/listRequest method returning a tools arrayReturn names, descriptions, and raw inputSchema objects manually
tools/callRequest method for every tool invocationDispatch on request.params.name and read request.params.arguments
fromJsonSchemaExported from @modelcontextprotocol/serverWrap a JSON Schema object as a validator you explicitly run
McpServer.registerToolHigh-level helper shown in the first-server guideDerives JSON Schema, validates arguments, and types the handler
completableHigh-level helper for prompt/resource autocompleteRegisters completion handling and advertises completions automatically
Client.setRequestHandlerClient-side equivalent registration patternHandles server-originated methods such as elicitation/create

Sources: docs/advanced/low-level-server.md, docs/get-started/first-server.md, docs/clients/server-requests.md, docs/servers/completion.md

A compact Server catalog usually starts with explicit capability declaration and a list handler. The documented example constructs a catalog server with name and version metadata, passes capabilities containing tools, and then sets the tools/list handler. Each tool entry includes a name, description, and inputSchema written as raw JSON Schema. The client and the model see that schema exactly as written. If the capability is omitted, the docs warn that registering the tools/list handler throws, because Server does not infer capability support from the existence of a handler.

Sources: docs/advanced/low-level-server.md

Execution Flow and Validation Responsibilities

The documented tools/call handler is intentionally simple: it checks whether request.params.name is search, returns an isError tool result for unknown tools, casts request.params.arguments to the expected shape, filters the catalog, and returns text content. This is the direct protocol view of a tool call. There is one method handler for every tool, so any multi-tool low-level server needs its own dispatch table or conditional logic. The result shape is also under your control: returning content produces a normal tool result, while returning isError true communicates a handled tool-level failure to the model.

Sources: docs/advanced/low-level-server.md

The same guide demonstrates the main edge case: calling the tool with an object whose query value is a number reaches the handler because the protocol layer checks only that arguments is an object. When the handler calls toLowerCase, it crashes, and callTool rejects with a ProtocolError using code minus 32603. That is different from resolving to an isError tool result. Low-level code therefore needs a validation step before business logic, especially when input comes from an LLM or an external host and the handler assumes typed values.

Sources: docs/advanced/low-level-server.md

fromJsonSchema is the documented bridge for low-level validation. Instead of relying on an unchecked cast, create a validator from the same JSON Schema shape you expose in tools/list, then run it inside the tools/call handler before touching the arguments. The docs note that registering tools/call again replaces the previous handler, which is useful for evolving an example but important in real servers: each method should have one final handler. Treat the JSON Schema, validator, and dispatch logic as a unit so the model-visible contract and runtime checks stay aligned.

Sources: docs/advanced/low-level-server.md

Relationship to Client Capabilities and Server-Originated Requests

The client request guide reinforces a protocol rule that also matters for low-level servers: capabilities are explicit contracts. A client declares sampling or elicitation support in the Client constructor, and a server only sends requests for declared capabilities. Handlers are then registered with setRequestHandler for method names such as elicitation/create and sampling/createMessage. Low-level server authors should mirror that discipline on the server side. Before sending or accepting specialized methods, make sure the negotiated capabilities support them, and keep handler registration aligned with the advertised capability object rather than assuming registration alone is enough.

Sources: docs/clients/server-requests.md, docs/advanced/low-level-server.md

This is especially important for compatibility and protocol-version behavior. The client guide notes that the same handler can answer a request pushed by the server or one fulfilled inside a callTool round, and that delivery paths can depend on protocol version. A low-level Server implementation should avoid baking transport assumptions into method handlers. The handler should operate on the request and return a protocol result, while connection, version, and transport concerns are handled outside that function. That separation keeps raw method control from turning into transport-specific branching scattered across business logic.

Sources: docs/clients/server-requests.md

Documentation Site Signals and Version Context

The requested source set also includes the VitePress theme files for the v2 and v1 documentation trees. The current docs theme extends the default VitePress theme and injects a Banner component at the layout-top slot. The v1 theme does the same while importing shared custom CSS from the main docs theme directory. For readers, the practical signal is that this low-level Server page belongs to the current v2 documentation family, while the repository still preserves v1 docs presentation separately. Follow current v2 pages when using split packages such as @modelcontextprotocol/server.

Sources: docs/.vitepress/theme/index.ts, docs/v1/.vitepress/theme/index.ts

Practical Guidance and Next Steps

Start with McpServer unless you can name the protocol behavior you need to own. If you need only a normal tool, resource, prompt, or completion callback, the high-level APIs reduce repeated code and prevent avoidable validation mistakes. Move down to Server when you are writing an adapter, testing raw method behavior, replacing high-level registration with a custom registry, or implementing a capability that is not represented by a helper. When you do, write down the method names, declared capabilities, request parameter assumptions, result shapes, and validation path before adding business logic.

Sources: docs/advanced/low-level-server.md, docs/get-started/first-server.md

A safe implementation checklist is short but strict. Declare capabilities in the constructor. Register one handler per method string. For list methods, return the exact schema and metadata you want clients and models to see. For call methods, dispatch by name, validate arguments before use, and distinguish handled tool failures from protocol exceptions. If you later add completion, elicitation, sampling, or custom methods, repeat the same pattern: advertise only what is supported, register the exact method handler, and test both valid and invalid request payloads with an in-memory client before serving over stdio or HTTP.

Sources: docs/advanced/low-level-server.md, docs/clients/server-requests.md, docs/servers/completion.md