> ## 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# reference

> Options, transports, delivery, logging, and limits of the AgentCat C# SDK.

This page describes the `AgentCat` NuGet package, version 1.x. For the first setup, see [C# setup](https://docs.agentcat.com/languages/csharp.md).

## Options

`WithAgentCat(projectId, configure)` and `AgentCatSdk.Track(options, projectId, agentCatOptions)` take the same `AgentCatOptions`. Every option is optional. The four tool-call hooks are `Func<AgentCatToolCall, CancellationToken, ValueTask<T>>`, and the table names their `T`.

| Option | Default | What it does |
| - | - | - |
| `Identify` | none | Hook that returns `UserIdentity?`. Names the user of each tool call. See [Identifying users](https://docs.agentcat.com/languages/csharp.md#identifying-users). |
| `EnableToolCallContext` | `true` | Adds the `context` parameter, where the agent explains why it calls the tool. This is the source of intent data. |
| `CustomContextDescription` | built-in text | Replaces the description of the `context` parameter. |
| `EnableReportMissing` | `true` | Adds the `get_more_tools` tool, so agents can report missing tools. |
| `EnableTracing` | `true` | Records tool calls and injects the `session_id` parameter. |
| `EnableAgentTracking` | `false` | Adds an `agent_id` parameter, so parallel agents on one task stay apart. |
| `ResolveSessionId` | none | Hook that returns `string?`: your own session key. AgentCat then injects no `session_id`. A `null` result gives the call a new session. |
| `EventTags` | none | Hook that returns `IReadOnlyDictionary<string, string>?`. Adds tags that you can filter on: keys of 32 characters or fewer, values of 200 or fewer, at most 50 tags. |
| `EventProperties` | none | Hook that returns `JsonObject?`. Adds JSON data to each event. |
| `RedactSensitiveInformation` | none | `Func<string, CancellationToken, ValueTask<string>>`. Rewrites every string of a tool-call event before delivery. |
| `RedactEvent` | none | `Func<AgentCatEvent, CancellationToken, ValueTask<AgentCatEvent?>>`. Returns the event changed, or `null` to drop it. An exception drops the event. |
| `Exporters` | empty | Sends events to OTLP, Datadog, Sentry, or PostHog too. See [Exporters](#exporters). |
| `ApiBaseUrl` | `https://api.agentcat.com` | The address that receives events, for every server of the process. |
| `DisableDiagnostics` | `false` | Stops sending AgentCat's own log lines to AgentCat. See [Diagnostics](#diagnostics). |
| `LogToHost` | `false` | Sends AgentCat's log lines to the host logger instead of `~/agentcat.log`. `WithAgentCat()` only. |
| `DisableShutdownHandlers` | `false` | Turns off the exit and signal handlers. See [Shutdown](#shutdown). |

> [!NOTE]
>
> We recommend that you keep the defaults of `EnableToolCallContext`, `EnableReportMissing`, `EnableTracing`, and `DisableDiagnostics`, because each one feeds a part of the dashboard or helps us fix a broken install. With `EnableToolCallContext = false`, the dashboard has no intent data. With `EnableReportMissing = false`, agents cannot report missing tools. With `EnableTracing = false`, AgentCat records nothing, and no hook runs.

AgentCat reads the options once, at install. `Exporters`, `DisableDiagnostics`, `LogToHost`, and `DisableShutdownHandlers` apply to the whole process, and the first install in the process decides them.

`UserIdentity` has `UserId` (set in the constructor), `UserName`, and `UserData` (a `JsonObject`). The hook argument, `AgentCatToolCall`, carries the tool name and arguments, the client name and version, `TransportSessionId`, `User` (the authenticated `ClaimsPrincipal`), `Headers`, and `Request`.

A `null` project ID, or no argument, reads `AGENTCAT_PROJECT_ID`. An empty string, or no project ID at all, turns on telemetry-only mode: events go to your exporters and not to AgentCat. Without exporters, the SDK writes a warning to the log.

The SDK reads these environment variables:

| Variable | What it does |
| - | - |
| `AGENTCAT_PROJECT_ID` | The project ID, when you pass `null` or no project ID. |
| `AGENTCAT_ENABLED` | Turns AgentCat on or off with no code. See [Turn AgentCat on or off](#turn-agentcat-on-or-off). |
| `AGENTCAT_API_URL` | The address that receives events, when `ApiBaseUrl` is not set. `MCPCAT_API_URL` is the old name. |
| `AGENTCAT_DISABLE_DIAGNOSTICS` | `true` stops the diagnostics, and `false` turns them on in a test run. |
| `AGENTCAT_DIAGNOSTICS_ENDPOINT`, `AGENTCAT_DIAGNOSTICS_TOKEN` | Send the diagnostics to your own OpenTelemetry collector. |

The SDK does not read `AGENTCAT_DEBUG_MODE`.

## Framework and transport details

`WithAgentCat()` extends the `IMcpServerBuilder` that `AddMcpServer()` returns. When AgentCat cannot track the server, the server runs untracked. A second `WithAgentCat()` on the same server installs nothing.

### Stateless HTTP

In stateless mode, the MCP SDK builds a new server for every POST, and AgentCat installs on each one. On a fresh process that has not listed its tools yet, AgentCat lists them itself, with a 5-second limit. See [Stateless servers](https://docs.agentcat.com/sdk/stateless-servers.md).

On `ModelContextProtocol` 2.x, `WithHttpTransport()` without options is already stateless. On 1.x it is stateful, so 1.x servers set `o.Stateless = true`. A stateful server takes the same call, and AgentCat still groups calls by its own `session_id`.

On stdio, AgentCat writes nothing to stdout or stderr. Your own console logs belong on stderr (`LogToStandardErrorThreshold = LogLevel.Trace`), because stdout carries the MCP messages.

### McpServer.Create

Without dependency injection, `AgentCatSdk.Track()` takes the `McpServerOptions` before `McpServer.Create`, because the server reads its filters when it is created. `Track()` returns the same options and never throws.

A project that references only `ModelContextProtocol.Core` 2.x gets `ModelContextProtocol` 1.0.0 through the AgentCat package, and `AddMcpServer()` fails in that mix. Such a project can use `Track()`, or reference `ModelContextProtocol` 2.x directly.

### Request headers and URL

On ASP.NET Core, each tool-call event carries the HTTP request under `parameters.extra.requestInfo`: every header, `authorization` and `cookie` included, and the full URL with its query string. [Redaction](https://docs.agentcat.com/privacy-security/client-side-redaction.md) shows a `RedactEvent` hook that removes credentials.

`WithAgentCat()` registers `HttpContextAccessor` when your app has none. `Track()` registers nothing, so it reads the request only when your app calls `AddHttpContextAccessor()`. On stdio, on .NET Framework, and with `PerSessionExecutionContext = true`, the event has no `requestInfo`.

### IAgentCat and custom events

`WithAgentCat()` registers `IAgentCat` as a singleton, with `PublishCustomEventAsync`, `FlushAsync`, and `ShutdownAsync`. A tool that takes `IAgentCat` and an `McpServer` can call `agentCat.PublishCustomEventAsync(server, projectId, data)`. See [Custom events](https://docs.agentcat.com/sdk/custom-events.md).

Without a `SessionId` in the data, the event has no session, because the tool never sees AgentCat's `session_id`. The task faults with `ArgumentException` for an empty project ID or an untracked server.

### What AgentCat changes in your server

* Each tool schema gets `session_id` and `context` (and `agent_id` with `EnableAgentTracking`). AgentCat removes these values before your tool runs, also with `EnableTracing = false`.
* On the first call of a task, the result starts with a text block that gives the agent its new `session_id`. For a tool with an output schema, AgentCat also puts the ID in `structuredContent`, as `mcp_session`.

## Runtimes, delivery, and shutdown

The SDK supports `ModelContextProtocol` 1.0.0 or later, on the 1.x and 2.x lines. On .NET Framework 4.7.2 or later, only stdio servers work, and the setup snippets need equivalents such as `new ValueTask<T>(value)`. On .NET 8 and 10, the package is trim and Native AOT compatible.

The four tool-call hooks start with the call and run alongside your tool. AgentCat waits at most 30 seconds for each, then sends the event without that value. The part before the first `await` runs on the request thread, so we recommend that you keep it short, and read request-scoped services there, before they are disposed.

AgentCat sends events from a process-wide queue with five background workers. The queue holds 10,000 events and drops the oldest when it is full. A failed send is tried again 3 times (after 1, 2, and 4 seconds), and each attempt times out after 10 seconds.

### Shutdown

AgentCat sends the queued events by itself after the host stops (`WithAgentCat()`, `Microsoft.Extensions.Hosting` 8 or later), at process exit, and, for `Track()` on .NET 8 and later, on SIGTERM and SIGINT. All paths share 5 seconds for events and 1 second for diagnostics, so a stop can take about 6 seconds longer than `HostOptions.ShutdownTimeout`.

We recommend a container grace period above the stop time of your server plus these 6 seconds (`terminationGracePeriodSeconds`, `docker stop --time`, or `stop_grace_period`). Events are lost on SIGKILL, on unhandled exceptions, when you dispose a service provider without a host, and when the official C# stdio client kills the server on dispose.

`AgentCatSdk.FlushAsync(timeout)` waits for the queue without closing it. `AgentCatSdk.ShutdownAsync()` closes the queue for good, so it fits only at the end of the process.

## Logging and debug mode

The SDK has no debug option. It always writes to `~/agentcat.log`, never to stdout or stderr, because stdout carries JSON-RPC on a stdio server. Each line starts with the UTC time and the SDK, .NET, and MCP SDK versions. Without a usable home directory, the SDK writes no log.

Useful log lines:

* `AgentCat setup complete | project ... | tracing=true context=true reportMissing=true exporters=0`: the install worked.
* `Successfully sent event ... | <user>`: an event reached AgentCat. The last value is the user ID from `Identify`, or `anonymous`.
* `Warning: Failed to track server - <error>`: the server runs untracked.

With `LogToHost = true` on `WithAgentCat()`, the lines go to the `ILoggerFactory` of the host, in the category `AgentCat`. Failures log as errors and routine lines as information, so `builder.Logging.AddFilter("AgentCat", LogLevel.Warning)` keeps only the problems.

### Diagnostics

To find broken installs, AgentCat sends every line that it logs to `https://otel.agentcat.com`. The lines can contain session IDs, user IDs, tool names, and error messages, but no payloads. Diagnostics are off in a test run.

## Testing

### Turn AgentCat on or off

`AGENTCAT_ENABLED` turns AgentCat on (`true`) or off (`false`). Without it, AgentCat is on, except in a test run: a process that loads xUnit, NUnit, MSTest, or TUnit. While AgentCat is off, it installs nothing, so tests that count your tools keep passing.

A server that your test starts as a child process is not a test run, so it sends events unless its environment has `AGENTCAT_ENABLED=false`. A production app that ships a test framework counts as a test run, and `AGENTCAT_ENABLED=true` turns AgentCat on for it.

### AgentCatSdk.Fake()

To check events in a test, create a fake with `AgentCatSdk.Fake()` before you build the host or call `Track()`. While the fake is current, every tracked server records its events into it and sends nothing:

```csharp theme={null}
using AgentCat;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Client;
using Xunit;

public class AddToolTests
{
    [Fact]
    public async Task The_add_call_is_tracked()
    {
        var cancellationToken = TestContext.Current.CancellationToken;

        // First: in a test run, AgentCat is off until a fake is current
        using var fake = AgentCatSdk.Fake();

        var builder = WebApplication.CreateBuilder();
        builder.WebHost.UseTestServer();
        builder.Services.AddMcpServer()
            .WithHttpTransport(o => o.Stateless = true)
            .WithTools<MyTools>()
            .WithAgentCat("proj_YOUR_PROJECT_ID");

        await using var app = builder.Build();
        app.MapMcp();
        await app.StartAsync(cancellationToken);

        var transport = new HttpClientTransport(
            new HttpClientTransportOptions { Endpoint = new Uri("http://localhost/") },
            app.GetTestClient());
        await using var client = await McpClient.CreateAsync(transport, cancellationToken: cancellationToken);
        await client.CallToolAsync(
            "add",
            new Dictionary<string, object?> { ["a"] = 2, ["b"] = 3 },
            cancellationToken: cancellationToken);

        await fake.AssertTrackedCountAsync(1, cancellationToken);
        await fake.AssertTrackedAsync(e => e.ResourceName == "add", cancellationToken);
    }
}
```

The fake runs your hooks and redaction, then records the event. It also has `AssertNothingTrackedAsync`, `AssertCustomEventPublishedAsync`, `GetEventsAsync`, and `GetLogsAsync`. Each method waits with no time limit, so we recommend passing your test framework's token.

The fake is process-wide, so tests that use one cannot run in parallel (in xUnit, use one collection with `DisableParallelization = true`). A stdio or stateful server that started before the fake stays untracked. Exact schema comparisons see `session_id` and `context`.

## Differences and known limits

* `null` as the project ID reads `AGENTCAT_PROJECT_ID`, and an empty string means no project. In the TypeScript SDK, `null` means no project.
* With `EnableTracing = false`, the C# SDK removes the injected parameters. The TypeScript SDK forwards them to your tool.
* AgentCat cuts each string to 32 KB, each event to 100 KB, and the intent text and error messages to 2,048 characters. Objects keep at most 10 levels and 100 keys or items for each level.
* A tool that declares its own `context` parameter gets no intent parameter. A tool with a top-level `oneOf`, `allOf`, or `anyOf` input schema gets no `session_id` and no `context`.
* In `Microsoft.Extensions.AI.Abstractions` before 10.9.0, tools created concurrently from one method can expose a `RequestContext` parameter named `context`, which AgentCat takes for the tool's own ([dotnet/extensions#7677](https://github.com/dotnet/extensions/pull/7677)). We recommend 10.9.0 or later.
* A server that sets `Handlers.CallToolWithAlternateHandler` (`ModelContextProtocol` 2.x) stays untracked, because the MCP SDK rejects `tools/call` filters with it.
* The redaction hooks run on the delivery workers with no time limit, so a blocking hook holds a worker.

### Exporters

* The first install of the process that adds exporters sets them up for every server. AgentCat ignores the exporters of later installs, but an empty `Exporters` leaves room for a later one.
* When AgentCat retries an event, it sends it to your exporters again, so an exporter can get up to 4 copies.
* An exporter never delays delivery to AgentCat. At most 10 exporter requests run and 1,000 wait, and AgentCat drops the oldest beyond that.
* PostHog gets the `parameters` and the `response` of each call, request headers included.

The [C# SDK README](https://github.com/agentcathq/agentcat-csharp-sdk#readme) has more detail on each option and runtime.


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