go-sdk Documentation

The official Go SDK for Model Context Protocol servers and clients. Maintained in collaboration with Google.

5,258 stars Go Go 21 files · ~55,481 tokens #go#mcp
RAW Doc

Technical Documentation: modelcontextprotocol/go-sdk

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

MCP Go SDK


This repository contains an implementation of the official Go software
development kit (SDK) for the Model Context Protocol (MCP).

Package / Feature documentation

The SDK consists of several importable packages:

The SDK endeavors to implement the full MCP spec. The docs/ directory
contains feature documentation, mapping the MCP spec to the packages above.

Version Compatibility

The following table shows which versions of the Go SDK support which versions of the MCP specification:

SDK Version Latest MCP Spec All Supported MCP Specs
v1.7.0+ 2026-07-28 2026-07-28, 2025-11-25*, 2025-06-18, 2025-03-26, 2024-11-05
v1.4.0 - v1.6.1 2025-11-25* 2025-11-25*, 2025-06-18, 2025-03-26, 2024-11-05
v1.2.0 - v1.3.1 2025-11-25** 2025-11-25**, 2025-06-18, 2025-03-26, 2024-11-05
v1.0.0 - v1.1.0 2025-06-18 2025-06-18, 2025-03-26, 2024-11-05

* Client side OAuth has experimental support.

** Partial support for 2025-11-25 (client side OAuth and Sampling with tools not available).

The roots, sampling, and logging features are deprecated as of protocol version
2026-07-28 by
SEP-2577.
The SDK continues to support them for compatibility during the deprecation
window (at least twelve months). See the individual feature documentation for
migration guidance.

New releases of the SDK target only supported versions of Go. See
https://go.dev/doc/devel/release#policy for more information.

Getting started

To get started creating an MCP server, create an mcp.Server instance, add
features to it, and then run it over an mcp.Transport. For example, this
server adds a single simple tool, and then connects it to clients over
stdin/stdout:

go
package main

import (
	"context"
	"log"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

type Input struct {
	Name string `json:"name" jsonschema:"the name of the person to greet"`
}

type Output struct {
	Greeting string `json:"greeting" jsonschema:"the greeting to tell to the user"`
}

func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (
	*mcp.CallToolResult,
	Output,
	error,
) {
	return nil, Output{Greeting: "Hi " + input.Name}, nil
}

func main() {
	// Create a server with a single tool.
	server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
	mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
	// Run the server over stdin/stdout, until the client disconnects.
	if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
		log.Fatal(err)
	}
}

To communicate with that server, create an mcp.Client and connect it to the
corresponding server, by running the server command and communicating over its
stdin/stdout:

go
package main

import (
	"context"
	"log"
	"os/exec"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
	ctx := context.Background()

	// Create a new client, with no features.
	client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil)

	// Connect to a server over stdin/stdout.
	transport := &mcp.CommandTransport{Command: exec.Command("myserver")}
	session, err := client.Connect(ctx, transport, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	// Call a tool on the server.
	params := &mcp.CallToolParams{
		Name:      "greet",
		Arguments: map[string]any{"name": "you"},
	}
	res, err := session.CallTool(ctx, params)
	if err != nil {
		log.Fatalf("CallTool failed: %v", err)
	}
	if res.IsError {
		log.Fatal("tool failed")
	}
	for _, c := range res.Content {
		log.Print(c.(*mcp.TextContent).Text)
	}
}

The examples/ directory contains more example clients and
servers.

Contributing

We welcome contributions to the SDK! Please see
CONTRIBUTING.md for details of how to contribute.

Acknowledgements / Alternatives

Several third party Go MCP SDKs inspired the development and design of this
official SDK, and continue to be viable alternatives, notably
mcp-go, originally authored by Ed Zynda.
We are grateful to Ed as well as the other contributors to mcp-go, and to
authors and contributors of other SDKs such as
mcp-golang and
go-mcp. Thanks to their work, there
is a thriving ecosystem of Go MCP clients and servers.

License

This project is licensed under Apache 2.0 for new contributions, with existing
code under MIT - see the LICENSE file for details.


Overview (`docs/README.md`)

Overview

The SDK consists of several importable packages:

These docs mirror the official MCP spec.
Use the index below to learn how the SDK implements a particular aspect of the
protocol.

Base Protocol

  1. Lifecycle (Clients, Servers, and Sessions).
  2. Transports
    1. Stdio transport
    2. Streamable transport
    3. Custom transports
  3. Authorization
  4. Security
  5. Utilities
    1. Cancellation
    2. Ping
    3. Progress

Client Features

  1. Roots
  2. Sampling
  3. Elicitation

Server Features

  1. Prompts
  2. Resources
  3. Tools
  4. Utilities
    1. Completion
    2. Logging
    3. Pagination

TroubleShooting

See troubleshooting.md for a troubleshooting guide.

Backwards compatibility

See mcpgodebug.md for a list of backwards incompatible behavior changes
and description how they can be temporarily undone.

Rough edges

See rough_edges.md for a list of rough edges or API
oversights that can't be addressed due to our compatibility promise. We'll
revisit these if/when we move to a v2 of the SDK.


Overview (`examples/http/README.md`)

MCP HTTP Example

This example demonstrates how to use the Model Context Protocol (MCP) over HTTP using the streamable transport. It includes both a server and client implementation.

Overview

The example implements:

  • A server that provides a cityTime tool
  • A client that connects to the server, lists available tools, and calls the cityTime tool

Usage

Start the Server

bash
go run . server

This starts an MCP server on http://localhost:8000 (default) that provides a cityTime tool.

To run a client in another terminal:

bash
go run . client

The client will:

  1. Connect to the server
  2. List available tools
  3. Call the cityTime tool for NYC, San Francisco, and Boston
  4. Display the results

At any given time you can pass a custom URL to the program to run it on a custom host/port:

bash
go run . -host 0.0.0.0 -port 9000 server

Testing with real-world MCP Clients

Once the server is started, assuming it's the default localhost:8080, you can try to add it to a popular MCP client:

bash
claude mcp add -t http timezone http://localhost:8080

Once added, Claude Code will be able to discover and use the cityTime tool provided by this server.

In Claude Code:

text
> what's the timezone

⏺ I'll get the current time in a major US city for you.

⏺ timezone - cityTime (MCP)(city: "nyc")
  ⎿ The current time in New York City is 7:30:16 PM EDT on Wedn
    esday, July 23, 2025


⏺ The current timezone is EDT (Eastern Daylight Time), and it's
   7:30 PM on Wednesday, July 23, 2025.

> what timezones do you support?

⏺ The timezone tool supports three US cities:
  - NYC (Eastern Time)
  - SF (Pacific Time)
  - Boston (Eastern Time)

Overview (`examples/server/auth-middleware/README.md`)

MCP Server with Auth Middleware

This example demonstrates how to integrate the Go MCP SDK's auth.RequireBearerToken middleware with an MCP server to provide authenticated access to MCP tools and resources.

Features

The server provides authentication and authorization capabilities for MCP tools:

1. Authentication Methods

  • JWT Token Authentication: JSON Web Token-based authentication
  • API Key Authentication: API key-based authentication
  • Scope-based Access Control: Permission-based access to MCP tools

2. MCP Integration

  • Authenticated MCP Tools: Tools that require authentication and check permissions
  • Token Generation: Utility endpoints for generating test tokens
  • Middleware Integration: Seamless integration with MCP server handlers

Setup

bash
cd examples/server/auth-middleware
go mod tidy
go run main.go

Testing

bash
# Run all tests
go test -v

# Run benchmark tests
go test -bench=.

# Generate coverage report
go test -cover

Endpoints

Public Endpoints (No Authentication Required)

  • GET /health - Health check
  • GET /.well-known/oauth-protected-resource - OAuth 2.0 Protected Resource Metadata (RFC 9728)

MCP Endpoints (Authentication Required)

  • POST /mcp/jwt - MCP server with JWT authentication
  • POST /mcp/apikey - MCP server with API key authentication

Utility Endpoints

  • GET /generate-token - Generate JWT token
  • POST /generate-api-key - Generate API key

Available MCP Tools

The server provides three authenticated MCP tools:

1. Say Hi (`say_hi`)

A simple greeting tool that requires authentication.

Parameters:

  • None required

Required Scopes:

  • Any authenticated user

2. Get User Info (`get_user_info`)

Retrieves user information based on the provided user ID.

Parameters:

  • user_id (string): The user ID to get information for

Required Scopes:

  • read permission

3. Create Resource (`create_resource`)

Creates a new resource with the provided details.

Parameters:

  • name (string): The name of the resource
  • description (string): The description of the resource
  • content (string): The content of the resource

Required Scopes:

  • write permission

Example Usage

1. Generating JWT Token and Using MCP Tools

bash
# Generate a token
curl 'http://localhost:8080/generate-token?user_id=alice&scopes=read,write'

# Use MCP tool with JWT authentication
curl -H 'Authorization: Bearer <generated_token>' \
     -H 'Content-Type: application/json' \
     -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"say_hi","arguments":{}}}' \
     http://localhost:8080/mcp/jwt

2. Generating API Key and Using MCP Tools

bash
# Generate an API key
curl -X POST 'http://localhost:8080/generate-api-key?user_id=bob&scopes=read'

# Use MCP tool with API key authentication
curl -H 'Authorization: Bearer <generated_api_key>' \
     -H 'Content-Type: application/json' \
     -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_user_info","arguments":{"user_id":"test"}}}' \
     http://localhost:8080/mcp/apikey

3. Testing Scope Restrictions

bash
# Access MCP tool requiring write scope
curl -H 'Authorization: Bearer <token_with_write_scope>' \
     -H 'Content-Type: application/json' \
     -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_resource","arguments":{"name":"test","description":"test resource","content":"test content"}}}' \
     http://localhost:8080/mcp/jwt

Core Concepts

Authentication Integration

This example demonstrates how to integrate auth.RequireBearerToken middleware with an MCP server to provide authenticated access. The MCP server operates as an HTTP handler protected by authentication middleware.

Key Features

  1. MCP Server Integration: Create MCP server using mcp.NewServer
  2. Authentication Middleware: Protect MCP handlers with auth.RequireBearerToken
  3. Token Verification: Validate tokens using provided TokenVerifier functions
  4. Scope Checking: Verify required permissions (scopes) are present
  5. Expiration Validation: Check that tokens haven't expired
  6. Context Injection: Add verified token information to request context
  7. Authenticated MCP Tools: Tools that operate based on authentication information
  8. Error Handling: Return appropriate HTTP status codes and error messages on authentication failure

Implementation

go
// Create MCP server
server := mcp.NewServer(&mcp.Implementation{Name: "authenticated-mcp-server"}, nil)

// Create authentication middleware
authMiddleware := auth.RequireBearerToken(verifier, &auth.RequireBearerTokenOptions{
	Scopes: []string{"read", "write"},
})

// Create MCP handler
handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
	return server
}, nil)

// Apply authentication middleware to MCP handler
authenticatedHandler := authMiddleware(customMiddleware(handler))

Parameters

  • verifier: Function to verify tokens (TokenVerifier type)
  • opts: Authentication options
    • Scopes: List of required permissions
    • ResourceMetadataURL: OAuth 2.0 resource metadata URL

Error Responses

  • 401 Unauthorized: Token is invalid, expired, or missing
  • 403 Forbidden: Required scopes are insufficient
  • WWW-Authenticate Header: Included when resource metadata URL is configured

Implementation Details

1. TokenVerifier Implementation

go
func jwtVerifier(ctx context.Context, tokenString string) (*auth.TokenInfo, error) {
	// JWT token verification logic
	// On success: Return TokenInfo
	// On failure: Return auth.ErrInvalidToken
}

2. Using Authentication Information in MCP Tools

go
// Get authentication information in MCP tool
func MyTool(ctx context.Context, req *mcp.CallToolRequest, args MyArgs) (*mcp.CallToolResult, any, error) {
	// Extract authentication info from request
	userInfo := req.Extra.TokenInfo

	// Check scopes
	if !slices.Contains(userInfo.Scopes, "read") {
		return nil, nil, fmt.Errorf("insufficient permissions: read scope required")
	}

	// Execute tool logic
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			&mcp.TextContent{Text: "Tool executed successfully"},
		},
	}, nil, nil
}

3. Middleware Composition

go
// Combine authentication middleware with custom middleware
authenticatedHandler := authMiddleware(customMiddleware(mcpHandler))

Security Best Practices

  1. Environment Variables: Use environment variables for JWT secrets in production
  2. Database Storage: Store API keys in a database
  3. HTTPS Usage: Always use HTTPS in production environments
  4. Token Expiration: Set appropriate token expiration times
  5. Principle of Least Privilege: Grant only the minimum required scopes

CORS Support

The OAuth metadata endpoint (/.well-known/oauth-protected-resource) supports CORS to enable
cross-origin client discovery. It sets Access-Control-Allow-Origin: * by default because
OAuth metadata is public information meant for client discovery (RFC 9728 §3.1).

Custom CORS Policies

For more sophisticated CORS requirements (origin validation, credentials, etc.), wrap the handler
with a CORS middleware library:

Using github.com/rs/cors:

go
import "github.com/rs/cors"

c := cors.New(cors.Options{
    AllowedOrigins: []string{"https://example.com"},
    AllowedMethods: []string{"GET", "OPTIONS"},
})
http.Handle("/.well-known/oauth-protected-resource",
    c.Handler(auth.ProtectedResourceMetadataHandler(metadata)))

Using github.com/jub0bs/cors:

go
import "github.com/jub0bs/cors"

corsMiddleware, err := cors.NewMiddleware(cors.Config{
    Origins: []string{"https://example.com"},
    Methods: []string{"GET", "OPTIONS"},
})
http.Handle("/.well-known/oauth-protected-resource",
    corsMiddleware.Wrap(auth.ProtectedResourceMetadataHandler(metadata)))

Use Cases

Ideal for:

  • MCP servers requiring authentication and authorization
  • Applications needing scope-based access control
  • Systems requiring both JWT and API key authentication
  • Projects needing secure MCP tool access
  • Scenarios requiring audit trails and permission management

Examples:

  • Enterprise MCP servers with user management
  • Multi-tenant MCP applications
  • Secure API gateways with MCP integration
  • Development environments with authentication requirements
  • Production systems requiring fine-grained access control

Overview (`examples/server/proxy/README.md`)

Proxy Header Propagation Example

This example demonstrates how to propagate HTTP headers from an incoming MCP server request to an outgoing MCP client request.

This is particularly useful when building a gateway or proxy server that needs to forward authentication tokens, trace IDs, or other context-sensitive headers to downstream services.

Architecture

