Skip to main content
This page describes the AgentCat NuGet package, version 1.x. For the first setup, see C# setup.

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.
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: 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. 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 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. 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:
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). 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 has more detail on each option and runtime.