From 0a2de4eaa414a4c3a2094552866a60b7d2f3e324 Mon Sep 17 00:00:00 2001 From: dask-58 <140686560+dask-58@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:22:24 +0530 Subject: [PATCH 1/2] fix: Add OAuth Provider Presets to support GitHub OAuth --- client/oauth.go | 6 ++++ client/transport/oauth.go | 19 ++++++++++++ client/transport/oauth_provider.go | 18 ++++++++++++ examples/oauth_client/README.md | 46 ++++++++++++++++++------------ examples/oauth_client/main.go | 5 ++-- 5 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 client/transport/oauth_provider.go diff --git a/client/oauth.go b/client/oauth.go index d6e3ceb99..21b173089 100644 --- a/client/oauth.go +++ b/client/oauth.go @@ -22,6 +22,12 @@ type MemoryTokenStore = transport.MemoryTokenStore // NewMemoryTokenStore is a convenience function that wraps transport.NewMemoryTokenStore var NewMemoryTokenStore = transport.NewMemoryTokenStore +// OAuthProvider is a convenience type that wraps transport.OAuthProvider +type OAuthProvider = transport.OAuthProvider + +// GitHubProvider is a convenience variable that wraps transport.GitHubProvider +var GitHubProvider = transport.GitHubProvider + // NewOAuthStreamableHttpClient creates a new streamable-http-based MCP client with OAuth support. // Returns an error if the URL is invalid. func NewOAuthStreamableHttpClient(baseURL string, oauthConfig OAuthConfig, options ...transport.StreamableHTTPCOption) (*Client, error) { diff --git a/client/transport/oauth.go b/client/transport/oauth.go index 618b46d0c..7b4a392a8 100644 --- a/client/transport/oauth.go +++ b/client/transport/oauth.go @@ -37,6 +37,9 @@ type OAuthConfig struct { // HTTPClient is an optional HTTP client to use for requests. // If nil, a default HTTP client with a 30 second timeout will be used. HTTPClient *http.Client + // Provider is an optional OAuth provider configuration. + // If set, it overrides the auto-discovery mechanism. + Provider *OAuthProvider } // TokenStore is an interface for storing and retrieving OAuth tokens. @@ -358,6 +361,17 @@ type OAuthProtectedResource struct { // getServerMetadata fetches the OAuth server metadata func (h *OAuthHandler) getServerMetadata(ctx context.Context) (*AuthServerMetadata, error) { h.metadataOnce.Do(func() { + // If Provider is explicitly configured, use it directly + // This bypasses RFC 8414 discovery which fails for split-domain providers like GitHub + if h.config.Provider != nil { + h.serverMetadata = &AuthServerMetadata{ + Issuer: h.baseURL, // RFC 8414 issuers often don't match API domains + AuthorizationEndpoint: h.config.Provider.AuthorizationEndpoint, + TokenEndpoint: h.config.Provider.TokenEndpoint, + } + return + } + // If AuthServerMetadataURL is explicitly provided, use it directly if h.config.AuthServerMetadataURL != "" { h.fetchMetadataFromURL(ctx, h.config.AuthServerMetadataURL) @@ -543,6 +557,11 @@ func (h *OAuthHandler) getDefaultEndpoints(baseURL string) (*AuthServerMetadata, // RegisterClient performs dynamic client registration func (h *OAuthHandler) RegisterClient(ctx context.Context, clientName string) error { + // Check if the provider explicitly forbids dynamic registration + if h.config.Provider != nil && !h.config.Provider.SupportsDynamicRegistration { + return errors.New("dynamic client registration is not supported by this provider") + } + metadata, err := h.getServerMetadata(ctx) if err != nil { return fmt.Errorf("failed to get server metadata: %w", err) diff --git a/client/transport/oauth_provider.go b/client/transport/oauth_provider.go new file mode 100644 index 000000000..e5013d5e7 --- /dev/null +++ b/client/transport/oauth_provider.go @@ -0,0 +1,18 @@ +package transport + +// OAuthProvider defines the configuration for a specific OAuth provider. +// This is used to bypass auto-discovery for providers that do not support RFC 8414 +// or have split domains (e.g. GitHub). +type OAuthProvider struct { + AuthorizationEndpoint string + TokenEndpoint string + SupportsDynamicRegistration bool +} + +// GitHubProvider is the preset configuration for GitHub OAuth. +// GitHub does not support RFC 7591 dynamic registration and uses split domains for API and generic OAuth. +var GitHubProvider = &OAuthProvider{ + AuthorizationEndpoint: "https://github.com/login/oauth/authorize", + TokenEndpoint: "https://github.com/login/oauth/access_token", + SupportsDynamicRegistration: false, +} diff --git a/examples/oauth_client/README.md b/examples/oauth_client/README.md index a60bb7c5f..67f9161a7 100644 --- a/examples/oauth_client/README.md +++ b/examples/oauth_client/README.md @@ -1,15 +1,23 @@ # OAuth Client Example -This example demonstrates how to use the OAuth capabilities of the MCP Go client to authenticate with an MCP server that requires OAuth authentication. +This example demonstrates how to use the OAuth capabilities of the MCP Go client to authenticate with an MCP server that requires OAuth authentication, specifically configured for GitHub. ## Features -- OAuth 2.1 authentication with PKCE support -- Dynamic client registration +- OAuth 2.0 authentication +- Preset configuration for GitHub (`client.GitHubProvider`) - Authorization code flow - Token refresh - Local callback server for handling OAuth redirects +## Standard vs Manual Provider Configuration + +By default, the client uses **RFC 8414** to automatically discover the authorization server metadata from the MCP server's base URL. This works for most standard OAuth implementations. + +However, some providers (like GitHub) do not support RFC 8414 or host their OAuth endpoints on a different domain than their API. For these cases, you can provide an optional `Provider` configuration to manually specify the endpoints and capabilities. + +This example uses the manual `GitHubProvider` configuration. For standard providers, you can omit the `Provider` field and `PKCEEnabled` is `true` by default. + ## Usage ```bash @@ -23,18 +31,17 @@ go run main.go ## How it Works -1. The client attempts to initialize a connection to the MCP server -2. If the server requires OAuth authentication, it will return a 401 Unauthorized response -3. The client detects this and starts the OAuth flow: - - Generates PKCE code verifier and challenge +1. The example initializes the client with `client.GitHubProvider` to bypass incorrect auto-discovery for GitHub. +2. It uses `read:user` scope as required by GitHub. +3. PKCE is disabled as it is not supported by GitHub's OAuth implementation. +4. The client starts the OAuth flow: - Generates a state parameter for security - - Opens a browser to the authorization URL - - Starts a local server to handle the callback -4. The user authorizes the application in their browser -5. The authorization server redirects back to the local callback server -6. The client exchanges the authorization code for an access token -7. The client retries the initialization with the access token -8. The client can now make authenticated requests to the MCP server + - Opens a browser to the GitHub authorization URL + - Starts a local server to handle the callback at `http://localhost:8085/oauth/callback` +5. The user authorizes the application in their browser. +6. GitHub redirects back to the local callback server. +7. The client exchanges the authorization code for an access token. +8. The client initializes the connection to the MCP server with the authenticated token. ## Configuration @@ -43,17 +50,18 @@ Edit the following constants in `main.go` to match your environment: ```go const ( // Replace with your MCP server URL - serverURL = "https://api.example.com/v1/mcp" + serverURL = "https://api.githubcopilot.com/v1/mcp" // Use a localhost redirect URI for this example redirectURI = "http://localhost:8085/oauth/callback" ) ``` +**Note:** Ensure your GitHub OAuth App is configured with `http://localhost:8085/oauth/callback` as the Authorization callback URL. + ## OAuth Scopes -The example requests the following scopes: +The example requests the following scope: -- `mcp.read` - Read access to MCP resources -- `mcp.write` - Write access to MCP resources +- `read:user` - Read access to user profile (minimal scope for testing) -You can modify the scopes in the `oauthConfig` to match the requirements of your MCP server. \ No newline at end of file +You can modify the scopes in the `oauthConfig` to match the requirements of your specific application or MCP server. \ No newline at end of file diff --git a/examples/oauth_client/main.go b/examples/oauth_client/main.go index 27d3b6180..758302e79 100644 --- a/examples/oauth_client/main.go +++ b/examples/oauth_client/main.go @@ -30,9 +30,10 @@ func main() { ClientID: os.Getenv("MCP_CLIENT_ID"), ClientSecret: os.Getenv("MCP_CLIENT_SECRET"), RedirectURI: redirectURI, - Scopes: []string{"mcp.read", "mcp.write"}, + Scopes: []string{"read:user"}, TokenStore: tokenStore, - PKCEEnabled: true, // Enable PKCE for public clients + PKCEEnabled: false, // Disable PKCE for GitHub (not supported) + Provider: client.GitHubProvider, } // Create the client with OAuth support From caf1c7eca9fa5ea6e5c06322be5af36d94f87754 Mon Sep 17 00:00:00 2001 From: dask-58 <140686560+dask-58@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:32:04 +0530 Subject: [PATCH 2/2] feat: doc --- examples/oauth_client/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oauth_client/README.md b/examples/oauth_client/README.md index 67f9161a7..d3d07a0d7 100644 --- a/examples/oauth_client/README.md +++ b/examples/oauth_client/README.md @@ -33,7 +33,7 @@ go run main.go 1. The example initializes the client with `client.GitHubProvider` to bypass incorrect auto-discovery for GitHub. 2. It uses `read:user` scope as required by GitHub. -3. PKCE is disabled as it is not supported by GitHub's OAuth implementation. +3. PKCE is disabled for simplicity (GitHub OAuth Apps support PKCE optionally). 4. The client starts the OAuth flow: - Generates a state parameter for security - Opens a browser to the GitHub authorization URL