go-sdk/internal/docs/server.src.md
rishabh fbd36cb787
docs/server: fix logging defaults and the SetLoggingLevel method name (#1251)
The Logging section of docs/server.md says "For stateful sessions" twice
in a row with opposite meanings, so one of the two sentences has to be
wrong. The default level of "info" is set in only one place in the SDK,
`StreamableHTTPHandler.ephemeralConnectOpts` in mcp/streamable.go, which
handles stateless requests; a session created through `Server.Connect`
starts with an empty `ServerSessionState.LogLevel`, and
`ServerSession.Log` returns early while the level is empty, which is
exactly what the first sentence already says. The second sentence now
reads "For stateless sessions". The same section also tells readers to
call `SetLevel`, but there is no such exported method: the client-side
call is `ClientSession.SetLoggingLevel`, and `setLevel` is only the
unexported server handler, so the pkg.go.dev link on the page points at
an anchor that does not exist. For context, the stateless default
arrived in #411 and the paragraph describing it was written later in
#497, which is where the duplicated "stateful" came from.

Checked with `go doc ./mcp ClientSession` (only `SetLoggingLevel` is
exported) and `grep -rn 'LogLevel = "info"' mcp/` (a single hit,
mcp/streamable.go:516); docs/server.md was regenerated with `go generate
./internal/docs`, and `go build ./... && go test ./...` pass.

---------

Signed-off-by: Rishabh <rishabhsaiv@gmail.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-15 11:44:03 +00:00

26 KiB

Support for MCP server features

%toc

Prompts

MCP servers can provide LLM prompt templates (called simply prompts) to clients. Every prompt has a required name which identifies it, and a set of named arguments, which are strings.

Client-side: To list the server's prompts, use the ClientSession.Prompts iterator, or the lower-level ClientSession.ListPrompts (see pagination below). Set ClientOptions.PromptListChangedHandler to be notified of changes in the list of prompts.

Call ClientSession.GetPrompt to retrieve a prompt by name, providing arguments for expansion.

Server-side: Use Server.AddPrompt to add a prompt to the server along with its handler. The server will have the prompts capability if any prompt is added before the server is connected to a client, or if ServerOptions.HasPrompts is explicitly set. When a prompt is added, any clients already connected to the server will be notified via a notifications/prompts/list_changed notification.

%include ../../mcp/server_example_test.go prompts -

Prompt message content

A PromptMessage has two fields: a Role, either "user" or "assistant", and a single Content. That is the same interface a tool result uses (see Tool result content), so a prompt message can hold an image, audio, or a resource's contents as readily as text.

Media on its own says nothing about what the model should do with it, so send a text message alongside it. Embedding a resource puts its contents directly in the message, sparing the client a separate resources/read. Note that ResourceContents.URI is not checked against the resources the server has registered: it records where the contents came from, so use the URI of a real resource when there is one, as in the example below.

%include ../../mcp/server_example_test.go promptcontent -

Resources

In MCP terms, a resource is some data referenced by a URI. MCP servers can serve resources to clients. They can register resources individually, or register a resource template that uses a URI pattern to describe a collection of resources.

Client-side: Call ClientSession.ReadResource to read a resource. The SDK ensures that a read succeeds only if the URI matches a registered resource exactly, or matches the URI pattern of a resource template.

To list a server's resources and resource templates, use the ClientSession.Resources and ClientSession.ResourceTemplates iterators, or the lower-level ListXXX calls (see pagination). Set ClientOptions.ResourceListChangedHandler to be notified of changes in the lists of resources or resource templates.

Clients can be notified when the contents of a resource changes by subscribing to the resource's URI. Call ClientSession.Subscribe to subscribe to a resource and ClientSession.Unsubscribe to unsubscribe. Set ClientOptions.ResourceUpdatedHandler to be notified of changes to subscribed resources. On 2026-07-28 and later sessions the SDK delivers these notifications over a subscriptions/listen stream instead of the legacy resources/subscribe RPC; see Subscriptions (subscriptions/listen) for the wire-level details.

Server-side: Use Server.AddResource or Server.AddResourceTemplate to add a resource or resource template to the server along with its handler. A ResourceHandler maps a URI to the contents of a resource, which can include text, binary data, or both. If AddResource or AddResourceTemplate is called before a server is connected, the server will have the resources capability. The server will have the resources capability if any resource or resource template is added before the server is connected to a client, or if ServerOptions.HasResources is explicitly set. When a prompt is added, any clients already connected to the server will be notified via a notifications/resources/list_changed notification.

%include ../../mcp/server_example_test.go resources -

Binary resources

A ResourceContents carries its data in either Text or Blob. Binary data goes in Blob as plain bytes — the SDK base64-encodes it on the wire — and leaves Text empty, so a client tells the two apart by which field is populated.

%include ../../mcp/server_example_test.go binaryresource -

Resource subscriptions

A client interested in changes to one resource subscribes to its URI with ClientSession.Subscribe and stops with ClientSession.Unsubscribe. Set ServerOptions.SubscribeHandler and UnsubscribeHandler to track who is listening; setting either one gives the server the resources.subscribe capability.

Publish a change with Server.ResourceUpdated, which notifies every session subscribed to that URI. The notification carries only the URI, not the new contents, so a client that wants them reads the resource again.

%include ../../mcp/server_example_test.go subscribe -

Tools

MCP servers can provide tools to allow clients to interact with external systems or functionality. Tools are effectively remote function calls, and the Go SDK provides mechanisms to bind them to ordinary Go functions.

Client-side: To list the server's tools, use the ClientSession.Tools iterator, or the lower-level ClientSession.ListTools (see pagination). Set ClientOptions.ToolListChangedHandler to be notified of changes in the list of tools.

To call a tool, use ClientSession.CallTool with CallToolParams holding the name and arguments of the tool to call.

res, err := session.CallTool(ctx, &mcp.CallToolParams{
	Name:      "my_tool",
	Arguments: map[string]any{"name": "user"},
})

Arguments may be any value that can be marshaled to JSON.

Server-side: the basic API for adding a tool is symmetrical with the API for prompts or resources: Server.AddTool adds a Tool to the server along with its ToolHandler to handle it. The server will have the tools capability if any tool is added before the server is connected to a client, or if ServerOptions.HasTools is explicitly set. When a tool is added, any clients already connected to the server will be notified via a notifications/tools/list_changed notification.

However, the Server.AddTool API leaves it to the user to implement the tool handler correctly according to the spec, providing very little out of the box. In order to implement a tool, the user must do all of the following:

  • Provide a tool input and output schema.
  • Validate the tool arguments against its input schema.
  • Unmarshal the input schema into a Go value
  • Execute the tool logic.
  • Marshal the tool's structured output (if any) to JSON, and store it in the result's StructuredContent field as well as the unstructured Content field.
  • Validate that output JSON against the tool's output schema.
  • If any tool errors occurred, pack them into the unstructured content and set IsError to true.

For this reason, the SDK provides a generic AddTool function that handles this for you. It can bind a tool to any function with the following shape:

func(_ context.Context, request *CallToolRequest, input In) (result *CallToolResult, output Out, _ error)

This is like a ToolHandler, but with an extra arbitrary In input parameter, and Out output parameter.

Such a function can then be bound to the server using AddTool:

mcp.AddTool(server, &mcp.Tool{Name: "my_tool"}, handler)

This does the following automatically:

  • If Tool.InputSchema is unset, the input schema is inferred from the In type, which must be a struct or map.
  • If Tool.OutputSchema is unset and the Out type is not any, the output schema is inferred from the Out type. Per SEP-2106, Out may be any Go type whose inferred schema is a valid JSON Schema (struct, map, slice, primitive, etc.).
  • Optional jsonschema struct tags provide argument and output descriptions.
  • Tool arguments are validated against the input schema.
  • Tool arguments are marshaled into the In value.
  • Tool output (the Out value) is marshaled into the result's StructuredContent, as well as the unstructured Content.
  • Output is validated against the tool's output schema.
  • If an ordinary error is returned, it is stored int the CallToolResult and IsError is set to true.

In fact, under ordinary circumstances, the user can ignore CallToolRequest and CallToolResult.

For a more realistic example, consider a tool that retrieves the weather:

%include ../../mcp/tool_example_test.go weathertool -

In this case, we want to customize part of the inferred schema, though we can still infer the rest. Since we want to control the inference ourselves, we set the Tool.InputSchema explicitly:

%include ../../mcp/tool_example_test.go customschemas -

See mcp/tool_example_test.go for the full example, or examples/server/toolschemas for more examples of customizing tool schemas.

Tool result content

Alongside its structured output, a tool result carries a list of Content blocks in CallToolResult.Content, and may mix as many kinds as it needs:

Type Carries
TextContent plain text
ImageContent image data, with a MIME type
AudioContent audio data, with a MIME type
EmbeddedResource the contents of a resource, inline
ResourceLink a reference to a resource the client can read separately

ImageContent.Data and AudioContent.Data are plain []byte; the SDK base64-encodes them on the wire, so handlers never encode by hand, and base64 carried over from another MCP SDK must be decoded before it is assigned. An EmbeddedResource wraps a ResourceContents, which holds either Text or, for binary data, Blob. Prefer a ResourceLink when the client may not need the contents, since an embedded resource is transferred whether it is used or not.

When a handler bound with AddTool leaves Content unset, the SDK fills it with the JSON encoding of the output value; set Content explicitly, as below, to return anything else. StructuredContent is still populated from the output value either way.

%include ../../mcp/tool_example_test.go contenttypes -

ToolUseContent and ToolResultContent also implement Content, but are only valid in sampling messages, not in tool results.

Stateless server deployments: Some deployments create a new Server for each incoming request, re-registering tools every time. To avoid repeated schema generation, create a SchemaCache and share it across server instances:

var schemaCache = mcp.NewSchemaCache() // create once at startup

func handleRequest(w http.ResponseWriter, r *http.Request) {
    s := mcp.NewServer(impl, &mcp.ServerOptions{SchemaCache: schemaCache})
    mcp.AddTool(s, myTool, myHandler)
    // ...
}

List changed notifications

Adding or removing a feature on a connected server sends the matching notifications/*/list_changed to every client, which dispatches it to ClientOptions.ToolListChangedHandler, PromptListChangedHandler, or ResourceListChangedHandler. A notification reports only that the list changed, so a client that needs the new contents lists them again.

These notifications ride on capabilities, and capabilities are inferred from what is registered before Server.Connect (see Capabilities). A server that registers its features afterwards, as the example below does, has to declare ServerOptions.HasTools, HasPrompts, or HasResources itself, or the client is never told.

%include ../../mcp/server_example_test.go listchanged -

Multi Round-Trip Requests

SEP-2322 defines a new pattern for server-to-client requests (sampling, elicitation, roots). Instead of issuing a fresh JSON-RPC request mid-flight, the server returns an InputRequiredResult from its in-flight handler — the InputRequests field of CallToolResult, GetPromptResult, or ReadResourceResult carries the requests for additional information. The client responds by retrying the original call with InputResponses set.

The SDK supports this pattern from both sides without requiring callers to choose:

  • Returning input requests from a handler. Set InputRequests on the result you return. Leave Content / StructuredContent empty — returning both at once is a server bug and the SDK returns -32603 InternalError.
  • Calling the legacy APIs. ServerSession.Elicit, ServerSession.CreateMessage(WithTools), and ServerSession.ListRoots remain available and work for both new and legacy clients.

Talking to legacy clients

The server installs serverMultiRoundTripMiddleware automatically. For clients on a protocol version earlier than 2026-07-28, the middleware intercepts any InputRequiredResult your handler returns, fulfils each input request itself by calling the legacy server-initiated APIs (Elicit, CreateMessage, ListRoots), and re-invokes your handler exactly once with the responses already populated. This means a handler written in the MRTR style works against both old and new clients without code changes.

Example

The following example shows a "greet" tool whose handler asks the user for their name via elicitation before producing the final greeting. The handler runs twice — once to issue the elicitation, once to consume the response — but the client call site sees a single CallTool returning the final result, because the SDK's MRTR middleware handles the round trip on either side of the wire (depending on the negotiated protocol version).

%include ../../mcp/server_example_test.go mrtr -

Cacheable list results

SEP-2549 adds ttlMs and cacheScope fields to the results of tools/list, prompts/list, resources/list, resources/templates/list, and resources/read. Both fields complement existing list results: clients use ttlMs as a freshness hint to reduce polling, and cacheScope ("public" or "private") controls whether shared intermediaries may cache the response.

Server-side defaults. The SDK fills in CacheScope = "public" whenever a result leaves it empty, since the field is required on the wire. Everything else is left as produced: a ResourceHandler that sets TTLMs or CacheScope on its ReadResourceResult keeps those values. The list results and server/discover are built by the SDK itself, so they start out with TTLMs = 0, which a client cache treats as "immediately stale".

Setting a policy. ServerOptions.SetCacheable decides the fields for every result that carries them. It runs after the corresponding handler, receives what that handler produced, and may keep, adjust, or replace it. The request is passed in so that policy can vary by method, session, or authorization context.

server := mcp.NewServer(impl, &mcp.ServerOptions{
    SetCacheable: func(_ context.Context, req mcp.Request, c *mcp.Cacheable) {
        // A 30s freshness hint, except where the handler chose its own.
        if c.TTLMs == 0 {
            c.TTLMs = 30_000
        }
    },
})

SetCacheable may run with the server's lock held, so it must not call back into the Server: adding or removing a feature, or ranging over Server.Sessions, deadlocks.

Utilities

Completion

To support the completion capability, the server needs a completion handler.

Client-side: completion is called using the ClientSession.Complete method.

Server-side: completion is enabled by setting ServerOptions.CompletionHandler. If this field is set to a non-nil value, the server will advertise the completions server capability, and use this handler to respond to completion requests.

%include ../../examples/server/completion/main.go completionhandler -

Logging

Note: The logging feature is deprecated as of protocol version 2026-07-28 (SEP-2577). It remains fully functional during the deprecation window (at least twelve months). The SDK continues to support logging for compatibility. Servers should migrate to stderr logging (for STDIO transports) or OpenTelemetry.

MCP servers can send logging messages to MCP clients. (This form of logging is distinct from server-side logging, where the server produces logs that remain server-side, for use by server maintainers.)

Server-side: The minimum log level is part of the server state. For stateful sessions, there is no default log level: no log messages will be sent until the client calls SetLoggingLevel (see below). For legacy stateless sessions the level defaults to "info". For sessionless 2026-07-28 requests there is no server-side default: each request carries its level in _meta (io.modelcontextprotocol/logLevel), and nothing is logged until the client sends one. (Note: logging is deprecated as of 2026-07-28 / SEP-2577.)

ServerSession.Log is the low-level way for servers to log to clients. It sends a logging notification to the client if the level of the message is at least the minimum log level.

For a simpler API, use NewLoggingHandler to obtain a slog.Handler. By setting LoggingHandlerOptions.MinInterval, the handler can be rate-limited to avoid spamming clients with too many messages.

Servers always report the logging capability.

Client-side: Set ClientOptions.LoggingMessageHandler to receive log messages.

Call ClientSession.SetLoggingLevel to change the log level for a session.

%include ../../mcp/server_example_test.go logging -

Capabilities

Server capabilities are advertised to clients during the initialization handshake. By default, the SDK advertises only the logging capability. Additional capabilities are automatically added when features are registered (e.g., adding a tool adds the tools capability).

Capability inference

When features such as tools, prompts, or resources are added to the server (e.g., via Server.AddTool), their capability is automatically inferred, with default value {listChanged:true}. Similarly, if the ServerOptions.SubscribeHandler or ServerOptions.CompletionHandler are set, the corresponding capability is added.

Explicit capabilities

To explicitly declare capabilities, or to override the default inferred capability, set ServerOptions.Capabilities. This sets the default server capabilities, before any capabilities are added based on configured handlers. If a capability is already present as a field in Capabilities, adding a feature or handler will not change its configuration.

This allows you to:

  • Disable default capabilities: Pass an empty &ServerCapabilities{} to disable all defaults, including logging.
  • Disable listChanged notifications: Set ListChanged: false on a capability to prevent the server from sending list-changed notifications when features are added or removed.
  • Pre-declare capabilities: Declare capabilities before features are registered, useful for servers that load features dynamically.
// Disable listChanged notifications for tools
server := mcp.NewServer(impl, &mcp.ServerOptions{
    Capabilities: &mcp.ServerCapabilities{
        Logging: &mcp.LoggingCapabilities{},
        Tools:   &mcp.ToolCapabilities{ListChanged: false},
    },
})

Deprecated: The HasPrompts, HasResources, and HasTools fields on ServerOptions are deprecated. Use Capabilities instead.

Extensions

SEP-2133 adds an extensions map to ServerCapabilities so that optional capabilities outside the core protocol can be declared on the wire. Keys are namespaced as "{vendor-prefix}/{extension-name}"; values are per-extension settings objects.

Pagination

Server-side feature lists may be paginated, using cursors. The SDK supports this by default.

Client-side: The ClientSession provides methods returning iterators for each feature type. These iterators are an iter.Seq2[Feature, error], where the error value indicates whether page retrieval failed.

The ClientSession also exposes ListXXX methods for fine-grained control over pagination.

Server-side: pagination is on by default, so in general nothing is required server-side. However, you may use ServerOptions.PageSize to customize the page size.