This example runs three components in a single process:

  1. Backend Server (Port 8082):

    • Exposes a tool echo_headers that returns the headers it received.
  2. Proxy Server (Port 8081):

    • Exposes a tool forward_headers.
    • Acts as a client to the Backend Server.
    • Uses a custom http.RoundTripper (HeaderForwardingTransport) to inject headers from the context into outgoing requests.
  3. Client:

    • Connects to the Proxy Server.
    • Calls forward_headers.

How it works

  1. The Client calls forward_headers on the Proxy Server.
  2. The Proxy Server receives the request. The request context contains the HTTP headers in req.Extra.Header.
  3. The Proxy's tool handler extracts these headers and places them into a new context.Context using a specific key (headerContextKey).
  4. The Proxy uses an mcp.Client configured with a custom HTTPClient that uses HeaderForwardingTransport.
  5. HeaderForwardingTransport.RoundTrip inspects the context of outgoing requests. If it finds headers under headerContextKey, it adds them to the HTTP request.
  6. The Backend Server receives the request with the propagated headers.

Running the Example

Run the example with:

bash
go run main.go

You should see output indicating:

  1. Backend and Proxy servers starting.
  2. Gateway receiving headers.
  3. Client receiving the result, which contains the echoed headers.

Example output:

text
2025/08/29 10:00:00 Starting Backend Server on :8082
2025/08/29 10:00:00 Starting Gateway Server on :8081
2025/08/29 10:00:00 Gateway received headers: map[...]
2025/08/29 10:00:00 Client received result: ...

Overview (`examples/server/sequentialthinking/README.md`)

Sequential Thinking MCP Server

This example shows a Model Context Protocol (MCP) server that enables dynamic and reflective problem-solving through structured thinking processes. It helps break down complex problems into manageable, sequential thought steps with support for revision and branching.

Features

The server provides three main tools for managing thinking sessions:

1. Start Thinking (`start_thinking`)

Begins a new sequential thinking session for a complex problem.

Parameters:

  • problem (string): The problem or question to think about
  • sessionId (string, optional): Custom session identifier
  • estimatedSteps (int, optional): Initial estimate of thinking steps needed

2. Continue Thinking (`continue_thinking`)

Adds the next thought step, revises previous steps, or creates alternative branches.

Parameters:

  • sessionId (string): The thinking session to continue
  • thought (string): The current thought or analysis
  • nextNeeded (bool, optional): Whether another thinking step is needed
  • reviseStep (int, optional): Step number to revise (1-based)
  • createBranch (bool, optional): Create an alternative reasoning path
  • estimatedTotal (int, optional): Update total estimated steps

3. Review Thinking (`review_thinking`)

Provides a complete review of the thinking process for a session.

Parameters:

  • sessionId (string): The session to review

Resources

Thinking History (`thinking://sessions` or `thinking://{sessionId}`)

Access thinking session data and history in JSON format.

  • thinking://sessions - List all thinking sessions
  • thinking://{sessionId} - Get specific session details

Core Concepts

Sequential Processing

Problems are broken down into numbered thought steps that build upon each other, maintaining context and allowing for systematic analysis.

Dynamic Revision

Any previous thought step can be revised and updated, with the system tracking which thoughts have been modified.

Alternative Branching

Create alternative reasoning paths to explore different approaches to the same problem, allowing for comparative analysis.

Adaptive Planning

The estimated number of thinking steps can be adjusted dynamically as understanding of the problem evolves.

Running the Server

Standard I/O Mode

bash
go run .

HTTP Mode

bash
go run . -http :8080

Example Usage

Starting a Thinking Session

json
{
  "method": "tools/call",
  "params": {
    "name": "start_thinking",
    "arguments": {
      "problem": "How should I design a scalable microservices architecture?",
      "sessionId": "architecture_design",
      "estimatedSteps": 8
    }
  }
}

Adding Sequential Thoughts

json
{
  "method": "tools/call", 
  "params": {
    "name": "continue_thinking",
    "arguments": {
      "sessionId": "architecture_design",
      "thought": "First, I need to identify the core business domains and their boundaries to determine service decomposition."
    }
  }
}

Revising a Previous Step

json
{
  "method": "tools/call",
  "params": {
    "name": "continue_thinking", 
    "arguments": {
      "sessionId": "architecture_design",
      "thought": "Actually, before identifying domains, I should analyze the current system's pain points and requirements.",
      "reviseStep": 1
    }
  }
}

Creating an Alternative Branch

json
{
  "method": "tools/call",
  "params": {
    "name": "continue_thinking",
    "arguments": {
      "sessionId": "architecture_design", 
      "thought": "Alternative approach: Start with a monolith-first strategy and extract services gradually.",
      "createBranch": true
    }
  }
}

Completing the Thinking Process

json
{
  "method": "tools/call",
  "params": {
    "name": "continue_thinking",
    "arguments": {
      "sessionId": "architecture_design",
      "thought": "Based on this analysis, I recommend starting with 3 core services: User Management, Order Processing, and Inventory Management.",
      "nextNeeded": false
    }
  }
}

Reviewing the Complete Process

json
{
  "method": "tools/call",
  "params": {
    "name": "review_thinking", 
    "arguments": {
      "sessionId": "architecture_design"
    }
  }
}

Session State Management

Each thinking session maintains:

  • Session metadata: ID, problem statement, creation time, current status
  • Thought sequence: Ordered list of thoughts with timestamps and revision history
  • Progress tracking: Current step and estimated total steps
  • Branch relationships: Links to alternative reasoning paths
  • Status management: Active, completed, or paused sessions

Use Cases

Ideal for:

  • Complex problem analysis requiring step-by-step breakdown
  • Design decisions needing systematic evaluation
  • Scenarios where initial scope is unclear and may evolve
  • Problems requiring alternative approach exploration
  • Situations needing detailed reasoning documentation

Examples:

  • Software architecture design
  • Research methodology planning
  • Strategic business decisions
  • Technical troubleshooting
  • Creative problem solving
  • Academic research planning

Client (`docs/client.md`)

Support for MCP client features

  1. Roots
    1. Roots list changed
  2. Sampling
  3. Elicitation
    1. Schema defaults and enums
    2. Completing a URL elicitation
  4. Multi Round-Trip Requests
  5. Capabilities
    1. Capability inference
    2. Explicit capabilities
    3. Extensions

Roots

Note: The roots 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 roots for compatibility. New code
should pass paths via tool parameters, resource URIs, or configuration
instead.

MCP allows clients to specify a set of filesystem
"roots".
The SDK supports this as follows:

Client-side: The SDK client always has the roots.listChanged capability.
To add roots to a client, use the
Client.AddRoots
and
Client.RemoveRoots
methods. If any servers are already connected to the
client, a call to AddRoot or RemoveRoots will result in a
notifications/roots/list_changed notification to each connected server.

Server-side: To query roots from the server, use the
ServerSession.ListRoots
method. To receive notifications about root changes, set
ServerOptions.RootsListChangedHandler.
For protocol versions 2026-07-28 and later, ListRoots requests are
delivered via the
Multi Round-Trip Requests
pattern.

text
/* Detailed source-code truncated for AI context efficiency. */

Roots list changed

Client.AddRoots and Client.RemoveRoots notify every connected server that
the list changed. Servers observe this through
ServerOptions.RootsListChangedHandler;
as with the server-side list-changed notifications, it reports only that
something changed, so read the list back with ServerSession.ListRoots.

go
func Example_rootsListChanged() {
	ctx := context.Background()

	changed := make(chan struct{}, 2)
	s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
		RootsListChangedHandler: func(context.Context, *mcp.RootsListChangedRequest) {
			changed <- struct{}{}
		},
	})

	c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
	c.AddRoots(&mcp.Root{URI: "file:///project"})

	t1, t2 := mcp.NewInMemoryTransports()
	ss, err := s.Connect(ctx, t1, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer ss.Close()

	// ListRoots is a server-initiated request, so this session negotiates a
	// protocol version that still allows one.
	cs, err := c.Connect(ctx, t2, &mcp.ClientSessionOptions{ProtocolVersion: "2025-11-25"})
	if err != nil {
		log.Fatal(err)
	}
	defer cs.Close()

	// Roots added after the client connects notify every connected server.
	c.AddRoots(&mcp.Root{URI: "file:///scratch"})
	<-changed

	// The notification says only that the list changed, so read it back.
	res, err := ss.ListRoots(ctx, nil)
	if err != nil {
		log.Fatal(err)
	}
	for _, root := range res.Roots {
		fmt.Println(root.URI)
	}

	c.RemoveRoots("file:///scratch")
	<-changed
	fmt.Println("roots changed again")

	// Output:
	// file:///project
	// file:///scratch
	// roots changed again
}

Sampling

Note: The sampling 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 sampling for compatibility. Servers
that need LLM completions should call LLM provider APIs directly.

Sampling
is a way for servers to leverage the client's AI capabilities. It is
implemented in the SDK as follows:

Client-side: To add the sampling capability to a client, set
ClientOptions.CreateMessageHandler.
This function is invoked whenever the server requests sampling.

Server-side: To use sampling from the server, call
ServerSession.CreateMessage.

For protocol versions 2026-07-28 and later, sampling requests are
delivered via the
Multi Round-Trip Requests
pattern.

text
/* Detailed source-code truncated for AI context efficiency. */

Elicitation

Elicitation
allows servers to request user inputs. It is implemented in the SDK as follows:

Client-side: To add the elicitation capability to a client, set
ClientOptions.ElicitationHandler.
The elicitation handler must return a result that matches the requested schema;
otherwise, elicitation returns an error. If your handler supports URL mode
elicitation
,
you must declare that capability explicitly (see Capabilities)

Server-side: To use elicitation from the server, call
ServerSession.Elicit.

For protocol versions 2026-07-28 and later, elicitation requests are
delivered via the
Multi Round-Trip Requests
pattern.

text
/* Detailed source-code truncated for AI context efficiency. */

Schema defaults and enums

ElicitParams.RequestedSchema is a flat schema of primitive fields, which the
client renders as a form. Two field keywords shape that form.

A Default (SEP-1034) prefills a
field. When the user accepts without supplying it, the SDK fills the field in
from the schema before the result reaches either side's caller — the client
does so after its elicitation handler returns, and ServerSession.Elicit does
so again on receipt. This is unconditional; there is no opt-in flag. Marking a
defaulted field Required defeats it: accepted content is validated against
the schema before defaults are applied, so an answer that omits the field is
rejected rather than defaulted.

An Enum (SEP-1330) restricts a
field to a fixed set of values, which the client renders as a choice. Enums
are supported only on "string" fields; declaring one on another type is
rejected. To label the choices, set the legacy enumNames keyword through
Schema.Extra, with exactly one name per enum value — a mismatched length is
rejected.

text
/* Detailed source-code truncated for AI context efficiency. */

Completing a URL elicitation

In URL mode the user finishes out of band, in a browser, so nothing in the
elicitation result tells the client when they are done. The server signals that
with
ServerSession.NotifyElicitationComplete,
passing the same ElicitationID the request carried; the client observes it
through
ClientOptions.ElicitationCompleteHandler.
Send it from whatever endpoint the hosted flow redirects back to.

The notification matters most when a handler rejects a request with
URLElicitationRequiredError:
the client parks the original request until a notification names that
ElicitationID, and then retries it automatically. Until one arrives, the
client waits.

text
/* Detailed source-code truncated for AI context efficiency. */

Multi Round-Trip Requests

SEP-2322
introduces the MRTR pattern: server-to-client requests for sampling,
elicitation, and roots are no longer issued as fresh JSON-RPC requests but
are carried inside the in-flight reply of a tools/call, prompts/get, or
resources/read. The client must respond by retrying the original request
with the produced responses.

The SDK installs clientMultiRoundTripMiddleware for every client by
default. The middleware:

  1. Inspects each tools/call/prompts/get/resources/read reply.
  2. If the result's NeedsInput() is true, fans out the InputRequests map
    concurrently, calling the configured handler for each (elicit,
    createMessage/createMessageWithTools, or listRoots).
  3. Threads the server-supplied opaque RequestState back unchanged.
  4. Retries the original request with the responses set, repeating until the
    result no longer needs input.

The middleware is enabled by default. To opt out, set
ClientOptions.MultiRoundTrip.Disabled = true;
the client will then surface input-required results directly to the caller
(the returned CallToolResult, GetPromptResult, or ReadResourceResult
will report NeedsInput() == true and expose the server's InputRequests
and opaque RequestState). Your code must fulfil each request and re-issue
the original call with InputResponses set and RequestState echoed
back.

For legacy (<= 2025-11-25) servers, the SDK transparently sends server
requests on the legacy server-initiated channel; the MRTR machinery is a
no-op in that direction. For legacy clients talking to MRTR-style servers,
the server SDK applies the inverse compatibility shim — see the
server-side documentation.

Capabilities

Client capabilities are advertised to servers during the initialization
handshake. By default, the SDK advertises the logging
capability. Additional capabilities are automatically added when server
features are added (e.g. via AddTool), or when handlers are set in the
ServerOptions struct (e.g., setting CompletionHandler adds the
completions capability), or may be configured explicitly.

Capability inference

When handlers are set on ClientOptions (e.g., CreateMessageHandler or
ElicitationHandler), the corresponding capability is automatically added if
not already present, with a default configuration.

For elicitation, if the handler is set but no Capabilities.Elicitation is
specified, the client defaults to form elicitation. To enable URL elicitation
or both modes, configure Capabilities.Elicitation
explicitly
.

See the ClientCapabilities
documentation

for further details on inference.

Explicit capabilities

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

This allows you to:

  • Disable default capabilities: Pass an empty &ClientCapabilities{} to
    disable all defaults, including roots.
  • Disable listChanged notifications: Set ListChanged: false on a
    capability to prevent the client from sending list-changed notifications
    when roots are added or removed.
  • Configure elicitation modes: Specify which elicitation modes (form, URL)
    the client supports.
go
// Configure elicitation modes and disable roots.
client := mcp.NewClient(impl, &mcp.ClientOptions{
    Capabilities: &mcp.ClientCapabilities{
        Elicitation: &mcp.ElicitationCapabilities{
            Form: &mcp.FormElicitationCapabilities{},
            URL:  &mcp.URLElicitationCapabilities{},
        },
    },
    ElicitationHandler: handler,
})

Extensions

SEP-2133
adds an extensions map to ClientCapabilities and 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.


Mcpgodebug (`docs/mcpgodebug.md`)

Backwards compatibility and MCPGODEBUG

According to our compatibility promise, we can't break backward compatibility
of the SDK API. However, sometimes we need to change the behavior of the SDK
in a backward-incompatible way in order to fix bugs or security issues.
In those cases we introduce temporary compatibility parameters, that can be
used to opt-out of the new behavior. They are usually maintained for two
minor release cycles and then removed.

The compatibility parameters are provided via the MCPGODEBUG environment
variable. The value of the variable is a comma-separated list of parameter
value assignments, e.g.:

