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

oauthex.mddocs/

OAuth Extensions

The oauthex package implements OAuth 2.0 extensions including protected resource metadata (RFC 9728), authorization server metadata (RFC 8414), and dynamic client registration (RFC 7591).

Important: Most features require the mcp_go_client_oauth build tag:

go build -tags mcp_go_client_oauth

Import Path

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

Protected Resource Metadata (RFC 9728)

Fetching Metadata

func GetProtectedResourceMetadata(ctx context.Context, resourceURL string, c *http.Client) (*ProtectedResourceMetadata, error)

Retrieves protected resource metadata from the resource server.

Note: Requires mcp_go_client_oauth build tag.

Parameters:

  • ctx: Request context
  • resourceURL: Base URL of the protected resource
  • c: HTTP client (uses http.DefaultClient if nil)

Returns:

  • *ProtectedResourceMetadata: Resource metadata
  • error: Retrieval error

Example:

metadata, err := oauthex.GetProtectedResourceMetadata(
	ctx,
	"https://api.example.com",
	http.DefaultClient,
)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Resource: %s\n", metadata.Resource)
fmt.Printf("Auth servers: %v\n", metadata.AuthorizationServers)
fmt.Printf("Scopes: %v\n", metadata.ScopesSupported)
func GetProtectedResourceMetadataFromHeader(ctx context.Context, wwwAuthHeader string, c *http.Client) (*ProtectedResourceMetadata, error)

Retrieves protected resource metadata from a WWW-Authenticate header.

Note: Requires mcp_go_client_oauth build tag.

Parameters:

  • ctx: Request context
  • wwwAuthHeader: Value of WWW-Authenticate header from 401 response
  • c: HTTP client (uses http.DefaultClient if nil)

Returns:

  • *ProtectedResourceMetadata: Resource metadata
  • error: Retrieval error

Example:

// After receiving 401 response
resp, err := client.Get("https://api.example.com/mcp")
if err != nil {
	log.Fatal(err)
}

if resp.StatusCode == 401 {
	metadata, err := oauthex.GetProtectedResourceMetadataFromHeader(
		ctx,
		resp.Header.Get("WWW-Authenticate"),
		http.DefaultClient,
	)
	if err != nil {
		log.Fatal(err)
	}

	// Use metadata to initiate OAuth flow
	fmt.Printf("Auth servers: %v\n", metadata.AuthorizationServers)
}

Protected Resource Metadata Type

type ProtectedResourceMetadata struct {
	Resource                              string   `json:"resource"`
	AuthorizationServers                  []string `json:"authorization_servers,omitempty"`
	JWKSURI                               string   `json:"jwks_uri,omitempty"`
	ScopesSupported                       []string `json:"scopes_supported,omitempty"`
	BearerMethodsSupported                []string `json:"bearer_methods_supported,omitempty"`
	ResourceSigningAlgValuesSupported     []string `json:"resource_signing_alg_values_supported,omitempty"`
	ResourceName                          string   `json:"resource_name,omitempty"`
	ResourceDocumentation                 string   `json:"resource_documentation,omitempty"`
	ResourcePolicyURI                     string   `json:"resource_policy_uri,omitempty"`
	ResourceTOSURI                        string   `json:"resource_tos_uri,omitempty"`
	TLSClientCertificateBoundAccessTokens bool     `json:"tls_client_certificate_bound_access_tokens,omitempty"`
	AuthorizationDetailsTypesSupported    []string `json:"authorization_details_types_supported,omitempty"`
	DPOPSigningAlgValuesSupported         []string `json:"dpop_signing_alg_values_supported,omitempty"`
	DPOPBoundAccessTokensRequired         bool     `json:"dpop_bound_access_tokens_required,omitempty"`
}

Metadata for an OAuth 2.0 protected resource (RFC 9728).

Note: Requires mcp_go_client_oauth build tag.

Fields:

  • Resource: Protected resource's resource identifier (required)
  • AuthorizationServers: OAuth 2.0 authorization server issuer identifiers
  • JWKSURI: URL of the protected resource's JSON Web Key Set
  • ScopesSupported: OAuth 2.0 scope values for authorization requests (recommended)
  • BearerMethodsSupported: Methods of sending bearer tokens ("header", "body", "query")
  • ResourceSigningAlgValuesSupported: JWS signing algorithms for resource responses
  • ResourceName: Human-readable name (recommended)
  • ResourceDocumentation: URL for developer documentation
  • ResourcePolicyURI: URL for policy information
  • ResourceTOSURI: URL for terms of service
  • TLSClientCertificateBoundAccessTokens: Support for mutual TLS (RFC 8705)
  • AuthorizationDetailsTypesSupported: Type values for authorization_details (RFC 9396)
  • DPOPSigningAlgValuesSupported: JWS algorithms for DPoP proof JWTs (RFC 9449)
  • DPOPBoundAccessTokensRequired: Whether DPoP is required (RFC 9449)

