Skip to content

Conversation

@halllo
Copy link

@halllo halllo commented Oct 4, 2025

I added the possibility to plugin a token cache, so that the client can be made to reuse its access token beyond the lifetime of the transport. By default tokens are only cached for the lifetime of the transport.

Fixes #749
Fixes #597

Motivation and Context

When the client acquires an access token and a refresh token, it should be able to utilze them to make authenticated requests to the server even after it was restarted. The client should not need to go through the OAuth dance again and again every time it is initialised (unless the access token is no longer valid and no refresh token was acquired or the refresh token was revoked).

How Has This Been Tested?

I tested it via a proof of concept application without a token cache (using the default InMemoryTokenCache) and with a custom implementation of a file-based token cache.

Breaking Changes

The introduction of the ITokenCache is backwards compatible. If no custom token cache is provided, it falls back to an InMemoryTokenCache with the same lifetime as the (oauth enabled http) transport (like before).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

  • I made TokenContainer public, so that it can be passed into custom token caches.
  • I also made its ObtainedAt property serialisable, because it needs to remember when it was obtained after serialization to calculate the expiration.
    These two changes seemed more reasonable than introducing another token type for cache serialization only. I didn't find usages that could be disrupted by this.

Copy link
Contributor

@halter73 halter73 left a comment

Choose a reason for hiding this comment

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

Can you add tests for supplying a custom ITokenCache that verifies it gets used?

@localden @DavidParks8 Do you have any thoughts on this?

@halllo
Copy link
Author

halllo commented Oct 11, 2025

Can you add tests for supplying a custom ITokenCache that verifies it gets used?

I added tests that verify a custom token cache is used to look up cached tokens and to store new ones.

Since I wanted to test the public API, I had to use HttpClientTransport, because AuthenticatingMcpHttpClient and ClientOAuthProvider are internal. Unfortunately that required me to mock out quite a lot of auth related http requests.

What do you think?

halllo added a commit to halllo/McpExperiments that referenced this pull request Oct 15, 2025
@halllo
Copy link
Author

halllo commented Oct 15, 2025

Until this is approved and merged, I use reflection magic to extract and inject the oauth token like this:

var httpClientTransport = new HttpClientTransport(...)

var token = File.Exists("token.json") ? File.ReadAllText("token.json") : null;
httpClientTransport.InjectOAuthToken(token);

await using var mcpClient = await McpClient.CreateAsync(httpClientTransport);

File.WriteAllText("token.json", httpClientTransport.ExtractOAuthToken());

@halter73
Copy link
Contributor

halter73 commented Nov 19, 2025

@halllo I updated this PR to do what I suggested in this comment and add an OAuthTest base. This should make it easier to add tests for the upcoming OAuth SEPs tracked by 2025-11-25 Implementation (view).

@stephentoub @eiriktsarpalis @localden It'd be nice if one of you could sign off on this. @halllo Did most of the work, and it looks good to me, but I made some big changes to the tests and updated the refresh token flow slightly, so it works even when we don't know the expiration which aligns more with the TypeScript SDK.

This is similar to tokens() and saveTokens() methods in the OAuthClientProvider interface specified in the TypeScript SDK and the get_tokens and set_tokens method specified in the TokenStorage class in the Python SDK. Like these APIs, the ITokenCache is designed to be unique for each user and resource (i.e. MCP server URL). Both of these are bigger than the ITokenCache interface added by this PR, but I still think it's enough to be useful. Later, we can consider adding extensibility that allows restoring state after resuming the authorization code redirect handling in a different process which could be useful for clients used by stateless web servers, but I don't think that is necessary to enable a basic token cache support.

Comment on lines 15 to 20
/// <summary>
/// A generic implementation of an OAuth authorization provider for MCP. This does not do any advanced token
/// protection or caching - it acquires a token and server metadata and holds it in memory.
/// This is suitable for demonstration and development purposes.
/// </summary>
internal sealed partial class ClientOAuthProvider
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe these XML docs should be updated, now that tokens aren't necessarily stored in memory?

Copy link
Author

@halllo halllo Nov 21, 2025

Choose a reason for hiding this comment

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

How about just

A generic implementation of an OAuth authorization provider.

?

Comment on lines 181 to 185
// This provider only supports Bearer scheme
if (!string.Equals(scheme, BearerScheme, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("This credential provider only supports the Bearer scheme");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Not related directly to this PR, but can't this use the ThrowIfNotBearerScheme() helper?

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.

Extend ClientOAuthProvider with Pluggable Token Storage for Offline Use OAuth authentication enhanced

4 participants