text
MCPGODEBUG=parameter1=value1,parameter2=value2

`MCPGODEBUG` history

1.9.0

Options listed below were added and will be removed in the 1.11.0 version of the SDK.

  • structuredcontentfloat64 added. If set to 1, numbers in
    CallToolResult.StructuredContent are decoded as float64, restoring the
    previous behavior. The default behavior decodes numbers as json.Number so
    that large integers and other exact JSON values do not silently lose
    precision.

  • disablepromptargsvalidation added. If set to 1, Server.getPrompt will
    call the prompt handler even when a required argument of the prompt is
    missing from the request, restoring the previous behavior. The default
    behavior was changed to reject such requests with -32602 (Invalid Params),
    as the specification asks.

  • allowmissingclientparams added. If set to 1, a Client accepts
    elicitation/create, notifications/elicitation/complete and
    notifications/resources/updated messages whose params member is missing
    or null, restoring the previous behavior (the handler then observes nil
    params, which crashed the built-in elicitation handlers). The default
    behavior was changed to reject such messages as invalid requests, as the
    specification lists params as required for all three.

  • nopreinitinvalidrequest added. If set to 1, a legacy server session that
    has not been initialized refuses a request other than initialize or ping
    with JSON-RPC error code 0, restoring the previous behavior. The default
    behavior was changed to refuse it with -32600 (Invalid Request): 0
    tells a client nothing and is indistinguishable from an unset code. See
    issue #1367.

  • nointernalerrorcode added. If set to 1, an error returned by a request
    handler that carries no JSON-RPC error code is sent with code 0, restoring
    the previous behavior. The default behavior was changed to send it with
    -32603 (Internal error): 0 tells the peer nothing and is
    indistinguishable from an unset code. The error's message is unchanged.
    See issue #1394.

  • fatalnewprotocolerrors added. If set to 1, a streamable HTTP client
    permanently fails its connection when an independent 2026-07-28 request
    fails, restoring the previous behavior. By default, the failure is returned
    only to the affected call.

1.8.0

Options listed below were added and will be removed in the 1.9.0 version of the SDK.

  • plaintextstatefulrejection added. If set to 1, a stateful
    StreamableHTTPHandler will respond with a plain-text http.Error 400 body
    when it receives a request carrying per-request metadata (i.e. an
    io.modelcontextprotocol/protocolVersion _meta field, or an
    MCP-Protocol-Version header >= 2026-07-28), restoring the previous
    behavior. The default behavior was changed so that the server responds with a
    JSON-RPC error of code CodeUnsupportedProtocolVersion (-32022) carrying
    an UnsupportedProtocolVersionData payload that advertises the legacy
    versions the server supports. This lets the client's
    existing renegotiation logic recover and prevents the failure from tearing
    down the underlying connection.

  • blockingcancelnotify added. If set to 1, a cancelled call waits
    synchronously for the best-effort notifications/cancelled message to be
    delivered (up to notifyCancellationTimeout, currently 5 s) before
    returning to the caller, restoring the previous behavior. The delivery
    error is joined into the caller's returned error. The default behavior was
    changed so that the call is retired immediately and the notification is
    sent asynchronously off the caller's return path: the caller returns as
    soon as its context is cancelled and cannot be delayed by a slow or
    unresponsive peer. See issue #1150.

Options below were removed, according to plan:

  • seterroroverwrite. SetError now always preserves existing Content.

  • enableoriginverification. A nil
    StreamableHTTPOptions.CrossOriginProtection now always means no
    cross-origin protection. Wrap the handler with cross-origin protection
    middleware instead.

  • disablecontenttypecheck. Content-Type validation on HTTP POST requests can
    no longer be disabled.

  • disablelocalhostprotection, whose removal had been postponed to this
    release. Set the DisableLocalhostProtection field in
    StreamableHTTPOptions or SSEOptions to opt out of DNS rebinding
    protection.

1.7.0

Options listed below were added and will be removed in the 1.9.0 version of the SDK.

  • customresnotfounderrcode added. If set to 1, ResourceNotFoundError will
    use the custom error code -32002 instead of the standard -32602 (Invalid
    Params), restoring the previous behavior. The default behavior was changed to
    align with SEP-2164 and the JSON-RPC specification.

  • hintomitempty added. If set to 1, ToolAnnotations JSON marshaling
    will omit ReadOnlyHint and IdempotentHint when their value is false,
    restoring the previous behavior. The default behavior was changed to always
    serialize these fields, since their Go types are bare bool (not *bool)
    and omitting false made it indistinguishable from unset.

  • allowsessionsinstateless added. If set to 1, stateless streamable HTTP
    servers will read the Mcp-Session-Id request header (or generate one via
    GetSessionID), set it on response headers, and accept DELETE requests,
    restoring the previous behavior. The default behavior was changed so that
    stateless servers ignore session IDs entirely and reject DELETE with 405.

  • nomethodnotfoundcodeinerror added. If set to 1, the jsonrpc2 layer will not
    include the MethodNotFound Error (-32601) in the error response when the
    requested method in STDIO transport is not found. The default behavior was
    changed to include the MethodNotFound Error in the error response when the
    requested method in STDIO transport is not found.

  • noprotocolerrorbody added. If set to 1, the streamable HTTP client will
    not attempt to decode the JSON-RPC error body of a non-2xx HTTP response,
    and any non-transient error will permanently fail the connection, restoring
    the previous behavior. The default behavior was changed so that the client
    attempts to decode the JSON-RPC error body of a non-2xx response, surfaces
    the underlying JSON-RPC error, and wraps it with jsonrpc2.ErrRejected so
    that per-call rejections do not tear down the session.

  • nowrapinvalidparams added. If set to 1, the server will not wrap
    params-decoding failures with jsonrpc2.ErrInvalidParams, so wire responses
    carry the zero-value error code 0 instead of -32602 ("invalid params"),
    restoring the previous behavior. The default behavior was changed so that
    the server always reports params-decoding failures with the standard
    -32602 code.

  • disablecompleteparamsvalidation added. If set to 1, Server.complete
    will not validate that the required ref and argument.name fields on
    CompleteParams are present, restoring the previous behavior of dispatching
    the request to the completion handler unconditionally. The default behavior
    was changed to reject malformed requests with -32602 (Invalid Params).

1.6.1

Options listed below were added and will be removed in the 1.8.0 version of the SDK.

  • disablecontenttypecheck added. If set to 1, Content-Type validation on
    HTTP POST requests will be disabled, allowing requests with non-JSON or missing
    Content-Type headers. The default behavior is to validate that HTTP POST
    requests have Content-Type: application/json.

1.6.0

Options listed below were added and will be removed in the 1.8.0 version of the SDK.

  • seterroroverwrite added. If set to 1, SetError will always overwrite
    Content with the error text, restoring the previous behavior. The default
    behavior was changed to preserve existing Content if it has already been
    populated.

  • enableoriginverification added. If set to 1, default (zero-value)
    cross-origin protection will be applied when
    StreamableHTTPOptions.CrossOriginProtection is nil, restoring the
    behavior from v1.4.1-v1.5.0. The default behavior was changed to not
    enable cross-origin protection.

  • disablelocalhostprotection removal was postponed until 1.8.0, as it is now
    also used in the SSE transport.

Options below were removed:

  • jsonescaping, according to plan,

  • disablecrossoriginprotection, it was replaced by
    enableoriginverification after the default was changed to not enable
    cross-origin protection.

1.4.1

Options listed below will be removed in the 1.6.0 version of the SDK.

  • disablecrossoriginprotection added. If set to 1, newly added cross-origin
    protection will be disabled. The default behavior was changed to enable
    cross-origin protection.

1.4.0

Options listed below will be removed in the 1.6.0 version of the SDK.

  • jsonescaping added. If set to 1, JSON marshaling will preserve the previous
    behavior of escaping HTML characters in JSON strings. The default behavior
    was changed to not escape HTML characters, to be consistent with other SDKs.

  • disablelocalhostprotection added. If set to 1, newly added DNS rebinding
    protection will be disabled. The default behavior was changed to enable DNS rebinding
    protection. The protection can also be disabled by setting the
    DisableLocalhostProtection field in the StreamableHTTPOptions or
    SSEOptions struct to true, which is the recommended way to disable
    the protection long term. Removal of this option was postponed until 1.8.0.


Protocol (`docs/protocol.md`)

Support for the MCP base protocol

  1. Lifecycle
    1. Discovery
    2. Per-request metadata keys
    3. Per-response metadata keys
    4. Subscriptions
  2. Transports
    1. Stdio Transport
    2. Streamable Transport
    3. Legacy SSE Transport
    4. Custom transports
    5. Concurrency
  3. Authorization
    1. Server
    2. Client
  4. Security
    1. Confused Deputy
    2. Token Passthrough
    3. Server-Side Request Forgery
    4. Session Hijacking
    5. Issuer Mix-Up
  5. Utilities
    1. Cancellation
    2. Ping
    3. Progress
    4. Error codes

Lifecycle

The SDK provides an API for defining both MCP clients and servers, and
connecting them over various transports. When a client and server are
connected, it creates a logical session.

The MCP specification defines two lifecycle models, and the SDK supports both
transparently based on the negotiated protocol version:

  • The legacy initialize handshake
    (lifecycle)
    used by protocol versions through 2025-11-25.
  • A stateless model introduced in 2026-07-28 by
    SEP-2575,
    in which there is no initialize/notifications/initialized handshake, and
    each request carries its protocol version and client capabilities in
    _meta. Clients SHOULD also include their identity (clientInfo) on every
    request, and servers SHOULD include their identity (serverInfo) on every
    response.

In both models, the SDK exposes the same API:

  • A Client is a logical MCP client, configured with various
    ClientOptions.
  • When a client is connected to a server using
    Client.Connect,
    it creates a
    ClientSession.
    This session is initialized during the Connect method, and provides methods
    to communicate with the server peer.
  • A Server is a logical MCP server, configured with various
    ServerOptions.
  • When a server is connected to a client using
    Server.Connect,
    it creates a
    ServerSession.

In the legacy model, the server session is not considered initialized until
the client sends the notifications/initialized message. Use
ServerOptions.InitializedHandler to listen for this event, or just use the
session through various feature handlers (such as a ToolHandler). Requests
to the server are rejected until the client has initialized the session.

In the stateless model (2026-07-28+), there is no handshake. The server
processes the first request the moment it arrives, validates the per-request
_meta fields.

Both ClientSession and ServerSession have a Close method to terminate the
session, and a Wait method to await session termination by the peer. Typically,
it is the client's responsibility to end the session.

go
func Example_lifecycle() {
	ctx := context.Background()

	// Create a client and server. Under protocol version 2026-07-28, there
	// is no dedicated initialize/initialized handshake: the session is
	// implicitly live the moment the client issues its first request.
	client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
	server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)

	// Connect the server and client using in-memory transports.
	t1, t2 := mcp.NewInMemoryTransports()
	serverSession, err := server.Connect(ctx, t1, nil)
	if err != nil {
		log.Fatal(err)
	}
	clientSession, err := client.Connect(ctx, t2, nil)
	if err != nil {
		log.Fatal(err)
	}

	// Now shut down the session by closing the client, and waiting for the
	// server session to end.
	if err := clientSession.Close(); err != nil {
		log.Fatal(err)
	}
	if err := serverSession.Wait(); err != nil {
		log.Fatal(err)
	}
	// Output:
}

Discovery

Introduced in 2026-07-28 by
SEP-2575,
the server/discover RPC lets a client discover a server's supported
protocol versions, capabilities, and identity before issuing any other
request. Servers implementing 2026-07-28 MUST implement it.

  • Server: Server.Connect registers the
    server/discover handler automatically; the response is computed from the
    server's static configuration (capabilities, server info, instructions) and
    the transport-filtered list of supported protocol versions.
  • Client: Client.Connect calls server/discover first and
    uses the result to negotiate a mutually supported version. If discovery
    fails or the server does not support the latest version, the client falls back to the
    legacy initialize handshake.

A server advertises and negotiates every protocol version the SDK supports;
set ServerOptions.SupportedProtocolVersions
to narrow that set, for example to stop serving a revision your deployment has
retired.

go
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v1.0.0"}, &mcp.ServerOptions{
	SupportedProtocolVersions: []string{"2026-07-28", "2025-11-25"},
})

The set can only narrow support, never widen it; the field's documentation
covers how each protocol era answers a request at an excluded version.

Per-request metadata keys

When the negotiated protocol version is 2026-07-28 or later, every request
carries these keys inside its _meta map (constants live in
mcp/protocol.go):

Constant Wire key Type Required
MetaKeyProtocolVersion io.modelcontextprotocol/protocolVersion string Yes
MetaKeyClientCapabilities io.modelcontextprotocol/clientCapabilities *ClientCapabilities Yes
MetaKeyClientInfo io.modelcontextprotocol/clientInfo *Implementation No
MetaKeyLogLevel io.modelcontextprotocol/logLevel LoggingLevel (deprecated by SEP-2577) No

The client populates the required keys automatically on every outgoing
request, and populates clientInfo when configured with an *Implementation
(the default). Server-side handlers can read them through
ServerRequest[P].ProtocolVersion(), ServerRequest[P].ClientInfo(), and
ServerRequest[P].ClientCapabilities().

Per-response metadata keys

Under the same protocol version, servers SHOULD identify themselves on every
response. The SDK populates this
key automatically on every outgoing response:

Constant Wire key Type Required
MetaKeyServerInfo io.modelcontextprotocol/serverInfo *Implementation No

Subscriptions

Introduced in 2026-07-28 by
SEP-2575,
subscriptions/listen replaces the legacy resources/subscribe RPC and
the GET-based SSE endpoint with a single long-lived request that
multiplexes every kind of server-to-client change notification a client
opts in to. On the wire the client sends one subscriptions/listen
request whose notifications field enumerates what it wants
(toolsListChanged, promptsListChanged, resourcesListChanged, and/or
a list of resource URIs in resourceSubscriptions); the server replies
first with a notifications/subscriptions/acknowledged notification
reporting the honored subset, then streams every change notification on
the same request, and finally closes with a SubscriptionsListenResult
when it tears the subscription down.

Transports

A
transport
can be used to send JSON-RPC messages from client to server, or vice-versa.

In the SDK, this is achieved by implementing the
Transport
interface, which creates a (logical) bidirectional stream of JSON-RPC messages.
Most transport implementations described below are specific to either the
client or server: a "client transport" is something that can be used to connect
a client to a server, and a "server transport" is something that can be used to
connect a server to a client. However, it's possible for a transport to be both
a client and server transport, such as the InMemoryTransport used in the
lifecycle example above.

Transports should not be reused for multiple connections: if you need to create
multiple connections, use different transports.

Stdio Transport

In the
stdio
transport clients communicate with an MCP server running in a subprocess using
newline-delimited JSON over its stdin/stdout.

