The auth package provides OAuth 2.0 bearer token authentication and authorization for MCP servers.
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.
import "github.com/modelcontextprotocol/go-sdk/auth"Returns HTTP middleware that verifies bearer tokens using a custom verifier.
func RequireBearerToken(
verifier TokenVerifier,
opts *RequireBearerTokenOptions,
) func(http.Handler) http.HandlerParameters:
verifier: Function to verify and extract information from tokensopts: Optional configuration for the middlewareReturns: Middleware function that can wrap HTTP handlers
Behavior:
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))
}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 haveFunction type for verifying bearer tokens.
type TokenVerifier func(
ctx context.Context,
token string,
req *http.Request,
) (*TokenInfo, error)Parameters:
ctx: Request contexttoken: Bearer token string to verifyreq: HTTP request (for additional verification context)Returns:
TokenInfo: Information extracted from the tokenerror: Error if verification fails (should wrap ErrInvalidToken or ErrOAuth)Implementation guidelines:
ErrInvalidToken for invalid/malformed tokens (results in 401)ErrOAuth for OAuth protocol errors (results in 400)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
}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 tokenExpiration: When the token expiresExtra: Additional token claims or metadataRetrieves TokenInfo from a request context.
func TokenInfoFromContext(ctx context.Context) *TokenInfoReturns: 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 that TokenVerifier should return for invalid tokens.
var ErrInvalidToken = errors.New("invalid token")Usage: Wrap this error when token verification fails due to:
Error that TokenVerifier should return for OAuth protocol errors.
var ErrOAuth = errors.New("oauth error")Usage: Wrap this error for OAuth-specific protocol violations.
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))
}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.
An HTTP RoundTripper that automatically handles OAuth 2.0 authentication when encountering 401 Unauthorized responses.
type HTTPTransport struct {
// contains filtered or unexported fields
}Behavior:
http.RoundTripper interfaceFunction type for conducting OAuth flows.
type OAuthHandler func(
req *http.Request,
res *http.Response,
) (oauth2.TokenSource, error)Parameters:
req: The HTTP request that triggered authenticationres: The 401 Unauthorized responseReturns:
oauth2.TokenSource: Token source for future requestserror: Error if OAuth flow failsImplementation notes:
oauthex.GetProtectedResourceMetadataFromHeader to discover auth requirementsCreates 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 configurationReturns:
*HTTPTransport: New transport instanceerror: Error if handler is nilExample:
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")
}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,
},
)Important notes:
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:
Example WWW-Authenticate header:
WWW-Authenticate: Bearer resource_metadata=https://example.com/.well-known/oauth-resource-metadataThe client can then fetch this URL to get metadata about the protected resource (see OAuth Extensions for the metadata format).