Skip to content

Credential storage for community servers#461

Merged
cutecatfann merged 3 commits intomainfrom
credential_storage
Apr 1, 2026
Merged

Credential storage for community servers#461
cutecatfann merged 3 commits intomainfrom
credential_storage

Conversation

@cutecatfann
Copy link
Copy Markdown
Contributor

What I did
For Jira MCPT-482: Credential storage for community servers (docker pass)

  • Adds per-server OAuth mode routing so community servers in Desktop mode store and read credentials via docker pass, while catalog servers continue using Secrets Engine and CE mode continues using the credential helper
  • Introduces OAuthMode enum (Desktop, CE, Community, Auto) and DetermineOAuthMode(ctx, isCommunity) to replace the global IsCEMode() binary at per-server decision points
  • Implements docker pass read/write operations for OAuth tokens (docker/mcp/oauth/{server}) and DCR clients (docker/mcp/oauth-dcr/{server}) following the existing credstore.go pattern
  • Wires per-server mode through the gateway startup loop and provider lifecycle so the refresh loop writes to the correct backend

The credential storage routing for a given server follows this decision tree:

  Is CE mode (no Desktop)?                                                                                                              
    └─ yes → OAuthModeCE: credential helper ({authEndpoint}/{provider})                                                                 
    └─ no  → Is server tagged "community"?                                                                                              
                └─ no  → OAuthModeDesktop: Secrets Engine (unchanged)                                                                   
                └─ yes → Is McpGatewayOAuth flag ON?                                                                                    
                           └─ no  → OAuthModeDesktop (fallback)                                                                         
                           └─ yes → Is docker pass available?                                                                           
                                      └─ no  → OAuthModeDesktop (fallback + warning)                                                  
                                      └─ yes → OAuthModeCommunity: docker pass                                                          

Both the docker-pass Secrets Engine plugin and the desktop-mcp-oauth plugin match the docker/mcp/oauth/{server} key path. For community servers, only docker-pass has an entry (Desktop's OAuth manager never wrote one), so the Secrets Engine resolves correctly.
For catalog servers, the Desktop plugin responds first (more specific pattern match).

@cutecatfann cutecatfann self-assigned this Mar 31, 2026
@cutecatfann cutecatfann requested a review from a team as a code owner March 31, 2026 21:35
Copy link
Copy Markdown
Contributor

@saucow saucow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary: Per-server credential routing for community servers. Mode enum, three-way branching in the credential helper, docker pass storage, provider refresh wiring. State model is correct across all combinations — CE, Desktop catalog, Desktop community with flag on/off, ModeAuto backward compat. All startProvider/NewProvider callers updated. Docker pass writes via CLI, reads via Secrets Engine. IsCommunity() on Server type is a nice touch. Preflight docker pass check in run.go done once, not per-server.

Two minor suggestions:

1. Reconcile ShouldUseGatewayOAuth and DetermineMode

PR 459 added ShouldUseGatewayOAuth(ctx, isCommunity) bool. This PR adds DetermineMode(ctx, isCommunity) Mode. Same decision tree, different return types. DetermineMode is what the new code actually uses — ShouldUseGatewayOAuth just sits there. As Tasks #4 and #5 wire up the remaining call sites, having both creates ambiguity about which to reach for.

ShouldUseGatewayOAuth should become a wrapper:

func ShouldUseGatewayOAuth(ctx context.Context, isCommunity bool) bool {
    return DetermineMode(ctx, isCommunity) != ModeDesktop
}

2. Mode logs as integer

run.go logs mode=%d which outputs mode=3. A String() method on Mode would help:

func (m Mode) String() string {
    switch m {
    case ModeDesktop:   return "Desktop"
    case ModeCE:        return "CE"
    case ModeCommunity: return "Community"
    default:            return "Auto"
    }
}

🤖 Review assisted by Claude Code

@cutecatfann cutecatfann requested a review from saucow April 1, 2026 15:54
Copy link
Copy Markdown
Contributor

@saucow saucow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@cutecatfann cutecatfann merged commit 1e217f2 into main Apr 1, 2026
8 checks passed
@cutecatfann cutecatfann deleted the credential_storage branch April 1, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants