> ## 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#: McpServer.Create

> Track a C# MCP server that you create yourself with McpServer.Create: AgentCatSdk.Track() before Create, custom events, and delivery at shutdown.

When you create the server yourself with `McpServer.Create`, without dependency injection, pass its `McpServerOptions` to `AgentCatSdk.Track()` before you call `McpServer.Create`. Servers that `AddMcpServer()` builds, which includes every ASP.NET Core HTTP server, use [`WithAgentCat()`](https://docs.agentcat.com/sdk/csharp/aspnet-core) instead.

The requirements are those of [ASP.NET Core and the generic host](https://docs.agentcat.com/sdk/csharp/aspnet-core.md#requirements).

## Track the options before `McpServer.Create`

```csharp theme={null}
using AgentCat;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

var options = new McpServerOptions
{
    ServerInfo = new Implementation { Name = "echo-mcp", Version = "1.0.0" },
    ToolCollection = [McpServerTool.Create((string message) => $"Echo: {message}", new() { Name = "echo" })],
};

// Track the options BEFORE McpServer.Create
AgentCatSdk.Track(options, "proj_YOUR_PROJECT_ID", new AgentCatOptions
{
    EnableAgentTracking = true,
});

await using (var server = McpServer.Create(new StdioServerTransport("echo-mcp"), options))
{
    await server.RunAsync();
}
```

* **Before `McpServer.Create`.** The server reads the filters of its options when it is created, so a `Track()` call after `McpServer.Create` does not reach that server.
* **The project ID.** `null` 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](https://docs.agentcat.com/integrations.md) still get them.
* **Options.** The third argument is an `AgentCatOptions` object with the [same properties](https://docs.agentcat.com/sdk/csharp/aspnet-core.md#options) as for `WithAgentCat()`. AgentCat reads it once, during the call: later changes have no effect.
* **It never throws.** `Track()` returns the same `McpServerOptions`. When AgentCat cannot track the server, it returns them unchanged and the server runs untracked.
* **Tracking again changes nothing.** A second `Track()` on the same options installs nothing, and the first call's project ID and options stay.

## Custom events

Pass the `McpServer` that `McpServer.Create` returned, or, in a tool, the `McpServer` that the tool receives:

```csharp theme={null}
using System.Text.Json.Nodes;
using AgentCat;
using ModelContextProtocol.Server;

var upgrade = McpServerTool.Create(
    async (McpServer server, string plan) =>
    {
        await AgentCatSdk.PublishCustomEventAsync(server, "proj_YOUR_PROJECT_ID", new CustomEventData
        {
            ResourceName = "subscription_upgraded",
            Parameters = new JsonObject { ["plan"] = plan },
        });

        return $"Upgraded to {plan}";
    },
    new() { Name = "upgrade", Description = "Upgrades the subscription." });
```

The project ID must not be empty, but the event goes to the project that the server was tracked with. An event without a `SessionId` in its `CustomEventData` has no session. [Custom Events](https://docs.agentcat.com/sdk/custom-events.md) has the fields and the session-string form.

## Shutdown

AgentCat sends events from a process-wide queue in the background, and you call nothing at shutdown. `Track()` sends the queued events when the process ends: at process exit, and, on .NET 8 and later, on SIGTERM and SIGINT. A stop sends the events for at most 5 seconds, then the last diagnostics batch for at most 1 second.

* **Allow 6 seconds.** Set the grace period of your container above the time that your server needs to stop, plus 6 seconds.
* **The signal handlers never cancel the signal**, so the process ends with its usual exit code. They do nothing when a .NET host in the same process cancelled the signal first.
* **To wait for the events in your own code**, call `await AgentCatSdk.FlushAsync(TimeSpan.FromSeconds(5))`. It returns `true` when nothing is left to send. `AgentCatSdk.ShutdownAsync()` also closes the queue: call it once, when your own code ends the process.
* **`DisableShutdownHandlers = true`** on the first install of the process turns these handlers off. Then call `FlushAsync` or `ShutdownAsync` before the process ends, or the queued events are lost.

A stdio server that the official C# MCP client started gets killed when the client is disposed, so no handler runs. [Shutdown](https://docs.agentcat.com/sdk/csharp/operations.md#shutdown) has the details: the stop budget, the order of signal handlers on .NET 8 and .NET 10, and .NET Framework.


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