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).
This package provides types and utilities for OAuth 2.0, enabling both servers and clients to work with OAuth 2.0 protected resources:
Build Tag Note: Client-side functions (metadata discovery, authorization server metadata, dynamic client registration) require building with the mcp_go_client_oauth build tag. Server-side functionality does not require special build tags.
import "github.com/modelcontextprotocol/go-sdk/oauthex"Metadata for an OAuth 2.0 protected resource as defined in RFC 9728.
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"`
}Fields:
Resource: Protected resource's resource identifier (required)AuthorizationServers: List of OAuth authorization server issuer identifiers (RFC 8414)JWKSURI: URL of the protected resource's JSON Web Key Set documentScopesSupported: Scope values used in authorization requests for this resource (recommended)BearerMethodsSupported: Methods for sending bearer tokens ("header", "body", "query")ResourceSigningAlgValuesSupported: JWS signing algorithms for resource responsesResourceName: Human-readable name for display to end usersResourceDocumentation: URL of developer documentationResourcePolicyURI: URL of usage policy informationResourceTOSURI: URL of terms of serviceTLSClientCertificateBoundAccessTokens: Whether mutual-TLS bound tokens are supported (default: false)AuthorizationDetailsTypesSupported: Supported 'type' values for authorization_details (RFC 9396)DPOPSigningAlgValuesSupported: JWS algorithms for validating DPoP proof JWTs (RFC 9449)DPOPBoundAccessTokensRequired: Whether DPoP-bound tokens are always required (default: false)Example metadata:
{
"resource": "https://api.example.com",
"authorization_servers": [
"https://auth.example.com"
],
"scopes_supported": [
"mcp:read",
"mcp:write",
"mcp:admin"
],
"bearer_methods_supported": [
"header"
],
"resource_name": "Example MCP Server",
"resource_documentation": "https://api.example.com/docs"
}When implementing an OAuth-protected MCP server, publish metadata at the well-known location:
/.well-known/oauth-protected-resourceor at a custom location specified in the WWW-Authenticate header.
Example server implementation:
package main
import (
"encoding/json"
"net/http"
"github.com/modelcontextprotocol/go-sdk/oauthex"
)
func main() {
// Define protected resource metadata
metadata := &oauthex.ProtectedResourceMetadata{
Resource: "https://api.example.com",
AuthorizationServers: []string{
"https://auth.example.com",
},
ScopesSupported: []string{
"mcp:read",
"mcp:write",
},
BearerMethodsSupported: []string{"header"},
ResourceName: "Example MCP API",
ResourceDocumentation: "https://api.example.com/docs",
}
// Serve metadata at well-known location
http.HandleFunc("/.well-known/oauth-protected-resource", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(metadata)
})
// Serve MCP endpoints with authentication
// ... (see auth.md for authentication setup)
http.ListenAndServe(":8080", nil)
}Note: Functions in this section require the mcp_go_client_oauth build tag.
Retrieves protected resource metadata from a resource server by its ID.
func GetProtectedResourceMetadataFromID(
ctx context.Context,
resourceID string,
c *http.Client,
) (*ProtectedResourceMetadata, error)Parameters:
ctx: Context for the HTTP requestresourceID: The resource ID (HTTPS URL of the resource server)c: HTTP client to use (uses http.DefaultClient if nil)Returns:
*ProtectedResourceMetadata: Retrieved metadataerror: Error if retrieval or validation failsBehavior:
/.well-known/oauth-protected-resource into the resource ID pathresource field matches the resource IDExample:
import (
"context"
"net/http"
"github.com/modelcontextprotocol/go-sdk/oauthex"
)
func discoverFromID(ctx context.Context) (*oauthex.ProtectedResourceMetadata, error) {
// For resource https://api.example.com/server
// Fetches from https://api.example.com/.well-known/oauth-protected-resource/server
metadata, err := oauthex.GetProtectedResourceMetadataFromID(
ctx,
"https://api.example.com/server",
nil, // use default HTTP client
)
if err != nil {
return nil, err
}
// Use metadata for OAuth flow
authServer := metadata.AuthorizationServers[0]
scopes := metadata.ScopesSupported
// ... configure OAuth client
return metadata, nil
}Retrieves protected resource metadata using information from WWW-Authenticate headers.
func GetProtectedResourceMetadataFromHeader(
ctx context.Context,
serverURL string,
header http.Header,
c *http.Client,
) (*ProtectedResourceMetadata, error)Parameters:
ctx: Context for the HTTP requestserverURL: The URL the client used to access the resource serverheader: HTTP headers from the 401 Unauthorized responsec: HTTP client to use (uses http.DefaultClient if nil)Returns:
*ProtectedResourceMetadata: Retrieved metadata, or nil if no metadata URL in headererror: Error if retrieval or validation failsBehavior:
resource field matches serverURLExample:
import (
"context"
"net/http"
"github.com/modelcontextprotocol/go-sdk/oauthex"
)
func handleUnauthorized(ctx context.Context, serverURL string, resp *http.Response) error {
// Get metadata from 401 response headers
metadata, err := oauthex.GetProtectedResourceMetadataFromHeader(
ctx,
serverURL,
resp.Header,
nil,
)
if err != nil {
return err
}
if metadata == nil {
// No metadata available, use fallback authentication
return nil
}
// Discover authorization server and initiate OAuth flow
authServerURL := metadata.AuthorizationServers[0]
requiredScopes := metadata.ScopesSupported
// ... perform OAuth flow
return nil
}Parses WWW-Authenticate header strings into authentication challenges.
func ParseWWWAuthenticate(headers []string) ([]challenge, error)Parameters:
headers: Slice of WWW-Authenticate header valuesReturns:
[]challenge: Parsed challenges (internal type, not exported)error: Error if header is malformedBehavior:
Note: This is a lower-level function. Typically use GetProtectedResourceMetadataFromHeader instead, which calls this internally.
Extracts the resource metadata URL from parsed authentication challenges.
func ResourceMetadataURL(cs []challenge) stringParameters:
cs: Slice of parsed challengesReturns:
string: Resource metadata URL, or empty string if not foundBehavior:
resource_metadata parameterNote: This is a lower-level function. Typically use GetProtectedResourceMetadataFromHeader instead, which calls this internally.
Note: Functions and types in this section require the mcp_go_client_oauth build tag.
Metadata for an OAuth 2.0 authorization server as defined in RFC 8414.
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"`
}Fields:
Required fields:
Issuer: Authorization server identifier URLAuthorizationEndpoint: OAuth 2.0 authorization endpoint URLTokenEndpoint: OAuth 2.0 token endpoint URLJWKSURI: JSON Web Key Set document URLResponseTypesSupported: Supported response_type valuesRecommended fields:
RegistrationEndpoint: Dynamic Client Registration endpoint URLScopesSupported: Supported OAuth 2.0 scope valuesResponseModesSupported: Supported response_mode valuesGrantTypesSupported: Supported OAuth 2.0 grant typesCodeChallengeMethodsSupported: Supported PKCE challenge methodsOptional fields:
ServiceDocumentation: Developer documentation URLUILocalesSupported: Supported BCP47 language tagsOpPolicyURI: Operator policy URLOpTOSURI: Terms of service URLRevocationEndpoint: Token revocation endpoint URLIntrospectionEndpoint: Token introspection endpoint URLRetrieves authorization server metadata from an OAuth authorization server.
func GetAuthServerMeta(
ctx context.Context,
issuerURL string,
c *http.Client,
) (*AuthServerMeta, error)Parameters:
ctx: Context for the HTTP requestissuerURL: The authorization server's issuer URLc: HTTP client to use (uses http.DefaultClient if nil)Returns:
*AuthServerMeta: Retrieved authorization server metadataerror: Error if retrieval, validation, or PKCE check failsBehavior:
/.well-known/oauth-authorization-server/.well-known/openid-configurationIssuer field matches issuerURLExample:
import (
"context"
"github.com/modelcontextprotocol/go-sdk/oauthex"
"golang.org/x/oauth2"
)
func setupOAuthClient(ctx context.Context, authServerURL string) (*oauth2.Config, error) {
// Fetch authorization server metadata
metadata, err := oauthex.GetAuthServerMeta(ctx, authServerURL, nil)
if err != nil {
return nil, err
}
// Configure OAuth 2.0 client using discovered endpoints
config := &oauth2.Config{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
Endpoint: oauth2.Endpoint{
AuthURL: metadata.AuthorizationEndpoint,
TokenURL: metadata.TokenEndpoint,
},
Scopes: []string{"mcp:read", "mcp:write"},
}
return config, nil
}Note: Functions and types in this section require the mcp_go_client_oauth build tag.
Dynamic Client Registration (DCR) allows clients to register themselves with an authorization server programmatically per RFC 7591.
Client metadata for the DCR POST request.
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"`
}Fields:
Required:
RedirectURIs: Redirection URI strings for redirect-based flowsOptional (with defaults):
TokenEndpointAuthMethod: Authentication method for token endpoint (default: "client_secret_basic")GrantTypes: OAuth 2.0 grant types (default: ["authorization_code"])ResponseTypes: OAuth 2.0 response types (default: ["code"])Recommended:
ClientName: Human-readable client nameClientURI: Web page with client informationOptional:
LogoURI: Logo URL for displayScope: Space-separated scope valuesContacts: Contact email addressesTOSURI: Terms of service URLPolicyURI: Privacy policy URLJWKSURI: JSON Web Key Set URLJWKS: JSON Web Key Set passed by valueSoftwareID: Unique identifier for client softwareSoftwareVersion: Version identifier for client softwareSoftwareStatement: JWT asserting client metadataResponse from the authorization server after successful registration.
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"`
}Fields:
ClientRegistrationMetadata: All registered metadata (embedded, may contain modified/defaulted values)ClientID: Newly issued OAuth 2.0 client identifier (required)ClientSecret: Optional client secret stringClientIDIssuedAt: Unix timestamp when client_id was issuedClientSecretExpiresAt: Unix timestamp when secret expires (0 if never expires, required if client_secret issued)Error response from the authorization server for failed registration.
type ClientRegistrationError struct {
ErrorCode string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}Fields:
ErrorCode: Required error code (RFC 7591, Section 3.2.2)ErrorDescription: Optional human-readable error messageMethods:
func (e *ClientRegistrationError) Error() stringReturns formatted error string.
Performs Dynamic Client Registration.
func RegisterClient(
ctx context.Context,
registrationEndpoint string,
clientMeta *ClientRegistrationMetadata,
c *http.Client,
) (*ClientRegistrationResponse, error)Parameters:
ctx: Context for the HTTP requestregistrationEndpoint: DCR endpoint URL (from AuthServerMeta.RegistrationEndpoint)clientMeta: Client metadata to registerc: HTTP client to use (uses http.DefaultClient if nil)Returns:
*ClientRegistrationResponse: Registration response on successerror: Error if registration fails (may be *ClientRegistrationError)Behavior:
client_id fieldExample:
import (
"context"
"fmt"
"github.com/modelcontextprotocol/go-sdk/oauthex"
)
func registerNewClient(ctx context.Context, authServer *oauthex.AuthServerMeta) error {
// Prepare client metadata
clientMeta := &oauthex.ClientRegistrationMetadata{
RedirectURIs: []string{"https://myapp.example.com/callback"},
ClientName: "My MCP Client",
ClientURI: "https://myapp.example.com",
GrantTypes: []string{"authorization_code", "refresh_token"},
ResponseTypes: []string{"code"},
Scope: "mcp:read mcp:write",
Contacts: []string{"admin@myapp.example.com"},
}
// Register with authorization server
response, err := oauthex.RegisterClient(
ctx,
authServer.RegistrationEndpoint,
clientMeta,
nil,
)
if err != nil {
// Check if it's a registration error
if regErr, ok := err.(*oauthex.ClientRegistrationError); ok {
return fmt.Errorf("registration failed: %s - %s",
regErr.ErrorCode, regErr.ErrorDescription)
}
return err
}
// Use registered credentials
fmt.Printf("Registered! Client ID: %s\n", response.ClientID)
if response.ClientSecret != "" {
fmt.Printf("Client Secret: %s\n", response.ClientSecret)
if !response.ClientSecretExpiresAt.IsZero() {
fmt.Printf("Secret expires: %s\n", response.ClientSecretExpiresAt)
}
}
return nil
}The oauthex package complements the auth package by providing metadata discovery:
WWW-Authenticate: Bearer resource_metadata=https://api.example.com/.well-known/oauth-protected-resourceExample client flow:
package main
import (
"context"
"fmt"
"net/http"
"github.com/modelcontextprotocol/go-sdk/mcp"
"golang.org/x/oauth2"
)
func connectWithOAuth(serverURL string) (*mcp.ClientSession, error) {
ctx := context.Background()
// Attempt connection without token
client := mcp.NewClient(&mcp.Implementation{Name: "oauth-client"}, nil)
transport := &mcp.StreamableClientTransport{Endpoint: serverURL}
session, err := client.Connect(ctx, transport, nil)
if err != nil {
// Check if error indicates authentication required
// (implementation-specific error handling)
// Fetch protected resource metadata
// (using custom HTTP client or oauthex utilities)
// Discover authorization server
authServerURL := "https://auth.example.com" // from metadata
// Configure OAuth client
config := &oauth2.Config{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
Endpoint: oauth2.Endpoint{
AuthURL: authServerURL + "/authorize",
TokenURL: authServerURL + "/token",
},
Scopes: []string{"mcp:read", "mcp:write"},
}
// Obtain token (various OAuth flows available)
token, err := config.PasswordCredentialsToken(ctx, "username", "password")
if err != nil {
return nil, err
}
// Retry with authenticated client
httpClient := config.Client(ctx, token)
transport = &mcp.StreamableClientTransport{
Endpoint: serverURL,
HTTPClient: httpClient,
}
return client.Connect(ctx, transport, nil)
}
return session, nil
}This implementation follows RFC 9728 (OAuth 2.0 Protected Resource Metadata) with the following notes:
Supported features:
Unsupported features:
The implementation can safely ignore signed metadata per §2.2 of the specification.
There are two primary ways to discover protected resource metadata:
Metadata can be discovered using the well-known URI pattern:
https://{host}/.well-known/oauth-protected-resource{/path}For example, if the resource server is at https://api.example.com/mcp, the metadata would be at:
https://api.example.com/.well-known/oauth-protected-resource/mcpWhen a client attempts to access a protected resource without proper authentication, the server returns a 401 Unauthorized response with a WWW-Authenticate header:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"The client can parse this header to discover the metadata URL.
Publish metadata to help clients discover authentication requirements:
metadata := &oauthex.ProtectedResourceMetadata{
Resource: "https://api.example.com",
AuthorizationServers: []string{"https://auth.example.com"},
ScopesSupported: []string{"mcp:read", "mcp:write"},
ResourceName: "Example MCP Server",
}
http.HandleFunc("/.well-known/oauth-protected-resource", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(metadata)
})Fetch and parse metadata to discover authentication requirements:
import (
"encoding/json"
"net/http"
"github.com/modelcontextprotocol/go-sdk/oauthex"
)
func discoverAuthRequirements(resourceURL string) (*oauthex.ProtectedResourceMetadata, error) {
// Fetch metadata
metadataURL := resourceURL + "/.well-known/oauth-protected-resource"
resp, err := http.Get(metadataURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Parse metadata
var metadata oauthex.ProtectedResourceMetadata
if err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil {
return nil, err
}
// Use metadata to configure OAuth flow
fmt.Printf("Authorization Servers: %v\n", metadata.AuthorizationServers)
fmt.Printf("Required Scopes: %v\n", metadata.ScopesSupported)
return &metadata, nil
}resource field matches your resource URL