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
ModelContextProtocol1.0.0 or later, on the 1.x or 2.x line. HTTP servers also useModelContextProtocol.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
Configurestep of the server options, so it sees the tools and handlers thatWithTools,WithCallToolHandlerand the other builder methods add, before or after it. - The project ID.
null, or no argument, reads it from theAGENTCAT_PROJECT_IDenvironment 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
AgentCatOptionsobject (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
ArgumentNullExceptionfor anullbuilder. 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 itsApiBaseUrlapplies, because all servers of a process share one event queue. IAgentCat.WithAgentCat()also registersIAgentCatas 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 atools/listserves thetools/callof a later request. - A fresh process. When a
tools/callreaches 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, thenToolCollection), with a 5-second limit, to learn which parameters it added. - Sessions. AgentCat adds a
session_idparameter to each tool: the agent sendsstarton its first call, gets ases_ID in the result, and sends that ID on its later calls. See Stateless Servers. - The default transport. On
ModelContextProtocol2.x,WithHttpTransport()without options is already stateless. On 1.x it is stateful.
Stateful HTTP
A stateful server needs the same call: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 addsWithAgentCat() in the same way:
Request headers and URL
On an ASP.NET Core server, AgentCat copies the HTTP request of each tool call into its event, underparameters.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:
authorizationandcookiego out too, and so doesx-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,(;forcookie). - 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’sHttpContextAccessorwhen your app has none.AgentCatSdk.Track()registers nothing, so it reads the request only when your app callsAddHttpContextAccessor(). - Legacy SSE. On the legacy SSE transport, the request is the session’s
GET /sse, not thePOSTthat 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 norequestInfo, andcall.Headersis empty.
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 theMcpServer that it receives:
- The server. In stateless HTTP, the
McpServeris 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
SessionIdin itsCustomEventDatahas no session. The tool cannot read AgentCat’ssession_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
ArgumentExceptionfor 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.Hosting8 or later runs the flush after the host stops. Older hosts and the legacyWebHostrely on the process-exit handler.- A service provider without a host runs no flush. If you build and dispose a service provider yourself, call
FlushAsyncorShutdownAsyncbefore you dispose it. DisableShutdownHandlers = trueon the first install of the process turns off the process-exit handler. The flush after the host stops stays on.
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.