Authorization Server Metadata (RFC 8414)

Fetching Server Metadata

func GetAuthServerMeta(ctx context.Context, issuerURL string, c *http.Client) (*AuthServerMeta, error)

Retrieves authorization server metadata from the OAuth 2.0 authorization server.

Note: Requires mcp_go_client_oauth build tag.

Parameters:

  • ctx: Request context
  • issuerURL: Issuer identifier URL
  • c: HTTP client (uses http.DefaultClient if nil)

Returns:

  • *AuthServerMeta: Authorization server metadata
  • error: Retrieval error

Example:

authMeta, err := oauthex.GetAuthServerMeta(
	ctx,
	"https://auth.example.com",
	http.DefaultClient,
)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Issuer: %s\n", authMeta.Issuer)
fmt.Printf("Token endpoint: %s\n", authMeta.TokenEndpoint)
fmt.Printf("Authorization endpoint: %s\n", authMeta.AuthorizationEndpoint)
fmt.Printf("Scopes: %v\n", authMeta.ScopesSupported)

Authorization Server Metadata Type

type AuthServerMeta struct {
	Issuer                                             string   `json:"issuer"`
	AuthorizationEndpoint                              string   `json:"authorization_endpoint"`
	TokenEndpoint                                      string   `json:"token_endpoint"`
	JWKSURI                                            string   `json:"jwks_uri"`
	RegistrationEndpoint                               string   `json:"registration_endpoint,omitempty"`
	ScopesSupported                                    []string `json:"scopes_supported,omitempty"`
	ResponseTypesSupported                             []string `json:"response_types_supported"`
	ResponseModesSupported                             []string `json:"response_modes_supported,omitempty"`
	GrantTypesSupported                                []string `json:"grant_types_supported,omitempty"`
	TokenEndpointAuthMethodsSupported                  []string `json:"token_endpoint_auth_methods_supported,omitempty"`
	TokenEndpointAuthSigningAlgValuesSupported         []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
	ServiceDocumentation                               string   `json:"service_documentation,omitempty"`
	UILocalesSupported                                 []string `json:"ui_locales_supported,omitempty"`
	OpPolicyURI                                        string   `json:"op_policy_uri,omitempty"`
	OpTOSURI                                           string   `json:"op_tos_uri,omitempty"`
	RevocationEndpoint                                 string   `json:"revocation_endpoint,omitempty"`
	RevocationEndpointAuthMethodsSupported             []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
	RevocationEndpointAuthSigningAlgValuesSupported    []string `json:"revocation_endpoint_auth_signing_alg_values_supported,omitempty"`
	IntrospectionEndpoint                              string   `json:"introspection_endpoint,omitempty"`
	IntrospectionEndpointAuthMethodsSupported          []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
	IntrospectionEndpointAuthSigningAlgValuesSupported []string `json:"introspection_endpoint_auth_signing_alg_values_supported,omitempty"`
	CodeChallengeMethodsSupported                      []string `json:"code_challenge_methods_supported,omitempty"`
}

Metadata for an OAuth 2.0 authorization server (RFC 8414).

Note: Requires mcp_go_client_oauth build tag.

Dynamic Client Registration (RFC 7591)

Registering a Client

func RegisterClient(ctx context.Context, registrationEndpoint string, clientMeta *ClientRegistrationMetadata, c *http.Client) (*ClientRegistrationResponse, error)

Performs Dynamic Client Registration according to RFC 7591.

Note: Requires mcp_go_client_oauth build tag.

Parameters:

  • ctx: Request context
  • registrationEndpoint: Client registration endpoint URL
  • clientMeta: Client metadata for registration
  • c: HTTP client (uses http.DefaultClient if nil)

Returns:

  • *ClientRegistrationResponse: Registration response with credentials
  • error: Registration error (may be *ClientRegistrationError)

Example:

// Register a new client
registration, err := oauthex.RegisterClient(
	ctx,
	"https://auth.example.com/register",
	&oauthex.ClientRegistrationMetadata{
		RedirectURIs:            []string{"http://localhost:8080/callback"},
		TokenEndpointAuthMethod: "client_secret_basic",
		GrantTypes:              []string{"authorization_code", "refresh_token"},
		ResponseTypes:           []string{"code"},
		ClientName:              "My MCP Client",
		ClientURI:               "https://myapp.example.com",
		Scope:                   "mcp:read mcp:write",
	},
	http.DefaultClient,
)
if err != nil {
	if regErr, ok := err.(*oauthex.ClientRegistrationError); ok {
		log.Printf("Registration failed: %s - %s", regErr.ErrorCode, regErr.ErrorDescription)
	} else {
		log.Fatal(err)
	}
	return
}