Client-side: the client side of the stdio transport is implemented by
CommandTransport,
which starts the a exec.Cmd as a subprocess and communicates over its
stdin/stdout.

Server-side: the server side of the stdio transport is implemented by
StdioTransport,
which connects over the current processes os.Stdin and os.Stdout.

Streamable Transport

The streamable
transport

API is implemented across three types:

  • StreamableHTTPHandler: anhttp.Handler that serves streamable MCP
    sessions.
  • StreamableServerTransport: a Transport that implements the server side of
    the streamable transport.
  • StreamableClientTransport: a Transport that implements the client side of
    the streamable transport.

To create a streamable MCP server, you create a StreamableHTTPHandler and
pass it an mcp.Server:

go
// TODO: Until we have a way to clean up abandoned sessions, this test will leak goroutines (see #499)
func ExampleStreamableHTTPHandler() {
	// Create a new streamable handler, using the same MCP server for every request.
	//
	// Here, we configure it to serves application/json responses rather than
	// text/event-stream, just so the output below doesn't use random event ids.
	server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.1.0"}, nil)
	handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
		return server
	}, &mcp.StreamableHTTPOptions{JSONResponse: true})
	httpServer := httptest.NewServer(handler)
	defer httpServer.Close()

	// The SDK is currently permissive of some missing keys in "params".
	resp := mustPostMessage(`{"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {"protocolVersion":"2025-11-25"}}`, httpServer.URL)
	fmt.Println(resp)
	// Output:
	// {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{}},"protocolVersion":"2025-11-25","serverInfo":{"name":"server","version":"v0.1.0"}}}
}

The StreamableHTTPHandler handles the HTTP requests and creates a new
StreamableServerTransport for each new session. The transport is then used to
communicate with the client.

On the client side, you create a StreamableClientTransport and use it to
connect to the server:

go
transport := &mcp.StreamableClientTransport{
	Endpoint: "http://localhost:8080/mcp",
}
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v1.0.0"}, nil)
session, err := client.Connect(ctx, transport, nil)

The StreamableClientTransport handles the HTTP requests and communicates with
the server using the streamable transport protocol.

HTTP Headers

SEP-2243
standardises a small set of MCP-specific HTTP headers, sent and validated by
the SDK only for sessions negotiated on protocol version 2026-07-28 or
later.

Header Direction Purpose
Mcp-Protocol-Version request Mirrors _meta.io.modelcontextprotocol/protocolVersion from the body
Mcp-Session-Id request/response Logical session identifier (removed in stateless mode)
Mcp-Method request Mirrors the JSON-RPC method field; mismatch ⇒ -32020
Mcp-Name request Mirrors the request's principal name (tools/call.params.name, prompts/get.params.name, resources/read.params.uri); mismatch ⇒ -32020
Mcp-Param-{Header} request Per-tool parameter passthrough; see below

The header-name set is exhaustive: for protocol versions earlier than
2026-07-28, only Mcp-Protocol-Version and Mcp-Session-Id are recognised.

Body↔header mirroring. When Mcp-Method or Mcp-Name is present but
does not match the JSON-RPC body, the server returns
CodeHeaderMismatch (-32020). The same applies to a
mismatch between Mcp-Protocol-Version and the body's
_meta.io.modelcontextprotocol/protocolVersion.

Mcp-Param-* passthrough. A tool may annotate properties of its
InputSchema with x-mcp-header: "<HeaderName>". When a client invokes that
tool over HTTP, the SDK serializes the matching argument(s) as
Mcp-Param-<HeaderName> request headers (using =?base64?...?= encoding for
non-ASCII values). On the server, the headers are validated against the body
and either accepted or rejected with -32020. Properties so annotated must
be typed string, integer, or boolean; tools that fail validation at
registration time are silently dropped from tools/list.

Resumability and Redelivery

By default, the streamable server does not support resumability or
redelivery

of messages, because doing so requires either a persistent storage solution or
unbounded memory usage (see also
#580).

To enable resumability, set StreamableHTTPOptions.EventStore to a non-nil
value. The SDK provides a MemoryEventStore for testing or simple use cases;
for production use it is generally advisable to use a more sophisticated
implementation.

Note: SEP-2575
removes SSE stream resumability (Last-Event-ID, SSE event IDs) for protocol
version 2026-07-28. The SDK preserves the EventStore and Last-Event-ID
code paths for backward compatibility with 2025-11-25 and earlier; on
2026-07-28 sessions, a broken response stream loses the in-flight request
and the client must re-issue it as a new request with a new ID.

Stateless Mode

The streamable server supports a stateless mode by setting
StreamableHTTPOptions.Stateless,
which is where the server does not perform any validation of the session id,
and uses a temporary session to handle requests. In this mode, it is impossible
for the server to make client requests, as there is no way for the client's
response to reach the session.

However, it is still possible for the server to access the ServerSession.ID
to see the logical session

[!WARNING]
Stateless mode is not directly discussed in the spec, and is still being
defined. See modelcontextprotocol/modelcontextprotocol#1364,
modelcontextprotocol/modelcontextprotocol#1372, or
modelcontextprotocol/modelcontextprotocol#1442 for potential refinements.

Required for 2026-07-28: the streamable HTTP transport accepts
requests at protocol version 2026-07-28 only when Stateless = true.
Requests at that version against a non-stateless handler are rejected.

See examples/server/distributed for
an example using stateless mode to implement a server distributed across
multiple processes.

Legacy SSE Transport

Before the streamable transport, the
2024-11-05
spec defined an HTTP transport built from two endpoints: a hanging GET that
streams server-to-client messages as server-sent events, and a per-session
endpoint the client POSTs its messages to. The SDK still implements it, for
talking to peers that predate the streamable transport. New deployments should
use the streamable transport instead.

It is implemented across three types, mirroring the streamable ones:

  • SSEHandler:
    an http.Handler that creates a session per incoming GET and routes POSTs to
    the right session. Construct it with
    NewSSEHandler,
    which takes a function returning the Server to serve a given request.
  • SSEServerTransport:
    one such session. Reach for it directly only when serving the two endpoints
    yourself; SSEHandler creates these for you.
  • SSEClientTransport:
    the client side, which needs only the Endpoint of the GET.
go
func ExampleSSEHandler() {
	server := mcp.NewServer(&mcp.Implementation{Name: "adder", Version: "v0.0.1"}, nil)
	mcp.AddTool(server, &mcp.Tool{Name: "add", Description: "add two numbers"}, Add)

	handler := mcp.NewSSEHandler(func(*http.Request) *mcp.Server { return server }, nil)
	httpServer := httptest.NewServer(handler)
	defer httpServer.Close()

	ctx := context.Background()
	transport := &mcp.SSEClientTransport{Endpoint: httpServer.URL}
	client := mcp.NewClient(&mcp.Implementation{Name: "test", Version: "v1.0.0"}, nil)
	cs, err := client.Connect(ctx, transport, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer cs.Close()

	res, err := cs.CallTool(ctx, &mcp.CallToolParams{
		Name:      "add",
		Arguments: map[string]any{"x": 1, "y": 2},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Content[0].(*mcp.TextContent).Text)

	// Output: 3
}

See examples/server/sse
for a standalone server.

Custom transports

The SDK supports custom
transports

by implementing the
Transport
interface: a logical bidirectional stream of JSON-RPC messages.

Full example: examples/server/custom-transport.

Concurrency

In general, MCP offers no guarantees about concurrency semantics: if a client
or server sends a notification, the spec says nothing about when the peer
observes that notification relative to other request. However, the Go SDK
implements the following heuristics:

  • If a notifying method (such as notifications/progress or
    notifications/initialized) returns, then it is guaranteed that the peer
    observes that notification before other notifications or calls from the same
    client goroutine.
  • Calls (such as tools/call) are handled asynchronously with respect to
    each other.

See
modelcontextprotocol/go-sdk#26
for more background.

Authorization

Server

To write an MCP server that performs authorization,
use RequireBearerToken.
This function is middleware that wraps an HTTP handler, such as the one returned
by NewStreamableHTTPHandler, to provide support for verifying bearer tokens.
The middleware function checks every request for an Authorization header with a bearer token,
and invokes the
TokenVerifier
passed to RequireBearerToken to parse the token and perform validation.
The middleware function checks expiration and scopes (if they are provided in
RequireBearerTokenOptions.Scopes), so the
TokenVerifier doesn't have to.
If RequireBearerTokenOptions.ResourceMetadataURL is set and verification fails,
the middleware function sets the WWW-Authenticate header as required by the Protected Resource
Metadata spec
.

Server handlers, such as tool handlers, can obtain the TokenInfo returned by the TokenVerifier
from req.Extra.TokenInfo, where req is the handler's request. (For example, a
CallToolRequest.)
HTTP handlers wrapped by the RequireBearerToken middleware can obtain the TokenInfo from the context
with auth.TokenInfoFromContext.

OAuth Protected Resource Metadata

Servers implementing OAuth 2.0 authorization should expose a protected resource metadata endpoint
as specified in RFC 9728. This endpoint provides
clients with information about the resource server's OAuth configuration, including which
authorization servers can be used and what scopes are supported.

The SDK provides ProtectedResourceMetadataHandler
to serve this metadata. The handler automatically sets CORS headers (Access-Control-Allow-Origin: *)
to support cross-origin client discovery, as the metadata contains only public configuration information.

Example usage:

go
metadata := &oauthex.ProtectedResourceMetadata{
    Resource: "https://example.com/mcp",
    AuthorizationServers: []string{
        "https://auth.example.com/.well-known/openid-configuration",
    },
    ScopesSupported: []string{"read", "write"},
}
http.Handle("/.well-known/oauth-protected-resource",
    auth.ProtectedResourceMetadataHandler(metadata))

For more sophisticated CORS policies, wrap the handler with a CORS middleware like
github.com/rs/cors or github.com/jub0bs/cors.

The auth middleware example shows how to implement authorization for both JWT tokens and API keys.

Client

Client-side authorization is supported via the
StreamableClientTransport.OAuthHandler
field. If the handler is provided, the transport will automatically use it to
add an Authorization: Bearer <token> header to every request. The transport
will also call the handler's Authorize method if the server returns
401 Unauthorized or 403 Forbidden errors to perform the authorization flow
or facilitate scope step-up authorization.

The SDK implements the Authorization Code flow in
auth.AuthorizationCodeHandler.
This handler supports:

To use it, configure the handler and assign it to the transport:

go
authHandler, _ := auth.NewAuthorizationCodeHandler(&auth.AuthorizationCodeHandlerConfig{
	RedirectURL: "https://myapp.com/oauth2-callback",
	// Configure one of the following:
	// ClientIDMetadataDocumentConfig: ...
	// PreregisteredClientConfig: ...
	// DynamicClientRegistrationConfig: ...
	AuthorizationCodeFetcher: func(ctx context.Context, args *auth.AuthorizationArgs) (*auth.AuthorizationResult, error) {
		// Open the args.URL in a browser and return the resulting code, state, and iss.
		// See full example in examples/auth/client/main.go.
		code := ...
		state := ...
		iss := ... // "iss" query parameter from the redirect URI (RFC 9207)
		return &auth.AuthorizationResult{Code: code, State: state, Iss: iss}, nil
	},
})

transport := &mcp.StreamableClientTransport{
	Endpoint:     "https://example.com/mcp",
	OAuthHandler: authHandler,
}
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
session, err := client.Connect(ctx, transport, nil)

The auth.AuthorizationCodeHandler automatically manages token refreshing (if the server provides a refresh token) and step-up authentication (when the server returns insufficient_scope error).

Enterprise Managed Authorization (SEP-990)

For enterprise SSO scenarios where users authenticate with an enterprise Identity Provider (IdP),
the SDK provides
extauth.EnterpriseHandler,
an implementation of OAuthHandler that automates the Enterprise Managed Authorization flow:

  1. OIDC Login: User authenticates with enterprise IdP → ID Token
  2. Token Exchange (RFC 8693): ID Token → ID-JAG at IdP
  3. JWT Bearer Grant (RFC 7523): ID-JAG → Access Token at MCP Server

To use enterprise managed authorization, create an EnterpriseHandler and assign it to your transport:

text
/* Detailed source-code truncated for AI context efficiency. */

The EnterpriseHandler automatically manages the token exchange flow. Note that it intentionally does not support refresh tokens - when an access token expires, the entire authorization flow is repeated to ensure enterprise policies are consistently enforced.

For a complete working example, see examples/auth/enterprise.

Security

Here we discuss the mitigations described under
the MCP's Security Best Practices section, and how we handle them.

Confused Deputy

The mitigation,
obtaining user consent for dynamically registered clients, is mostly the
responsibility of the MCP Proxy server implementation. The SDK client does
generate cryptographically secure random state values for each authorization
request by default and validates them when the authorization code is returned.
Mismatched state values will result in an error.

Token Passthrough

The mitigation, accepting only tokens that were issued for the server, depends on the structure
of tokens and is the responsibility of the
TokenVerifier
provided to
RequireBearerToken.

Server-Side Request Forgery

The OAuth discovery helpers apply these mitigations by default:

  • Enforce HTTPS. Reject http:// URLs (except loopback: localhost, 127.0.0.1, ::1)
    and redirects that downgrade https to a non-https scheme.

  • Block Private IP Ranges. Reject targets that resolve to a non-public address
    (private, link-local including the 169.254.169.254 metadata endpoint, CGNAT, multicast,
    unspecified). The check runs on the initial URL and at dial time, guarding against DNS rebinding.

  • Validate Redirect Targets. Reject redirects to non-public addresses and cap the redirect count.

  • Use Egress Proxies. Out of scope for the SDK; configure separately.

Opting out. Passing a custom http.Client can bypass these checks, making SSRF
protection your responsibility. Specifically: a custom Transport.DialContext/DialTLSContext,
a non-*http.Transport, or a configured Proxy disables the dial-time IP check; setting
CheckRedirect replaces all redirect validation. In these cases, implement your own IP
blocking, redirect validation, and DNS pinning.

Session Hijacking

The mitigations are as follows:

  • Verify all inbound requests. The RequireBearerToken
    middleware function will verify all HTTP requests that it receives. It is the
    user's responsibility to wrap that function around all handlers in their server.

  • Secure session IDs. This SDK generates cryptographically secure session IDs by default.
    If you create your own with
    ServerOptions.GetSessionID, it is your responsibility to ensure they are secure.
    We recommend using crypto/rand.Text.

  • Binding session IDs to user information. The SDK supports this mitigation through
    TokenInfo.UserID.
    When a TokenVerifier
    sets UserID on the returned TokenInfo, the streamable transport will:

    1. Store the user ID when a new session is created.
    2. Verify that subsequent requests to that session include a token with the same UserID.
    3. Reject requests with a 403 Forbidden if the user ID doesn't match.

    Recommendation: If your TokenVerifier can extract a user identifier from the token
    (such as a sub claim in a JWT, or a user ID associated with an API key), set
    TokenInfo.UserID to enable this protection. This prevents an attacker with a valid
    token from hijacking another user's session by guessing or obtaining their session ID.

Issuer Mix-Up

The mitigation against issuer mix-up attacks is
implemented per RFC 9207. The SDK client validates
the iss parameter in authorization responses to ensure they originated from the expected
authorization server:

  • If iss is present in the redirect URI, the SDK verifies it matches the issuer from the
    authorization server's metadata. A mismatch results in an error.
  • If iss is absent but the authorization server advertises
    authorization_response_iss_parameter_supported: true in its RFC 8414
    metadata, the SDK rejects the response with an error.

The AuthorizationCodeFetcher is responsible for extracting the iss query parameter from
the redirect URI and returning it in AuthorizationResult.Iss.

Utilities

Cancellation

Cancellation is implemented with context cancellation. Cancelling a context
used in a method on ClientSession or ServerSession will terminate the RPC
and send a "notifications/cancelled" message to the peer.

When an RPC exits due to a cancellation error, there's a guarantee that the
cancellation notification has been sent, but there's no guarantee that the
server has observed it (see concurrency).

On a session using protocol version 2026-07-28 or later over a stdio-style
transport (StdioTransport, CommandTransport, IOTransport or
InMemoryTransport), a server that receives a cancellation notification sends
no response for the cancelled request, as the spec requires. Over the
streamable HTTP transport, a 2026-07-28 request is cancelled by closing its
HTTP request instead (see StreamableHTTPOptions.PropagateRequestCancellation).

text
/* Detailed source-code truncated for AI context efficiency. */

Ping

Ping
support is symmetrical for client and server.

To initiate a ping, call
ClientSession.Ping
or
ServerSession.Ping.

To have the client or server session automatically ping its peer, and close the
session if the ping fails, set
ClientOptions.KeepAlive
or
ServerOptions.KeepAlive.

Note: ping is removed from the protocol as of 2026-07-28 by
SEP-2575.
The SDK preserves the API for backward compatibility with legacy clients;
when a session negotiates 2026-07-28 or later, Ping returns
MethodNotFound (-32601) locally, without a round trip, and
KeepAlive should not be enabled.

Progress

Progress
reporting is possible by reading the progress token from request metadata and
calling either
ClientSession.NotifyProgress
or
ServerSession.NotifyProgress.
To listen to progress notifications, set
ClientOptions.ProgressNotificationHandler
or
ServerOptions.ProgressNotificationHandler.

Issue #460 discusses some potential ergonomic improvements to this API.

go
func Example_progress() {
	server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
	mcp.AddTool(server, &mcp.Tool{Name: "makeProgress"}, func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) {
		if token := req.Params.GetProgressToken(); token != nil {
			for i := range 3 {
				params := &mcp.ProgressNotificationParams{
					Message:       "frobbing widgets",
					ProgressToken: token,
					Progress:      float64(i),
					Total:         2,
				}
				req.Session.NotifyProgress(ctx, params) // ignore error
			}
		}
		return &mcp.CallToolResult{}, nil, nil
	})
	client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
		ProgressNotificationHandler: func(_ context.Context, req *mcp.ProgressNotificationClientRequest) {
			fmt.Printf("%s %.0f/%.0f\n", req.Params.Message, req.Params.Progress, req.Params.Total)
		},
	})
	ctx := context.Background()
	t1, t2 := mcp.NewInMemoryTransports()
	if _, err := server.Connect(ctx, t1, nil); err != nil {
		log.Fatal(err)
	}

	session, err := client.Connect(ctx, t2, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()
	if _, err := session.CallTool(ctx, &mcp.CallToolParams{
		Name: "makeProgress",
		Meta: mcp.Meta{"progressToken": "abc123"},
	}); err != nil {
		log.Fatal(err)
	}
	// Output:
	// frobbing widgets 0/2
	// frobbing widgets 1/2
	// frobbing widgets 2/2
}

