Skip to main content

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 built-in OAuth 2.0 authentication support for securing MCP servers and clients. This includes automatic token management, dynamic client registration, and PKCE (Proof Key for Code Exchange) flow.

Client-Side Authentication

Basic OAuth Configuration

Configure OAuth when creating the HTTP transport:
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;

var transport = new HttpClientTransport(new()
{
    Endpoint = new Uri("https://api.example.com/mcp"),
    OAuth = new()
    {
        RedirectUri = new Uri("http://localhost:8080/callback"),
        ClientId = "your-client-id",  // Optional - will use DCR if not provided
        ClientSecret = "your-secret",  // Optional - for confidential clients
    }
});

var client = await McpClient.CreateAsync(transport);
The SDK automatically:
  • Detects 401/403 responses requiring authentication
  • Discovers authorization servers from resource metadata
  • Performs the OAuth authorization code flow with PKCE
  • Manages access tokens and refresh tokens
  • Retries requests with fresh tokens

Custom Authorization Flow

Provide a custom AuthorizationRedirectDelegate to control how the authorization URL is handled:
~/workspace/source/samples/ProtectedMcpClient/Program.cs
var transport = new HttpClientTransport(new()
{
    Endpoint = new Uri(serverUrl),
    OAuth = new()
    {
        RedirectUri = new Uri("http://localhost:1179/callback"),
        AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
        DynamicClientRegistration = new()
        {
            ClientName = "MyMcpClient",
        },
    }
});

static async Task<string?> HandleAuthorizationUrlAsync(
    Uri authorizationUrl,
    Uri redirectUri,
    CancellationToken cancellationToken)
{
    Console.WriteLine("Starting OAuth authorization flow...");
    Console.WriteLine($"Opening browser to: {authorizationUrl}");

    var listenerPrefix = redirectUri.GetLeftPart(UriPartial.Authority);
    if (!listenerPrefix.EndsWith("/")) listenerPrefix += "/";

    using var listener = new HttpListener();
    listener.Prefixes.Add(listenerPrefix);

    try
    {
        listener.Start();
        Console.WriteLine($"Listening for OAuth callback on: {listenerPrefix}");

        OpenBrowser(authorizationUrl);

        var context = await listener.GetContextAsync();
        var query = HttpUtility.ParseQueryString(context.Request.Url?.Query ?? string.Empty);
        var code = query["code"];

        // Send response to browser
        string responseHtml = "<html><body><h1>Authentication complete</h1><p>You can close this window.</p></body></html>";
        byte[] buffer = Encoding.UTF8.GetBytes(responseHtml);
        context.Response.ContentLength64 = buffer.Length;
        context.Response.ContentType = "text/html";
        context.Response.OutputStream.Write(buffer, 0, buffer.Length);
        context.Response.Close();

        return code;
    }
    finally
    {
        if (listener.IsListening) listener.Stop();
    }
}

Dynamic Client Registration

When no ClientId is provided, the SDK automatically performs RFC 7591 dynamic client registration:
var transport = new HttpClientTransport(new()
{
    Endpoint = new Uri("https://api.example.com/mcp"),
    OAuth = new()
    {
        RedirectUri = new Uri("http://localhost:8080/callback"),
        DynamicClientRegistration = new()
        {
            ClientName = "My MCP Application",
            ClientUri = new Uri("https://myapp.example.com"),
            InitialAccessToken = "optional-registration-token",
            ResponseDelegate = async (response, ct) =>
            {
                // Save client credentials for reuse
                await SaveCredentialsAsync(response.ClientId, response.ClientSecret);
            }
        }
    }
});

OAuth Options

Complete ClientOAuthOptions configuration:
OAuth = new()
{
    // Required: Where auth codes are sent after authorization
    RedirectUri = new Uri("http://localhost:8080/callback"),

    // Optional: Pre-registered client credentials
    ClientId = "your-client-id",
    ClientSecret = "your-client-secret",

    // Optional: Client metadata document URL (alternative to DCR)
    ClientMetadataDocumentUri = new Uri("https://myapp.example.com/.well-known/client"),

    // Optional: Requested scopes (overrides server suggestions)
    Scopes = new[] { "mcp.tools", "mcp.resources" },

    // Optional: Select from multiple authorization servers
    AuthServerSelector = (servers) => servers.FirstOrDefault(s => s.Host == "preferred.auth.com"),

    // Optional: Custom authorization flow handler
    AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,

    // Optional: Dynamic client registration settings
    DynamicClientRegistration = new()
    {
        ClientName = "My Application",
        ClientUri = new Uri("https://myapp.example.com"),
        InitialAccessToken = "initial-token",
        ResponseDelegate = async (response, ct) => { /* Save credentials */ }
    },

    // Optional: Additional authorization parameters
    AdditionalAuthorizationParameters = new Dictionary<string, string>
    {
        ["audience"] = "https://api.example.com"
    },

    // Optional: Token cache for persistence across sessions
    TokenCache = new FileTokenCache("tokens.json")
}

