or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

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

auth.mddocs/

Authentication

The auth package provides OAuth bearer token authentication for MCP servers and HTTP transport with automatic OAuth flow support for clients.

Import Path

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

Server-Side Authentication

Token Verification

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

Function that checks validity of a bearer token and extracts information.

Parameters:

  • ctx: Request context
  • token: Bearer token from Authorization header
  • req: HTTP request for additional context

Returns:

  • *TokenInfo: Information extracted from valid token
  • error: ErrInvalidToken for invalid tokens, ErrOAuth for OAuth protocol errors

Example:

func myVerifier(ctx context.Context, token string, req *http.Request) (*auth.TokenInfo, error) {
	// Verify token (e.g., JWT validation, database lookup)
	claims, err := validateJWT(token)
	if err != nil {
		return nil, auth.ErrInvalidToken
	}

	// Extract token information
	return &auth.TokenInfo{
		Scopes:     claims.Scopes,
		Expiration: claims.ExpiresAt,
		Extra: map[string]any{
			"user_id": claims.UserID,
			"tenant":  claims.Tenant,
		},
	}, nil
}

Token Information

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

Information extracted from a bearer token.

Fields:

  • Scopes: OAuth scopes granted to the token
  • Expiration: Token expiration time
  • Extra: Additional token claims or metadata

Middleware

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

Returns HTTP middleware that verifies bearer tokens using the provided verifier.

Behavior:

  • On success: Adds TokenInfo to request context, calls next handler
  • On failure: Returns 401 with WWW-Authenticate header for OAuth flow

Parameters:

  • verifier: Function to verify tokens
  • opts: Options for authentication (can be nil)

Returns: Middleware function

Example:

// Create token verifier
verifier := func(ctx context.Context, token string, req *http.Request) (*auth.TokenInfo, error) {
	return validateToken(token)
}

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

// Apply to MCP handler
mcpHandler := mcp.NewStreamableHTTPHandler(getServer, nil)
http.Handle("/mcp", authMiddleware(mcpHandler))

Middleware Options

type RequireBearerTokenOptions struct {
	ResourceMetadataURL string
	Scopes              []string
}

Options for RequireBearerToken middleware.

Fields:

  • ResourceMetadataURL: URL for resource server metadata in OAuth flow (RFC 9728)
  • Scopes: Required OAuth scopes for access

Accessing Token Information

func TokenInfoFromContext(ctx context.Context) *TokenInfo

Returns TokenInfo stored in context by RequireBearerToken middleware, or nil if none.

Example:

func myHandler(w http.ResponseWriter, req *http.Request) {
	// Get token info from context
	tokenInfo := auth.TokenInfoFromContext(req.Context())
	if tokenInfo == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	// Check scopes
	if !hasScope(tokenInfo.Scopes, "admin") {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	// Access extra claims
	userID := tokenInfo.Extra["user_id"].(string)
	log.Printf("Request from user: %s", userID)

	// Continue processing
	w.Write([]byte("Success"))
}

Error Types

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

Error that TokenVerifier should return if token cannot be verified (expired, malformed, etc.).

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

Error that TokenVerifier should return for OAuth-specific protocol errors.

Client-Side Authentication

Note: Client OAuth features require the mcp_go_client_oauth build tag.

go build -tags mcp_go_client_oauth

HTTP Transport with OAuth

type HTTPTransport struct {
	// Has unexported fields
}

HTTP round tripper that follows the MCP OAuth protocol on 401 responses.

Functions:

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

Creates a new HTTPTransport with OAuth handler.

Parameters:

  • handler: Function to conduct OAuth flow
  • opts: Transport options (can be nil)

Returns:

  • *HTTPTransport: HTTP transport with OAuth support
  • error: Construction error

Methods:

func (t *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error)

Implements http.RoundTripper interface. On 401 response, invokes OAuth handler.

Example:

// Create OAuth handler
oauthHandler := func(req *http.Request, res *http.Response) (oauth2.TokenSource, error) {
	// Extract OAuth metadata from WWW-Authenticate header
	metadata, err := oauthex.GetProtectedResourceMetadataFromHeader(
		req.Context(),
		res.Header.Get("WWW-Authenticate"),
		http.DefaultClient,
	)
	if err != nil {
		return nil, err
	}

	// Get authorization server metadata
	authServer := metadata.AuthorizationServers[0]
	authMeta, err := oauthex.GetAuthServerMeta(req.Context(), authServer, http.DefaultClient)
	if err != nil {
		return nil, err
	}

	// Register client dynamically
	clientReg, err := oauthex.RegisterClient(
		req.Context(),
		authMeta.RegistrationEndpoint,
		&oauthex.ClientRegistrationMetadata{
			RedirectURIs: []string{"http://localhost:8080/callback"},
			GrantTypes:   []string{"authorization_code"},
		},
		http.DefaultClient,
	)
	if err != nil {
		return nil, err
	}

	// Conduct OAuth flow
	config := &oauth2.Config{
		ClientID:     clientReg.ClientID,
		ClientSecret: clientReg.ClientSecret,
		Endpoint: oauth2.Endpoint{
			AuthURL:  authMeta.AuthorizationEndpoint,
			TokenURL: authMeta.TokenEndpoint,
		},
		Scopes: metadata.ScopesSupported,
	}

	// Get authorization code (implementation specific)
	authCode := getAuthorizationCode(config)

	// Exchange for token
	token, err := config.Exchange(req.Context(), authCode)
	if err != nil {
		return nil, err
	}

	return oauth2.StaticTokenSource(token), nil
}

// Create HTTP transport with OAuth
transport, err := auth.NewHTTPTransport(oauthHandler, &auth.HTTPTransportOptions{
	Base: http.DefaultTransport,
})
if err != nil {
	log.Fatal(err)
}

// Use with HTTP client
client := &http.Client{Transport: transport}
resp, err := client.Get("https://api.example.com/mcp")
// Automatically handles OAuth flow on 401

HTTP Transport Options

type HTTPTransportOptions struct {
	Base http.RoundTripper
}

Options for NewHTTPTransport.

Fields:

  • Base: Underlying transport to use (defaults to http.DefaultTransport)

OAuth Handler

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

Function that conducts OAuth flow and returns TokenSource on user approval.

Parameters:

  • req: Original HTTP request that received 401
  • res: 401 response with WWW-Authenticate header

Returns:

  • oauth2.TokenSource: Token source for authenticated requests
  • error: OAuth flow error

Complete Authentication Examples

Server with JWT Authentication

package main

import (
	"context"
	"log"
	"net/http"
	"time"
	"github.com/golang-jwt/jwt/v5"
	"github.com/modelcontextprotocol/go-sdk/auth"
	"github.com/modelcontextprotocol/go-sdk/mcp"
)

var jwtSecret = []byte("your-secret-key")

func verifyJWT(ctx context.Context, tokenString string, req *http.Request) (*auth.TokenInfo, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
		return jwtSecret, nil
	})
	if err != nil {
		return nil, auth.ErrInvalidToken
	}

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

	// Extract scopes
	scopesRaw, _ := claims["scopes"].([]any)
	scopes := make([]string, len(scopesRaw))
	for i, s := range scopesRaw {
		scopes[i] = s.(string)
	}

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

	return &auth.TokenInfo{
		Scopes:     scopes,
		Expiration: time.Unix(int64(exp), 0),
		Extra: map[string]any{
			"user_id": claims["sub"],
		},
	}, nil
}

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

	// Add authentication middleware
	authMiddleware := auth.RequireBearerToken(verifyJWT, &auth.RequireBearerTokenOptions{
		ResourceMetadataURL: "https://api.example.com/.well-known/oauth-protected-resource",
		Scopes:              []string{"mcp:access"},
	})

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

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

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Using Token Info in Server Session

