The auth package provides OAuth bearer token authentication for MCP servers and HTTP transport with automatic OAuth flow support for clients.
import "github.com/modelcontextprotocol/go-sdk/auth"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 contexttoken: Bearer token from Authorization headerreq: HTTP request for additional contextReturns:
*TokenInfo: Information extracted from valid tokenerror: ErrInvalidToken for invalid tokens, ErrOAuth for OAuth protocol errorsExample:
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
}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 tokenExpiration: Token expiration timeExtra: Additional token claims or metadatafunc RequireBearerToken(verifier TokenVerifier, opts *RequireBearerTokenOptions) func(http.Handler) http.HandlerReturns HTTP middleware that verifies bearer tokens using the provided verifier.
Behavior:
Parameters:
verifier: Function to verify tokensopts: 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))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 accessfunc TokenInfoFromContext(ctx context.Context) *TokenInfoReturns 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"))
}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.
Note: Client OAuth features require the mcp_go_client_oauth build tag.
go build -tags mcp_go_client_oauthtype 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 flowopts: Transport options (can be nil)Returns:
*HTTPTransport: HTTP transport with OAuth supporterror: Construction errorMethods:
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 401type HTTPTransportOptions struct {
Base http.RoundTripper
}Options for NewHTTPTransport.
Fields:
Base: Underlying transport to use (defaults to http.DefaultTransport)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 401res: 401 response with WWW-Authenticate headerReturns:
oauth2.TokenSource: Token source for authenticated requestserror: OAuth flow errorpackage 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))
}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
}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
}
}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)
})
}ErrInvalidToken for authentication failuresErrOAuth for protocol-level issues