fmt.Printf("Client ID: %s\n", registration.ClientID)
fmt.Printf("Client Secret: %s\n", registration.ClientSecret)
fmt.Printf("Issued at: %s\n", registration.ClientIDIssuedAt)

Client Registration Metadata

type ClientRegistrationMetadata struct {
	RedirectURIs            []string `json:"redirect_uris"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method,omitempty"`
	GrantTypes              []string `json:"grant_types,omitempty"`
	ResponseTypes           []string `json:"response_types,omitempty"`
	ClientName              string   `json:"client_name,omitempty"`
	ClientURI               string   `json:"client_uri,omitempty"`
	LogoURI                 string   `json:"logo_uri,omitempty"`
	Scope                   string   `json:"scope,omitempty"`
	Contacts                []string `json:"contacts,omitempty"`
	TOSURI                  string   `json:"tos_uri,omitempty"`
	PolicyURI               string   `json:"policy_uri,omitempty"`
	JWKSURI                 string   `json:"jwks_uri,omitempty"`
	JWKS                    string   `json:"jwks,omitempty"`
	SoftwareID              string   `json:"software_id,omitempty"`
	SoftwareVersion         string   `json:"software_version,omitempty"`
	SoftwareStatement       string   `json:"software_statement,omitempty"`
}

Client metadata for Dynamic Client Registration POST request (RFC 7591).

Note: Requires mcp_go_client_oauth build tag.

Fields:

  • RedirectURIs: Redirection URI values used by the client (required)
  • TokenEndpointAuthMethod: Authentication method ("client_secret_basic", "client_secret_post", etc.)
  • GrantTypes: OAuth 2.0 grant types ("authorization_code", "client_credentials", etc.)
  • ResponseTypes: OAuth 2.0 response types ("code", "token", etc.)
  • ClientName: Human-readable client name
  • ClientURI: URL of client's home page
  • LogoURI: URL of client's logo image
  • Scope: Space-separated scope values
  • Contacts: Contact email addresses
  • TOSURI: URL of client's terms of service
  • PolicyURI: URL of client's privacy policy
  • JWKSURI: URL of client's JSON Web Key Set
  • JWKS: Client's JSON Web Key Set by value
  • SoftwareID: Identifier for the client software
  • SoftwareVersion: Version of the client software
  • SoftwareStatement: Software statement (JWT)

Client Registration Response

type ClientRegistrationResponse struct {
	ClientRegistrationMetadata
	ClientID              string    `json:"client_id"`
	ClientSecret          string    `json:"client_secret,omitempty"`
	ClientIDIssuedAt      time.Time `json:"client_id_issued_at,omitempty"`
	ClientSecretExpiresAt time.Time `json:"client_secret_expires_at,omitempty"`
}

Response from authorization server on successful client registration (RFC 7591).

Note: Requires mcp_go_client_oauth build tag.