Error codes

The SDK uses the standard JSON-RPC base codes (parse error -32700,
invalid request -32600, method not found -32601, invalid params
-32602, internal error -32603) plus the following MCP-specific codes:

Constant Code Meaning
CodeHeaderMismatch -32020 An MCP HTTP header (Mcp-Method, Mcp-Name, Mcp-Protocol-Version, or Mcp-Param-*) does not match the JSON-RPC body
CodeMissingRequiredClientCapabilities -32021 Server requires client capabilities the client did not declare
CodeUnsupportedProtocolVersion -32022 Requested protocol version not supported. Data: UnsupportedProtocolVersionData{Supported []string, Requested string}
CodeResourceNotFound -32602 Resource URI not found (variable; was -32002 before SEP-2164)

The error code allocation policy defined in 2026-07-28
(SEP-2575)
partitions the JSON-RPC server-error range:

  • -32000 to -32019: implementation-defined; existing SDK usage is grandfathered.
  • -32020 to -32099: reserved for the MCP specification.

Quick Start (`docs/quick_start.md`)

Quick Start

  1. Installation
  2. Getting started

Installation

text
go get github.com/modelcontextprotocol/go-sdk/mcp

Getting started

To get started creating an MCP server, create an mcp.Server instance, add
features to it, and then run it over an mcp.Transport. For example, this
server adds a single simple tool, and then connects it to clients over
stdin/stdout:

go
package main

import (
	"context"
	"log"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

type Input struct {
	Name string `json:"name" jsonschema:"the name of the person to greet"`
}

type Output struct {
	Greeting string `json:"greeting" jsonschema:"the greeting to tell to the user"`
}

func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (
	*mcp.CallToolResult,
	Output,
	error,
) {
	return nil, Output{Greeting: "Hi " + input.Name}, nil
}

func main() {
	// Create a server with a single tool.
	server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
	mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
	// Run the server over stdin/stdout, until the client disconnects.
	if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
		log.Fatal(err)
	}
}

To communicate with that server, create an mcp.Client and connect it to the
corresponding server, by running the server command and communicating over its
stdin/stdout:

go
package main

import (
	"context"
	"log"
	"os/exec"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
	ctx := context.Background()

	// Create a new client, with no features.
	client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil)

	// Connect to a server over stdin/stdout.
	transport := &mcp.CommandTransport{Command: exec.Command("myserver")}
	session, err := client.Connect(ctx, transport, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	// Call a tool on the server.
	params := &mcp.CallToolParams{
		Name:      "greet",
		Arguments: map[string]any{"name": "you"},
	}
	res, err := session.CallTool(ctx, params)
	if err != nil {
		log.Fatalf("CallTool failed: %v", err)
	}
	if res.IsError {
		log.Fatal("tool failed")
	}
	for _, c := range res.Content {
		log.Print(c.(*mcp.TextContent).Text)
	}
}

The examples/ directory contains more example clients and
servers.


Rough Edges (`docs/rough_edges.md`)

Rough edges: API decisions to reconsider for v2

This file collects a list of API oversights or rough edges that we've uncovered
post v1.0.0, along with their current workarounds. These issues can't be
addressed without breaking backward compatibility, but we'll revisit them for
v2.

  • EventStore.Open is unnecessary. This was an artifact of an earlier version
    of the SDK where event persistence and delivery were combined.

    Workaround: Open may be implemented as a no-op.

  • Event need not have been exported: it's an implementation detail of the SSE
    and streamable transports. Also the 'Name' field is a misnomer: it should be
    'event'.

  • Enforcing valid tool names: with
    SEP-986
    landing after the SDK was at v1, we missed an opportunity to panic on invalid
    tool names. Instead, we have to simply produce an error log. In v2, we should
    panic.

  • Inconsistent naming.

    • ResourceUpdatedNotificationsParams should probably have just been
      ResourceUpdatedParams, as we don't include the word 'notification' in
      other notification param types.
    • Similarly, ProgressNotificationParams should probably have been
      ProgressParams.
  • AudioContent.MarshalJSON should have had a pointer receiver, to be
    consistent with other content types.

  • ClientCapabilities.Roots should have been a distinguished struct pointer
    (see #607).

    Workaround: use ClientCapabilities.RootsV2, which aligns with the
    semantics of other capability fields.

  • Default capabilities should have been empty. Instead, servers default to
    advertising logging, and clients default to advertising roots with
    listChanged: true. This is confusing because a nil Capabilities field
    does not mean "no capabilities".

    Workaround: to advertise no capabilities, set
    ServerOptions.Capabilities or ClientOptions.Capabilities to an empty
    &ServerCapabilities{} or &ClientCapabilities{} respectively.

  • CreateMessageResult.Content is singular Content, but the 2025-11-25 spec
    allows content to be a single block or an array (for parallel tool calls).
    We added CreateMessageResultWithTools (with Content []Content) as a
    workaround, matching the TypeScript SDK's approach. In v2,
    CreateMessageResult should use []Content directly. Similarly,
    SamplingMessage.Content should become []Content to support sending
    multiple tool_result blocks in a single user message.

  • We didn't actually need CallToolParams and CallToolParamsRaw, since even when
    we're unmarshalling into a custom Go type (for the mcp.AddTool convenience
    wrapper) we need to first unmarshal into a map[string]any in order to do
    server-side validation of required fields. CallToolParams could have just had
    a map[string]any.

  • StreamableHTTPOptions.CrossOriginProtection should not have been part of
    the SDK API. Cross-origin protection is a general HTTP concern, not specific
    to MCP, and can be applied as standard HTTP middleware.

  • ToolAnnotations (mcp/protocol.go) should have all fields typed as *bool
    for full control to define what is being sent over the wire. Different
    MCP clients have different requirements, and some of them require all fields
    to be explicitly set to either true or false.


Server (`docs/server.md`)

Support for MCP server features

  1. Prompts
    1. Prompt message content
  2. Resources
    1. Binary resources
    2. Resource subscriptions
  3. Tools
    1. Tool result content
  4. List changed notifications
  5. Multi Round-Trip Requests
    1. Talking to legacy clients
    2. Example
  6. Cacheable list results
  7. Utilities
    1. Completion
    2. Logging
  8. Capabilities
    1. Capability inference
    2. Explicit capabilities
    3. Extensions
    4. Pagination

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.

text
/* Detailed source-code truncated for AI context efficiency. */

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.

text
/* Detailed source-code truncated for AI context efficiency. */

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.

text
/* Detailed source-code truncated for AI context efficiency. */

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.

go
func Example_binaryResource() {
	ctx := context.Background()

	s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
	s.AddResource(&mcp.Resource{URI: "file:///logo.png", MIMEType: "image/png"},
		func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
			return &mcp.ReadResourceResult{
				Contents: []*mcp.ResourceContents{{
					URI:      "file:///logo.png",
					MIMEType: "image/png",
					Blob:     []byte("\x89PNG\r\n\x1a\n"),
				}},
			}, nil
		})

	c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
	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()

	res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: "file:///logo.png"})
	if err != nil {
		log.Fatal(err)
	}
	contents := res.Contents[0]
	fmt.Printf("%s: %d bytes of %s, text is %q\n",
		contents.URI, len(contents.Blob), contents.MIMEType, contents.Text)

	// Output:
	// file:///logo.png: 8 bytes of image/png, text is ""
}

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.

text
/* Detailed source-code truncated for AI context efficiency. */

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.

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
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:

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
    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:

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 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.

text
/* Detailed source-code truncated for AI context efficiency. */

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:

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)
    // ...
}

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.

text
/* Detailed source-code truncated for AI context efficiency. */

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. To have the client call again without asking it for
    input, return only a RequestState; the client echoes it on the retry.
    Leave Content / StructuredContent empty — returning
    content together with InputRequests or RequestState 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
with the responses already populated. A result with a RequestState and no
input requests re-invokes it with that state and no responses. If the handler asks for more input,
the middleware repeats this until the handler completes, up to the same
limit of 10 rounds that the client-side middleware applies. 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).

text
/* Detailed source-code truncated for AI context efficiency. */

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.

go
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.

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).
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.

text
/* Detailed source-code truncated for AI context efficiency. */

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.
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
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.


Troubleshooting (`docs/troubleshooting.md`)

Troubleshooting

The Model Context Protocol is a complicated spec that leaves some room for
interpretation. Client and server SDKs can behave differently, or can be more
or less strict about their inputs. And of course, bugs happen.

When you encounter a problem using the Go SDK, these instructions can help
collect information that will be useful in debugging. Please try to provide
this information in a bug report, so that maintainers can more quickly
understand what's going wrong.

And most of all, please do file bugs.

Using the MCP inspector

To debug an MCP server, you can use the MCP
inspector
. This is
useful for testing your server and verifying that it works with the typescript
SDK, as well as inspecting MCP traffic.

Collecting MCP logs

For stdio transport connections, you can also
inspect MCP traffic using a LoggingTransport:

go
func ExampleLoggingTransport() {
	ctx := context.Background()
	t1, t2 := mcp.NewInMemoryTransports()
	server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
	serverSession, err := server.Connect(ctx, t1, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer serverSession.Close()

	client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
	var b bytes.Buffer
	logTransport := &mcp.LoggingTransport{Transport: t2, Writer: &b}
	clientSession, err := client.Connect(ctx, logTransport, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer clientSession.Close()

	// Sort for stability: reads are concurrent to writes.
	for _, line := range slices.Sorted(strings.SplitSeq(b.String(), "\n")) {
		fmt.Println(line)
	}

	// Output:
	// read: {"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"server","version":"v0.0.1"}},"ttlMs":0,"cacheScope":"public","supportedVersions":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"capabilities":{"logging":{}}}}
	// write: {"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/clientCapabilities":{"roots":{"listChanged":true}},"io.modelcontextprotocol/clientInfo":{"name":"client","version":"v0.0.1"},"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}
}

That example uses a bytes.Buffer, but you can also log to a file, or to
os.Stderr.

Inspecting HTTP traffic

There are a couple different ways to investigate traffic to an HTTP transport
(streamable or legacy SSE).

The first is to use an HTTP middleware:

go
func ExampleStreamableHTTPHandler_middleware() {
	server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.1.0"}, nil)
	handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
		return server
	}, &mcp.StreamableHTTPOptions{Stateless: true})
	loggingHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// Example debugging; you could also capture the response.
		body, err := io.ReadAll(req.Body)
		if err != nil {
			log.Fatal(err)
		}
		req.Body.Close() // ignore error
		req.Body = io.NopCloser(bytes.NewBuffer(body))
		fmt.Println(req.Method, string(body))
		handler.ServeHTTP(w, req)
	})
	httpServer := httptest.NewServer(loggingHandler)
	defer httpServer.Close()

	// The SDK is currently permissive of some missing keys in "params".
	mustPostMessage(`{"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {}}`, httpServer.URL)
	// Output:
	// POST {"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {}}
}

