Documentation
¶
Overview ¶
Package config defines host-neutral OAuth configuration, requirement and credential types shared by MCP clients. It deliberately contains no host-specific user, session or storage concepts: a host application (e.g. a workspace runtime) implements the ProviderRegistry and CredentialResolver interfaces and installs them into an MCP client; viant/mcp owns protocol behaviour (requirement compilation, metadata discovery, credential attachment and 401 recovery) only.
Index ¶
- Constants
- func ChallengeMetadataURL(wwwAuthenticate string) string
- func DiscoverProtectedResource(ctx context.Context, metadataURL string, options *DiscoveryOptions) (*meta.ProtectedResourceMetadata, error)
- func IsLinkRequired(err error) bool
- func NormalizeIssuer(issuer string) string
- func NormalizeScopes(scopes []string) []string
- func ValidResolution(value string) bool
- func ValidReusePolicy(value string) bool
- func ValidTokenType(value string) bool
- type Credential
- type CredentialResolver
- type DiscoveryOptions
- type OAuthClient
- type OAuthLinkRequiredError
- type OAuthProvider
- type ProviderRegistry
- type Requirement
- type Resolution
- type StaticRegistry
- type TokenType
- type WorkspaceTokenReusePolicy
Constants ¶
const ( // ModeOAuth marks delegated OAuth mode: credentials are obtained through // an external CredentialResolver from a referenced or inline provider. ModeOAuth = "oauth" )
Mode selects how an MCP client authenticates.
Variables ¶
This section is empty.
Functions ¶
func ChallengeMetadataURL ¶
ChallengeMetadataURL extracts the resource_metadata parameter from a WWW-Authenticate challenge header value, e.g.
Bearer resource_metadata="https://host/.well-known/oauth-protected-resource"
Scheme and parameter names are matched case-insensitively (RFC 9110 §11.1) and the parameter value may be quoted or unquoted. It returns an empty string when the header is not a Bearer challenge or the parameter is absent; it never returns header content through an error.
func DiscoverProtectedResource ¶
func DiscoverProtectedResource(ctx context.Context, metadataURL string, options *DiscoveryOptions) (*meta.ProtectedResourceMetadata, error)
DiscoverProtectedResource fetches and decodes RFC 9728 protected-resource metadata from metadataURL with bounded timeout, redirects and body size. Unless options.AllowHTTP is set, metadataURL must be HTTPS. Discovery fails closed: options.ExpectedOrigin is required — the initial URL and every redirect must match its parsed scheme and host exactly — unless the caller explicitly opts out with options.AllowCrossOrigin.
func IsLinkRequired ¶
IsLinkRequired reports whether err (or any error it wraps) is an OAuthLinkRequiredError.
func NormalizeIssuer ¶
NormalizeIssuer canonicalizes an issuer for exact comparison: it trims whitespace and trailing slashes. No prefix or substring matching is ever performed on the result.
func NormalizeScopes ¶
NormalizeScopes trims, deduplicates and sorts scopes for exact comparison.
func ValidResolution ¶
ValidResolution reports whether value is empty or a known resolution policy.
func ValidReusePolicy ¶
ValidReusePolicy reports whether value is empty or a known reuse policy.
func ValidTokenType ¶
ValidTokenType reports whether value is empty or a known token type.
Types ¶
type Credential ¶
type Credential struct {
Token string
TokenType TokenType
ExpiresAt time.Time
ProviderRef string
Resource string
Scopes []string
}
Credential is the outbound-only credential value returned by a CredentialResolver. viant/mcp attaches Token to outbound MCP transport requests and never installs it into any host identity context.
func (*Credential) Expired ¶
func (c *Credential) Expired(now time.Time) bool
Expired reports whether the credential carries an expiry in the past.
func (*Credential) Verify ¶
func (c *Credential) Verify(requirement *Requirement, now time.Time) error
Verify checks that the credential is usable and that any metadata the resolver supplied does not contradict the requirement. Empty credential metadata fields are not validated. The returned error never contains the credential token value.
type CredentialResolver ¶
type CredentialResolver interface {
Resolve(ctx context.Context, requirement Requirement) (*Credential, error)
Refresh(ctx context.Context, requirement Requirement) (*Credential, error)
Invalidate(ctx context.Context, requirement Requirement) error
}
CredentialResolver acquires, refreshes and invalidates outbound MCP credentials. Hosts own persistence, user identity, interactive linking and refresh policy; viant/mcp only calls these hooks and attaches the returned value to transport requests.
Resolve is invoked before initialize/discovery and before every outbound request in eager mode (ResolutionEager); in challenge mode (ResolutionChallenge) it is invoked only after a 401 challenge.
Refresh is invoked at most once after a 401 rejection, followed by at most one retry. Refresh must by contract bypass any stale access-token cache and mint a fresh credential while retaining the stored refresh credential: viant/mcp never calls Invalidate before Refresh, so an implementation must not depend on Invalidate to evict a stale access token.
Invalidate is called only after a credential has been rejected terminally — a refreshed credential rejected with 401, or a challenge-mode credential rejected on its single retry. Implementations may drop the rejected credential record, including its refresh capability, when it fires.
Resolve and Refresh may return *OAuthLinkRequiredError to signal that interactive (re-)linking is needed; viant/mcp propagates it without opening a browser. Any other Resolve/Refresh error is treated as ordinary/transient and propagates unchanged — it is never converted into link-required.
type DiscoveryOptions ¶
type DiscoveryOptions struct {
// Timeout caps the whole fetch; defaults to 10s.
Timeout time.Duration
// MaxRedirects caps redirect following; defaults to 3.
MaxRedirects int
// MaxBodyBytes caps the decoded response size; defaults to 1 MiB.
MaxBodyBytes int64
// AllowHTTP permits plain http metadata URLs (tests/dev only); production
// hosts must leave this false so only HTTPS metadata is trusted.
AllowHTTP bool
// ExpectedOrigin pins the metadata fetch to the MCP/resource origin: the
// initial metadata URL and every redirect must match its parsed scheme
// and host exactly. Hosts must set it to the protected-resource / MCP
// transport origin whenever the metadata URL is taken from an untrusted
// WWW-Authenticate challenge, so a hostile challenge cannot steer the
// client into fetching arbitrary URLs (SSRF). It is required by default:
// discovery fails closed when it is empty unless AllowCrossOrigin is set.
ExpectedOrigin string
// AllowCrossOrigin is the explicit host-approved opt-out of the
// ExpectedOrigin pin. Only when it is true may ExpectedOrigin be left
// empty; an empty ExpectedOrigin alone never silently approves a fetch.
AllowCrossOrigin bool
// Transport overrides the HTTP transport used for the fetch.
Transport http.RoundTripper
}
DiscoveryOptions bounds protected-resource metadata fetches.
type OAuthClient ¶
type OAuthClient struct {
ConfigURL string `yaml:"configURL,omitempty" json:"configURL,omitempty"`
RedirectURI string `yaml:"redirectURI,omitempty" json:"redirectURI,omitempty"`
Confidential bool `yaml:"confidential,omitempty" json:"confidential,omitempty"`
UsePKCE bool `yaml:"usePKCE,omitempty" json:"usePKCE,omitempty"`
RefreshLead string `yaml:"refreshLead,omitempty" json:"refreshLead,omitempty"`
ClockSkew string `yaml:"clockSkew,omitempty" json:"clockSkew,omitempty"`
Scopes []string `yaml:"scopes,omitempty" json:"scopes,omitempty"`
}
OAuthClient describes one OAuth client registration. ConfigURL references an external secret resource (e.g. SCY) holding client id/secret and endpoints; secrets never appear inline.
func (*OAuthClient) ClockSkewDuration ¶
func (c *OAuthClient) ClockSkewDuration() (time.Duration, error)
ClockSkewDuration parses ClockSkew; zero when unset.
func (*OAuthClient) Clone ¶
func (c *OAuthClient) Clone() *OAuthClient
Clone returns a deep copy of the client registration. It is nil-safe.
func (*OAuthClient) RefreshLeadDuration ¶
func (c *OAuthClient) RefreshLeadDuration() (time.Duration, error)
RefreshLeadDuration parses RefreshLead; zero when unset.
type OAuthLinkRequiredError ¶
type OAuthLinkRequiredError struct {
ServerName string
ProviderRef string
Issuer string
Resource string
Scopes []string
// MetadataURL carries the protected-resource metadata URL the requirement
// was enriched from (challenge-mode provider learning), so hosts can
// persist the learned issuer/resource binding.
MetadataURL string
// Cause preserves the underlying failure (e.g. refresh rejection).
Cause error
}
OAuthLinkRequiredError signals that no usable credential exists (or can be refreshed) for a requirement and interactive (re-)linking through the host is needed. It is the terminal, typed outcome of the transport-level 401 recovery sequence (one refresh, one retry).
func NewLinkRequired ¶
func NewLinkRequired(requirement *Requirement, cause error) *OAuthLinkRequiredError
NewLinkRequired builds an OAuthLinkRequiredError from a requirement, preserving cause. When cause is already an *OAuthLinkRequiredError it is returned unchanged so resolver-produced details survive.
func (*OAuthLinkRequiredError) Error ¶
func (e *OAuthLinkRequiredError) Error() string
Error implements error.
func (*OAuthLinkRequiredError) Unwrap ¶
func (e *OAuthLinkRequiredError) Unwrap() error
Unwrap exposes the underlying cause for errors.Is/As inspection.
type OAuthProvider ¶
type OAuthProvider struct {
ID string `yaml:"id,omitempty" json:"id,omitempty"`
Issuer string `yaml:"issuer" json:"issuer"`
DiscoveryURL string `yaml:"discoveryURL,omitempty" json:"discoveryURL,omitempty"`
DefaultClient string `yaml:"defaultClient,omitempty" json:"defaultClient,omitempty"`
Clients map[string]*OAuthClient `yaml:"clients,omitempty" json:"clients,omitempty"`
}
OAuthProvider describes an OAuth authorization server and its registered clients. It carries configuration references only — never secret material.
func (*OAuthProvider) Client ¶
func (p *OAuthProvider) Client(ref string) (*OAuthClient, string, error)
Client resolves a client registration by reference, falling back to DefaultClient when ref is empty. It fails when the selection is ambiguous.
func (*OAuthProvider) Clone ¶
func (p *OAuthProvider) Clone() *OAuthProvider
Clone returns a deep copy of the provider. It is nil-safe.
func (*OAuthProvider) Validate ¶
func (p *OAuthProvider) Validate() error
Validate checks provider structural correctness. Issuer and DiscoveryURL must be absolute HTTPS URLs (plain HTTP is permitted only for loopback development hosts); client redirect URIs must be well-formed absolute URIs.
type ProviderRegistry ¶
type ProviderRegistry interface {
// ResolveProvider returns the provider registered under ref.
ResolveProvider(ctx context.Context, ref string) (*OAuthProvider, error)
// MatchIssuer returns the single provider whose normalized issuer equals
// the normalized argument; it must fail when the match is ambiguous.
MatchIssuer(ctx context.Context, issuer string) (*OAuthProvider, error)
}
ProviderRegistry resolves OAuth provider definitions. Hosts implement it on top of their own configuration store; StaticRegistry offers an in-memory implementation.
type Requirement ¶
type Requirement struct {
// ServerName identifies the MCP client/server definition.
ServerName string
// ProviderRef references a provider registered with the host registry.
ProviderRef string
// ClientRef selects a client registration within the provider.
ClientRef string
// Issuer is the normalized OAuth issuer when known.
Issuer string
// Resource is the protected resource (audience) the credential must target.
Resource string
// Scopes are the normalized, deduplicated required scopes.
Scopes []string
// TokenType selects access token (default) versus ID token.
TokenType TokenType
// Resolution selects eager (default) versus challenge-only resolution.
Resolution Resolution
// ReusePolicy is forwarded to the resolver; viant/mcp does not act on it.
ReusePolicy WorkspaceTokenReusePolicy
// Provider carries the inline provider definition when the client was
// configured with one instead of ProviderRef.
Provider *OAuthProvider
// MetadataURL records the RFC 9728 protected-resource metadata URL the
// requirement was enriched from (challenge-mode provider learning). It is
// informational for the resolver/host; viant/mcp never re-fetches it.
MetadataURL string
}
Requirement is the compiled, host-neutral description of the credential an MCP client needs for one server. It is derived once from client configuration (and optionally trusted protected-resource metadata) and then passed to a CredentialResolver before transport use.
func (*Requirement) ApplyProtectedResourceMetadata ¶
func (r *Requirement) ApplyProtectedResourceMetadata(metadata *meta.ProtectedResourceMetadata) error
ApplyProtectedResourceMetadata merges trusted protected-resource metadata into the requirement. Populated fields are cross-checked with parsed exact comparisons — a mismatch is a configuration error, never an opportunity to silently rewrite the requirement. Empty fields are filled from metadata.
func (*Requirement) Clone ¶
func (r *Requirement) Clone() *Requirement
Clone returns a deep copy of the requirement so callers can never mutate the original through the returned value. It is nil-safe.
func (*Requirement) Validate ¶
func (r *Requirement) Validate() error
Validate checks requirement consistency.
type Resolution ¶
type Resolution string
Resolution controls when a credential is resolved.
const ( // ResolutionEager resolves a credential before initialize/discovery and // before every outbound transport use (default). ResolutionEager Resolution = "eager" // ResolutionChallenge resolves a credential only after a 401 challenge. ResolutionChallenge Resolution = "challenge" )
type StaticRegistry ¶
type StaticRegistry struct {
// contains filtered or unexported fields
}
StaticRegistry is an in-memory ProviderRegistry for hosts with a fixed provider set and for tests. Hosts with dynamic configuration implement ProviderRegistry directly.
func NewStaticRegistry ¶
func NewStaticRegistry(providers ...*OAuthProvider) (*StaticRegistry, error)
NewStaticRegistry builds a registry from the supplied providers, keyed by provider ID. It fails on empty/duplicate IDs or invalid providers.
func (*StaticRegistry) Add ¶
func (r *StaticRegistry) Add(provider *OAuthProvider) error
Add registers a provider.
func (*StaticRegistry) MatchIssuer ¶
func (r *StaticRegistry) MatchIssuer(_ context.Context, issuer string) (*OAuthProvider, error)
MatchIssuer implements ProviderRegistry. It hard-fails when more than one provider shares the normalized issuer — ordering is never used as a tie-break.
func (*StaticRegistry) ResolveProvider ¶
func (r *StaticRegistry) ResolveProvider(_ context.Context, ref string) (*OAuthProvider, error)
ResolveProvider implements ProviderRegistry.
type TokenType ¶
type TokenType string
TokenType identifies which token kind is attached to outbound MCP requests.
type WorkspaceTokenReusePolicy ¶
type WorkspaceTokenReusePolicy string
WorkspaceTokenReusePolicy tells the host resolver whether a host (workspace) token may satisfy the requirement. viant/mcp never validates or forwards a host token itself; the policy is carried to the resolver verbatim.
const ( // ReusePolicyNever forbids host-token reuse (default). ReusePolicyNever WorkspaceTokenReusePolicy = "never" // ReusePolicyIfCompatible permits reuse only after the resolver has // validated full issuer/audience/resource/scope/type compatibility. ReusePolicyIfCompatible WorkspaceTokenReusePolicy = "ifCompatible" )