Skip to content

feat: Implement OAuth in the client #296

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 25, 2025
Merged

feat: Implement OAuth in the client #296

merged 14 commits into from
May 25, 2025

Conversation

ezynda3
Copy link
Contributor

@ezynda3 ezynda3 commented May 15, 2025

Description

Fixes #295

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • MCP spec compatibility implementation
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Tests only (no functional changes)
  • Other (please describe):

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly

MCP Spec Compliance

  • This PR implements a feature defined in the MCP specification
  • Link to relevant spec section: Link text
  • Implementation follows the specification exactly

Additional Information

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive OAuth 2.0 authentication support, including PKCE, dynamic client registration, and authorization code flow.
    • Added utilities for secure token management, in-memory token storage, and OAuth state generation.
    • Enhanced HTTP client with OAuth integration, enabling automatic token handling and error signaling for authorization requirements.
    • Provided detailed documentation and a runnable example demonstrating OAuth authentication with an MCP server.
  • Bug Fixes

    • Not applicable.
  • Tests

    • Added extensive unit tests covering OAuth client initialization, token handling, PKCE utilities, error recognition, and HTTP client OAuth behavior.
  • Documentation

    • Added a README explaining OAuth client usage, configuration, and workflow for authenticating with an MCP server.

Copy link
Contributor

coderabbitai bot commented May 15, 2025

Walkthrough

OAuth 2.0 client support is introduced to the MCP Go client, including PKCE, token management, and dynamic client registration. New utility functions, types, and error handling mechanisms are provided for OAuth integration. The transport layer is extended to inject OAuth tokens into HTTP requests and handle authorization errors. Comprehensive tests and an example usage with documentation are added.

Changes

Files/Paths Change Summary
client/oauth.go
client/oauth_test.go
client/transport/oauth.go
client/transport/oauth_test.go
client/transport/oauth_utils.go
client/transport/oauth_utils_test.go
New files implementing OAuth 2.0 client support, including types for configuration and tokens, PKCE utilities, token storage, metadata discovery, authorization flows, and error handling. Exposes helper functions and type aliases for external use and centralizes OAuth client setup and utilities. Includes comprehensive tests for OAuth client initialization, token expiration, token store behavior, redirect URI validation, and PKCE utilities.
client/transport/streamable_http.go
client/transport/streamable_http_oauth_test.go
Adds OAuth authentication support to the HTTP transport client. Introduces an option for OAuth configuration, injects Authorization headers, handles OAuth authorization errors, and exposes methods to check OAuth state and access the handler. Includes tests verifying OAuth token injection, error handling for unauthorized responses, and OAuth state reporting.
examples/oauth_client/main.go New Go example program demonstrating OAuth client usage with PKCE, dynamic registration, browser-based authorization, callback handling, token exchange, and authenticated requests to an MCP server. Handles authorization errors and retries initialization after obtaining tokens.
examples/oauth_client/README.md New documentation file describing the OAuth client example, its workflow, usage instructions, configuration, and supported OAuth scopes.

Assessment against linked issues

Objective Addressed Explanation
Support OAuth 2.0 authorization flow including handling HTTP 401 Unauthorized from MCP server
Enable client to initiate full OAuth flow and authorize users correctly
Provide integration with MCP Go client for OAuth-enabled connections

Suggested labels

area: sdk

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (1.64.8)

Error: you are using a configuration file for golangci-lint v2 with golangci-lint v1: please use golangci-lint v2
Failed executing command with error: you are using a configuration file for golangci-lint v2 with golangci-lint v1: please use golangci-lint v2


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b279531 and 775e9e5.

