Skip to content

Palpid/McpAuthExt

Repository files navigation

Palpid.McpAuth

Reusable C# library that implements MCP OAuth2 authentication with Keycloak, including Dynamic Client Registration (RFC 7591). Handles JWT Bearer validation, per-tool authorization via [McpAuthorize], automatic client registration at startup, and Protected Resource Metadata (RFC 9728). One call to AddMcpOAuth2() and the auth is done -- your MCP server tools only need a [McpAuthorize] attribute to require authentication and specific scopes.

Prerequisites

  • .NET 9 SDK
  • Docker (for Keycloak)
  • curl and jq (for the Keycloak setup script)

Quickstart

1. Start Keycloak

docker run -d -p 8080:8080 \
  -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
  -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:latest start-dev

2. Create a Keycloak Realm

KC_REALM=my-realm KC_AUDIENCE=my-server KC_SCOPE=my:scope \
  KC_ADMIN_PASSWORD=admin ./scripts/keycloak-create-realm.sh

This script creates the realm, enables anonymous DCR (by removing the Trusted Hosts policy), creates an OAuth scope with an audience mapper, and adds it as a default scope. Re-running the script is safe -- it verifies and corrects state idempotently.

3. Install the Package

dotnet add package Palpid.McpAuth

Also published to GitHub Packages as an alternative feed -- see docs/usage.md for setup instructions if you prefer that source.

4. Configure Your MCP Server

using McpAuth.Authorization;
using McpAuth.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMcpOAuth2(options =>
{
    options.AuthServerUrl = "http://localhost:8080/realms/my-realm";
    options.Audience = "my-server";
    options.ServerUrl = "http://localhost:3000";
    options.ScopesSupported = ["my:scope"];
});

builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithTools<MyTools>()
    .AddAuthorizationFilters()
    .WithRequestFilters(filters =>
    {
        filters.AddCallToolFilter(McpAuthorizationFilter.Create());
    });

var app = builder.Build();

app.MapMcpOAuth2Metadata();
app.MapMcp();

app.Run("http://localhost:3000");

5. Decorate Protected Tools

using McpAuth.Authorization;
using ModelContextProtocol.Server;

public class MyTools
{
    [McpServerTool, Description("Public tool -- no auth required")]
    public static string PublicTool() => "anyone can call this";

    [McpServerTool, McpAuthorize(Scopes = ["my:scope"])]
    [Description("Protected tool -- requires my:scope")]
    public static string ProtectedTool() => "only authenticated users";
}

Tools without [McpAuthorize] are public and accessible without a token. Tools decorated with [McpAuthorize] require a valid JWT with the specified scopes.

6. Run and Verify

dotnet run
# In another terminal:
curl http://localhost:3000/.well-known/oauth-protected-resource | jq .

Expected output: a JSON document with resource, authorization_servers, and scopes_supported fields, confirming the server advertises its OAuth2 protection metadata per RFC 9728.

DiceServer Example

The repository includes a complete demo server in src/DiceServer/ with a public roll_d6 tool and a protected roll_xdy tool that requires the dice:roll scope. See src/DiceServer/Program.cs for the full integration pattern, including CORS configuration and environment-based settings.

Advanced Usage

For advanced McpAuthOptions configuration (ClockSkew, JwksCacheInterval, RequireHttpsMetadata), architecture internals, end-to-end verification with curl, troubleshooting common errors, and GitHub Packages feed setup, see docs/usage.md.

Product Overview

For a visual, non-technical walkthrough of what McpAuth does and why (suitable for sharing outside the engineering team), see docs/product-overview.html.

License

Apache License 2.0. See LICENSE file.

About

Add OAuth2 auth to any MCP server in one line: AddMcpOAuth2(). JWT Bearer, per-tool scopes, and automatic Keycloak client registration (RFC 7591).

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages