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_oauthimport "github.com/modelcontextprotocol/go-sdk/oauthex"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 contextresourceURL: Base URL of the protected resourcec: HTTP client (uses http.DefaultClient if nil)Returns:
*ProtectedResourceMetadata: Resource metadataerror: Retrieval errorExample:
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 contextwwwAuthHeader: Value of WWW-Authenticate header from 401 responsec: HTTP client (uses http.DefaultClient if nil)Returns:
*ProtectedResourceMetadata: Resource metadataerror: Retrieval errorExample:
// 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)
}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 identifiersJWKSURI: URL of the protected resource's JSON Web Key SetScopesSupported: OAuth 2.0 scope values for authorization requests (recommended)BearerMethodsSupported: Methods of sending bearer tokens ("header", "body", "query")ResourceSigningAlgValuesSupported: JWS signing algorithms for resource responsesResourceName: Human-readable name (recommended)ResourceDocumentation: URL for developer documentationResourcePolicyURI: URL for policy informationResourceTOSURI: URL for terms of serviceTLSClientCertificateBoundAccessTokens: 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)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 contextissuerURL: Issuer identifier URLc: HTTP client (uses http.DefaultClient if nil)Returns:
*AuthServerMeta: Authorization server metadataerror: Retrieval errorExample:
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)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.
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 contextregistrationEndpoint: Client registration endpoint URLclientMeta: Client metadata for registrationc: HTTP client (uses http.DefaultClient if nil)Returns:
*ClientRegistrationResponse: Registration response with credentialserror: 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)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 nameClientURI: URL of client's home pageLogoURI: URL of client's logo imageScope: Space-separated scope valuesContacts: Contact email addressesTOSURI: URL of client's terms of servicePolicyURI: URL of client's privacy policyJWKSURI: URL of client's JSON Web Key SetJWKS: Client's JSON Web Key Set by valueSoftwareID: Identifier for the client softwareSoftwareVersion: Version of the client softwareSoftwareStatement: Software statement (JWT)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:
ClientID: OAuth 2.0 client identifierClientSecret: OAuth 2.0 client secret (if issued)ClientIDIssuedAt: Time when client_id was issuedClientSecretExpiresAt: Time when client_secret expires (0 if doesn't expire)Methods:
func (r *ClientRegistrationResponse) MarshalJSON() ([]byte, error)
func (r *ClientRegistrationResponse) UnmarshalJSON(data []byte) errortype 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 descriptionMethods:
func (e *ClientRegistrationError) Error() string//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))
}//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
}
}