or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

auth.mdindex.mdjsonrpc.mdmcp-client.mdmcp-protocol.mdmcp-server.mdmcp-transports.mdmcp-types.mdoauthex.md
tile.json

auth.mddocs/

Authentication

The auth package provides OAuth 2.0 bearer token authentication and authorization for MCP servers.

Overview

The auth package enables MCP servers to verify bearer tokens from OAuth 2.0 flows, extract token information, and enforce scope-based access control. It integrates with HTTP middleware to protect MCP endpoints.

Package Information

import "github.com/modelcontextprotocol/go-sdk/auth"

Bearer Token Middleware

RequireBearerToken

Returns HTTP middleware that verifies bearer tokens using a custom verifier.

func RequireBearerToken(
    verifier TokenVerifier,
    opts *RequireBearerTokenOptions,
) func(http.Handler) http.Handler

Parameters:

  • verifier: Function to verify and extract information from tokens
  • opts: Optional configuration for the middleware

Returns: Middleware function that can wrap HTTP handlers

Behavior:

  • Extracts bearer token from Authorization header
  • Calls verifier to validate the token
  • Checks required scopes if specified
  • Verifies token expiration
  • Adds TokenInfo to request context on success
  • Returns 401 Unauthorized with WWW-Authenticate header on failure
  • Returns 403 Forbidden if token lacks required scopes

Example:

import (
    "net/http"
    "github.com/modelcontextprotocol/go-sdk/auth"
    "github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
    // Define token verifier
    verifier := func(ctx context.Context, token string, req *http.Request) (*auth.TokenInfo, error) {
        // Verify JWT or lookup token in database
        claims, err := verifyJWT(token)
        if err != nil {
            return nil, auth.ErrInvalidToken
        }

        return &auth.TokenInfo{
            Scopes:     claims.Scopes,
            Expiration: claims.ExpiresAt,
            Extra: map[string]any{
                "user_id": claims.Subject,
            },
        }, nil
    }

    // Create middleware
    authMiddleware := auth.RequireBearerToken(verifier, &auth.RequireBearerTokenOptions{
        ResourceMetadataURL: "https://example.com/.well-known/oauth-resource-metadata",
        Scopes:              []string{"mcp:read", "mcp:write"},
    })

    // Wrap MCP handler
    handler := mcp.NewStreamableHTTPHandler(getServer, nil)
    http.Handle("/mcp", authMiddleware(handler))
}

RequireBearerTokenOptions

Configuration for bearer token middleware.

type RequireBearerTokenOptions struct {
    ResourceMetadataURL string
    Scopes              []string
}

Fields:

  • ResourceMetadataURL: URL for resource server metadata OAuth flow (returned in WWW-Authenticate header)
  • Scopes: Required scopes that the token must have

Token Verification

TokenVerifier

Function type for verifying bearer tokens.

type TokenVerifier func(
    ctx context.Context,
    token string,
    req *http.Request,
) (*TokenInfo, error)

Parameters:

  • ctx: Request context
  • token: Bearer token string to verify
  • req: HTTP request (for additional verification context)

Returns:

  • TokenInfo: Information extracted from the token
  • error: Error if verification fails (should wrap ErrInvalidToken or ErrOAuth)

Implementation guidelines:

  • Return error wrapping ErrInvalidToken for invalid/malformed tokens (results in 401)
  • Return error wrapping ErrOAuth for OAuth protocol errors (results in 400)
  • Return other errors for internal server errors (results in 500)
  • Verify token signature/authenticity
  • Extract claims and scopes
  • Return TokenInfo with expiration and scopes

Example implementation with JWT:

import (
    "github.com/golang-jwt/jwt/v5"
    "github.com/modelcontextprotocol/go-sdk/auth"
)

func jwtVerifier(ctx context.Context, token string, req *http.Request) (*auth.TokenInfo, error) {
    // Parse and verify JWT
    parsed, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
        // Return your verification key
        return publicKey, nil
    })

    if err != nil {
        return nil, fmt.Errorf("%w: %v", auth.ErrInvalidToken, err)
    }

    if !parsed.Valid {
        return nil, auth.ErrInvalidToken
    }

    claims, ok := parsed.Claims.(jwt.MapClaims)
    if !ok {
        return nil, auth.ErrInvalidToken
    }

    // Extract scopes
    scopesRaw, _ := claims["scope"].(string)
    scopes := strings.Split(scopesRaw, " ")

    // Extract expiration
    exp, _ := claims["exp"].(float64)
    expiration := time.Unix(int64(exp), 0)

    return &auth.TokenInfo{
        Scopes:     scopes,
        Expiration: expiration,
        Extra: map[string]any{
            "sub": claims["sub"],
            "iss": claims["iss"],
        },
    }, nil
}

Token Information

TokenInfo

Information extracted from a verified bearer token.

type TokenInfo struct {
    Scopes     []string
    Expiration time.Time
    Extra      map[string]any
}

