Documentation
¶
Overview ¶
Package oauth implements a generic, metadata-driven OAuth 2.0 credential manager for remote MCP servers. OAuth-only MCP servers (e.g. Slack's public MCP at https://mcp.slack.com/mcp) reject hand-pasted tokens with -32001 invalid_token because they only trust a bearer access token minted by their own OAuth flow. The model-router can only present a static bearer read from a mounted Secret, so this component obtains and refreshes OAuth access tokens (rfc8414 discovery + authorization_code/refresh_token grants) and writes them into the Secret that the MCPServer's auth.bearerToken already points at. The same logic is reused for any MCP server that publishes OAuth metadata — Slack is merely the first consumer.
Index ¶
- func RequeueInterval(resp *TokenResponse, refreshInterval time.Duration) time.Duration
- type AuthorizationServerMetadata
- type Config
- type CtrlSecretIO
- type K8sSecrets
- type Metadata
- type ProtectedResourceMetadata
- type SecretIO
- type SecretKeyRef
- type TokenManager
- func (m *TokenManager) Discover(ctx context.Context, serverURL string) (*Metadata, error)
- func (m *TokenManager) ExchangeCode(ctx context.Context, meta *Metadata, cfg *Config, code, codeVerifier string) (*TokenResponse, error)
- func (m *TokenManager) LoginURL(meta *Metadata, cfg *Config) (authURL, codeVerifier string, err error)
- func (m *TokenManager) Refresh(ctx context.Context, meta *Metadata, cfg *Config) (*TokenResponse, error)
- func (m *TokenManager) RefreshOnce(ctx context.Context, meta *Metadata, cfg *Config) (*TokenResponse, error)
- func (m *TokenManager) Run(ctx context.Context, meta *Metadata, cfg *Config) error
- type TokenResponse
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RequeueInterval ¶
func RequeueInterval(resp *TokenResponse, refreshInterval time.Duration) time.Duration
RequeueInterval returns how long after which the controller/oauth-proxy should refresh again: cfg.RefreshInterval when set, else 80% of the token's lifetime when expires_in is known, else a 50m default (Slack omits expires_in).
Types ¶
type AuthorizationServerMetadata ¶
type AuthorizationServerMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
GrantTypesSupported []string `json:"grant_types_supported"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
ScopesSupported []string `json:"scopes_supported"`
ResponseTypesSupported []string `json:"response_types_supported"`
}
AuthorizationServerMetadata is the response of /.well-known/oauth-authorization-server (RFC 8414).
type Config ¶
type Config struct {
// ServerURL is the MCP server URL (e.g. https://mcp.slack.com/mcp). The issuer
// origin is derived from it for metadata discovery.
ServerURL string
// ClientID / ClientSecret are the OAuth client credentials (Slack app credentials).
ClientID string
ClientSecret string
// RedirectURI is the OAuth redirect URI registered in the app.
RedirectURI string
// Scopes overrides discovery.scopes_supported. If empty, the server's scopes win.
Scopes []string
// RefreshToken is the current refresh token, if any (seeds the flow without a browser).
RefreshToken string
// RefreshTokenSecretRef is where to persist a (rotated) refresh token.
RefreshTokenSecretRef SecretKeyRef
// AccessTokenSecretRef is the Secret/key the model-router reads as the bearer
// (referenced by MCPServer.spec.auth.bearerToken).
AccessTokenSecretRef SecretKeyRef
// RefreshInterval bounds how often to attempt a refresh when no expires_in is known.
// A zero value defaults to 50m.
RefreshInterval time.Duration
}
Config drives the token manager for one MCP server.
type CtrlSecretIO ¶
CtrlSecretIO implements SecretIO against a controller-runtime client (the one the operator's controllers already hold). The MCPServer reconciler uses it to satisfy OAuth-managed MCPServers without a separate sidecar proxy.
func NewCtrlSecretIO ¶
func NewCtrlSecretIO(c client.Client) *CtrlSecretIO
NewCtrlSecretIO returns a SecretIO backed by a controller-runtime client.
type K8sSecrets ¶
type K8sSecrets struct {
Client kubernetes.Interface
}
K8sSecrets implements SecretIO over a Kubernetes cluster using client-go. It is what cmd/oauth-proxy uses at runtime; tests use the in-package fakeSecretIO.
func NewK8sSecrets ¶
func NewK8sSecrets(client kubernetes.Interface) *K8sSecrets
NewK8sSecrets returns a SecretIO backed by a kubernetes clientset.
type Metadata ¶
type Metadata struct {
AuthorizationEndpoint string
TokenEndpoint string
GrantTypes []string
CodeChallengeMethods []string
TokenEndpointAuthMethods []string
ScopesSupported []string
BearerMethods []string
AccessTokenSecretRef SecretKeyRef // where to write access_token
}
Metadata is the resolved, server-agnostic OAuth configuration for an MCP server.
type ProtectedResourceMetadata ¶
type ProtectedResourceMetadata struct {
AuthorizationServers []string `json:"authorization_servers"`
BearerMethodsSupported []string `json:"bearer_methods_supported"`
ScopesSupported []string `json:"scopes_supported"`
Resource string `json:"resource"`
TokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported,omitempty"`
}
ProtectedResourceMetadata is the response of /.well-known/oauth-protected-resource (RFC 9700 / MCP OAuth discovery).
type SecretIO ¶
type SecretIO interface {
// ReadKey returns the value and whether the key exists.
ReadKey(ctx context.Context, namespace, name, key string) ([]byte, bool, error)
// WriteKey creates-or-updates a single key in the Secret.
WriteKey(ctx context.Context, namespace, name, key string, value []byte) error
}
SecretIO abstracts reading and writing Secret key/values so the manager is testable without a Kubernetes client. The Kubernetes implementation is k8sSecretIO.
type SecretKeyRef ¶
SecretKeyRef references a Kubernetes Secret key.
type TokenManager ¶
TokenManager refreshes OAuth access tokens for an MCP server and persists them into a Secret. It is reusable across any OAuth-only MCP: discovery is driven by the server's published metadata, not by hardcoded endpoints.
func New ¶
func New(httpClient *http.Client, secrets SecretIO) *TokenManager
New returns a TokenManager backed by the given HTTP client and Secret store.
func (*TokenManager) Discover ¶
Discover resolves OAuth Metadata for an MCP server by following RFC 8414 / the MCP oauth-protected-resource discovery chain from ServerURL's issuer origin.
func (*TokenManager) ExchangeCode ¶
func (m *TokenManager) ExchangeCode(ctx context.Context, meta *Metadata, cfg *Config, code, codeVerifier string) (*TokenResponse, error)
ExchangeCode exchanges an authorization code (PKCE) for access+refresh tokens. It is used by the one-time seed flow.
func (*TokenManager) LoginURL ¶
func (m *TokenManager) LoginURL(meta *Metadata, cfg *Config) (authURL, codeVerifier string, err error)
LoginURL returns the authorization endpoint URL for the one-time browser code exchange (PKCE, S256). It also returns the generated code_verifier that the caller must preserve and pass to ExchangeCode.
func (*TokenManager) Refresh ¶
func (m *TokenManager) Refresh(ctx context.Context, meta *Metadata, cfg *Config) (*TokenResponse, error)
Refresh exchanges a refresh token for a fresh access token at meta.TokenEndpoint. It uses client_secret_post (client_id/secret in the form body), the grant type the server advertises, and returns the parsed token response.
func (*TokenManager) RefreshOnce ¶
func (m *TokenManager) RefreshOnce(ctx context.Context, meta *Metadata, cfg *Config) (*TokenResponse, error)
RefreshOnce re-reads the current refresh token from its secret (so rotated tokens persist across cycles/restarts), exchanges it for a fresh access token, and writes both the access token (to AccessTokenSecretRef) and any rotated refresh token (to RefreshTokenSecretRef). It returns the token response so callers can schedule the next refresh from resp.ExpiresIn.
type TokenResponse ¶
type TokenResponse struct {
OK bool `json:"ok"` // Slack: absent means standard OAuth (treat as success unless error)
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"` // absent => token not rotated; keep existing
ExpiresIn int `json:"expires_in,omitempty"` // seconds; absent => unknown lifetime
TokenType string `json:"token_type,omitempty"`
Error string `json:"error,omitempty"` // OAuth error code (e.g. invalid_grant) or Slack error
}
TokenResponse is the union of standard OAuth2 and Slack-style token responses.
func (*TokenResponse) ErrorOrMessage ¶
func (t *TokenResponse) ErrorOrMessage() string