The C# MCP SDK (`Authdog.Mcp.Sdk`) is a bearer-token verifier for [Model Context Protocol](https://modelcontextprotocol.io) 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](https://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:

```bash
git clone https://github.com/authdog/mcp-sdk.git
dotnet add reference ../mcp-sdk/csharp/Authdog.Mcp.Sdk.csproj
```

The 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. |

```csharp
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`.

```csharp
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`:

```csharp
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:

```csharp
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.

## Related

- [MCP SDK](/docs/mcp-sdk)
- [MCP authentication](/docs/mcp-authentication)
