Skip to content

Commit cf09171

Browse files
committed
Add dynamic OAuth discovery for community MCP servers
Community servers from third-party registries lack oauth.providers metadata, so the existing IsRemoteOAuthServer() gate prevents DCR entries from being created. This adds a fallback path that probes remote servers for OAuth support using DiscoverOAuthRequirements() and creates pending DCR entries. - Add RegisterProviderForDynamicDiscovery() in pkg/oauth/dcr_registration.go - Add fallback branch in workingset, server enable, and gateway mcpadd - Expand gateway OAuth condition to match remote servers without oauth.providers
1 parent d0224db commit cf09171

File tree

4 files changed

+63
-11
lines changed

4 files changed

+63
-11
lines changed

cmd/docker-mcp/server/enable.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ func update(ctx context.Context, docker docker.Client, dockerCli command.Cli, ad
7979
fmt.Printf("Server %s requires OAuth authentication but DCR is disabled.\n", serverName)
8080
fmt.Printf(" To enable automatic OAuth setup, run: docker mcp feature enable mcp-oauth-dcr\n")
8181
fmt.Printf(" Or set up OAuth manually using: docker mcp oauth authorize %s\n", serverName)
82+
} else if mcpOAuthDcrEnabled && server.Type == "remote" && !server.IsOAuthServer() && server.Remote.URL != "" {
83+
// Community server without oauth.providers — probe for OAuth
84+
if pkgoauth.IsCEMode() {
85+
fmt.Printf("Remote server %s enabled. Run 'docker mcp oauth authorize %s' if authentication is required\n", serverName, serverName)
86+
} else {
87+
if err := pkgoauth.RegisterProviderForDynamicDiscovery(ctx, serverName, server.Remote.URL); err != nil {
88+
fmt.Printf("Warning: Dynamic OAuth discovery failed for %s: %v\n", serverName, err)
89+
}
90+
}
8291
}
8392
} else {
8493
return fmt.Errorf("server %s not found in catalog", serverName)

pkg/gateway/mcpadd.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ func addServerHandler(g *Gateway, clientConfig *clientConfig) mcp.ToolHandler {
289289
// Handle OAuth DCR only when the client supports elicitation (e.g. not stdio-based clients)
290290
if g.McpOAuthDcrEnabled &&
291291
serverConfig != nil &&
292-
serverConfig.Spec.IsRemoteOAuthServer() {
292+
(serverConfig.Spec.IsRemoteOAuthServer() ||
293+
(serverConfig.Spec.Type == "remote" && !serverConfig.Spec.IsOAuthServer() && serverConfig.Spec.Remote.URL != "")) {
293294

294295
init := req.Session.InitializeParams()
295296
if init != nil &&
@@ -444,7 +445,14 @@ func (g *Gateway) getRemoteOAuthServerStatus(ctx context.Context, serverName str
444445
if !providerExists {
445446
// Register DCR client with DD so user can authorize
446447
if err := oauth.RegisterProviderForLazySetup(ctx, serverName); err != nil {
447-
log.Logf("Warning: Failed to register OAuth provider for %s: %v", serverName, err)
448+
// Fallback: try dynamic discovery for community servers without oauth.providers
449+
if serverConfig, _, found := g.configuration.Find(serverName); found && serverConfig.Spec.Remote.URL != "" {
450+
if err := oauth.RegisterProviderForDynamicDiscovery(ctx, serverName, serverConfig.Spec.Remote.URL); err != nil {
451+
log.Logf("Warning: Failed to register OAuth provider for %s: %v", serverName, err)
452+
}
453+
} else {
454+
log.Logf("Warning: Failed to register OAuth provider for %s: %v", serverName, err)
455+
}
448456
}
449457

450458
// Start provider (CE mode only - Desktop mode doesn't need polling)

pkg/oauth/dcr_registration.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package oauth
33
import (
44
"context"
55
"fmt"
6+
"time"
67

7-
"github.com/docker/mcp-gateway/pkg/catalog"
8+
oauthhelpers "github.com/docker/mcp-gateway-oauth-helpers"
89
"github.com/docker/mcp-gateway/pkg/desktop"
10+
11+
"github.com/docker/mcp-gateway/pkg/catalog"
912
)
1013

1114
// RegisterProviderForLazySetup registers a DCR provider with Docker Desktop
@@ -46,6 +49,33 @@ func RegisterProviderForLazySetup(ctx context.Context, serverName string) error
4649
return client.RegisterDCRClientPending(ctx, serverName, dcrRequest)
4750
}
4851

52+
// RegisterProviderForDynamicDiscovery probes a remote server for OAuth support
53+
// and creates a pending DCR entry if the server requires OAuth.
54+
// This is used for community servers that lack oauth.providers metadata in the catalog.
55+
// Idempotent - safe to call multiple times for the same server.
56+
func RegisterProviderForDynamicDiscovery(ctx context.Context, serverName, serverURL string) error {
57+
client := desktop.NewAuthClient()
58+
59+
// Idempotent check - already registered?
60+
_, err := client.GetDCRClient(ctx, serverName)
61+
if err == nil {
62+
return nil // Already registered
63+
}
64+
65+
// Probe the server with a timeout to discover OAuth requirements
66+
probeCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
67+
defer cancel()
68+
discovery, err := oauthhelpers.DiscoverOAuthRequirements(probeCtx, serverURL)
69+
if err != nil || !discovery.RequiresOAuth {
70+
return nil // Server doesn't need OAuth, not an error
71+
}
72+
73+
// Register with DD (pending DCR state) using server name as provider name
74+
return client.RegisterDCRClientPending(ctx, serverName, desktop.RegisterDCRRequest{
75+
ProviderName: serverName,
76+
})
77+
}
78+
4979
// RegisterProviderWithSnapshot registers a DCR provider using OAuth metadata from the server snapshot
5080
// This avoids querying the catalog since the snapshot already contains all necessary OAuth information
5181
// Idempotent - safe to call multiple times for the same server

pkg/workingset/oauth.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,20 @@ func RegisterOAuthProvidersForServers(ctx context.Context, servers []Server) {
2424
if server.Snapshot == nil {
2525
continue
2626
}
27-
if !server.Snapshot.Server.IsRemoteOAuthServer() {
28-
continue
29-
}
30-
31-
serverName := server.Snapshot.Server.Name
32-
providerName := server.Snapshot.Server.OAuth.Providers[0].Provider
27+
if server.Snapshot.Server.IsRemoteOAuthServer() {
28+
serverName := server.Snapshot.Server.Name
29+
providerName := server.Snapshot.Server.OAuth.Providers[0].Provider
3330

34-
if err := oauth.RegisterProviderWithSnapshot(ctx, serverName, providerName); err != nil {
35-
log.Log(fmt.Sprintf("Warning: Failed to register OAuth provider for %s: %v", serverName, err))
31+
if err := oauth.RegisterProviderWithSnapshot(ctx, serverName, providerName); err != nil {
32+
log.Log(fmt.Sprintf("Warning: Failed to register OAuth provider for %s: %v", serverName, err))
33+
}
34+
} else if server.Snapshot.Server.Type == "remote" && server.Snapshot.Server.Remote.URL != "" {
35+
// Community servers without oauth.providers: probe for OAuth dynamically
36+
serverName := server.Snapshot.Server.Name
37+
serverURL := server.Snapshot.Server.Remote.URL
38+
if err := oauth.RegisterProviderForDynamicDiscovery(ctx, serverName, serverURL); err != nil {
39+
log.Log(fmt.Sprintf("Warning: Failed dynamic OAuth discovery for %s: %v", serverName, err))
40+
}
3641
}
3742
}
3843
}

0 commit comments

Comments
 (0)