func myToolHandler(ctx context.Context, req *mcp.CallToolRequest, input Input) (
	*mcp.CallToolResult,
	Output,
	error,
) {
	// Access token info from request extra
	if req.Extra.TokenInfo != nil {
		userID := req.Extra.TokenInfo.Extra["user_id"].(string)
		log.Printf("Tool called by user: %s", userID)

		// Check scopes
		if !hasScope(req.Extra.TokenInfo.Scopes, "admin") {
			return &mcp.CallToolResult{IsError: true}, Output{},
				fmt.Errorf("insufficient permissions")
		}
	}

	// Process tool
	return nil, output, nil
}

func hasScope(scopes []string, required string) bool {
	for _, s := range scopes {
		if s == required {
			return true
		}
	}
	return false
}

Database-Backed Token Verification

func databaseVerifier(db *sql.DB) auth.TokenVerifier {
	return func(ctx context.Context, token string, req *http.Request) (*auth.TokenInfo, error) {
		var userID string
		var scopes string
		var expiresAt time.Time

		err := db.QueryRowContext(ctx,
			"SELECT user_id, scopes, expires_at FROM tokens WHERE token = $1",
			token,
		).Scan(&userID, &scopes, &expiresAt)

		if err == sql.ErrNoRows {
			return nil, auth.ErrInvalidToken
		}
		if err != nil {
			return nil, err
		}

		if time.Now().After(expiresAt) {
			return nil, auth.ErrInvalidToken
		}

		return &auth.TokenInfo{
			Scopes:     strings.Split(scopes, ","),
			Expiration: expiresAt,
			Extra: map[string]any{
				"user_id": userID,
			},
		}, nil
	}
}

Rate Limiting with Token Info

type rateLimiter struct {
	limits map[string]*rate.Limiter
	mu     sync.Mutex
}

func (rl *rateLimiter) middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		tokenInfo := auth.TokenInfoFromContext(req.Context())
		if tokenInfo == nil {
			next.ServeHTTP(w, req)
			return
		}

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

		rl.mu.Lock()
		limiter, exists := rl.limits[userID]
		if !exists {
			limiter = rate.NewLimiter(rate.Limit(10), 100) // 10 req/s, burst 100
			rl.limits[userID] = limiter
		}
		rl.mu.Unlock()

		if !limiter.Allow() {
			http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
			return
		}

		next.ServeHTTP(w, req)
	})
}

Best Practices

Token Verification

  • Always validate token signature and expiration
  • Cache verification results when possible
  • Log authentication failures for security monitoring
  • Use secure token storage

Scope Management

  • Define fine-grained scopes (e.g., "mcp:tools:read", "mcp:resources:write")
  • Check scopes at handler level, not just middleware
  • Document required scopes in tool/resource definitions
  • Use least privilege principle

Error Handling

  • Return ErrInvalidToken for authentication failures
  • Return ErrOAuth for protocol-level issues
  • Don't leak information in error messages
  • Log detailed errors server-side

Security

  • Use HTTPS in production
  • Rotate secrets regularly
  • Implement token revocation
  • Monitor for suspicious activity
  • Set appropriate token expiration times