# Support for MCP server features 1. [Prompts](#prompts) 1. [Resources](#resources) 1. [Tools](#tools) 1. [Multi Round-Trip Requests](#multi-round-trip-requests) 1. [Talking to legacy clients](#talking-to-legacy-clients) 1. [Example](#example) 1. [Cacheable list results](#cacheable-list-results) 1. [Utilities](#utilities) 1. [Completion](#completion) 1. [Logging](#logging) 1. [Capabilities](#capabilities) 1. [Capability inference](#capability-inference) 1. [Explicit capabilities](#explicit-capabilities) 1. [Extensions](#extensions) 1. [Pagination](#pagination) ## Prompts MCP servers can provide LLM prompt templates (called simply [_prompts_](https://modelcontextprotocol.io/specification/2025-06-18/server/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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Prompts) iterator, or the lower-level [`ClientSession.ListPrompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ListPrompts) (see [pagination](#pagination) below). Set [`ClientOptions.PromptListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.PromptListChangedHandler) to be notified of changes in the list of prompts. Call [`ClientSession.GetPrompt`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.GetPrompt) to retrieve a prompt by name, providing arguments for expansion. **Server-side**: Use [`Server.AddPrompt`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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. ```go func Example_prompts() { ctx := context.Background() promptHandler := func(ctx context.Context, req *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { return &mcp.GetPromptResult{ Description: "Hi prompt", Messages: []*mcp.PromptMessage{ { Role: "user", Content: &mcp.TextContent{Text: "Say hi to " + req.Params.Arguments["name"]}, }, }, }, nil } // Create a server with a single prompt. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) prompt := &mcp.Prompt{ Name: "greet", Arguments: []*mcp.PromptArgument{ { Name: "name", Description: "the name of the person to greet", Required: true, }, }, } s.AddPrompt(prompt, promptHandler) // Create a client. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) // Connect the server and client. t1, t2 := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() // List the prompts. for p, err := range cs.Prompts(ctx, nil) { if err != nil { log.Fatal(err) } fmt.Println(p.Name) } // Get the prompt. res, err := cs.GetPrompt(ctx, &mcp.GetPromptParams{ Name: "greet", Arguments: map[string]string{"name": "Pat"}, }) if err != nil { log.Fatal(err) } for _, msg := range res.Messages { fmt.Println(msg.Role, msg.Content.(*mcp.TextContent).Text) } // Output: // greet // user Say hi to Pat } ``` ## 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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Resources) and [`ClientSession.ResourceTemplates`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ResourceTemplates) iterators, or the lower-level `ListXXX` calls (see [pagination](#pagination)). Set [`ClientOptions.ResourceListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Subscribe) to subscribe to a resource and [`ClientSession.Unsubscribe`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Unsubscribe) to unsubscribe. Set [`ClientOptions.ResourceUpdatedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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`)](protocol.md#subscriptions-subscriptionslisten) for the wire-level details. **Server-side**: Use [`Server.AddResource`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddResource) or [`Server.AddResourceTemplate`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddResourceTemplate) to add a resource or resource template to the server along with its handler. A [`ResourceHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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. ```go func Example_resources() { ctx := context.Background() resources := map[string]string{ "file:///a": "a", "file:///dir/x": "x", "file:///dir/y": "y", } handler := func(_ context.Context, req *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { uri := req.Params.URI c, ok := resources[uri] if !ok { return nil, mcp.ResourceNotFoundError(uri) } return &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{{URI: uri, Text: c}}, }, nil } // Create a server with a single resource. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) s.AddResource(&mcp.Resource{URI: "file:///a"}, handler) s.AddResourceTemplate(&mcp.ResourceTemplate{URITemplate: "file:///dir/{f}"}, handler) // Create a client. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) // Connect the server and client. t1, t2 := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() // List resources and resource templates. for r, err := range cs.Resources(ctx, nil) { if err != nil { log.Fatal(err) } fmt.Println(r.URI) } for r, err := range cs.ResourceTemplates(ctx, nil) { if err != nil { log.Fatal(err) } fmt.Println(r.URITemplate) } // Read resources. for _, path := range []string{"a", "dir/x", "b"} { res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: "file:///" + path}) if err != nil { fmt.Println(err) } else { fmt.Println(res.Contents[0].Text) } } // Output: // file:///a // file:///dir/{f} // a // x // calling "resources/read": Resource not found } ``` ## Tools MCP servers can provide [tools](https://modelcontextprotocol.io/specification/2025-06-18/server/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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Tools) iterator, or the lower-level [`ClientSession.ListTools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ListTools) (see [pagination](#pagination)). Set [`ClientOptions.ToolListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ToolListChangedHandler) to be notified of changes in the list of tools. To call a tool, use [`ClientSession.CallTool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.CallTool) with `CallToolParams` holding the name and arguments of the tool to call. ```go 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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.AddTool) adds a [`Tool`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Tool) to the server along with its [`ToolHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.HasPrompts) 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 `StructuredOutput` 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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#AddTool) function that handles this for you. It can bind a tool to any function with the following shape: ```go 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`: ```go 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 `StructuredOutput`, 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: ```go type WeatherInput struct { Location Location `json:"location" jsonschema:"user location"` Days int `json:"days" jsonschema:"number of days to forecast"` } type WeatherOutput struct { Summary string `json:"summary" jsonschema:"a summary of the weather forecast"` Confidence Probability `json:"confidence" jsonschema:"confidence, between 0 and 1"` AsOf time.Time `json:"asOf" jsonschema:"the time the weather was computed"` DailyForecast []Forecast `json:"dailyForecast" jsonschema:"the daily forecast"` Source string `json:"source,omitempty" jsonschema:"the organization providing the weather forecast"` } func WeatherTool(ctx context.Context, req *mcp.CallToolRequest, in WeatherInput) (*mcp.CallToolResult, WeatherOutput, error) { perfectWeather := WeatherOutput{ Summary: "perfect", Confidence: 1.0, AsOf: time.Now(), } for range in.Days { perfectWeather.DailyForecast = append(perfectWeather.DailyForecast, Forecast{ Forecast: "another perfect day", Type: Sunny, Rain: 0.0, High: 72.0, Low: 72.0, }) } return nil, perfectWeather, nil } ``` 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: ```go // Distinguished Go types allow custom schemas to be reused during inference. customSchemas := map[reflect.Type]*jsonschema.Schema{ reflect.TypeFor[Probability](): {Type: "number", Minimum: jsonschema.Ptr(0.0), Maximum: jsonschema.Ptr(1.0)}, reflect.TypeFor[WeatherType](): {Type: "string", Enum: []any{Sunny, PartlyCloudy, Cloudy, Rainy, Snowy}}, } opts := &jsonschema.ForOptions{TypeSchemas: customSchemas} in, err := jsonschema.For[WeatherInput](opts) if err != nil { log.Fatal(err) } // Furthermore, we can tweak the inferred schema, in this case limiting // forecasts to 0-10 days. daysSchema := in.Properties["days"] daysSchema.Minimum = jsonschema.Ptr(0.0) daysSchema.Maximum = jsonschema.Ptr(10.0) // Output schema inference can reuse our custom schemas from input inference. out, err := jsonschema.For[WeatherOutput](opts) if err != nil { log.Fatal(err) } // Now add our tool to a server. Since we've customized the schemas, we need // to override the default schema inference. server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) mcp.AddTool(server, &mcp.Tool{ Name: "weather", InputSchema: in, OutputSchema: out, }, WeatherTool) ``` _See [mcp/tool_example_test.go](https://github.com/modelcontextprotocol/go-sdk/blob/main/mcp/tool_example_test.go) for the full example, or [examples/server/toolschemas](https://github.com/modelcontextprotocol/go-sdk/blob/main/examples/server/toolschemas/main.go) for more examples of customizing tool schemas._ **Stateless server deployments:** Some deployments create a new [`Server`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server) for each incoming request, re-registering tools every time. To avoid repeated schema generation, create a [`SchemaCache`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#SchemaCache) and share it across server instances: ```go 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) // ... } ``` ## Multi Round-Trip Requests [SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/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). ```go // Example_mrtr demonstrates the [Multi Round-Trip Requests] pattern // (SEP-2322). A tool handler signals "I need more information from the user" // by returning a [mcp.CallToolResult] whose `InputRequests` field carries the // requests for the additional information. Each request can be an // [mcp.ElicitParams] (ask the user a question), // [mcp.CreateMessageParams] (sample from the client's LLM), or // [mcp.ListRootsParams] (list the client's roots). // // On protocol version `2026-07-28` and later, the client's // `clientMultiRoundTripMiddleware` fulfils each input request from the // configured handler and retries the original call transparently. On earlier // protocol versions, the server's `serverMultiRoundTripMiddleware` performs // the equivalent dance from the server side by calling the legacy // server-to-client API (`ServerSession.Elicit`, `CreateMessage`, or // `ListRoots`) and re-invoking the handler with the response. Either way, // the user-facing client call site sees only the final result. // // [Multi Round-Trip Requests]: https://modelcontextprotocol.io/specification/draft/basic/patterns#multi-round-trip-requests func Example_mrtr() { ctx := context.Background() // Server: a "greet" tool that asks the user for their name before // returning a greeting. The handler runs twice per logical call: // once to issue the elicitation, once to consume the response. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) mcp.AddTool(s, &mcp.Tool{Name: "greet"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) { if len(req.Params.InputResponses) == 0 { // First call: ask the user for their name. The map key // ("user_name") is a label the handler chooses; the client must // echo it back in InputResponses. The wire-level method name // ("elicitation/create") is derived from the value's Go type. return &mcp.CallToolResult{ InputRequests: mcp.InputRequestMap{ "user_name": &mcp.ElicitParams{ Message: "What's your name?", RequestedSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "name": {Type: "string"}, }, }, }, }, // RequestState is an opaque token the client echoes back on // the retry, letting the handler resume its work without // per-session storage. RequestState: "step=1", }, nil, nil } // Retry: read the elicitation response and produce the final greeting. name := req.Params.InputResponses["user_name"].(*mcp.ElicitResult).Content["name"].(string) return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: "Hello " + name}}, }, nil, nil }) // Client: declares an elicitation handler. The SDK's MRTR middleware // uses it to fulfil any input request the tool handler returns. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ ElicitationHandler: func(_ context.Context, _ *mcp.ElicitRequest) (*mcp.ElicitResult, error) { return &mcp.ElicitResult{Action: "accept", Content: map[string]any{"name": "MCP Go"}}, nil }, }) ct, st := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, st, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, ct, nil) if err != nil { log.Fatal(err) } defer cs.Close() // Single call site. The MRTR round trip is invisible to the caller. res, err := cs.CallTool(ctx, &mcp.CallToolParams{Name: "greet"}) if err != nil { log.Fatal(err) } fmt.Println(res.Content[0].(*mcp.TextContent).Text) } ``` ## Cacheable list results [SEP-2549](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/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.** After paginating, the SDK sets `CacheScope = "public"` and leaves `TTLMs = 0` (which the client cache treats as "immediately stale"). A handler that wants its responses cached must set `TTLMs` explicitly on the returned result. ```go mcp.AddTool(server, &mcp.Tool{Name: "expensive"}, func(...) (*mcp.CallToolResult, any, error) { // ... }) // Override the default for tools/list: server.AddSendingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler { return func(ctx context.Context, method string, req mcp.Request) (mcp.Result, error) { res, err := next(ctx, method, req) if lr, ok := res.(*mcp.ListToolsResult); ok { lr.TTLMs = 30_000 // 30s cache freshness hint } return res, err } }) ``` ## Utilities ### Completion To support the [completion](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion) capability, the server needs a completion handler. **Client-side**: completion is called using the [`ClientSession.Complete`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Complete) method. **Server-side**: completion is enabled by setting [`ServerOptions.CompletionHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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. ```go myCompletionHandler := func(_ context.Context, req *mcp.CompleteRequest) (*mcp.CompleteResult, error) { // In a real application, you'd implement actual completion logic here. // For this example, we return a fixed set of suggestions. var suggestions []string switch req.Params.Ref.Type { case "ref/prompt": suggestions = []string{"suggestion1", "suggestion2", "suggestion3"} case "ref/resource": suggestions = []string{"suggestion4", "suggestion5", "suggestion6"} default: return nil, fmt.Errorf("unrecognized content type %s", req.Params.Ref.Type) } return &mcp.CompleteResult{ Completion: mcp.CompletionResultDetails{ HasMore: false, Total: len(suggestions), Values: suggestions, }, }, nil } // Create the MCP Server instance and assign the handler. // No server running, just showing the configuration. _ = mcp.NewServer(&mcp.Implementation{Name: "server"}, &mcp.ServerOptions{ CompletionHandler: myCompletionHandler, }) ``` ### Logging > **Note:** The logging feature is deprecated as of protocol version > 2026-07-28 > ([SEP-2577](https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging)). > 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 `SetLevel` (see below). For stateful sessions, the level defaults to "info". [`ServerSession.Log`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#NewLoggingHandler) to obtain a [`slog.Handler`](https://pkg.go.dev/log/slog#Handler). By setting [`LoggingHandlerOptions.MinInterval`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.LoggingMessageHandler) to receive log messages. Call [`ClientSession.SetLevel`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.SetLevel) to change the log level for a session. ```go func Example_logging() { ctx := context.Background() // Create a server. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) // Create a client that displays log messages. done := make(chan struct{}) // solely for the example var nmsgs atomic.Int32 c := mcp.NewClient( &mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ LoggingMessageHandler: func(_ context.Context, r *mcp.LoggingMessageRequest) { m := r.Params.Data.(map[string]any) fmt.Println(m["msg"], m["value"]) if nmsgs.Add(1) == 2 { // number depends on logger calls below close(done) } }, }) // Connect the server and client. t1, t2 := mcp.NewInMemoryTransports() ss, err := s.Connect(ctx, t1, nil) if err != nil { log.Fatal(err) } defer ss.Close() cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() // Set the minimum log level to "info". if err := cs.SetLoggingLevel(ctx, &mcp.SetLoggingLevelParams{Level: "info"}); err != nil { log.Fatal(err) } // Get a slog.Logger for the server session. logger := slog.New(mcp.NewLoggingHandler(ss, nil)) // Log some things. logger.Info("info shows up", "value", 1) logger.Debug("debug doesn't show up", "value", 2) logger.Warn("warn shows up", "value", 3) // Wait for them to arrive on the client. // In a real application, the log messages would appear asynchronously // while other work was happening. <-done // Output: // info shows up 1 // warn shows up 3 } ``` ## 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](#capability-inference), set [`ServerOptions.Capabilities`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#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. ```go // 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](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/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](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/pagination), using cursors. The SDK supports this by default. **Client-side**: The `ClientSession` provides methods returning [iterators](https://go.dev/blog/range-functions) for each feature type. These iterators are an `iter.Seq2[Feature, error]`, where the error value indicates whether page retrieval failed. - [`ClientSession.Prompts`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Prompts) iterates prompts. - [`ClientSession.Resource`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Resource) iterates resources. - [`ClientSession.ResourceTemplates`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.ResourceTemplates) iterates resource templates. - [`ClientSession.Tools`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Tools) iterates tools. 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`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.PageSize) to customize the page size.