Skip to main content
This page covers servers that dependency injection builds with AddMcpServer() from the official C# MCP SDK, ModelContextProtocol: HTTP servers on ASP.NET Core, stateless or stateful, and stdio servers on the .NET generic host. For a server that you create yourself with McpServer.Create, see McpServer.Create.

Requirements

  • ModelContextProtocol 1.0.0 or later, on the 1.x or 2.x line. HTTP servers also use ModelContextProtocol.AspNetCore.
  • .NET 8 or later. The package also targets .NET Standard 2.0, for stdio servers on .NET Framework 4.7.2 or later.

Add WithAgentCat() to the server builder

WithAgentCat() is an extension method of the IMcpServerBuilder that AddMcpServer() returns, in the Microsoft.Extensions.DependencyInjection namespace like the MCP SDK’s own builder methods:
  • Anywhere in the chain. AgentCat installs after every Configure step of the server options, so it sees the tools and handlers that WithTools, WithCallToolHandler and the other builder methods add, before or after it.
  • The project ID. null, or no argument, reads it from the AGENTCAT_PROJECT_ID environment variable. An empty string, or no project ID at all, tracks the server but sends no events to AgentCat: your exporters still get them.
  • Options. The second argument sets the properties of an AgentCatOptions object (see Options). AgentCat calls it once, when the first server options are built, and reads the options then: later changes have no effect.
  • It never throws, except ArgumentNullException for a null builder. When AgentCat cannot track the server, the server runs untracked.
  • Once per server. A second WithAgentCat() call on the same server installs nothing, and the first call’s project ID and options stay. Only its ApiBaseUrl applies, because all servers of a process share one event queue.
  • IAgentCat. WithAgentCat() also registers IAgentCat as a singleton, unless your app registered one already (see IAgentCat and custom events).

Stateless HTTP

Stateless HTTP is the recommended transport of the MCP specification, and the primary path of the C# SDK. A complete server:
  • A new server for every request. In stateless mode the MCP SDK builds new server options and a new server for every POST. AgentCat installs on each of them, and they all share one tracker per WithAgentCat() call, so what AgentCat learns from a tools/list serves the tools/call of a later request.
  • A fresh process. When a tools/call reaches a process that has not listed its tools yet, for example a new instance behind a load balancer, AgentCat lists the tools itself (your list handler, then ToolCollection), with a 5-second limit, to learn which parameters it added.
  • Sessions. AgentCat adds a session_id parameter to each tool: the agent sends start on its first call, gets a ses_ ID in the result, and sends that ID on its later calls. See Stateless Servers.
  • The default transport. On ModelContextProtocol 2.x, WithHttpTransport() without options is already stateless. On 1.x it is stateful.

Stateful HTTP

A stateful server needs the same call:
AgentCat groups the calls of a task by its session_id parameter here too, not by the transport’s Mcp-Session-Id. Hooks can read that one as call.TransportSessionId.

stdio on the generic host

A stdio server on the generic host adds WithAgentCat() in the same way:
AgentCat writes nothing to stdout or stderr. When the client closes the input, the host stops, and AgentCat sends the queued events (see Shutdown).

Request headers and URL

On an ASP.NET Core server, AgentCat copies the HTTP request of each tool call into its event, under parameters.extra.requestInfo: headers holds the request headers, and url the full URL of the request. Your hooks get the same headers in call.Headers.
  • Every header, as received. AgentCat filters no header: authorization and cookie go out too, and so does x-forwarded-for, like any other header. AgentCat records no IP address of its own. Header names are lower-case, and a header sent several times has one value, joined with , (; for cookie).
  • The URL keeps its query string, which can carry an API key or other credentials.
  • How AgentCat reads the request. WithAgentCat() registers ASP.NET Core’s HttpContextAccessor when your app has none. AgentCatSdk.Track() registers nothing, so it reads the request only when your app calls AddHttpContextAccessor().
  • Legacy SSE. On the legacy SSE transport, the request is the session’s GET /sse, not the POST that carried the call, so the headers and the URL belong to the session.
  • No request. On stdio servers, on .NET Framework, with PerSessionExecutionContext = true, and for a tool call whose client opts in to MCP 2.x tasks, AgentCat has no request to read. The event then has no requestInfo, and call.Headers is empty.
To keep credentials out of your events, remove them in a RedactEvent hook. The hook also gets custom events, whose parameters can have any shape, and an exception in the hook drops the event, so test each level by type. This hook removes the authorization and cookie headers and the query string of the URL:

IAgentCat and custom events

IAgentCat has the methods of the static AgentCatSdk class except Track() and Fake(): the two forms of PublishCustomEventAsync, FlushAsync and ShutdownAsync. Inject it where you publish custom events. WithAgentCat() registers it unless your app registered an IAgentCat already. To test what your server tracks, use AgentCatSdk.Fake(): the fake records the tool-call events and the custom events.

Custom events from a tool

A tool publishes a custom event for its server with the McpServer that it receives:
  • The server. In stateless HTTP, the McpServer is a new object for every request. AgentCat finds the tracked server through its options, so any of them works.
  • The project ID must not be empty, but the event goes to the project that the server was tracked with.
  • The session. An event without a SessionId in its CustomEventData has no session. The tool cannot read AgentCat’s session_id: AgentCat removes it from the arguments before your tool runs.
  • The task completes when AgentCat has queued the event, not when it has sent it. It faults with an ArgumentException for an empty project ID or a server that AgentCat does not track, unless AgentCat was off when a server was set up in this process: it then logs a warning instead. Other failures never reach your code.

Shutdown

AgentCat sends events from a process-wide queue in the background, and you call nothing at shutdown. When the host stops, AgentCat waits until every hosted service has stopped, the MCP stdio session and Kestrel included. Then it sends the queued events for at most 5 seconds, and its last diagnostics batch for at most 1 second. It also sends the queued events at process exit.
  • Allow 6 seconds. AgentCat ignores the shutdown token of the host, so a stop can take up to 6 seconds longer than HostOptions.ShutdownTimeout. Set the grace period of your container above that.
  • Microsoft.Extensions.Hosting 8 or later runs the flush after the host stops. Older hosts and the legacy WebHost rely on the process-exit handler.
  • A service provider without a host runs no flush. If you build and dispose a service provider yourself, call FlushAsync or ShutdownAsync before you dispose it.
  • DisableShutdownHandlers = true on the first install of the process turns off the process-exit handler. The flush after the host stops stays on.
Shutdown has the details: the stop budget that all stop paths share, FlushAsync and ShutdownAsync, and the cases where nothing runs, such as a stdio server that the official C# client kills.

Options

WithAgentCat() and AgentCatSdk.Track() take the same AgentCatOptions. The four tool-call hooks (ResolveSessionId, Identify, EventTags and EventProperties) are Func<AgentCatToolCall, CancellationToken, ValueTask<T>>, and the Type column names their T. AgentCat starts the four tool-call hooks when the call starts, so they run alongside your tool, and it waits at most 30 seconds for each before it sends the event without that result. A hook that throws changes nothing but its own result. C# differences and limits lists what to know about hooks and the server’s filters.