Tencent/WeKnora · error

principal context is required to connect to an OAuth MCP ser

Error message

principal context is required to connect to an OAuth MCP service

What it means

OAuth token storage is keyed by a principal (user identity). buildOAuthConfig requires a valid Principal after normalization, optionally falling back to config.UserID as a web-user principal. If neither yields a valid principal, the client cannot know whose OAuth token to load/refresh, so it fails before connecting.

Source

Thrown at internal/mcp/client.go:266

// buildOAuthConfig returns the OAuth configuration for an OAuth-enabled MCP
// service, or (_, false, nil) when the service does not use OAuth. It loads
// the dynamically-registered client_id and wires a per-user token store so
// the transport injects the invoking user's bearer token and refreshes it.
func buildOAuthConfig(config *ClientConfig, httpClient *http.Client) (transport.OAuthConfig, bool, error) {
	svc := config.Service
	if !svc.AuthConfig.IsOAuth() {
		return transport.OAuthConfig{}, false, nil
	}
	if config.OAuthRepo == nil {
		return transport.OAuthConfig{}, false, fmt.Errorf("OAuth repository is required for OAuth MCP services")
	}
	principal := config.Principal.Normalize()
	if !principal.Valid() && config.UserID != "" {
		principal = types.Principal{Type: types.PrincipalWebUser, ID: config.UserID}.Normalize()
	}
	if !principal.Valid() {
		return transport.OAuthConfig{}, false, fmt.Errorf("principal context is required to connect to an OAuth MCP service")
	}
	config.Principal = principal

	oauthCfg := transport.OAuthConfig{
		Scopes:                svc.AuthConfig.Scopes,
		TokenStore:            newManagedTokenStore(config.OAuthRepo, config.TenantID, principal, svc.ID),
		PKCEEnabled:           true,
		AuthServerMetadataURL: svc.AuthConfig.AuthServerMetadataURL,
		HTTPClient:            httpClient,
	}
	if regClient, err := config.OAuthRepo.GetClient(context.Background(), config.TenantID, svc.ID); err == nil && regClient != nil {
		oauthCfg.ClientID = regClient.ClientID
		oauthCfg.ClientSecret = regClient.ClientSecret
		oauthCfg.RedirectURI = regClient.RedirectURI
	}
	return oauthCfg, true, nil
}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Set config.Principal to the authenticated user's principal (type + ID) before creating the client
  2. Or set config.UserID; it is converted to a PrincipalWebUser principal automatically
  3. For non-interactive jobs, use a dedicated service-account principal or switch the service to a non-OAuth auth strategy
  4. Propagate the user ID from the request context through to the MCP client config

Example fix

// before
cfg := &ClientConfig{Service: svc, OAuthRepo: repo, TenantID: tid} // no principal/user
// after
cfg := &ClientConfig{Service: svc, OAuthRepo: repo, TenantID: tid,
    Principal: types.Principal{Type: types.PrincipalWebUser, ID: userID}.Normalize()}
Defensive patterns

Strategy: validation

Validate before calling

func requirePrincipal(cfg *ClientConfig) error {
    if cfg.Service == nil || !cfg.Service.AuthConfig.IsOAuth() { return nil }
    p := cfg.Principal.Normalize()
    if !p.Valid() && cfg.UserID != "" {
        p = types.Principal{Type: types.PrincipalWebUser, ID: cfg.UserID}.Normalize()
    }
    if !p.Valid() { return errors.New("valid principal or UserID required for OAuth MCP service") }
    return nil
}

Type guard

func hasPrincipalContext(cfg *ClientConfig) bool {
    return cfg.Principal.Normalize().Valid() || cfg.UserID != ""
}

Try / catch

_, err := NewMCPClient(cfg)
if err != nil && strings.Contains(err.Error(), "principal context is required") {
    return fmt.Errorf("no user context available for OAuth service %s; cannot authorize", cfg.Service.ID)
}

Prevention

When it happens

Trigger: NewMCPClient for an OAuth service where config.Principal is zero/invalid AND config.UserID is empty, or config.UserID is set but still produces an invalid normalized principal (e.g. empty ID).

Common situations: Background/system jobs calling the MCP client without a user context; request context lost between service layers so UserID was never propagated; machine-to-machine calls to a per-user OAuth service that has no service-account principal.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/2fddea188e8798b8. Report an issue: GitHub.