Server-Side Authentication

Basic MCP Authentication

Enable MCP authentication on the server:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
    .AddMcpAuthentication(options =>
    {
        options.ResourceMetadata = new ProtectedResourceMetadata
        {
            Resource = "https://api.example.com/mcp",
            AuthorizationServers = ["https://auth.example.com"],
            ScopesSupported = ["mcp.tools", "mcp.resources"],
            BearerMethodsSupported = ["header"]
        };
    });

builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithTools<MyTools>();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapMcp();
app.Run();

Resource Metadata Discovery

The MCP authentication handler automatically serves resource metadata at /.well-known/oauth-protected-resource:
GET /.well-known/oauth-protected-resource HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Content-Type: application/json

{
  "resource": "https://api.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["mcp.tools", "mcp.resources"],
  "bearer_methods_supported": ["header"]
}

Custom Resource Metadata Path

Configure a custom metadata endpoint path:
.AddMcpAuthentication(options =>
{
    options.ResourceMetadataUri = new Uri("/api/mcp/metadata", UriKind.Relative);
    options.ResourceMetadata = new ProtectedResourceMetadata
    {
        Resource = "https://api.example.com/mcp",
        AuthorizationServers = ["https://auth.example.com"]
    };
});

Authorization with Scopes

Use ASP.NET Core authorization policies to protect specific tools or resources:
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireToolsScope", policy =>
        policy.RequireClaim("scope", "mcp.tools"));

    options.AddPolicy("RequireAdminScope", policy =>
        policy.RequireClaim("scope", "mcp.admin"));
});
Apply to MCP endpoints:
[McpServerToolType]
public class ProtectedTools
{
    [McpServerTool]
    [Description("Public tool - no auth required")]
    public static string PublicTool() => "Available to all";

    [McpServerTool]
    [Authorize(Policy = "RequireToolsScope")]
    [Description("Protected tool - requires mcp.tools scope")]
    public static string ProtectedTool() => "Requires authentication";

    [McpServerTool]
    [Authorize(Policy = "RequireAdminScope")]
    [Description("Admin tool - requires mcp.admin scope")]
    public static string AdminTool() => "Requires admin access";
}

JWT Bearer Authentication

Integrate with JWT bearer tokens:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://auth.example.com";
        options.Audience = "https://api.example.com";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true
        };
    })
    .AddMcpAuthentication(options =>
    {
        options.ResourceMetadata = new ProtectedResourceMetadata
        {
            Resource = "https://api.example.com/mcp",
            AuthorizationServers = ["https://auth.example.com"]
        };
    });

Token Management

Token Caching

The SDK caches tokens in memory by default. Implement ITokenCache for persistence:
public class FileTokenCache : ITokenCache
{
    private readonly string _filePath;

    public FileTokenCache(string filePath) => _filePath = filePath;

    public async Task<TokenContainer?> GetTokensAsync(CancellationToken cancellationToken)
    {
        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)
    {
        var json = JsonSerializer.Serialize(tokens);
        await File.WriteAllTextAsync(_filePath, json, cancellationToken);
    }
}
Use the custom cache:
OAuth = new()
{
    RedirectUri = new Uri("http://localhost:8080/callback"),
    TokenCache = new FileTokenCache("oauth-tokens.json")
}

Automatic Token Refresh

The SDK automatically refreshes expired tokens using refresh tokens:
// First request: Performs OAuth flow and gets tokens
var result1 = await client.CallToolAsync("myTool");

// Later request: Automatically refreshes if access token expired
var result2 = await client.CallToolAsync("myTool");

Security Best Practices

1

Use HTTPS in production

Always use HTTPS for production endpoints:
Endpoint = new Uri("https://api.example.com/mcp"), // ✅ HTTPS
// Endpoint = new Uri("http://api.example.com/mcp"), // ❌ HTTP
2

Secure redirect URIs

Use localhost for development, registered URIs for production:
// Development
RedirectUri = new Uri("http://localhost:8080/callback")

// Production
RedirectUri = new Uri("https://myapp.example.com/oauth/callback")
3

Store credentials securely

Never hardcode credentials. Use environment variables or secret managers:
ClientId = Environment.GetEnvironmentVariable("OAUTH_CLIENT_ID"),
ClientSecret = Environment.GetEnvironmentVariable("OAUTH_CLIENT_SECRET")
4

Validate scopes

Request minimum necessary scopes and validate on the server:
Scopes = new[] { "mcp.tools" }  // Minimal scopes
The SDK uses PKCE (Proof Key for Code Exchange) by default for additional security. This protects against authorization code interception attacks.

API Reference

Build docs developers (and LLMs) love