> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentcat.com/llms.txt
> Use this file to discover all available pages before exploring further.

# C#

> Set up AgentCat in a C# MCP server, with identity and every feature on.

> [!TIP]
>
> Your coding agent can do the setup for you with this prompt:
>
> ```text theme={null}
> Read and follow https://docs.agentcat.com/languages/csharp.md to set up AgentCat in this MCP server. Follow its Guidance for AI agents.
> ```

## Supported MCP SDKs

| MCP library | Versions | Transports | Runtime |
| - | - | - | - |
| `ModelContextProtocol.AspNetCore` | 1.0.0 and later (1.x, 2.x) | stateless HTTP, streamable HTTP | .NET 8 or later |
| `ModelContextProtocol`, generic host | 1.0.0 and later (1.x, 2.x) | stdio | .NET 8 or later, .NET Framework 4.7.2 or later |
| `ModelContextProtocol`, `McpServer.Create` | 1.0.0 and later (1.x, 2.x) | stdio, streams | .NET 8 or later, .NET Framework 4.7.2 or later |

For a new server, we recommend `ModelContextProtocol.AspNetCore` 2.x with stateless HTTP, because 2.x implements the stateless MCP protocol of 2026-07-28. One `AgentCat` package supports both lines. We tested these steps on .NET 8 and 10, not on .NET Framework.

## Installation

```bash theme={null}
dotnet add package AgentCat
```

The SDK needs your project ID, which you can find in the [AgentCat dashboard](https://agentcat.com/app/settings/projects/). We recommend the `AGENTCAT_PROJECT_ID` environment variable, which keeps the ID out of your code. The SDK reads it when you pass no project ID, so the snippets pass none.

## Adding AgentCat to your server

For a server that `AddMcpServer()` builds, `WithAgentCat()` goes anywhere in the builder chain. With `McpServer.Create`, `AgentCatSdk.Track()` goes on the server options before `McpServer.Create`:

```csharp Stateless HTTP theme={null}
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<MyTools>()
    .WithAgentCat();

var app = builder.Build();
app.MapMcp();
app.Run();
```

```csharp Generic host (stdio) theme={null}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);

// stdout carries the MCP messages, so the console logs go to stderr
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<MyTools>()
    .WithAgentCat();

await builder.Build().RunAsync();
```

```csharp McpServer.Create theme={null}
using AgentCat;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

var options = new McpServerOptions
{
    ServerInfo = new Implementation { Name = "my-server", Version = "1.0.0" },
    ToolCollection = [/* McpServerTool.Create(...) */],
};

// Before McpServer.Create, because the server reads its options when it is created
AgentCatSdk.Track(options);

await using var server = McpServer.Create(new StdioServerTransport("my-server"), options);
await server.RunAsync();
```

A stateless HTTP server builds a new MCP server for each request, and `WithAgentCat()` tracks each one by itself, so it needs no extra code.

## Identifying users

> [!NOTE]
>
> We strongly recommend that you identify your users. Without identity, the dashboard cannot show who uses your server, and each session stays anonymous.

The `Identify` hook tells AgentCat which user made each tool call. We recommend the first of these two levels that your server can give.

### Full identity (recommended)

Full identity gives each session a real user ID and name, read from the request:

```csharp Stateless HTTP theme={null}
using AgentCat;

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<MyTools>()
    .WithAgentCat(configure: options =>
    {
        options.Identify = async (call, cancellationToken) =>
        {
            if (!call.Headers.TryGetValue("authorization", out var header)) return null;
            var token = header.Replace("Bearer ", "");
            var user = await FindUserByTokenAsync(token, cancellationToken); // your own lookup
            return user is null ? null : new UserIdentity(user.Id) { UserName = user.Name };
        };
    });
```

```csharp Generic host (stdio) theme={null}
using AgentCat;

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<MyTools>()
    .WithAgentCat(configure: options =>
    {
        options.Identify = async (call, cancellationToken) =>
        {
            // stdio has no headers: read the key that your server already uses
            var apiKey = Environment.GetEnvironmentVariable("MY_SERVER_API_KEY");
            if (apiKey is null) return null;
            var user = await FindUserByTokenAsync(apiKey, cancellationToken); // your own lookup
            return user is null ? null : new UserIdentity(user.Id) { UserName = user.Name };
        };
    });
```

```csharp McpServer.Create theme={null}
using AgentCat;

AgentCatSdk.Track(options, agentCatOptions: new AgentCatOptions
{
    Identify = async (call, cancellationToken) =>
    {
        // stdio has no headers: read the key that your server already uses
        var apiKey = Environment.GetEnvironmentVariable("MY_SERVER_API_KEY");
        if (apiKey is null) return null;
        var user = await FindUserByTokenAsync(apiKey, cancellationToken); // your own lookup
        return user is null ? null : new UserIdentity(user.Id) { UserName = user.Name };
    },
});
```

### Anonymized identity (the minimum)

If you prefer not to send user details, a stable ID works too, for example a hash of the token or API key. The same user always gets the same ID:

```csharp Stateless HTTP theme={null}
using System.Security.Cryptography;
using System.Text;
using AgentCat;

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<MyTools>()
    .WithAgentCat(configure: options =>
    {
        options.Identify = (call, cancellationToken) =>
        {
            if (!call.Headers.TryGetValue("authorization", out var token))
                return ValueTask.FromResult<UserIdentity?>(null);
            var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(token)));
            return ValueTask.FromResult<UserIdentity?>(new UserIdentity($"anon-{hash[..16].ToLowerInvariant()}"));
        };
    });
```

```csharp Generic host (stdio) theme={null}
using System.Security.Cryptography;
using System.Text;
using AgentCat;

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<MyTools>()
    .WithAgentCat(configure: options =>
    {
        options.Identify = (call, cancellationToken) =>
        {
            var apiKey = Environment.GetEnvironmentVariable("MY_SERVER_API_KEY");
            if (apiKey is null) return ValueTask.FromResult<UserIdentity?>(null);
            var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(apiKey)));
            return ValueTask.FromResult<UserIdentity?>(new UserIdentity($"anon-{hash[..16].ToLowerInvariant()}"));
        };
    });
```

```csharp McpServer.Create theme={null}
using System.Security.Cryptography;
using System.Text;
using AgentCat;

AgentCatSdk.Track(options, agentCatOptions: new AgentCatOptions
{
    Identify = (call, cancellationToken) =>
    {
        var apiKey = Environment.GetEnvironmentVariable("MY_SERVER_API_KEY");
        if (apiKey is null) return ValueTask.FromResult<UserIdentity?>(null);
        var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(apiKey)));
        return ValueTask.FromResult<UserIdentity?>(new UserIdentity($"anon-{hash[..16].ToLowerInvariant()}"));
    },
});
```

`call.Headers` holds the HTTP request headers, with lower-case names, and is empty on stdio. With ASP.NET Core authentication, the checked user is in `call.User`. If the hook throws or takes more than 30 seconds, the event still goes out, without identity.

## Recommended settings

AgentCat turns on every feature by default, and we recommend that you keep them that way. Each one adds a view of how agents use your server:

* **Intent** (`EnableToolCallContext`): agents tell you why they call each tool, which powers session goals.
* **Agent feedback** (`EnableReportMissing`): agents can report tools that they looked for and did not find, through the `get_more_tools` tool.
* **Sessions** (`EnableTracing`): AgentCat records each tool call and groups the calls of one task into one session, so you can replay it.
* **Diagnostics** (`DisableDiagnostics` turns it off): the SDK sends us its own log lines, without payloads, so we can fix a broken install faster.

If you need to turn one off, the [reference page](https://docs.agentcat.com/languages/csharp/reference.md#options) explains each option.

## Testing your setup

The C# SDK always writes its log to `~/agentcat.log`, so it needs no debug option. The log shows the setup and each event that AgentCat sends.

1. Watch the log:

```bash theme={null}
tail -f ~/agentcat.log
```

2. Start your real server with `AGENTCAT_PROJECT_ID` set, as you start it in production.
3. Call one tool with an MCP client, for example the MCP Inspector CLI:

```bash theme={null}
npx @modelcontextprotocol/inspector --cli http://localhost:5000/ --transport http \
  --header "Authorization: Bearer <token>" \
  --method tools/call --tool-name <tool> --tool-arg <name>=<value> \
  --tool-arg session_id=start --tool-arg "context=Testing that AgentCat records this tool call."
```

For a stdio server, you can replace the URL and `--transport http` with the command that starts your server, and leave out `--header`.

4. Look for `Successfully sent event ... | mcp:tools/call | ...` in the log. The line ends with the user ID from `Identify`, or `anonymous`. In [Sessions](https://agentcat.com/app/sessions/), the new session shows the user name from `Identify`.

A coding agent can confirm the event with the [AgentCat MCP server](https://docs.agentcat.com/agent/connect.md). If no event arrives, the log shows whether the setup worked and whether a send failed.

## Guidance for AI agents

1. Keep `Identify` in place, because without it every user is anonymous; if the developer does not want to send user details, offer the anonymized ID.
2. Return the same ID for the same user on every call, because a random value or a timestamp splits one user into many.
3. Keep raw tokens, API keys, and passwords out of `UserId`, `UserName`, and `UserData`, because AgentCat stores and shows them.
4. Leave `EnableToolCallContext`, `EnableReportMissing`, and `EnableTracing` at their defaults, because each one feeds a part of the dashboard.
5. Pass no project ID, so that the SDK reads `AGENTCAT_PROJECT_ID` and the ID stays out of committed code.
6. Call `AgentCatSdk.Track()` before `McpServer.Create`, because the server reads its options only when it is created.
7. Run `dotnet package search AgentCat --exact-match` before you pin a version, because a version from memory can be old.
8. Test through the real server start, because AgentCat stays off in test runs and a separate script can skip `WithAgentCat()`.

## Next steps

> [card] [C# reference](https://docs.agentcat.com/languages/csharp/reference.md)
>
> Options, transports, shutdown, testing, and limits

> [card] [Custom events](https://docs.agentcat.com/sdk/custom-events.md)
>
> Send your own events

> [card] [Tags and properties](https://docs.agentcat.com/sdk/event-tags-properties.md)
>
> Add data that you can filter on

> [card] [Redaction](https://docs.agentcat.com/privacy-security/client-side-redaction.md)
>
> Remove sensitive data in your server

> [card] [Exporters](https://docs.agentcat.com/integrations.md)
>
> Copies of events for OpenTelemetry, Datadog, Sentry, or PostHog


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.