Fields:

  • Scopes: OAuth scopes granted to the token
  • Expiration: When the token expires
  • Extra: Additional token claims or metadata

TokenInfoFromContext

Retrieves TokenInfo from a request context.

func TokenInfoFromContext(ctx context.Context) *TokenInfo

Returns: TokenInfo if present in context, nil otherwise

Example:

func toolHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    // Get token info from request context
    tokenInfo := auth.TokenInfoFromContext(req.Session.Context())
    if tokenInfo != nil {
        userID := tokenInfo.Extra["user_id"]
        // Use user ID for authorization decisions
    }

    // ... handle tool call
}

Access in ServerRequest:

// TokenInfo is also available via request.Extra
func handler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    if req.Extra != nil && req.Extra.TokenInfo != nil {
        scopes := req.Extra.TokenInfo.Scopes
        // Check scopes for fine-grained authorization
    }
    // ... process request
}

Error Types

ErrInvalidToken

Error that TokenVerifier should return for invalid tokens.

var ErrInvalidToken = errors.New("invalid token")

Usage: Wrap this error when token verification fails due to:

  • Malformed token
  • Invalid signature
  • Token not found in database
  • Failed cryptographic verification

ErrOAuth

Error that TokenVerifier should return for OAuth protocol errors.

var ErrOAuth = errors.New("oauth error")

Usage: Wrap this error for OAuth-specific protocol violations.

Complete Example

package main