The second is to use a general purpose tool to inspect http traffic, such as
wireshark or
tcpdump.


Internal Docs README.Src (`internal/docs/README.src.md`)

Overview

The SDK consists of several importable packages:

These docs mirror the official MCP spec.
Use the index below to learn how the SDK implements a particular aspect of the
protocol.

Base Protocol

  1. Lifecycle (Clients, Servers, and Sessions).
  2. Transports
    1. Stdio transport
    2. Streamable transport
    3. Custom transports
  3. Authorization
  4. Security
  5. Utilities
    1. Cancellation
    2. Ping
    3. Progress

Client Features

  1. Roots
  2. Sampling
  3. Elicitation

Server Features

  1. Prompts
  2. Resources
  3. Tools
  4. Utilities
    1. Completion
    2. Logging
    3. Pagination

TroubleShooting

See troubleshooting.md for a troubleshooting guide.

Backwards compatibility

See mcpgodebug.md for a list of backwards incompatible behavior changes
and description how they can be temporarily undone.

Rough edges

See rough_edges.md for a list of rough edges or API
oversights that can't be addressed due to our compatibility promise. We'll
revisit these if/when we move to a v2 of the SDK.


Internal Docs Client.Src (`internal/docs/client.src.md`)

Support for MCP client features

%toc

Roots

Note: The roots 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 roots for compatibility. New code
should pass paths via tool parameters, resource URIs, or configuration
instead.

MCP allows clients to specify a set of filesystem
"roots".
The SDK supports this as follows:

Client-side: The SDK client always has the roots.listChanged capability.
To add roots to a client, use the
Client.AddRoots
and
Client.RemoveRoots
methods. If any servers are already connected to the
client, a call to AddRoot or RemoveRoots will result in a
notifications/roots/list_changed notification to each connected server.

Server-side: To query roots from the server, use the
ServerSession.ListRoots
method. To receive notifications about root changes, set
ServerOptions.RootsListChangedHandler.
For protocol versions 2026-07-28 and later, ListRoots requests are
delivered via the
Multi Round-Trip Requests
pattern.

%include ../../mcp/client_example_test.go roots -

Roots list changed

Client.AddRoots and Client.RemoveRoots notify every connected server that
the list changed. Servers observe this through
ServerOptions.RootsListChangedHandler;
as with the server-side list-changed notifications, it reports only that
something changed, so read the list back with ServerSession.ListRoots.

%include ../../mcp/client_example_test.go rootslistchanged -

Sampling

Note: The sampling 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 sampling for compatibility. Servers
that need LLM completions should call LLM provider APIs directly.

Sampling
is a way for servers to leverage the client's AI capabilities. It is
implemented in the SDK as follows:

Client-side: To add the sampling capability to a client, set
ClientOptions.CreateMessageHandler.
This function is invoked whenever the server requests sampling.

Server-side: To use sampling from the server, call
ServerSession.CreateMessage.

For protocol versions 2026-07-28 and later, sampling requests are
delivered via the
Multi Round-Trip Requests
pattern.

%include ../../mcp/client_example_test.go sampling -

Elicitation

Elicitation
allows servers to request user inputs. It is implemented in the SDK as follows:

Client-side: To add the elicitation capability to a client, set
ClientOptions.ElicitationHandler.
The elicitation handler must return a result that matches the requested schema;
otherwise, elicitation returns an error. If your handler supports URL mode
elicitation
,
you must declare that capability explicitly (see Capabilities)

Server-side: To use elicitation from the server, call
ServerSession.Elicit.

For protocol versions 2026-07-28 and later, elicitation requests are
delivered via the
Multi Round-Trip Requests
pattern.

%include ../../mcp/client_example_test.go elicitation -

Schema defaults and enums

ElicitParams.RequestedSchema is a flat schema of primitive fields, which the
client renders as a form. Two field keywords shape that form.

A Default (SEP-1034) prefills a
field. When the user accepts without supplying it, the SDK fills the field in
from the schema before the result reaches either side's caller — the client
does so after its elicitation handler returns, and ServerSession.Elicit does
so again on receipt. This is unconditional; there is no opt-in flag. Marking a
defaulted field Required defeats it: accepted content is validated against
the schema before defaults are applied, so an answer that omits the field is
rejected rather than defaulted.

An Enum (SEP-1330) restricts a
field to a fixed set of values, which the client renders as a choice. Enums
are supported only on "string" fields; declaring one on another type is
rejected. To label the choices, set the legacy enumNames keyword through
Schema.Extra, with exactly one name per enum value — a mismatched length is
rejected.

%include ../../mcp/client_example_test.go elicitationschema -

Completing a URL elicitation

In URL mode the user finishes out of band, in a browser, so nothing in the
elicitation result tells the client when they are done. The server signals that
with
ServerSession.NotifyElicitationComplete,
passing the same ElicitationID the request carried; the client observes it
through
ClientOptions.ElicitationCompleteHandler.
Send it from whatever endpoint the hosted flow redirects back to.

The notification matters most when a handler rejects a request with
URLElicitationRequiredError:
the client parks the original request until a notification names that
ElicitationID, and then retries it automatically. Until one arrives, the
client waits.

%include ../../mcp/client_example_test.go elicitationcomplete -

Multi Round-Trip Requests

SEP-2322
introduces the MRTR pattern: server-to-client requests for sampling,
elicitation, and roots are no longer issued as fresh JSON-RPC requests but
are carried inside the in-flight reply of a tools/call, prompts/get, or
resources/read. The client must respond by retrying the original request
with the produced responses.

The SDK installs clientMultiRoundTripMiddleware for every client by
default. The middleware:

  1. Inspects each tools/call/prompts/get/resources/read reply.
  2. If the result's NeedsInput() is true, fans out the InputRequests map
    concurrently, calling the configured handler for each (elicit,
    createMessage/createMessageWithTools, or listRoots).
  3. Threads the server-supplied opaque RequestState back unchanged.
  4. Retries the original request with the responses set, repeating until the
    result no longer needs input.

The middleware is enabled by default. To opt out, set
ClientOptions.MultiRoundTrip.Disabled = true;
the client will then surface input-required results directly to the caller
(the returned CallToolResult, GetPromptResult, or ReadResourceResult
will report NeedsInput() == true and expose the server's InputRequests
and opaque RequestState). Your code must fulfil each request and re-issue
the original call with InputResponses set and RequestState echoed
back.

For legacy (<= 2025-11-25) servers, the SDK transparently sends server
requests on the legacy server-initiated channel; the MRTR machinery is a
no-op in that direction. For legacy clients talking to MRTR-style servers,
the server SDK applies the inverse compatibility shim — see the
server-side documentation.

Capabilities

Client capabilities are advertised to servers during the initialization
handshake. By default, the SDK advertises the logging
capability. Additional capabilities are automatically added when server
features are added (e.g. via AddTool), or when handlers are set in the
ServerOptions struct (e.g., setting CompletionHandler adds the
completions capability), or may be configured explicitly.

Capability inference

When handlers are set on ClientOptions (e.g., CreateMessageHandler or
ElicitationHandler), the corresponding capability is automatically added if
not already present, with a default configuration.

For elicitation, if the handler is set but no Capabilities.Elicitation is
specified, the client defaults to form elicitation. To enable URL elicitation
or both modes, configure Capabilities.Elicitation
explicitly
.

See the ClientCapabilities
documentation

for further details on inference.

Explicit capabilities

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

This allows you to:

  • Disable default capabilities: Pass an empty &ClientCapabilities{} to
    disable all defaults, including roots.
  • Disable listChanged notifications: Set ListChanged: false on a
    capability to prevent the client from sending list-changed notifications
    when roots are added or removed.
  • Configure elicitation modes: Specify which elicitation modes (form, URL)
    the client supports.
go
// Configure elicitation modes and disable roots.
client := mcp.NewClient(impl, &mcp.ClientOptions{
    Capabilities: &mcp.ClientCapabilities{
        Elicitation: &mcp.ElicitationCapabilities{
            Form: &mcp.FormElicitationCapabilities{},
            URL:  &mcp.URLElicitationCapabilities{},
        },
    },
    ElicitationHandler: handler,
})

Extensions

SEP-2133
adds an extensions map to ClientCapabilities and 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.


Internal Docs Mcpgodebug.Src (`internal/docs/mcpgodebug.src.md`)

Backwards compatibility and MCPGODEBUG

According to our compatibility promise, we can't break backward compatibility
of the SDK API. However, sometimes we need to change the behavior of the SDK
in a backward-incompatible way in order to fix bugs or security issues.
In those cases we introduce temporary compatibility parameters, that can be
used to opt-out of the new behavior. They are usually maintained for two
minor release cycles and then removed.

The compatibility parameters are provided via the MCPGODEBUG environment
variable. The value of the variable is a comma-separated list of parameter
value assignments, e.g.:

text
MCPGODEBUG=parameter1=value1,parameter2=value2

`MCPGODEBUG` history

1.9.0

Options listed below were added and will be removed in the 1.11.0 version of the SDK.

  • structuredcontentfloat64 added. If set to 1, numbers in
    CallToolResult.StructuredContent are decoded as float64, restoring the
    previous behavior. The default behavior decodes numbers as json.Number so
    that large integers and other exact JSON values do not silently lose
    precision.

  • disablepromptargsvalidation added. If set to 1, Server.getPrompt will
    call the prompt handler even when a required argument of the prompt is
    missing from the request, restoring the previous behavior. The default
    behavior was changed to reject such requests with -32602 (Invalid Params),
    as the specification asks.

  • allowmissingclientparams added. If set to 1, a Client accepts
    elicitation/create, notifications/elicitation/complete and
    notifications/resources/updated messages whose params member is missing
    or null, restoring the previous behavior (the handler then observes nil
    params, which crashed the built-in elicitation handlers). The default
    behavior was changed to reject such messages as invalid requests, as the
    specification lists params as required for all three.

  • nopreinitinvalidrequest added. If set to 1, a legacy server session that
    has not been initialized refuses a request other than initialize or ping
    with JSON-RPC error code 0, restoring the previous behavior. The default
    behavior was changed to refuse it with -32600 (Invalid Request): 0
    tells a client nothing and is indistinguishable from an unset code. See
    issue #1367.

  • nointernalerrorcode added. If set to 1, an error returned by a request
    handler that carries no JSON-RPC error code is sent with code 0, restoring
    the previous behavior. The default behavior was changed to send it with
    -32603 (Internal error): 0 tells the peer nothing and is
    indistinguishable from an unset code. The error's message is unchanged.
    See issue #1394.

  • fatalnewprotocolerrors added. If set to 1, a streamable HTTP client
    permanently fails its connection when an independent 2026-07-28 request
    fails, restoring the previous behavior. By default, the failure is returned
    only to the affected call.

1.8.0

Options listed below were added and will be removed in the 1.9.0 version of the SDK.

  • plaintextstatefulrejection added. If set to 1, a stateful
    StreamableHTTPHandler will respond with a plain-text http.Error 400 body
    when it receives a request carrying per-request metadata (i.e. an
    io.modelcontextprotocol/protocolVersion _meta field, or an
    MCP-Protocol-Version header >= 2026-07-28), restoring the previous
    behavior. The default behavior was changed so that the server responds with a
    JSON-RPC error of code CodeUnsupportedProtocolVersion (-32022) carrying
    an UnsupportedProtocolVersionData payload that advertises the legacy
    versions the server supports. This lets the client's
    existing renegotiation logic recover and prevents the failure from tearing
    down the underlying connection.

  • blockingcancelnotify added. If set to 1, a cancelled call waits
    synchronously for the best-effort notifications/cancelled message to be
    delivered (up to notifyCancellationTimeout, currently 5 s) before
    returning to the caller, restoring the previous behavior. The delivery
    error is joined into the caller's returned error. The default behavior was
    changed so that the call is retired immediately and the notification is
    sent asynchronously off the caller's return path: the caller returns as
    soon as its context is cancelled and cannot be delayed by a slow or
    unresponsive peer. See issue #1150.

Options below were removed, according to plan:

  • seterroroverwrite. SetError now always preserves existing Content.

  • enableoriginverification. A nil
    StreamableHTTPOptions.CrossOriginProtection now always means no
    cross-origin protection. Wrap the handler with cross-origin protection
    middleware instead.

  • disablecontenttypecheck. Content-Type validation on HTTP POST requests can
    no longer be disabled.

  • disablelocalhostprotection, whose removal had been postponed to this
    release. Set the DisableLocalhostProtection field in
    StreamableHTTPOptions or SSEOptions to opt out of DNS rebinding
    protection.

1.7.0

Options listed below were added and will be removed in the 1.9.0 version of the SDK.

  • customresnotfounderrcode added. If set to 1, ResourceNotFoundError will
    use the custom error code -32002 instead of the standard -32602 (Invalid
    Params), restoring the previous behavior. The default behavior was changed to
    align with SEP-2164 and the JSON-RPC specification.

  • hintomitempty added. If set to 1, ToolAnnotations JSON marshaling
    will omit ReadOnlyHint and IdempotentHint when their value is false,
    restoring the previous behavior. The default behavior was changed to always
    serialize these fields, since their Go types are bare bool (not *bool)
    and omitting false made it indistinguishable from unset.

  • allowsessionsinstateless added. If set to 1, stateless streamable HTTP
    servers will read the Mcp-Session-Id request header (or generate one via
    GetSessionID), set it on response headers, and accept DELETE requests,
    restoring the previous behavior. The default behavior was changed so that
    stateless servers ignore session IDs entirely and reject DELETE with 405.

  • nomethodnotfoundcodeinerror added. If set to 1, the jsonrpc2 layer will not
    include the MethodNotFound Error (-32601) in the error response when the
    requested method in STDIO transport is not found. The default behavior was
    changed to include the MethodNotFound Error in the error response when the
    requested method in STDIO transport is not found.

  • noprotocolerrorbody added. If set to 1, the streamable HTTP client will
    not attempt to decode the JSON-RPC error body of a non-2xx HTTP response,
    and any non-transient error will permanently fail the connection, restoring
    the previous behavior. The default behavior was changed so that the client
    attempts to decode the JSON-RPC error body of a non-2xx response, surfaces
    the underlying JSON-RPC error, and wraps it with jsonrpc2.ErrRejected so
    that per-call rejections do not tear down the session.

  • nowrapinvalidparams added. If set to 1, the server will not wrap
    params-decoding failures with jsonrpc2.ErrInvalidParams, so wire responses
    carry the zero-value error code 0 instead of -32602 ("invalid params"),
    restoring the previous behavior. The default behavior was changed so that
    the server always reports params-decoding failures with the standard
    -32602 code.

  • disablecompleteparamsvalidation added. If set to 1, Server.complete
    will not validate that the required ref and argument.name fields on
    CompleteParams are present, restoring the previous behavior of dispatching
    the request to the completion handler unconditionally. The default behavior
    was changed to reject malformed requests with -32602 (Invalid Params).