📒 Files selected for processing (1)
  • client/transport/streamable_http.go (14 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/transport/streamable_http.go
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ezynda3 ezynda3 changed the title Implement OAuth in the client feat: Implement OAuth in the client May 15, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (14)
examples/oauth_client/README.md (1)

43-50: Include warning about using example URLs

The README uses example URLs that need to be replaced. Consider adding a prominent warning to emphasize that these are placeholder values that must be replaced with actual endpoint URLs.

 ```go
 const (
     // Replace with your MCP server URL
     serverURL = "https://api.example.com/v1/mcp"
     // Use a localhost redirect URI for this example
     redirectURI = "http://localhost:8085/oauth/callback"
 )
+
+// IMPORTANT: The URLs above are examples only. You must replace them with your actual MCP server URL
+// and configure your OAuth server to accept the redirect URI you specify.

</blockquote></details>
<details>
<summary>client/transport/oauth_test.go (1)</summary><blockquote>

`51-92`: **Consider adding edge cases to memory token store test**

While the basic functionality of the memory token store is tested, consider adding tests for:
1. Overwriting an existing token
2. Handling nil token values
3. Testing token expiration integration

These additional tests would provide more comprehensive coverage of potential edge cases.

</blockquote></details>
<details>
<summary>client/transport/streamable_http_oauth_test.go (2)</summary><blockquote>

`24-30`: **Check `json.Encoder.Encode` return value in test server handler**  

The Encode call can fail (e.g., if the connection is closed) and the error is silently ignored.  Because the test server controls the response, capturing failures helps surface unexpected regressions earlier.

```diff
-		json.NewEncoder(w).Encode(map[string]any{
+		if err := json.NewEncoder(w).Encode(map[string]any{
 			"jsonrpc": "2.0",
 			"id":      1,
 			"result":  "success",
-		})
+		}); err != nil {
+			t.Errorf("handler Encode failed: %v", err)
+		}
🧰 Tools
🪛 GitHub Check: lint

[failure] 25-25:
Error return value of (*encoding/json.Encoder).Encode is not checked (errcheck)


33-43: Handle tokenStore.SaveToken error

SaveToken returns an error (e.g., disk-full for file-based stores). Swallowing it in tests hides real issues and causes the linter failure you’re seeing.

-	tokenStore.SaveToken(validToken)
+	if err := tokenStore.SaveToken(validToken); err != nil {
+		t.Fatalf("SaveToken failed: %v", err)
+	}
🧰 Tools
🪛 GitHub Check: lint

[failure] 42-42:
Error return value of tokenStore.SaveToken is not checked (errcheck)

client/oauth_test.go (4)

25-40: Check Encode error in test server

Same issue as in the transport test: ignore of json.Encoder.Encode triggers errcheck lint failure.

-		json.NewEncoder(w).Encode(map[string]any{
+		if err := json.NewEncoder(w).Encode(map[string]any{
 		    ...
-		})
+		}); err != nil {
+			t.Errorf("handler Encode failed: %v", err)
+		}
🧰 Tools
🪛 GitHub Check: lint

[failure] 28-28:
Error return value of (*encoding/json.Encoder).Encode is not checked (errcheck)

🪛 GitHub Actions: golangci-lint

[error] 28-28: golangci-lint: Error return value of (*encoding/json.Encoder).Encode is not checked (errcheck)


43-53: Handle SaveToken error

-	tokenStore.SaveToken(validToken)
+	if err := tokenStore.SaveToken(validToken); err != nil {
+		t.Fatalf("SaveToken failed: %v", err)
+	}
🧰 Tools
🪛 GitHub Check: lint

[failure] 52-52:
Error return value of tokenStore.SaveToken is not checked (errcheck)


63-91: Test does not confirm Authorization header propagation

NewOAuthStreamableHttpClient is started, but no RPC/notification is sent. Unless the higher-level client automatically performs an initialize call (which this test does not assert), the server never receives a request; thus the OAuth path is untested.
Consider invoking a dummy RPC to ensure that Authorization is attached and 401 handling works.


93-123: Minor: table-driven tests improve readability for error-helper utilities

You can collapse the assertions for IsOAuthAuthorizationRequiredError / GetOAuthHandler into a table-driven loop, reducing duplication.

client/transport/streamable_http.go (2)

459-467: Public API: consider naming GetOAuthHandler / IsOAuthEnabled error semantics

The getters are convenient, but exposing the handler directly leaks implementation details (e.g., token store internals) and allows callers to mutate internal state.
If read-only access is intended, document it explicitly or expose narrower functionality (e.g., methods to start auth flow). Otherwise, keep as is.


430-437: Remember to drain/close body on 401 branch

When returning early on 401 you skip the deferred resp.Body.Close() (it’s after the return). Add io.Copy(io.Discard, resp.Body) + resp.Body.Close() before returning to avoid leaking connections.

-		if resp.StatusCode == http.StatusUnauthorized && c.oauthHandler != nil {
-			return &OAuthAuthorizationRequiredError{
-				Handler: c.oauthHandler,
-			}
+		if resp.StatusCode == http.StatusUnauthorized && c.oauthHandler != nil {
+			io.Copy(io.Discard, resp.Body)
+			resp.Body.Close()
+			return &OAuthAuthorizationRequiredError{Handler: c.oauthHandler}
 		}
examples/oauth_client/main.go (1)

205-214: Surface unsupported-OS error from openBrowser

When runtime.GOOS is not recognised we silently replace the error with our own, but never return it.
Consider returning the error so the caller can decide how to proceed (e.g. print the URL once, then exit).

-func openBrowser(url string) {
+func openBrowser(url string) error {
     var err error
@@
     }
 
-    if err != nil {
-        log.Printf("Failed to open browser: %v", err)
-        fmt.Printf("Please open the following URL in your browser: %s\n", url)
-    }
+    if err != nil {
+        // Caller can log or decide to fall back.
+        return fmt.Errorf("openBrowser: %w", err)
+    }
+    return nil
 }

Call-sites can treat a non-nil error the same way you already do.
This keeps the helper composable and testable.

client/oauth.go (1)

26-34: Duplicate WithOAuth options can lead to confusing behaviour

NewOAuthStreamableHttpClient always appends its own transport.WithOAuth(oauthConfig) to the supplied options.
If the caller already provided a WithOAuth option (perhaps with different scopes or a different token store) the last option silently wins.

Consider guarding against duplicates or, at minimum, documenting that callers must not pass an explicit OAuth option.

client/transport/oauth.go (2)

61-66: Add safety margin when checking token expiry

Network latency and clock-skew can cause a token to be considered valid locally but rejected by the server a few seconds later.
Add a grace period (e.g. 30 s) when evaluating IsExpired.

-return time.Now().After(t.ExpiresAt)
+const skew = 30 * time.Second
+return time.Now().Add(skew).After(t.ExpiresAt)

386-447: State parameter is accepted but never verified

ProcessAuthorizationResponse receives state yet never uses it.
Although the example verifies the state before calling this method, the
handler itself should still compare the server-echoed state to the one it
generated to prevent CSRF when used directly.

At a minimum document this expectation; preferably add the check and return an
error on mismatch.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between e7d2547 and 78d1996.

📒 Files selected for processing (10)
  • client/oauth.go (1 hunks)
  • client/oauth_test.go (1 hunks)
  • client/transport/oauth.go (1 hunks)
  • client/transport/oauth_test.go (1 hunks)
  • client/transport/oauth_utils.go (1 hunks)
  • client/transport/oauth_utils_test.go (1 hunks)
  • client/transport/streamable_http.go (8 hunks)
  • client/transport/streamable_http_oauth_test.go (1 hunks)
  • examples/oauth_client/README.md (1 hunks)
  • examples/oauth_client/main.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
client/transport/oauth_utils_test.go (2)
client/transport/oauth_utils.go (4)
  • GenerateRandomString (11-17)
  • GenerateCodeVerifier (20-23)
  • GenerateCodeChallenge (26-31)
  • GenerateState (34-36)
client/oauth.go (3)
  • GenerateCodeVerifier (38-38)
  • GenerateCodeChallenge (41-41)
  • GenerateState (44-44)
client/transport/streamable_http.go (2)
client/oauth.go (3)
  • OAuthConfig (10-10)
  • OAuthAuthorizationRequiredError (47-47)
  • GetOAuthHandler (56-61)
client/transport/oauth.go (3)
  • OAuthConfig (18-34)
  • NewOAuthHandler (120-129)
  • OAuthHandler (111-117)
client/transport/oauth_utils.go (1)
client/oauth.go (3)
  • GenerateCodeVerifier (38-38)
  • GenerateCodeChallenge (41-41)
  • GenerateState (44-44)
client/transport/oauth_test.go (3)
client/oauth.go (2)
  • Token (13-13)
  • NewMemoryTokenStore (22-22)
client/transport/oauth.go (2)
  • Token (45-58)
  • NewMemoryTokenStore (75-77)
client/transport/oauth_utils.go (1)
  • ValidateRedirectURI (39-56)
client/oauth.go (4)
client/transport/oauth.go (6)
  • OAuthConfig (18-34)
  • Token (45-58)
  • TokenStore (37-42)
  • MemoryTokenStore (69-72)
  • NewMemoryTokenStore (75-77)
  • OAuthHandler (111-117)
client/transport/streamable_http.go (4)
  • StreamableHTTPCOption (21-21)
  • WithOAuth (37-41)
  • NewStreamableHTTP (76-95)
  • OAuthAuthorizationRequiredError (145-147)
client/client.go (2)
  • Client (16-25)
  • NewClient (44-54)
client/transport/oauth_utils.go (3)
  • GenerateCodeVerifier (20-23)
  • GenerateCodeChallenge (26-31)
  • GenerateState (34-36)
client/transport/oauth.go (2)
client/oauth.go (5)
  • TokenStore (16-16)
  • Token (13-13)
  • NewMemoryTokenStore (22-22)
  • MemoryTokenStore (19-19)
  • OAuthConfig (10-10)
client/client.go (1)
  • Client (16-25)
🪛 GitHub Check: lint
client/transport/streamable_http_oauth_test.go

[failure] 25-25:
Error return value of (*encoding/json.Encoder).Encode is not checked (errcheck)


[failure] 42-42:
Error return value of tokenStore.SaveToken is not checked (errcheck)

client/oauth_test.go

[failure] 28-28:
Error return value of (*encoding/json.Encoder).Encode is not checked (errcheck)


[failure] 52-52:
Error return value of tokenStore.SaveToken is not checked (errcheck)

examples/oauth_client/main.go

[failure] 181-181:
Error return value of w.Write is not checked (errcheck)

🪛 GitHub Actions: golangci-lint
client/oauth_test.go

[error] 28-28: golangci-lint: Error return value of (*encoding/json.Encoder).Encode is not checked (errcheck)

🔇 Additional comments (8)
client/transport/oauth_utils_test.go (3)

8-31: Well-implemented comprehensive test for random string generation

The test thoroughly verifies the GenerateRandomString function by checking different string lengths and ensuring randomness through non-repetition of generated values.


33-69: Thorough PKCE verification tests

The test for code verifier and challenge generation is comprehensive, covering expected length validation, randomness verification, and challenge consistency for the same verifier. This ensures the PKCE implementation is robust and compliant with OAuth standards.


71-88: Appropriate state parameter testing

The test effectively verifies that state parameters have the expected length and are generated with sufficient randomness to prevent CSRF attacks.

examples/oauth_client/README.md (1)

3-38: Well-structured OAuth integration documentation

The README provides excellent documentation on the OAuth client capabilities, usage, and workflow. It gives users a clear understanding of how the OAuth flow works with the MCP client.

client/transport/oauth_test.go (2)

8-49: Well-structured token expiration test

The test cases for token expiration cover all relevant scenarios (valid, expired, and no expiration) using a table-driven testing approach.


94-139: Comprehensive redirect URI validation tests

The test cases thoroughly verify redirect URI validation rules, covering all security-critical scenarios including HTTPS requirement, localhost exception, and invalid schemes.

client/transport/oauth_utils.go (2)

11-17: Secure random string generation approach

The implementation correctly uses crypto/rand for secure random generation and base64 URL encoding for string conversion, which is appropriate for security-sensitive OAuth parameters.


20-36: Compliant PKCE and state parameter implementation

The code verifier, challenge, and state generation functions follow OAuth 2.0 and PKCE (RFC 7636) specifications with appropriate string lengths and cryptographic methods.

@ezynda3 ezynda3 marked this pull request as draft May 15, 2025 19:16
@ezynda3 ezynda3 marked this pull request as ready for review May 15, 2025 20:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
client/transport/oauth.go (1)

287-305: ⚠️ Potential issue

Validate authBaseURL to prevent returning relative paths

The getDefaultEndpoints function still doesn't validate that authBaseURL has a scheme and host after clearing the path. This could result in returning relative endpoint paths that will fail when used with http.NewRequest.

Apply this fix to validate the URL:

func (h *OAuthHandler) getDefaultEndpoints(baseURL string) *AuthServerMetadata {
	// Parse the base URL to extract the authority
	parsedURL, err := url.Parse(baseURL)
	if err != nil {
		return nil
	}

	// Discard any path component to get the authorization base URL
	parsedURL.Path = ""
	authBaseURL := parsedURL.String()
+	
+	// Ensure we have a valid URL with scheme and host
+	if parsedURL.Scheme == "" || parsedURL.Host == "" {
+		return nil
+	}

	return &AuthServerMetadata{
		Issuer:                authBaseURL,
		AuthorizationEndpoint: authBaseURL + "/authorize",
		TokenEndpoint:         authBaseURL + "/token",
		RegistrationEndpoint:  authBaseURL + "/register",
	}
}
🧹 Nitpick comments (3)
client/transport/oauth.go (3)

36-42: Consider enhancing TokenStore interface for token revocation

The TokenStore interface could benefit from methods for deleting or invalidating tokens, which would be useful for implementing logout or token revocation features.

// TokenStore is an interface for storing and retrieving OAuth tokens
type TokenStore interface {
	// GetToken returns the current token
	GetToken() (*Token, error)
	// SaveToken saves a token
	SaveToken(token *Token) error
+	// DeleteToken removes the stored token
+	// Useful for implementing logout or token revocation
+	DeleteToken() error
}

This would require updating the MemoryTokenStore implementation accordingly.


125-128: Make HTTP timeout configurable

The HTTP client timeout is hardcoded to 30 seconds, which might not be appropriate for all environments or operations.

type OAuthConfig struct {
	// ... existing fields
+	// HTTPTimeout is the timeout for HTTP requests (defaults to 30 seconds)
+	HTTPTimeout time.Duration
}

func NewOAuthHandler(config OAuthConfig) *OAuthHandler {
	if config.TokenStore == nil {
		config.TokenStore = NewMemoryTokenStore()
	}
+	
+	timeout := 30 * time.Second
+	if config.HTTPTimeout > 0 {
+		timeout = config.HTTPTimeout
+	}
	
	return &OAuthHandler{
		config:     config,
-		httpClient: &http.Client{Timeout: 30 * time.Second},
+		httpClient: &http.Client{Timeout: timeout},
	}
}

443-466: Consider validating the redirect URI in GetAuthorizationURL

While the function correctly adds the redirect URI to the authorization URL, it doesn't validate it. It might be worth adding a validation to ensure the redirect URI is properly URL-encoded and has a valid format.

func (h *OAuthHandler) GetAuthorizationURL(ctx context.Context, state, codeChallenge string) (string, error) {
	metadata, err := h.getServerMetadata(ctx)
	if err != nil {
		return "", fmt.Errorf("failed to get server metadata: %w", err)
	}

+	// Validate redirect URI
+	if _, err := url.Parse(h.config.RedirectURI); err != nil {
+		return "", fmt.Errorf("invalid redirect URI: %w", err)
+	}

	params := url.Values{}
	params.Set("response_type", "code")
	params.Set("client_id", h.config.ClientID)
	params.Set("redirect_uri", h.config.RedirectURI)
	params.Set("state", state)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 307415c and 95de2fb.

📒 Files selected for processing (5)
  • client/transport/oauth.go (1 hunks)
  • client/transport/oauth_test.go (1 hunks)
  • client/transport/oauth_utils.go (1 hunks)
  • client/transport/streamable_http.go (9 hunks)
  • client/transport/streamable_http_oauth_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • client/transport/oauth_utils.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • client/transport/oauth_test.go
  • client/transport/streamable_http.go
  • client/transport/streamable_http_oauth_test.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
client/transport/oauth.go (3)
client/oauth.go (5)
  • TokenStore (16-16)
  • Token (13-13)
  • NewMemoryTokenStore (22-22)
  • MemoryTokenStore (19-19)
  • OAuthConfig (10-10)
client/client.go (1)
  • Client (16-25)
client/transport/streamable_http.go (1)
  • ErrOAuthAuthorizationRequired (146-146)
🔇 Additional comments (5)
client/transport/oauth.go (5)

140-145: Looks good - Fixed empty token check

The code now properly checks if the access token is empty before considering it valid, addressing the previous issue where empty access tokens would be treated as valid.


232-242: Improved metadata URL validation

The code now properly checks if AuthServerMetadataURL is empty and returns a clear error message instead of attempting to parse an empty string.


68-95: Memory store implementation looks good

The MemoryTokenStore implementation properly uses a mutex for thread safety and has appropriate error handling.


60-66: Token expiration check is well implemented

The IsExpired method correctly handles the case where ExpiresAt is zero and properly compares with the current time.


307-378: The dynamic client registration looks good

The RegisterClient method properly implements OAuth dynamic client registration, handling both public and confidential clients correctly.

@pottekkat pottekkat added type: enhancement New feature or enhancement request area: mcp spec Issues related to MCP specification compliance labels May 16, 2025
@giridhar-murthy-glean
Copy link
Contributor

@ezynda3 -- modulo the couple of comments above, this PR looks good. Could you please help with getting this merged? Thanks.

ezynda3 and others added 3 commits May 22, 2025 16:58
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
client/oauth.go (2)

56-61: Use errors.As to recognize wrapped errors

The current implementation of GetOAuthHandler uses direct type assertion which will fail when errors are wrapped. This is the same issue that was previously fixed in IsOAuthAuthorizationRequiredError.

func GetOAuthHandler(err error) *transport.OAuthHandler {
-	if oauthErr, ok := err.(*OAuthAuthorizationRequiredError); ok {
-		return oauthErr.Handler
-	}
+	var oauthErr *OAuthAuthorizationRequiredError
+	if errors.As(err, &oauthErr) {
+		return oauthErr.Handler
+	}
	return nil
}

3-7: ⚠️ Potential issue

Add the errors package to imports

The IsOAuthAuthorizationRequiredError function on line 52 uses errors.As, but the errors package is not imported. This will cause compilation errors.

import (
+	"errors"
	"fmt"

	"github.com/mark3labs/mcp-go/client/transport"
)
client/transport/oauth.go (3)

294-312: Improve getDefaultEndpoints to handle empty base URLs

The function may return relative paths if the base URL lacks a scheme or host. This would cause runtime failures when used in HTTP requests.

func (h *OAuthHandler) getDefaultEndpoints(baseURL string) *AuthServerMetadata {
	// Parse the base URL to extract the authority
	parsedURL, err := url.Parse(baseURL)
	if err != nil {
		return nil
	}

	// Discard any path component to get the authorization base URL
	parsedURL.Path = ""
	authBaseURL := parsedURL.String()
+	
+	// Check if the URL has scheme and host
+	if authBaseURL == "" {
+		return nil
+	}

	return &AuthServerMetadata{
		Issuer:                authBaseURL,
		AuthorizationEndpoint: authBaseURL + "/authorize",
		TokenEndpoint:         authBaseURL + "/token",
		RegistrationEndpoint:  authBaseURL + "/register",
	}
}

194-197: Parse OAuth error responses properly

The current implementation doesn't attempt to parse OAuth error responses, which typically follow a standardized JSON format. This makes error handling less informative.

if resp.StatusCode != http.StatusOK {
	body, _ := io.ReadAll(resp.Body)
+	
+	// Try to parse as OAuth error response
+	var oauthError struct {
+		Error            string `json:"error"`
+		ErrorDescription string `json:"error_description,omitempty"`
+	}
+	
+	if err := json.Unmarshal(body, &oauthError); err == nil && oauthError.Error != "" {
+		return nil, fmt.Errorf("oauth error: %s - %s", oauthError.Error, oauthError.ErrorDescription)
+	}
+	
	return nil, fmt.Errorf("refresh token request failed with status %d: %s", resp.StatusCode, body)
}

This same pattern should also be applied to error handling in ProcessAuthorizationResponse (lines 427-430) and RegisterClient (lines 364-367).


387-406: Add state parameter validation

The ProcessAuthorizationResponse function doesn't validate the state parameter against an expected value, which is important for preventing CSRF attacks.

func (h *OAuthHandler) ProcessAuthorizationResponse(ctx context.Context, code, state, codeVerifier string) error {
+	// Validate state parameter (assuming you store expected state elsewhere)
+	// This could be done by comparing with a stored value or using a signed state
+	// For example, if you have a stateStore interface:
+	// if !h.stateStore.ValidateState(state) {
+	//     return errors.New("invalid state parameter, possible CSRF attack")
+	// }
+
	metadata, err := h.getServerMetadata(ctx)
	if err != nil {
		return fmt.Errorf("failed to get server metadata: %w", err)
	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d41414 and 53088d0.

📒 Files selected for processing (2)
  • client/oauth.go (1 hunks)
  • client/transport/oauth.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
client/oauth.go (4)
client/transport/oauth.go (6)
  • OAuthConfig (18-34)
  • Token (45-58)
  • TokenStore (37-42)
  • MemoryTokenStore (69-72)
  • NewMemoryTokenStore (75-77)
  • OAuthHandler (111-117)
client/transport/streamable_http.go (4)
  • StreamableHTTPCOption (22-22)
  • WithOAuth (44-48)
  • NewStreamableHTTP (84-103)
  • OAuthAuthorizationRequiredError (155-157)
client/client.go (2)
  • Client (16-25)
  • NewClient (44-54)
client/transport/oauth_utils.go (3)
  • GenerateCodeVerifier (21-24)
  • GenerateCodeChallenge (27-32)
  • GenerateState (35-37)
🪛 GitHub Check: lint
client/oauth.go

[failure] 52-52:
undefined: errors (typecheck)

client/transport/oauth.go

[failure] 216-216:
invalid character U+2026 '…' in identifier


[failure] 225-225:
syntax error: unexpected name context in argument list; possibly missing comma or )) (typecheck)


[failure] 216-216:
invalid character U+2026 '…' in identifier


[failure] 225-225:
syntax error: unexpected name context in argument list; possibly missing comma or ) (typecheck)


[failure] 216-216:
illegal character U+2026 '…' (typecheck)


[failure] 225-225:
missing ',' in argument list (typecheck)


[failure] 226-226:
missing ',' in argument list (typecheck)


[failure] 227-227:
expected operand, found '}' (typecheck)

🪛 GitHub Check: test
client/transport/oauth.go

[failure] 216-216:
invalid character U+2026 '…' in identifier


[failure] 225-225:
syntax error: unexpected name context in argument list; possibly missing comma or )

🪛 GitHub Actions: go
client/transport/oauth.go

[error] 216-216: Invalid character U+2026 '…' in identifier at line 216.

🔇 Additional comments (5)
client/oauth.go (3)

26-35: The function logic looks good

The NewOAuthStreamableHttpClient function correctly creates a new client with OAuth support by adding the OAuth option to the transport options. It properly handles errors from creating the transport.


37-45: Good use of variable declaration for function aliasing

Using variable declarations to alias the transport functions is a clean approach for re-exporting functionality from the transport package.


50-53: The implementation is correct

The function uses errors.As correctly to check if the error is (or wraps) an OAuthAuthorizationRequiredError.

🧰 Tools
🪛 GitHub Check: lint

[failure] 52-52:
undefined: errors (typecheck)

client/transport/oauth.go (2)

142-145: Good improvement for empty access token check

I see you've added a check for empty access tokens in getValidToken, which was a previously identified issue. This ensures that empty tokens won't be treated as valid.


209-212: Good handling of refresh tokens

The code now properly preserves old refresh tokens when the server doesn't provide a new one, which was a previously identified issue.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
client/transport/oauth.go (2)

196-199: 🛠️ Refactor suggestion

Parse OAuth error responses properly for better diagnostics.

OAuth 2.0 servers return errors in a standardized JSON format, but the current implementation doesn't attempt to parse these responses.

if resp.StatusCode != http.StatusOK {
	body, _ := io.ReadAll(resp.Body)
+	
+	// Try to parse as OAuth error response
+	var oauthError struct {
+		Error            string `json:"error"`
+		ErrorDescription string `json:"error_description,omitempty"`
+	}
+	
+	if err := json.Unmarshal(body, &oauthError); err == nil && oauthError.Error != "" {
+		return nil, fmt.Errorf("oauth error: %s - %s", oauthError.Error, oauthError.ErrorDescription)
+	}
+	
	return nil, fmt.Errorf("refresh token request failed with status %d: %s", resp.StatusCode, body)
}

This same pattern should be applied to other HTTP response error handling sections (lines 467-470, 547-550).


397-415: 🛠️ Refactor suggestion

Validate parsed URL in getDefaultEndpoints to prevent relative paths.

If baseURL lacks scheme/host, the resulting endpoints will be relative paths like /authorize, /token, which will fail at runtime when passed to HTTP requests.

// getDefaultEndpoints returns default OAuth endpoints based on the base URL
func (h *OAuthHandler) getDefaultEndpoints(baseURL string) *AuthServerMetadata {
	// Parse the base URL to extract the authority
	parsedURL, err := url.Parse(baseURL)
	if err != nil {
		return nil
	}

	// Discard any path component to get the authorization base URL
	parsedURL.Path = ""
	authBaseURL := parsedURL.String()
+	
+	// Validate that we have a complete URL with scheme and host
+	if parsedURL.Scheme == "" || parsedURL.Host == "" {
+		return nil
+	}

	return &AuthServerMetadata{
		Issuer:                authBaseURL,
		AuthorizationEndpoint: authBaseURL + "/authorize",
		TokenEndpoint:         authBaseURL + "/token",
		RegistrationEndpoint:  authBaseURL + "/register",
	}
}
🧹 Nitpick comments (3)
client/transport/oauth_test.go (2)

54-95: Consider verifying all token fields in the MemoryTokenStore test.

The test verifies access token, token type, and refresh token, but doesn't verify that ExpiresIn and ExpiresAt fields are properly preserved when retrieving tokens.

 	if retrievedToken.RefreshToken != token.RefreshToken {
 		t.Errorf("Expected refresh token to be %s, got %s", token.RefreshToken, retrievedToken.RefreshToken)
 	}
+	if retrievedToken.ExpiresIn != token.ExpiresIn {
+		t.Errorf("Expected expires in to be %d, got %d", token.ExpiresIn, retrievedToken.ExpiresIn)
+	}
+	// Check that ExpiresAt is preserved (within a small tolerance to account for time precision differences)
+	if !retrievedToken.ExpiresAt.Equal(token.ExpiresAt) {
+		t.Errorf("Expected expires at to be %v, got %v", token.ExpiresAt, retrievedToken.ExpiresAt)
+	}

196-221: Improve error condition specificity in server metadata test.

The error message check is very loose, making it hard to determine what specific error condition is being tested. Consider using a more targeted approach by mocking the HTTP client to return specific errors or test against specific error types rather than string content.

-	// Verify the error message contains something about a connection error
-	// since we're now trying to connect to the well-known endpoint
-	if !strings.Contains(err.Error(), "connection refused") && 
-	   !strings.Contains(err.Error(), "failed to send protected resource request") {
-		t.Errorf("Expected error message to contain connection error, got %s", err.Error())
-	}
+	// Verify that we get an error related to the server metadata discovery
+	if err == nil {
+		t.Fatalf("Expected error when getting server metadata with empty URL")
+	}
+	// The error should come from attempted discovery, not from invalid URL formatting
+	if strings.Contains(err.Error(), "invalid URL") {
+		t.Errorf("Got URL formatting error instead of discovery error: %s", err.Error())
+	}
client/transport/oauth.go (1)

369-390: Document the limitation of base URL extraction from redirect URI.

The current approach of extracting a base URL from the redirect URI assumes that the redirect URI is on the same domain as the OAuth server, which may not always be true.

// extractBaseURL extracts the base URL from the first request
func (h *OAuthHandler) extractBaseURL() (string, error) {
	// If we have a base URL from a previous request, use it
	if h.baseURL != "" {
		return h.baseURL, nil
	}

+	// Note: This approach assumes the redirect URI is on the same domain as the OAuth server,
+	// which may not always be true. In production, it's better to explicitly provide the base URL.
+
	// Otherwise, we need to infer it from the redirect URI
	if h.config.RedirectURI == "" {
		return "", fmt.Errorf("no base URL available and no redirect URI provided")
	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53088d0 and 8a999d6.

📒 Files selected for processing (4)
  • client/oauth.go (1 hunks)
  • client/transport/oauth.go (1 hunks)
  • client/transport/oauth_test.go (1 hunks)
  • client/transport/streamable_http_oauth_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/transport/streamable_http_oauth_test.go
  • client/oauth.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
client/transport/oauth.go (4)
client/oauth.go (5)
  • TokenStore (17-17)
  • Token (14-14)
  • NewMemoryTokenStore (23-23)
  • MemoryTokenStore (20-20)
  • OAuthConfig (11-11)
client/client.go (1)
  • Client (16-25)
client/transport/streamable_http.go (1)
  • ErrOAuthAuthorizationRequired (152-152)
mcp/types.go (1)
  • Resource (613-628)
🔇 Additional comments (11)
client/transport/oauth_test.go (4)

11-52: Well-structured test for token expiration logic.

The test comprehensively covers the IsExpired method with valid, expired, and no-expiration test cases.


97-157: Comprehensive test for redirect URI validation.

The test covers a wide range of use cases including secure HTTPS URIs, localhost variants (domain, IPv4, IPv6), and various invalid cases.


159-194: Well-implemented test for empty access token handling.

The test correctly verifies that attempting to get an authorization header with an empty access token returns the appropriate error type.


223-263: Good test coverage for CSRF protection.

The test properly verifies state parameter validation logic, including both matching and non-matching states, as well as edge cases like empty expected state.

client/transport/oauth.go (7)

18-34: Well-designed and documented OAuth configuration structure.

The OAuthConfig struct includes all necessary configuration options with clear documentation.


36-66: Clean token model with proper expiration handling.

The Token struct includes all standard OAuth token fields, and the IsExpired method correctly handles tokens with no expiration time.


68-95: Thread-safe in-memory token store implementation.

The MemoryTokenStore provides a simple, thread-safe implementation with proper error handling.


142-160: Robust token validation logic with proper error handling.

The getValidToken method correctly addresses the previous issue with empty access tokens by adding the check for token.AccessToken != "".


257-335: Comprehensive OAuth metadata discovery with RFC 9728 support.

The implementation correctly follows the RFC 9728 standard for OAuth Protected Resource metadata discovery and includes multiple fallback mechanisms.


490-502: Proper implementation of state validation for CSRF protection.

The state parameter validation is correctly implemented to prevent CSRF attacks, addressing the previous review comment.


570-596: Well-implemented authorization URL generation with PKCE support.

The method properly stores the state for later validation and includes all necessary OAuth parameters.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
client/transport/oauth_test.go (3)

54-95: MemoryTokenStore implementation looks good

The test properly verifies both error handling for empty stores and correct token persistence/retrieval functionality, checking all token fields.

Consider adding a test case for concurrent access to validate that the mutex in MemoryTokenStore provides proper thread safety.


196-221: Metadata URL empty handling test could be more robust

While the test correctly verifies that an empty metadata URL produces an error, the error message validation is somewhat brittle, relying on string matching that could break with implementation changes.

Consider refactoring this test to use a mock HTTP client that returns a specific error, making the test more resilient to implementation changes in error messages.


262-302: State validation test covers critical security cases

The test correctly verifies CSRF protection by checking:

  • Rejection of non-matching state values
  • Handling of empty expected states

This is essential for preventing cross-site request forgery attacks in the OAuth flow.

Consider adding a positive test case that mocks a successful token response when the state matches, to complete the coverage of the authorization flow.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a999d6 and 7d132c9.

📒 Files selected for processing (2)
  • client/transport/oauth.go (1 hunks)
  • client/transport/oauth_test.go (1 hunks)
🔇 Additional comments (12)
client/transport/oauth_test.go (4)

1-52: Test coverage for Token.IsExpired is comprehensive!

The test thoroughly covers all key scenarios for token expiration:

  • Valid non-expired tokens
  • Expired tokens
  • Tokens without expiration information

The test structure is clean with a good table-driven approach.


97-157: Redirect URI validation test is thorough and security-focused

The test covers critical security scenarios including:

  • Valid HTTPS URIs
  • Localhost exceptions (both hostname and IP formats including IPv6)
  • Invalid HTTP URIs
  • Empty and malformed URIs

This is important for preventing redirect-based attacks.


159-194: Good handling of empty access token case

The test verifies that authorization attempts with an empty access token properly trigger the authorization required error, which is an important security check.


223-260: OAuth error formatting test is clear and focused

The test properly validates that OAuth errors are formatted correctly both with and without descriptions.

client/transport/oauth.go (8)

1-140: Well-structured OAuth client implementation with comprehensive type definitions

The code provides a complete implementation of an OAuth 2.0 client with:

  • Clear configuration options including PKCE support
  • Thread-safe token storage
  • Proper token expiration handling
  • Clean interfaces for extensibility

The OAuthConfig includes all necessary fields for a compliant OAuth 2.0 client, and the token-related structures and methods handle the token lifecycle correctly.


142-160: Token validation logic is secure and properly implemented

The getValidToken method correctly:

  • Checks for token expiration
  • Verifies the access token is not empty
  • Falls back to refresh token flow when needed
  • Returns an authorization required error when a valid token isn't available

This ensures that only valid tokens are used for requests, which is crucial for security.


162-228: Token refresh implementation follows OAuth 2.0 best practices

The refresh token flow properly:

  • Sends the correct parameters to the token endpoint
  • Uses the client secret only when available
  • Sets appropriate headers for the request
  • Handles error responses by parsing OAuth error formats
  • Preserves the old refresh token if a new one isn't provided
  • Sets proper token expiration time

This ensures reliable token refresh according to the OAuth 2.0 specification.


230-275: Good error handling and utility methods

The implementation includes:

  • Public wrappers for internal methods
  • Getter methods for configuration values
  • Proper OAuth error extraction and formatting

The extractOAuthError function intelligently tries to parse standard OAuth error responses first, falling back to raw error messages when needed.


277-432: Excellent RFC 9728 metadata discovery implementation

The metadata discovery process is robust, following a cascading approach:

  1. Uses explicitly provided metadata URL if available
  2. Tries RFC 9728 OAuth Protected Resource discovery
  3. Falls back to OpenID Connect discovery
  4. Tries OAuth Authorization Server Metadata
  5. Constructs default endpoints as a last resort

The implementation uses sync.Once for thread safety and efficient caching, and includes proper error handling at each step. Base URL extraction is also handled gracefully.


434-462: Secure endpoint generation with proper validation

The getDefaultEndpoints method:

  • Parses the base URL correctly
  • Validates that the URL has both scheme and host components
  • Returns descriptive errors for invalid URLs
  • Constructs standard OAuth endpoints

This ensures that even when falling back to default endpoints, the URLs are valid and secure.


464-535: Dynamic client registration implementation is comprehensive

The client registration flow:

  • Verifies server support for registration
  • Adapts request parameters based on client type (public vs. confidential)
  • Includes all required registration parameters
  • Updates client configuration with server-provided credentials
  • Properly handles error responses

This enables the MCP client to work with OAuth servers that support dynamic registration.


537-643: Authorization flow implements critical security measures

The authorization flow implementation:

  • Properly validates state parameters to prevent CSRF attacks
  • Cleans up state after validation
  • Includes PKCE support for enhanced security
  • Correctly handles the token exchange process
  • Sets appropriate token expiration
  • Constructs properly formatted authorization URLs

These security measures are essential for a safe OAuth implementation.

@ezynda3 ezynda3 requested review from pottekkat and rwjblue-glean and removed request for giridhar-murthy-glean and david-hamilton-glean May 23, 2025 05:21
Copy link
Contributor

@giridhar-murthy-glean giridhar-murthy-glean left a comment

Choose a reason for hiding this comment

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

Couple of comments related to error parsing and metadata discovery.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
client/transport/streamable_http_oauth_test.go (2)

15-51: Improve test server logic clarity and consider edge cases.

The test server logic mixes request counting with authorization header validation, which can be confusing. Consider refactoring for better readability.

func TestStreamableHTTP_WithOAuth(t *testing.T) {
	// Track request count to simulate 401 on first request, then success
	requestCount := 0
-	authHeaderReceived := ""

	// Create a test server that requires OAuth
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		// Capture the Authorization header
-		authHeaderReceived = r.Header.Get("Authorization")

		// Check for Authorization header
		if requestCount == 0 {
			// First request - simulate 401 to test error handling
			requestCount++
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

+		// Capture and verify the Authorization header for subsequent requests
+		authHeaderReceived := r.Header.Get("Authorization")
		// Subsequent requests - verify the Authorization header
		if authHeaderReceived != "Bearer test-token" {
			t.Errorf("Expected Authorization header 'Bearer test-token', got '%s'", authHeaderReceived)
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

Additionally, consider adding test cases for:

  • Expired tokens
  • Invalid token format
  • Missing Authorization header scenarios

119-143: Good authorization flow verification with minor improvement opportunity.

The second request test properly verifies successful OAuth flow. However, there's a scope issue with the authHeaderReceived variable that was captured in the outer scope but verified here.

Consider moving the authorization header verification logic inside the test server handler for each specific request, or use a more structured approach to capture and verify headers per request to avoid potential race conditions or confusion.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d132c9 and b279531.

📒 Files selected for processing (3)
  • client/oauth.go (1 hunks)
  • client/transport/streamable_http.go (10 hunks)
  • client/transport/streamable_http_oauth_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/transport/streamable_http.go
  • client/oauth.go
🔇 Additional comments (6)
client/transport/streamable_http_oauth_test.go (6)

1-13: LGTM! Clean and appropriate imports.

The package declaration and imports are well-organized and include all necessary dependencies for OAuth transport testing.


54-64: LGTM! Proper token store setup with error handling.

The token store initialization and token saving are correctly implemented with appropriate error handling.


67-89: LGTM! Comprehensive OAuth configuration and verification.

The OAuth configuration setup and subsequent verification of OAuth enablement and handler presence are well-implemented and thorough.


92-116: Excellent OAuth error handling verification.

The test properly verifies that the first request returns an OAuthAuthorizationRequiredError with the correct handler, demonstrating good error type checking and OAuth flow understanding.


145-192: LGTM! Comprehensive unauthorized scenario testing.

This test effectively covers the scenario where no valid token is available, properly verifying that OAuthAuthorizationRequiredError is returned with the appropriate handler.


194-218: LGTM! Clean OAuth enablement flag testing.

The test properly verifies OAuth enablement status for both enabled and disabled configurations, providing good coverage of the feature flag functionality.

Copy link
Contributor

@giridhar-murthy-glean giridhar-murthy-glean left a comment

Choose a reason for hiding this comment

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

lgtm, modulo the comment related to base URL and server URL. Please address that before merging.

@ezynda3 ezynda3 merged commit 0c3f535 into main May 25, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: mcp spec Issues related to MCP specification compliance type: enhancement New feature or enhancement request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feature: Client support for OAuth
4 participants