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.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. OnModelContextProtocol 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 underparameters.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_idandcontext(andagent_idwithEnableAgentTracking). AgentCat removes these values before your tool runs, also withEnableTracing = 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 instructuredContent, asmcp_session.
Runtimes, delivery, and shutdown
The SDK supportsModelContextProtocol 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 fromIdentify, oranonymous.Warning: Failed to track server - <error>: the server runs untracked.
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 tohttps://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 withAgentCatSdk.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:
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
nullas the project ID readsAGENTCAT_PROJECT_ID, and an empty string means no project. In the TypeScript SDK,nullmeans 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
contextparameter gets no intent parameter. A tool with a top-leveloneOf,allOf, oranyOfinput schema gets nosession_idand nocontext. - In
Microsoft.Extensions.AI.Abstractionsbefore 10.9.0, tools created concurrently from one method can expose aRequestContextparameter namedcontext, which AgentCat takes for the tool’s own (dotnet/extensions#7677). We recommend 10.9.0 or later. - A server that sets
Handlers.CallToolWithAlternateHandler(ModelContextProtocol2.x) stays untracked, because the MCP SDK rejectstools/callfilters 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
Exportersleaves 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
parametersand theresponseof each call, request headers included.