Fields:

  • Inherits all fields from ClientRegistrationMetadata
  • ClientID: OAuth 2.0 client identifier
  • ClientSecret: OAuth 2.0 client secret (if issued)
  • ClientIDIssuedAt: Time when client_id was issued
  • ClientSecretExpiresAt: Time when client_secret expires (0 if doesn't expire)

Methods:

func (r *ClientRegistrationResponse) MarshalJSON() ([]byte, error)
func (r *ClientRegistrationResponse) UnmarshalJSON(data []byte) error

Client Registration Error

type ClientRegistrationError struct {
	ErrorCode        string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
}

Error response from authorization server for failed client registration (RFC 7591).

Note: Requires mcp_go_client_oauth build tag.

Fields:

  • ErrorCode: Error code ("invalid_redirect_uri", "invalid_client_metadata", etc.)
  • ErrorDescription: Human-readable error description

Methods:

func (e *ClientRegistrationError) Error() string

Complete OAuth Flow Example

//go:build mcp_go_client_oauth

package main

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

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

	// 1. Discover resource metadata from 401 response
	resp, err := http.Get("https://api.example.com/mcp")
	if err != nil {
		log.Fatal(err)
	}

	if resp.StatusCode != 401 {
		log.Fatal("Expected 401 response")
	}

	resourceMeta, err := oauthex.GetProtectedResourceMetadataFromHeader(
		ctx,
		resp.Header.Get("WWW-Authenticate"),
		http.DefaultClient,
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Resource: %s\n", resourceMeta.Resource)
	fmt.Printf("Scopes: %v\n", resourceMeta.ScopesSupported)

	// 2. Get authorization server metadata
	authServerURL := resourceMeta.AuthorizationServers[0]
	authMeta, err := oauthex.GetAuthServerMeta(ctx, authServerURL, http.DefaultClient)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Auth server: %s\n", authMeta.Issuer)
	fmt.Printf("Token endpoint: %s\n", authMeta.TokenEndpoint)

	// 3. Register client dynamically
	clientReg, err := oauthex.RegisterClient(
		ctx,
		authMeta.RegistrationEndpoint,
		&oauthex.ClientRegistrationMetadata{
			RedirectURIs:            []string{"http://localhost:8080/callback"},
			TokenEndpointAuthMethod: "client_secret_basic",
			GrantTypes:              []string{"authorization_code"},
			ResponseTypes:           []string{"code"},
			ClientName:              "MCP Client",
			Scope:                   "mcp:read mcp:write",
		},
		http.DefaultClient,
	)
	if err != nil {
		if regErr, ok := err.(*oauthex.ClientRegistrationError); ok {
			log.Fatalf("Registration error: %s - %s", regErr.ErrorCode, regErr.ErrorDescription)
		}
		log.Fatal(err)
	}

	fmt.Printf("Registered client: %s\n", clientReg.ClientID)

	// 4. Configure OAuth2
	config := &oauth2.Config{
		ClientID:     clientReg.ClientID,
		ClientSecret: clientReg.ClientSecret,
		Endpoint: oauth2.Endpoint{
			AuthURL:  authMeta.AuthorizationEndpoint,
			TokenURL: authMeta.TokenEndpoint,
		},
		RedirectURL: "http://localhost:8080/callback",
		Scopes:      resourceMeta.ScopesSupported,
	}

	// 5. Get authorization code (simplified)
	authURL := config.AuthCodeURL("state")
	fmt.Printf("Visit: %s\n", authURL)

	var authCode string
	fmt.Print("Enter authorization code: ")
	fmt.Scanln(&authCode)

	// 6. Exchange for token
	token, err := config.Exchange(ctx, authCode)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Access token: %s\n", token.AccessToken)

	// 7. Use authenticated HTTP client
	httpClient := config.Client(ctx, token)

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

	transport := &mcp.SSEClientTransport{
		Endpoint:   "https://api.example.com/mcp",
		HTTPClient: httpClient,
	}

	session, err := client.Connect(ctx, transport, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	// Use authenticated session
	tools, err := session.ListTools(ctx, &mcp.ListToolsParams{})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Found %d tools\n", len(tools.Tools))
}

OAuth Handler for HTTP Transport

//go:build mcp_go_client_oauth

func createOAuthHandler() auth.OAuthHandler {
	return func(req *http.Request, res *http.Response) (oauth2.TokenSource, error) {
		ctx := req.Context()

		// Get resource metadata from WWW-Authenticate header
		resourceMeta, err := oauthex.GetProtectedResourceMetadataFromHeader(
			ctx,
			res.Header.Get("WWW-Authenticate"),
			http.DefaultClient,
		)
		if err != nil {
			return nil, err
		}

		// Get auth server metadata
		authMeta, err := oauthex.GetAuthServerMeta(
			ctx,
			resourceMeta.AuthorizationServers[0],
			http.DefaultClient,
		)
		if err != nil {
			return nil, err
		}

		// Register client
		clientReg, err := oauthex.RegisterClient(
			ctx,
			authMeta.RegistrationEndpoint,
			&oauthex.ClientRegistrationMetadata{
				RedirectURIs:  []string{"http://localhost:8080/callback"},
				GrantTypes:    []string{"authorization_code"},
				ResponseTypes: []string{"code"},
				ClientName:    "Auto-registered Client",
				Scope:         strings.Join(resourceMeta.ScopesSupported, " "),
			},
			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: resourceMeta.ScopesSupported,
		}

		// Get token (implementation specific)
		token := conductOAuthFlow(ctx, config)

		return oauth2.StaticTokenSource(token), nil
	}
}

Best Practices

Metadata Caching

  • Cache resource and server metadata to reduce network calls
  • Implement cache expiration based on Cache-Control headers
  • Validate cached metadata before use

Error Handling

  • Check for ClientRegistrationError specifically
  • Implement retry logic for transient failures
  • Log registration errors for debugging
  • Handle expired registrations

Security

  • Validate metadata URLs (must be HTTPS in production)
  • Verify metadata signatures when available
  • Store client credentials securely
  • Implement token refresh logic
  • Use PKCE for public clients

Client Registration

  • Provide meaningful client names and descriptions
  • Set appropriate redirect URIs
  • Request minimal required scopes
  • Handle registration expiration
  • Keep software version updated