config

package
v0.21.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

View Source
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

func ChallengeMetadataURL(wwwAuthenticate string) string

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

func IsLinkRequired(err error) bool

IsLinkRequired reports whether err (or any error it wraps) is an OAuthLinkRequiredError.

func NormalizeIssuer

func NormalizeIssuer(issuer string) string

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

func NormalizeScopes(scopes []string) []string

NormalizeScopes trims, deduplicates and sorts scopes for exact comparison.

func ValidResolution

func ValidResolution(value string) bool

ValidResolution reports whether value is empty or a known resolution policy.

func ValidReusePolicy

func ValidReusePolicy(value string) bool

ValidReusePolicy reports whether value is empty or a known reuse policy.

func ValidTokenType

func ValidTokenType(value string) bool

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.

const (
	// TokenTypeAccessToken attaches the OAuth access token (default).
	TokenTypeAccessToken TokenType = "accessToken"
	// TokenTypeIDToken attaches the OpenID Connect ID token; it must be
	// requested explicitly.
	TokenTypeIDToken TokenType = "idToken"
)

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"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL