The C# MCP SDK (Authdog.Mcp.Sdk) is a bearer-token verifier for Model Context Protocol servers. AuthdogVerifier takes the access token an MCP client presents, calls Authdog GET /v1/userinfo with it, and returns an AuthInfo identity you attach to the request context to authorize tool and resource calls. It targets .NET 8.0 or higher.
Availability
Authdog.Mcp.Sdk is source-available in github.com/authdog/mcp-sdk (csharp/). Publication on NuGet is unverified, so dotnet add package Authdog.Mcp.Sdk may not resolve yet. Until it does, reference the project from a local checkout.
Install
Clone the repository and add a project reference:
git clone https://github.com/authdog/mcp-sdk.git
dotnet add reference ../mcp-sdk/csharp/Authdog.Mcp.Sdk.csprojThe package is version 0.1.0, MIT licensed, and depends on Microsoft.Extensions.Http and Microsoft.Extensions.DependencyInjection 7.0.0. Once it is published on NuGet, dotnet add package Authdog.Mcp.Sdk is the install command.
Configure
AuthdogVerifierOptions:
| Property | Type | Default | Description |
|---|---|---|---|
BaseUrl |
string |
empty string | Base URL of the Authdog API, for example https://api.authdog.com. A trailing / is trimmed. Set it explicitly. |
Timeout |
TimeSpan |
10 seconds | Per-request timeout. |
using Authdog.Mcp.Sdk;
var verifier = new AuthdogVerifier(new AuthdogVerifierOptions
{
BaseUrl = "https://api.authdog.com",
// Timeout = TimeSpan.FromSeconds(10), // default
});The constructor takes an optional second argument, an HttpClient, for testing or to share a pooled client. When you pass your own client, Timeout is not applied to it; configure the timeout on that client instead. Passing null options throws ArgumentNullException.
var verifier = new AuthdogVerifier(
new AuthdogVerifierOptions { BaseUrl = "https://api.authdog.com" },
httpClient);Verify a token
VerifyTokenAsync(string accessToken) sends GET {BaseUrl}/v1/userinfo with Authorization: Bearer <accessToken> and User-Agent: authdog-mcp-csharp-sdk/0.1.0:
using Authdog.Mcp.Sdk.Exceptions;
try
{
var authInfo = await verifier.VerifyTokenAsync("the-bearer-access-token");
Console.WriteLine($"Subject: {authInfo.Subject}");
Console.WriteLine($"Username: {authInfo.Username}");
Console.WriteLine($"Email: {authInfo.Email}");
Console.WriteLine($"Expires: {authInfo.ExpiresAt}");
}
catch (AuthenticationException ex)
{
// HTTP 401: invalid or expired token
}
catch (ApiException ex)
{
// any other non-2xx response, or a transport failure
}AuthInfo (namespace Authdog.Mcp.Sdk.Types):
| Member | Type | Description |
|---|---|---|
Token |
string |
The bearer access token that was verified. |
Subject |
string |
User.Id |
Username |
string |
User.UserName |
Email |
string? |
First User.Emails value, or null if none. |
Scopes |
IReadOnlyList<string> |
Always empty. Userinfo does not return scopes yet. |
ExpiresAt |
DateTimeOffset? |
UTC now plus Session.RemainingSeconds. null when RemainingSeconds is not greater than 0. |
User |
User |
The full user record from userinfo. |
Wire into an MCP server
For an HTTP MCP server on ASP.NET Core, verify the Authorization: Bearer header in middleware ahead of your MCP (Streamable HTTP) endpoint and stash the identity for your handlers:
using Authdog.Mcp.Sdk;
using Authdog.Mcp.Sdk.Exceptions;
using Authdog.Mcp.Sdk.Types;
app.Use(async (context, next) =>
{
var header = context.Request.Headers.Authorization.ToString();
if (!header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
var token = header["Bearer ".Length..].Trim();
try
{
AuthInfo authInfo = await verifier.VerifyTokenAsync(token);
context.Items["AuthInfo"] = authInfo;
await next();
}
catch (AuthenticationException)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
}
catch (ApiException)
{
context.Response.StatusCode = StatusCodes.Status502BadGateway;
}
});Tool and resource handlers then read context.Items["AuthInfo"] to authorize the call, for example by authInfo.Subject. The SDK does not ship its own middleware; this snippet is the pattern from the SDK README.
Errors
All exceptions live in Authdog.Mcp.Sdk.Exceptions and derive from AuthdogException, which derives from Exception.
| Exception | When | Message |
|---|---|---|
AuthenticationException |
HTTP 401 | Unauthorized - invalid or expired token |
ApiException |
Other non-2xx response | HTTP error <status>: <body> |
ApiException |
Network failure (HttpRequestException) |
Request failed: <cause> |
ApiException |
Timeout (TaskCanceledException) |
Request timeout: <cause> |
ApiException |
2xx response whose body is not valid JSON | Failed to parse response: <cause> |
Catch AuthdogException to handle any SDK error at once.