import (
    "context"
    "log"
    "net/http"
    "time"

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

func main() {
    // Create MCP server
    server := mcp.NewServer(&mcp.Implementation{
        Name:    "secure-server",
        Version: "1.0.0",
    }, nil)

    // Add a tool
    mcp.AddTool(server, &mcp.Tool{
        Name:        "get-data",
        Description: "Retrieves user data",
    }, func(ctx context.Context, req *mcp.CallToolRequest, input any) (*mcp.CallToolResult, any, error) {
        // Get token info from context
        tokenInfo := auth.TokenInfoFromContext(ctx)
        if tokenInfo == nil {
            return &mcp.CallToolResult{
                Content: []mcp.Content{
                    &mcp.TextContent{Text: "No authentication"},
                },
                IsError: true,
            }, nil, nil
        }

        userID := tokenInfo.Extra["user_id"].(string)

        return &mcp.CallToolResult{
            Content: []mcp.Content{
                &mcp.TextContent{Text: "Data for user: " + userID},
            },
        }, nil, nil
    })

    // Token verifier
    verifier := func(ctx context.Context, token string, req *http.Request) (*auth.TokenInfo, error) {
        // In production, verify JWT or lookup in database
        if token != "valid-token" {
            return nil, auth.ErrInvalidToken
        }

        return &auth.TokenInfo{
            Scopes:     []string{"mcp:read", "mcp:write"},
            Expiration: time.Now().Add(1 * time.Hour),
            Extra: map[string]any{
                "user_id": "user123",
            },
        }, nil
    }

    // Create authenticated MCP handler
    authMiddleware := auth.RequireBearerToken(verifier, &auth.RequireBearerTokenOptions{
        ResourceMetadataURL: "https://example.com/.well-known/oauth-resource-metadata",
        Scopes:              []string{"mcp:read"},
    })

    mcpHandler := mcp.NewStreamableHTTPHandler(
        func(req *http.Request) *mcp.Server {
            return server
        },
        nil,
    )

    // Apply authentication middleware
    http.Handle("/mcp", authMiddleware(mcpHandler))

    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

OAuth Client Transport

Note: Types and functions in this section require the mcp_go_client_oauth build tag.

The auth package provides an OAuth-aware HTTP transport for MCP clients that need to connect to OAuth-protected servers.

HTTPTransport

An HTTP RoundTripper that automatically handles OAuth 2.0 authentication when encountering 401 Unauthorized responses.

type HTTPTransport struct {
    // contains filtered or unexported fields
}

Behavior:

  • Implements http.RoundTripper interface
  • Intercepts 401 Unauthorized responses
  • Invokes OAuth handler to obtain token source
  • Retries request with OAuth token
  • Thread-safe for concurrent requests

OAuthHandler

Function type for conducting OAuth flows.

type OAuthHandler func(
    req *http.Request,
    res *http.Response,
) (oauth2.TokenSource, error)

Parameters:

  • req: The HTTP request that triggered authentication
  • res: The 401 Unauthorized response

Returns:

  • oauth2.TokenSource: Token source for future requests
  • error: Error if OAuth flow fails

Implementation notes:

  • Called once per HTTPTransport lifetime
  • Receives both request and response for context
  • Can use oauthex.GetProtectedResourceMetadataFromHeader to discover auth requirements
  • Should perform complete OAuth flow (authorization code, device code, etc.)

NewHTTPTransport

Creates a new OAuth-aware HTTP transport.

func NewHTTPTransport(
    handler OAuthHandler,
    opts *HTTPTransportOptions,
) (*HTTPTransport, error)

Parameters:

  • handler: OAuth handler function (required, cannot be nil)
  • opts: Optional configuration

Returns:

  • *HTTPTransport: New transport instance
  • error: Error if handler is nil

Example:

import (
    "context"
    "fmt"
    "net/http"
    "github.com/modelcontextprotocol/go-sdk/auth"
    "github.com/modelcontextprotocol/go-sdk/mcp"
    "github.com/modelcontextprotocol/go-sdk/oauthex"
    "golang.org/x/oauth2"
)

func connectWithOAuth(serverURL string) (*mcp.ClientSession, error) {
    ctx := context.Background()

    // Create OAuth handler
    oauthHandler := func(req *http.Request, res *http.Response) (oauth2.TokenSource, error) {
        // Discover authentication requirements
        metadata, err := oauthex.GetProtectedResourceMetadataFromHeader(
            ctx,
            serverURL,
            res.Header,
            nil,
        )
        if err != nil || metadata == nil {
            return nil, fmt.Errorf("failed to get metadata: %w", err)
        }

        // Get authorization server metadata
        authServerURL := metadata.AuthorizationServers[0]
        authMeta, err := oauthex.GetAuthServerMeta(ctx, authServerURL, nil)
        if err != nil {
            return nil, fmt.Errorf("failed to get auth server metadata: %w", err)
        }

        // Perform OAuth flow (example: device code flow)
        config := &oauth2.Config{
            ClientID: "your-client-id",
            Endpoint: oauth2.Endpoint{
                AuthURL:  authMeta.AuthorizationEndpoint,
                TokenURL: authMeta.TokenEndpoint,
            },
            Scopes: metadata.ScopesSupported,
        }

        // Obtain token (implementation depends on OAuth flow type)
        token, err := performOAuthFlow(ctx, config)
        if err != nil {
            return nil, err
        }

        return config.TokenSource(ctx, token), nil
    }

    // Create HTTP transport with OAuth support
    transport, err := auth.NewHTTPTransport(oauthHandler, nil)
    if err != nil {
        return nil, err
    }

    // Create HTTP client with OAuth transport
    httpClient := &http.Client{Transport: transport}

    // Use with MCP client
    client := mcp.NewClient(&mcp.Implementation{
        Name:    "oauth-client",
        Version: "1.0.0",
    }, nil)

    mcpTransport := &mcp.StreamableClientTransport{
        Endpoint:   serverURL,
        HTTPClient: httpClient,
    }

    return client.Connect(ctx, mcpTransport, nil)
}

func performOAuthFlow(ctx context.Context, config *oauth2.Config) (*oauth2.Token, error) {
    // Implement your OAuth flow (device code, authorization code, etc.)
    // This is a placeholder
    return nil, fmt.Errorf("implement OAuth flow")
}

HTTPTransportOptions

Configuration options for HTTPTransport.

type HTTPTransportOptions struct {
    Base http.RoundTripper
}

Fields:

  • Base: Underlying HTTP RoundTripper to use (defaults to http.DefaultTransport if nil)

Example:

import (
    "net/http"
    "time"
    "github.com/modelcontextprotocol/go-sdk/auth"
)

// Create transport with custom base
customTransport := &http.Transport{
    MaxIdleConns:    100,
    IdleConnTimeout: 90 * time.Second,
}

oauthTransport, err := auth.NewHTTPTransport(
    myOAuthHandler,
    &auth.HTTPTransportOptions{
        Base: customTransport,
    },
)

How HTTPTransport Works

  1. Initial Request: Client makes HTTP request through HTTPTransport
  2. 401 Response: Server returns 401 Unauthorized with WWW-Authenticate header
  3. OAuth Flow: HTTPTransport calls OAuthHandler to obtain TokenSource
  4. Token Obtained: Handler returns TokenSource (only called once)
  5. Retry Request: HTTPTransport wraps base with oauth2.Transport
  6. Authenticated: Subsequent requests automatically include bearer token
  7. Token Refresh: oauth2.Transport handles automatic token refresh

Important notes:

  • OAuth handler is called only once per transport lifetime
  • Once TokenSource is obtained, it's used for all future requests
  • Subsequent 401 responses are not processed (no re-authentication loop)
  • Thread-safe for concurrent requests
  • Request bodies are buffered to allow retry

Integration with Protected Resource Metadata

The auth package supports RFC 9728 (OAuth 2.0 Protected Resource Metadata) by including the resource metadata URL in the WWW-Authenticate header when authentication fails.

When a client receives a 401 Unauthorized response, it can fetch the resource metadata to discover:

  • Authorization servers
  • Supported scopes
  • Authentication requirements

Example WWW-Authenticate header:

WWW-Authenticate: Bearer resource_metadata=https://example.com/.well-known/oauth-resource-metadata

The client can then fetch this URL to get metadata about the protected resource (see OAuth Extensions for the metadata format).