1.6.1

Options listed below were added and will be removed in the 1.8.0 version of the SDK.

  • disablecontenttypecheck added. If set to 1, Content-Type validation on
    HTTP POST requests will be disabled, allowing requests with non-JSON or missing
    Content-Type headers. The default behavior is to validate that HTTP POST
    requests have Content-Type: application/json.

1.6.0

Options listed below were added and will be removed in the 1.8.0 version of the SDK.

  • seterroroverwrite added. If set to 1, SetError will always overwrite
    Content with the error text, restoring the previous behavior. The default
    behavior was changed to preserve existing Content if it has already been
    populated.

  • enableoriginverification added. If set to 1, default (zero-value)
    cross-origin protection will be applied when
    StreamableHTTPOptions.CrossOriginProtection is nil, restoring the
    behavior from v1.4.1-v1.5.0. The default behavior was changed to not
    enable cross-origin protection.

  • disablelocalhostprotection removal was postponed until 1.8.0, as it is now
    also used in the SSE transport.

Options below were removed:

  • jsonescaping, according to plan,

  • disablecrossoriginprotection, it was replaced by
    enableoriginverification after the default was changed to not enable
    cross-origin protection.

1.4.1

Options listed below will be removed in the 1.6.0 version of the SDK.

  • disablecrossoriginprotection added. If set to 1, newly added cross-origin
    protection will be disabled. The default behavior was changed to enable
    cross-origin protection.

1.4.0

Options listed below will be removed in the 1.6.0 version of the SDK.

  • jsonescaping added. If set to 1, JSON marshaling will preserve the previous
    behavior of escaping HTML characters in JSON strings. The default behavior
    was changed to not escape HTML characters, to be consistent with other SDKs.

  • disablelocalhostprotection added. If set to 1, newly added DNS rebinding
    protection will be disabled. The default behavior was changed to enable DNS rebinding
    protection. The protection can also be disabled by setting the
    DisableLocalhostProtection field in the StreamableHTTPOptions or
    SSEOptions struct to true, which is the recommended way to disable
    the protection long term. Removal of this option was postponed until 1.8.0.


Internal Docs Protocol.Src (`internal/docs/protocol.src.md`)

Support for the MCP base protocol

%toc

Lifecycle

The SDK provides an API for defining both MCP clients and servers, and
connecting them over various transports. When a client and server are
connected, it creates a logical session.

The MCP specification defines two lifecycle models, and the SDK supports both
transparently based on the negotiated protocol version:

  • The legacy initialize handshake
    (lifecycle)
    used by protocol versions through 2025-11-25.
  • A stateless model introduced in 2026-07-28 by
    SEP-2575,
    in which there is no initialize/notifications/initialized handshake, and
    each request carries its protocol version and client capabilities in
    _meta. Clients SHOULD also include their identity (clientInfo) on every
    request, and servers SHOULD include their identity (serverInfo) on every
    response.

In both models, the SDK exposes the same API:

  • A Client is a logical MCP client, configured with various
    ClientOptions.
  • When a client is connected to a server using
    Client.Connect,
    it creates a
    ClientSession.
    This session is initialized during the Connect method, and provides methods
    to communicate with the server peer.
  • A Server is a logical MCP server, configured with various
    ServerOptions.
  • When a server is connected to a client using
    Server.Connect,
    it creates a
    ServerSession.

In the legacy model, the server session is not considered initialized until
the client sends the notifications/initialized message. Use
ServerOptions.InitializedHandler to listen for this event, or just use the
session through various feature handlers (such as a ToolHandler). Requests
to the server are rejected until the client has initialized the session.

In the stateless model (2026-07-28+), there is no handshake. The server
processes the first request the moment it arrives, validates the per-request
_meta fields.

Both ClientSession and ServerSession have a Close method to terminate the
session, and a Wait method to await session termination by the peer. Typically,
it is the client's responsibility to end the session.

%include ../../mcp/mcp_example_test.go lifecycle -

Discovery

Introduced in 2026-07-28 by
SEP-2575,
the server/discover RPC lets a client discover a server's supported
protocol versions, capabilities, and identity before issuing any other
request. Servers implementing 2026-07-28 MUST implement it.

  • Server: Server.Connect registers the
    server/discover handler automatically; the response is computed from the
    server's static configuration (capabilities, server info, instructions) and
    the transport-filtered list of supported protocol versions.
  • Client: Client.Connect calls server/discover first and
    uses the result to negotiate a mutually supported version. If discovery
    fails or the server does not support the latest version, the client falls back to the
    legacy initialize handshake.

A server advertises and negotiates every protocol version the SDK supports;
set ServerOptions.SupportedProtocolVersions
to narrow that set, for example to stop serving a revision your deployment has
retired.

go
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v1.0.0"}, &mcp.ServerOptions{
	SupportedProtocolVersions: []string{"2026-07-28", "2025-11-25"},
})

The set can only narrow support, never widen it; the field's documentation
covers how each protocol era answers a request at an excluded version.

Per-request metadata keys

When the negotiated protocol version is 2026-07-28 or later, every request
carries these keys inside its _meta map (constants live in
mcp/protocol.go):

Constant Wire key Type Required
MetaKeyProtocolVersion io.modelcontextprotocol/protocolVersion string Yes
MetaKeyClientCapabilities io.modelcontextprotocol/clientCapabilities *ClientCapabilities Yes
MetaKeyClientInfo io.modelcontextprotocol/clientInfo *Implementation No
MetaKeyLogLevel io.modelcontextprotocol/logLevel LoggingLevel (deprecated by SEP-2577) No

The client populates the required keys automatically on every outgoing
request, and populates clientInfo when configured with an *Implementation
(the default). Server-side handlers can read them through
ServerRequest[P].ProtocolVersion(), ServerRequest[P].ClientInfo(), and
ServerRequest[P].ClientCapabilities().

Per-response metadata keys

Under the same protocol version, servers SHOULD identify themselves on every
response. The SDK populates this
key automatically on every outgoing response:

Constant Wire key Type Required
MetaKeyServerInfo io.modelcontextprotocol/serverInfo *Implementation No

Subscriptions

Introduced in 2026-07-28 by
SEP-2575,
subscriptions/listen replaces the legacy resources/subscribe RPC and
the GET-based SSE endpoint with a single long-lived request that
multiplexes every kind of server-to-client change notification a client
opts in to. On the wire the client sends one subscriptions/listen
request whose notifications field enumerates what it wants
(toolsListChanged, promptsListChanged, resourcesListChanged, and/or
a list of resource URIs in resourceSubscriptions); the server replies
first with a notifications/subscriptions/acknowledged notification
reporting the honored subset, then streams every change notification on
the same request, and finally closes with a SubscriptionsListenResult
when it tears the subscription down.

Transports

A
transport
can be used to send JSON-RPC messages from client to server, or vice-versa.

In the SDK, this is achieved by implementing the
Transport
interface, which creates a (logical) bidirectional stream of JSON-RPC messages.
Most transport implementations described below are specific to either the
client or server: a "client transport" is something that can be used to connect
a client to a server, and a "server transport" is something that can be used to
connect a server to a client. However, it's possible for a transport to be both
a client and server transport, such as the InMemoryTransport used in the
lifecycle example above.

Transports should not be reused for multiple connections: if you need to create
multiple connections, use different transports.

Stdio Transport

In the
stdio
transport clients communicate with an MCP server running in a subprocess using
newline-delimited JSON over its stdin/stdout.

Client-side: the client side of the stdio transport is implemented by
CommandTransport,
which starts the a exec.Cmd as a subprocess and communicates over its
stdin/stdout.

Server-side: the server side of the stdio transport is implemented by
StdioTransport,
which connects over the current processes os.Stdin and os.Stdout.

Streamable Transport

The streamable
transport

API is implemented across three types:

  • StreamableHTTPHandler: anhttp.Handler that serves streamable MCP
    sessions.
  • StreamableServerTransport: a Transport that implements the server side of
    the streamable transport.
  • StreamableClientTransport: a Transport that implements the client side of
    the streamable transport.

To create a streamable MCP server, you create a StreamableHTTPHandler and
pass it an mcp.Server:

%include ../../mcp/streamable_example_test.go streamablehandler -

The StreamableHTTPHandler handles the HTTP requests and creates a new
StreamableServerTransport for each new session. The transport is then used to
communicate with the client.

On the client side, you create a StreamableClientTransport and use it to
connect to the server:

go
transport := &mcp.StreamableClientTransport{
	Endpoint: "http://localhost:8080/mcp",
}
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v1.0.0"}, nil)
session, err := client.Connect(ctx, transport, nil)

The StreamableClientTransport handles the HTTP requests and communicates with
the server using the streamable transport protocol.

HTTP Headers

SEP-2243
standardises a small set of MCP-specific HTTP headers, sent and validated by
the SDK only for sessions negotiated on protocol version 2026-07-28 or
later.

Header Direction Purpose
Mcp-Protocol-Version request Mirrors _meta.io.modelcontextprotocol/protocolVersion from the body
Mcp-Session-Id request/response Logical session identifier (removed in stateless mode)
Mcp-Method request Mirrors the JSON-RPC method field; mismatch ⇒ -32020
Mcp-Name request Mirrors the request's principal name (tools/call.params.name, prompts/get.params.name, resources/read.params.uri); mismatch ⇒ -32020
Mcp-Param-{Header} request Per-tool parameter passthrough; see below

The header-name set is exhaustive: for protocol versions earlier than
2026-07-28, only Mcp-Protocol-Version and Mcp-Session-Id are recognised.

Body↔header mirroring. When Mcp-Method or Mcp-Name is present but
does not match the JSON-RPC body, the server returns
CodeHeaderMismatch (-32020). The same applies to a
mismatch between Mcp-Protocol-Version and the body's
_meta.io.modelcontextprotocol/protocolVersion.

Mcp-Param-* passthrough. A tool may annotate properties of its
InputSchema with x-mcp-header: "<HeaderName>". When a client invokes that
tool over HTTP, the SDK serializes the matching argument(s) as
Mcp-Param-<HeaderName> request headers (using =?base64?...?= encoding for
non-ASCII values). On the server, the headers are validated against the body
and either accepted or rejected with -32020. Properties so annotated must
be typed string, integer, or boolean; tools that fail validation at
registration time are silently dropped from tools/list.

Resumability and Redelivery

By default, the streamable server does not support resumability or
redelivery

of messages, because doing so requires either a persistent storage solution or
unbounded memory usage (see also
#580).

To enable resumability, set StreamableHTTPOptions.EventStore to a non-nil
value. The SDK provides a MemoryEventStore for testing or simple use cases;
for production use it is generally advisable to use a more sophisticated
implementation.

Note: SEP-2575
removes SSE stream resumability (Last-Event-ID, SSE event IDs) for protocol
version 2026-07-28. The SDK preserves the EventStore and Last-Event-ID
code paths for backward compatibility with 2025-11-25 and earlier; on
2026-07-28 sessions, a broken response stream loses the in-flight request
and the client must re-issue it as a new request with a new ID.

Stateless Mode

The streamable server supports a stateless mode by setting
StreamableHTTPOptions.Stateless,
which is where the server does not perform any validation of the session id,
and uses a temporary session to handle requests. In this mode, it is impossible
for the server to make client requests, as there is no way for the client's
response to reach the session.

However, it is still possible for the server to access the ServerSession.ID
to see the logical session

[!WARNING]
Stateless mode is not directly discussed in the spec, and is still being
defined. See modelcontextprotocol/modelcontextprotocol#1364,
modelcontextprotocol/modelcontextprotocol#1372, or
modelcontextprotocol/modelcontextprotocol#1442 for potential refinements.

Required for 2026-07-28: the streamable HTTP transport accepts
requests at protocol version 2026-07-28 only when Stateless = true.
Requests at that version against a non-stateless handler are rejected.

See examples/server/distributed for
an example using stateless mode to implement a server distributed across
multiple processes.

Legacy SSE Transport

Before the streamable transport, the
2024-11-05
spec defined an HTTP transport built from two endpoints: a hanging GET that
streams server-to-client messages as server-sent events, and a per-session
endpoint the client POSTs its messages to. The SDK still implements it, for
talking to peers that predate the streamable transport. New deployments should
use the streamable transport instead.

It is implemented across three types, mirroring the streamable ones:

  • SSEHandler:
    an http.Handler that creates a session per incoming GET and routes POSTs to
    the right session. Construct it with
    NewSSEHandler,
    which takes a function returning the Server to serve a given request.
  • SSEServerTransport:
    one such session. Reach for it directly only when serving the two endpoints
    yourself; SSEHandler creates these for you.
  • SSEClientTransport:
    the client side, which needs only the Endpoint of the GET.

%include ../../mcp/sse_example_test.go ssehandler -

See examples/server/sse
for a standalone server.

Custom transports

The SDK supports custom
transports

by implementing the
Transport
interface: a logical bidirectional stream of JSON-RPC messages.

Full example: examples/server/custom-transport.

Concurrency

In general, MCP offers no guarantees about concurrency semantics: if a client
or server sends a notification, the spec says nothing about when the peer
observes that notification relative to other request. However, the Go SDK
implements the following heuristics:

  • If a notifying method (such as notifications/progress or
    notifications/initialized) returns, then it is guaranteed that the peer
    observes that notification before other notifications or calls from the same
    client goroutine.
  • Calls (such as tools/call) are handled asynchronously with respect to
    each other.

See
modelcontextprotocol/go-sdk#26
for more background.

Authorization

Server

To write an MCP server that performs authorization,
use RequireBearerToken.
This function is middleware that wraps an HTTP handler, such as the one returned
by NewStreamableHTTPHandler, to provide support for verifying bearer tokens.
The middleware function checks every request for an Authorization header with a bearer token,
and invokes the
TokenVerifier
passed to RequireBearerToken to parse the token and perform validation.
The middleware function checks expiration and scopes (if they are provided in
RequireBearerTokenOptions.Scopes), so the
TokenVerifier doesn't have to.
If RequireBearerTokenOptions.ResourceMetadataURL is set and verification fails,
the middleware function sets the WWW-Authenticate header as required by the Protected Resource
Metadata spec
.

Server handlers, such as tool handlers, can obtain the TokenInfo returned by the TokenVerifier
from req.Extra.TokenInfo, where req is the handler's request. (For example, a
CallToolRequest.)
HTTP handlers wrapped by the RequireBearerToken middleware can obtain the TokenInfo from the context
with auth.TokenInfoFromContext.

OAuth Protected Resource Metadata

Servers implementing OAuth 2.0 authorization should expose a protected resource metadata endpoint
as specified in RFC 9728. This endpoint provides
clients with information about the resource server's OAuth configuration, including which
authorization servers can be used and what scopes are supported.

