Documentation Index
Fetch the complete documentation index at: https://mintlify.com/modelcontextprotocol/csharp-sdk/llms.txt
Use this file to discover all available pages before exploring further.
The MCP C# SDK provides OAuth 2.0 authentication support for HTTP-based transports, implementing the authorization code flow with PKCE (Proof Key for Code Exchange).
Namespace
ModelContextProtocol.Authentication
Overview
Authentication in the MCP SDK follows the OAuth 2.0 Authorization Code Flow with PKCE as specified in RFC 7636. The SDK handles:
- Protected Resource Metadata discovery (RFC 9728)
- Authorization Server Metadata discovery
- Dynamic Client Registration (RFC 7591)
- Authorization code exchange
- Token refresh
- Token caching
ClientOAuthOptions
Provides configuration options for OAuth authentication.
public sealed class ClientOAuthOptions
Properties
The OAuth redirect URI where the authorization code will be sent.
The OAuth client ID. If not provided, the client will attempt to register dynamically.
The OAuth client secret. Optional for public clients or when using PKCE without client authentication.
ClientMetadataDocumentUri
The HTTPS URL pointing to this client’s metadata document. When specified and the authorization server supports it, this URL is sent as the client identifier instead of performing dynamic client registration.
The OAuth scopes to request. When specified, these scopes override the scopes advertised by the protected resource.
AuthorizationRedirectDelegate
AuthorizationRedirectDelegate
The authorization redirect delegate for handling the OAuth authorization flow. If not specified, a default implementation prompts the user to enter the code manually.
AuthServerSelector
Func<IReadOnlyList<Uri>, Uri>
The authorization server selector function. Used to select which authorization server to use when multiple servers are available. If not specified, the first available server is selected.
DynamicClientRegistration
DynamicClientRegistrationOptions
Options to use during dynamic client registration. Only used when no ClientId is specified.
AdditionalAuthorizationParameters
IDictionary<string, string>
Additional parameters to include in the query string of the OAuth authorization request. Cannot override automatically set parameters like “redirect_uri”.
The token cache to use for storing and retrieving tokens beyond the lifetime of the transport. If not provided, an in-memory cache is used.
AuthorizationRedirectDelegate
Delegate for handling OAuth authorization redirects.
public delegate Task<string?> AuthorizationRedirectDelegate(
Uri authorizationUrl,
Uri redirectUri,
CancellationToken cancellationToken);
The authorization URL to present to the user.
The redirect URI where the authorization code will be sent.
Cancellation token to monitor for cancellation requests.
Returns: The authorization code extracted from the redirect, or null if authorization failed.
DynamicClientRegistrationOptions
Configuration for OAuth Dynamic Client Registration (RFC 7591).
public sealed class DynamicClientRegistrationOptions
Human-readable name of the client to be presented to the end-user during authorization.
URL of the home page of the client.
Initial access token to include in the registration request as specified by the authorization server.
ResponseDelegate
Func<DynamicClientRegistrationResponse, CancellationToken, Task>
Callback invoked with the registration response, allowing the application to persist client credentials.
ITokenCache
Interface for caching OAuth tokens.
public interface ITokenCache
{
Task<TokenContainer?> GetTokensAsync(CancellationToken cancellationToken = default);
Task StoreTokensAsync(TokenContainer tokens, CancellationToken cancellationToken = default);
}
InMemoryTokenCache
Default in-memory implementation of ITokenCache.
public sealed class InMemoryTokenCache : ITokenCache
Usage Examples
Basic OAuth Authentication
using ModelContextProtocol.Client;
using ModelContextProtocol.Authentication;
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri("https://api.example.com/mcp"),
OAuth = new ClientOAuthOptions
{
ClientId = "your-client-id",
ClientSecret = "your-client-secret",
RedirectUri = new Uri("http://localhost:3000/callback"),
Scopes = ["read", "write"]
}
});
await using var client = await McpClient.CreateAsync(transport);
Custom Authorization Redirect Handler
var oauth = new ClientOAuthOptions
{
ClientId = "your-client-id",
RedirectUri = new Uri("http://localhost:3000/callback"),
AuthorizationRedirectDelegate = async (authUrl, redirectUri, ct) =>
{
// Open browser to authorization URL
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = authUrl.ToString(),
UseShellExecute = true
});
// Start local HTTP server to capture redirect
using var listener = new HttpListener();
listener.Prefixes.Add($"{redirectUri.GetLeftPart(UriPartial.Authority)}/");
listener.Start();
var context = await listener.GetContextAsync();
var code = context.Request.QueryString["code"];
// Send response to browser
var response = context.Response;
var buffer = System.Text.Encoding.UTF8.GetBytes("Authorization successful! You can close this window.");
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, ct);
response.Close();
return code;
}
};
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri("https://api.example.com/mcp"),
OAuth = oauth
});
await using var client = await McpClient.CreateAsync(transport);
Dynamic Client Registration
var oauth = new ClientOAuthOptions
{
RedirectUri = new Uri("http://localhost:3000/callback"),
DynamicClientRegistration = new DynamicClientRegistrationOptions
{
ClientName = "My MCP Client",
ClientUri = new Uri("https://myapp.example.com"),
ResponseDelegate = async (response, ct) =>
{
// Persist the registered client credentials
await File.WriteAllTextAsync(
"client-credentials.json",
JsonSerializer.Serialize(new
{
ClientId = response.ClientId,
ClientSecret = response.ClientSecret
}),
ct);
Console.WriteLine($"Registered client: {response.ClientId}");
}
}
};
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri("https://api.example.com/mcp"),
OAuth = oauth
});
await using var client = await McpClient.CreateAsync(transport);
Custom Token Cache
public class FileTokenCache : ITokenCache
{
private readonly string _filePath;
public FileTokenCache(string filePath)
{
_filePath = filePath;
}
public async Task<TokenContainer?> GetTokensAsync(CancellationToken cancellationToken = default)
{
if (!File.Exists(_filePath))
return null;
var json = await File.ReadAllTextAsync(_filePath, cancellationToken);
return JsonSerializer.Deserialize<TokenContainer>(json);
}
public async Task StoreTokensAsync(TokenContainer tokens, CancellationToken cancellationToken = default)
{
var json = JsonSerializer.Serialize(tokens);
await File.WriteAllTextAsync(_filePath, json, cancellationToken);
}
}
// Usage
var oauth = new ClientOAuthOptions
{
ClientId = "your-client-id",
ClientSecret = "your-client-secret",
RedirectUri = new Uri("http://localhost:3000/callback"),
TokenCache = new FileTokenCache("tokens.json")
};
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri("https://api.example.com/mcp"),
OAuth = oauth
});
await using var client = await McpClient.CreateAsync(transport);
Multiple Authorization Servers
var oauth = new ClientOAuthOptions
{
ClientId = "your-client-id",
RedirectUri = new Uri("http://localhost:3000/callback"),
AuthServerSelector = servers =>
{
// Prefer auth.example.com if available
var preferred = servers.FirstOrDefault(s =>
s.Host.Equals("auth.example.com", StringComparison.OrdinalIgnoreCase));
return preferred ?? servers.FirstOrDefault();
}
};
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri("https://api.example.com/mcp"),
OAuth = oauth
});
await using var client = await McpClient.CreateAsync(transport);
Additional Authorization Parameters
var oauth = new ClientOAuthOptions
{
ClientId = "your-client-id",
RedirectUri = new Uri("http://localhost:3000/callback"),
AdditionalAuthorizationParameters = new Dictionary<string, string>
{
["prompt"] = "consent",
["access_type"] = "offline",
["state"] = "custom-state-value"
}
};
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri("https://api.example.com/mcp"),
OAuth = oauth
});
await using var client = await McpClient.CreateAsync(transport);
Authentication Flow
The OAuth authentication flow in the SDK follows these steps:
- Initial Request: Client makes a request to the protected MCP server
- 401/403 Challenge: Server responds with
WWW-Authenticate: Bearer header
- Protected Resource Metadata Discovery: Client fetches resource metadata to discover authorization servers
- Authorization Server Selection: Client selects an authorization server (using
AuthServerSelector if multiple are available)
- Authorization Server Metadata Discovery: Client fetches AS metadata from
.well-known endpoints
- Dynamic Client Registration (if needed): Client registers with the AS to obtain credentials
- Authorization Request: Client builds authorization URL and invokes
AuthorizationRedirectDelegate
- User Authorization: User authorizes the client (handled by custom delegate)
- Token Exchange: Client exchanges authorization code for access token using PKCE
- Token Storage: Tokens are stored in the configured
ITokenCache
- Retry Request: Original request is retried with
Authorization: Bearer <token> header
- Token Refresh: When tokens expire, refresh token is automatically used to obtain new tokens
Security Considerations
- Always use HTTPS endpoints in production (
https:// URIs)
- Store client secrets securely (use environment variables or secret management systems)
- Implement secure token storage (encrypt tokens at rest, use OS credential stores)
- Use PKCE (the SDK handles this automatically)
- Validate redirect URIs to prevent authorization code interception
- Consider token expiration and implement appropriate refresh logic
- For public clients, avoid storing client secrets
See Also