Tencent/WeKnora · error
dynamic client registration returned an empty client_id
Error message
dynamic client registration returned an empty client_id
What it means
Raised when dynamic client registration completes without error but the handler returns an empty client_id, meaning the provider response lacked the required identifier. StartAuthorization treats this as a hard failure because the authorization URL cannot be built without a client_id.
Source
Thrown at internal/mcp/oauth_manager.go:110
principal = principal.Normalize()
if !principal.Valid() {
return "", "", fmt.Errorf("principal context is required to authorize OAuth MCP service %s", service.ID)
}
h, err := m.newHandler(ctx, service, tenantID, principal, redirectURI)
if err != nil {
return "", "", err
}
// Register a client dynamically if we don't have one yet for this service.
existing, _ := m.repo.GetClient(ctx, tenantID, service.ID)
if existing == nil {
if err := h.RegisterClient(ctx, clientRegistrationName); err != nil {
return "", "", fmt.Errorf("dynamic client registration failed: %w", err)
}
clientID := h.GetClientID()
if clientID == "" {
return "", "", fmt.Errorf("dynamic client registration returned an empty client_id")
}
if err := m.repo.SaveClient(ctx, &types.MCPOAuthClient{
TenantID: tenantID,
ServiceID: service.ID,
ClientID: clientID,
RedirectURI: redirectURI,
}); err != nil {
logger.GetLogger(ctx).Warnf("failed to persist MCP oauth client: %v", err)
}
}
verifier, err := transport.GenerateCodeVerifier()
if err != nil {
return "", "", fmt.Errorf("failed to generate PKCE verifier: %w", err)
}
challenge := transport.GenerateCodeChallenge(verifier)
state, err := transport.GenerateState()
if err != nil {View on GitHub (pinned to 988cbb0330)
Solutions
- Inspect the provider's registration response for a non-standard body and fix parsing expectations
- Upgrade/patch the transport.OAuthHandler registration parsing to match the provider's response format
- Use a static, pre-registered client_id with the provider instead of dynamic registration
- Log the raw registration response to confirm what the provider returned
Defensive patterns
Strategy: try-catch
Validate before calling
// sanity-check the registration path with a dry-run against the provider
resp, err := http.Post(*svc.URL+"/register", "application/json", strings.NewReader(sampleReg))
if err == nil {
var body map[string]any
json.NewDecoder(resp.Body).Decode(&body)
if _, ok := body["client_id"]; !ok {
return fmt.Errorf("provider registration response lacks client_id; use static client")
}
} Type guard
func hasClientID(reg map[string]any) bool {
id, ok := reg["client_id"]
return ok && s, _ := id.(string); ok && s != ""
} Try / catch
_, _, err := mgr.StartAuthorizationForService(ctx, svc, tenantID, principal, redirect, "")
if err != nil && strings.Contains(err.Error(), "empty client_id") {
log.Warn("provider returned non-standard registration response; using static client")
return startAuthorizationWithStaticClient(ctx, svc, config.StaticClientFor(svc.ID))
}
if err != nil { return err } Prevention
- Verify the IdP's registration response includes client_id (RFC 7591 requirement)
- Keep the OAuth handler library updated for provider response-shape changes
- Capture the raw registration response in logs for debugging
- Prefer static client registration for providers with non-standard responses
When it happens
Trigger: Provider's /register endpoint returns 2xx with a body missing client_id (or the handler fails to parse it), immediately after h.RegisterClient succeeds.
Common situations: Provider returns a non-standard registration response (e.g. only registration_client_uri); misbehaving mock/idp in dev; response shape changed after a provider upgrade; JSON field name mismatch in parsing.
Related errors
- delete invalid MCP OAuth client registration: %w
- dynamic client registration failed: %w
- decode suggestion JSON: %w
- open source file: %w
- read source file: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/3b5745174be088aa.
Report an issue: GitHub.