The SDK provides ProtectedResourceMetadataHandler
to serve this metadata. The handler automatically sets CORS headers (Access-Control-Allow-Origin: *)
to support cross-origin client discovery, as the metadata contains only public configuration information.

Example usage:

go
metadata := &oauthex.ProtectedResourceMetadata{
    Resource: "https://example.com/mcp",
    AuthorizationServers: []string{
        "https://auth.example.com/.well-known/openid-configuration",
    },
    ScopesSupported: []string{"read", "write"},
}
http.Handle("/.well-known/oauth-protected-resource",
    auth.ProtectedResourceMetadataHandler(metadata))

For more sophisticated CORS policies, wrap the handler with a CORS middleware like
github.com/rs/cors or github.com/jub0bs/cors.

The auth middleware example shows how to implement authorization for both JWT tokens and API keys.

Client

Client-side authorization is supported via the
StreamableClientTransport.OAuthHandler
field. If the handler is provided, the transport will automatically use it to
add an Authorization: Bearer <token> header to every request. The transport
will also call the handler's Authorize method if the server returns
401 Unauthorized or 403 Forbidden errors to perform the authorization flow
or facilitate scope step-up authorization.

The SDK implements the Authorization Code flow in
auth.AuthorizationCodeHandler.
This handler supports:

To use it, configure the handler and assign it to the transport:

go
authHandler, _ := auth.NewAuthorizationCodeHandler(&auth.AuthorizationCodeHandlerConfig{
	RedirectURL: "https://myapp.com/oauth2-callback",
	// Configure one of the following:
	// ClientIDMetadataDocumentConfig: ...
	// PreregisteredClientConfig: ...
	// DynamicClientRegistrationConfig: ...
	AuthorizationCodeFetcher: func(ctx context.Context, args *auth.AuthorizationArgs) (*auth.AuthorizationResult, error) {
		// Open the args.URL in a browser and return the resulting code, state, and iss.
		// See full example in examples/auth/client/main.go.
		code := ...
		state := ...
		iss := ... // "iss" query parameter from the redirect URI (RFC 9207)
		return &auth.AuthorizationResult{Code: code, State: state, Iss: iss}, nil
	},
})

transport := &mcp.StreamableClientTransport{
	Endpoint:     "https://example.com/mcp",
	OAuthHandler: authHandler,
}
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
session, err := client.Connect(ctx, transport, nil)

The auth.AuthorizationCodeHandler automatically manages token refreshing (if the server provides a refresh token) and step-up authentication (when the server returns insufficient_scope error).

Enterprise Managed Authorization (SEP-990)

For enterprise SSO scenarios where users authenticate with an enterprise Identity Provider (IdP),
the SDK provides
extauth.EnterpriseHandler,
an implementation of OAuthHandler that automates the Enterprise Managed Authorization flow:

  1. OIDC Login: User authenticates with enterprise IdP → ID Token
  2. Token Exchange (RFC 8693): ID Token → ID-JAG at IdP
  3. JWT Bearer Grant (RFC 7523): ID-JAG → Access Token at MCP Server

To use enterprise managed authorization, create an EnterpriseHandler and assign it to your transport:

text
/* Detailed source-code truncated for AI context efficiency. */

The EnterpriseHandler automatically manages the token exchange flow. Note that it intentionally does not support refresh tokens - when an access token expires, the entire authorization flow is repeated to ensure enterprise policies are consistently enforced.

For a complete working example, see examples/auth/enterprise.

Security

Here we discuss the mitigations described under
the MCP's Security Best Practices section, and how we handle them.

Confused Deputy

The mitigation,
obtaining user consent for dynamically registered clients, is mostly the
responsibility of the MCP Proxy server implementation. The SDK client does
generate cryptographically secure random state values for each authorization
request by default and validates them when the authorization code is returned.
Mismatched state values will result in an error.

Token Passthrough

The mitigation, accepting only tokens that were issued for the server, depends on the structure
of tokens and is the responsibility of the
TokenVerifier
provided to
RequireBearerToken.

Server-Side Request Forgery

The OAuth discovery helpers apply these mitigations by default:

  • Enforce HTTPS. Reject http:// URLs (except loopback: localhost, 127.0.0.1, ::1)
    and redirects that downgrade https to a non-https scheme.

  • Block Private IP Ranges. Reject targets that resolve to a non-public address
    (private, link-local including the 169.254.169.254 metadata endpoint, CGNAT, multicast,
    unspecified). The check runs on the initial URL and at dial time, guarding against DNS rebinding.

  • Validate Redirect Targets. Reject redirects to non-public addresses and cap the redirect count.

  • Use Egress Proxies. Out of scope for the SDK; configure separately.

Opting out. Passing a custom http.Client can bypass these checks, making SSRF
protection your responsibility. Specifically: a custom Transport.DialContext/DialTLSContext,
a non-*http.Transport, or a configured Proxy disables the dial-time IP check; setting
CheckRedirect replaces all redirect validation. In these cases, implement your own IP
blocking, redirect validation, and DNS pinning.

Session Hijacking

The mitigations are as follows:

  • Verify all inbound requests. The RequireBearerToken
    middleware function will verify all HTTP requests that it receives. It is the
    user's responsibility to wrap that function around all handlers in their server.

  • Secure session IDs. This SDK generates cryptographically secure session IDs by default.
    If you create your own with
    ServerOptions.GetSessionID, it is your responsibility to ensure they are secure.
    We recommend using crypto/rand.Text.

  • Binding session IDs to user information. The SDK supports this mitigation through
    TokenInfo.UserID.
    When a TokenVerifier
    sets UserID on the returned TokenInfo, the streamable transport will:

    1. Store the user ID when a new session is created.
    2. Verify that subsequent requests to that session include a token with the same UserID.
    3. Reject requests with a 403 Forbidden if the user ID doesn't match.

    Recommendation: If your TokenVerifier can extract a user identifier from the token
    (such as a sub claim in a JWT, or a user ID associated with an API key), set
    TokenInfo.UserID to enable this protection. This prevents an attacker with a valid
    token from hijacking another user's session by guessing or obtaining their session ID.

Issuer Mix-Up

The mitigation against issuer mix-up attacks is
implemented per RFC 9207. The SDK client validates
the iss parameter in authorization responses to ensure they originated from the expected
authorization server:

  • If iss is present in the redirect URI, the SDK verifies it matches the issuer from the
    authorization server's metadata. A mismatch results in an error.
  • If iss is absent but the authorization server advertises
    authorization_response_iss_parameter_supported: true in its RFC 8414
    metadata, the SDK rejects the response with an error.

The AuthorizationCodeFetcher is responsible for extracting the iss query parameter from
the redirect URI and returning it in AuthorizationResult.Iss.

Utilities

Cancellation

Cancellation is implemented with context cancellation. Cancelling a context
used in a method on ClientSession or ServerSession will terminate the RPC
and send a "notifications/cancelled" message to the peer.

When an RPC exits due to a cancellation error, there's a guarantee that the
cancellation notification has been sent, but there's no guarantee that the
server has observed it (see concurrency).

On a session using protocol version 2026-07-28 or later over a stdio-style
transport (StdioTransport, CommandTransport, IOTransport or
InMemoryTransport), a server that receives a cancellation notification sends
no response for the cancelled request, as the spec requires. Over the
streamable HTTP transport, a 2026-07-28 request is cancelled by closing its
HTTP request instead (see StreamableHTTPOptions.PropagateRequestCancellation).

%include ../../mcp/mcp_example_test.go cancellation -

Ping

Ping
support is symmetrical for client and server.

To initiate a ping, call
ClientSession.Ping
or
ServerSession.Ping.

To have the client or server session automatically ping its peer, and close the
session if the ping fails, set
ClientOptions.KeepAlive
or
ServerOptions.KeepAlive.

Note: ping is removed from the protocol as of 2026-07-28 by
SEP-2575.
The SDK preserves the API for backward compatibility with legacy clients;
when a session negotiates 2026-07-28 or later, Ping returns
MethodNotFound (-32601) locally, without a round trip, and
KeepAlive should not be enabled.

Progress

Progress
reporting is possible by reading the progress token from request metadata and
calling either
ClientSession.NotifyProgress
or
ServerSession.NotifyProgress.
To listen to progress notifications, set
ClientOptions.ProgressNotificationHandler
or
ServerOptions.ProgressNotificationHandler.

Issue #460 discusses some potential ergonomic improvements to this API.

%include ../../mcp/mcp_example_test.go progress -

Error codes

The SDK uses the standard JSON-RPC base codes (parse error -32700,
invalid request -32600, method not found -32601, invalid params
-32602, internal error -32603) plus the following MCP-specific codes:

Constant Code Meaning
CodeHeaderMismatch -32020 An MCP HTTP header (Mcp-Method, Mcp-Name, Mcp-Protocol-Version, or Mcp-Param-*) does not match the JSON-RPC body
CodeMissingRequiredClientCapabilities -32021 Server requires client capabilities the client did not declare
CodeUnsupportedProtocolVersion -32022 Requested protocol version not supported. Data: UnsupportedProtocolVersionData{Supported []string, Requested string}
CodeResourceNotFound -32602 Resource URI not found (variable; was -32002 before SEP-2164)

The error code allocation policy defined in 2026-07-28
(SEP-2575)
partitions the JSON-RPC server-error range:

  • -32000 to -32019: implementation-defined; existing SDK usage is grandfathered.
  • -32020 to -32099: reserved for the MCP specification.

Internal Docs Quick Start.Src (`internal/docs/quick_start.src.md`)

Quick Start

%toc

Installation

text
go get github.com/modelcontextprotocol/go-sdk/mcp

Getting started

To get started creating an MCP server, create an mcp.Server instance, add
features to it, and then run it over an mcp.Transport. For example, this
server adds a single simple tool, and then connects it to clients over
stdin/stdout:

%include ../readme/server/server.go -

To communicate with that server, create an mcp.Client and connect it to the
corresponding server, by running the server command and communicating over its
stdin/stdout:

%include ../readme/client/client.go -

The examples/ directory contains more example clients and
servers.


Internal Docs Rough Edges.Src (`internal/docs/rough_edges.src.md`)

Rough edges: API decisions to reconsider for v2

This file collects a list of API oversights or rough edges that we've uncovered
post v1.0.0, along with their current workarounds. These issues can't be
addressed without breaking backward compatibility, but we'll revisit them for
v2.

  • EventStore.Open is unnecessary. This was an artifact of an earlier version
    of the SDK where event persistence and delivery were combined.

    Workaround: Open may be implemented as a no-op.

  • Event need not have been exported: it's an implementation detail of the SSE
    and streamable transports. Also the 'Name' field is a misnomer: it should be
    'event'.

  • Enforcing valid tool names: with
    SEP-986
    landing after the SDK was at v1, we missed an opportunity to panic on invalid
    tool names. Instead, we have to simply produce an error log. In v2, we should
    panic.

  • Inconsistent naming.

    • ResourceUpdatedNotificationsParams should probably have just been
      ResourceUpdatedParams, as we don't include the word 'notification' in
      other notification param types.
    • Similarly, ProgressNotificationParams should probably have been
      ProgressParams.
  • AudioContent.MarshalJSON should have had a pointer receiver, to be
    consistent with other content types.

  • ClientCapabilities.Roots should have been a distinguished struct pointer
    (see #607).

    Workaround: use ClientCapabilities.RootsV2, which aligns with the
    semantics of other capability fields.

  • Default capabilities should have been empty. Instead, servers default to
    advertising logging, and clients default to advertising roots with
    listChanged: true. This is confusing because a nil Capabilities field
    does not mean "no capabilities".

    Workaround: to advertise no capabilities, set
    ServerOptions.Capabilities or ClientOptions.Capabilities to an empty
    &ServerCapabilities{} or &ClientCapabilities{} respectively.

  • CreateMessageResult.Content is singular Content, but the 2025-11-25 spec
    allows content to be a single block or an array (for parallel tool calls).
    We added CreateMessageResultWithTools (with Content []Content) as a
    workaround, matching the TypeScript SDK's approach. In v2,
    CreateMessageResult should use []Content directly. Similarly,
    SamplingMessage.Content should become []Content to support sending
    multiple tool_result blocks in a single user message.

  • We didn't actually need CallToolParams and CallToolParamsRaw, since even when
    we're unmarshalling into a custom Go type (for the mcp.AddTool convenience
    wrapper) we need to first unmarshal into a map[string]any in order to do
    server-side validation of required fields. CallToolParams could have just had
    a map[string]any.

  • StreamableHTTPOptions.CrossOriginProtection should not have been part of
    the SDK API. Cross-origin protection is a general HTTP concern, not specific
    to MCP, and can be applied as standard HTTP middleware.

  • ToolAnnotations (mcp/protocol.go) should have all fields typed as *bool
    for full control to define what is being sent over the wire. Different
    MCP clients have different requirements, and some of them require all fields
    to be explicitly set to either true or false.


Internal Docs Server.Src (`internal/docs/server.src.md`)

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.

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
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:

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
    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:

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)
    // ...
}

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. To have the client call again without asking it for
    input, return only a RequestState; the client echoes it on the retry.
    Leave Content / StructuredContent empty — returning
    content together with InputRequests or RequestState 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
with the responses already populated. A result with a RequestState and no
input requests re-invokes it with that state and no responses. If the handler asks for more input,
the middleware repeats this until the handler completes, up to the same
limit of 10 rounds that the client-side middleware applies. 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.

go
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.
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
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.


Internal Docs Troubleshooting.Src (`internal/docs/troubleshooting.src.md`)

Troubleshooting

The Model Context Protocol is a complicated spec that leaves some room for
interpretation. Client and server SDKs can behave differently, or can be more
or less strict about their inputs. And of course, bugs happen.

When you encounter a problem using the Go SDK, these instructions can help
collect information that will be useful in debugging. Please try to provide
this information in a bug report, so that maintainers can more quickly
understand what's going wrong.

And most of all, please do file bugs.

Using the MCP inspector

To debug an MCP server, you can use the MCP
inspector
. This is
useful for testing your server and verifying that it works with the typescript
SDK, as well as inspecting MCP traffic.

Collecting MCP logs

For stdio transport connections, you can also
inspect MCP traffic using a LoggingTransport:

%include ../../mcp/transport_example_test.go loggingtransport -

That example uses a bytes.Buffer, but you can also log to a file, or to
os.Stderr.

Inspecting HTTP traffic

There are a couple different ways to investigate traffic to an HTTP transport
(streamable or legacy SSE).

The first is to use an HTTP middleware:

%include ../../mcp/streamable_example_test.go httpmiddleware -

The second is to use a general purpose tool to inspect http traffic, such as
wireshark or
tcpdump.


--- METRICS ---