---
title: "Publish a Native AOT tool server in C#"
url: https://recursiveai.org/onemodelcontext/cookbook/native-aot-server/
description: "Build a Model Context Protocol® server as a native executable with generated tool registration, .NET 10 and OneModelContext."
version: "1.0.1"
---

# Publish a Native AOT tool server in C#

**OneModelContext** is a free, open-source .NET SDK for the Model Context Protocol® from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneModelContext) · [GitHub](https://github.com/recursive-ai-oss/onemodelcontext-csharp) · [Docs](https://recursiveai.org/onemodelcontext/docs/) · [Download example (.zip)](https://recursiveai.org/onemodelcontext/cookbook/native-aot-server/native-aot-server.zip)

Compile a Model Context Protocol® (MCP) tool server to a platform-specific native executable.

```shell
dotnet add package RecursiveAI.OneModelContext
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using RecursiveAI.OneModelContext;
await ModelContext.ServeAsync(new Calculator());

public partial class Calculator
{
    [Tool] public int Add(int a, int b) => a + b;
}
```

Publish the downloaded project for Linux x64, then run the native executable:

```shell
dotnet publish -c Release -r linux-x64 -o publish
./publish/project
```

The binary is named after the project: the download has `project.csproj`, so its executable is `project` (`project.exe` on Windows). To serve an external client, run `./publish/project --stdio`; waiting silently for protocol input is normal.

Unzip [the example](https://recursiveai.org/onemodelcontext/cookbook/native-aot-server/native-aot-server.zip), then `dotnet run`.

The snippet at the top serves over standard input and output, with no application text. The core NuGet package includes the generator; it needs a `partial` class.

The download includes these lines inside the project file’s `<PropertyGroup>`:

```xml
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
```

`dotnet run` exercises the tool through an in-memory client and exits. Add `--stdio` to serve an external client instead. A native publish also requires the platform’s [Native AOT build prerequisites](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/#prerequisites).

## The full program

```csharp
using System.IO.Pipelines;
using RecursiveAI.OneModelContext;

if (args.Contains("--stdio"))
{
    await ModelContext.ServeAsync(new ExampleTools());
    return;
}
var toServer = new Pipe();
var toClient = new Pipe();
var server = new ContextServer("example", "1.0.0");
new ExampleTools().AddTo(server);
using var stop = new CancellationTokenSource();
var serving = server.RunAsync(ContextTransport.Stream(
    toServer.Reader.AsStream(), toClient.Writer.AsStream()), stop.Token);
await using var client = await ModelContext.ConnectAsync(
    ContextTransport.Stream(toClient.Reader.AsStream(), toServer.Writer.AsStream()));
Console.WriteLine((await client.CallToolAsync("add", System.Text.Json.JsonDocument.Parse("""{"a":2,"b":40}""").RootElement)).Text);
await stop.CancelAsync();
try { await serving; }
catch (OperationCanceledException) { }

public partial class ExampleTools
{
    [Tool(ReadOnly = true)]
    public int Add(int a, int b) => a + b;
}
```

`dotnet run` prints:

```text
42
```

## Variations

The publish command above builds a native version of the downloaded program. Running `./publish/project` prints:

```text
42
```

To wait for an external stdio client, use `./publish/project --stdio` instead. Keep standard output reserved for the protocol in this mode.

## Related recipes

- [Build a server in C#](https://recursiveai.org/onemodelcontext/cookbook/build-a-server-in-csharp/)
- [Connect to a server](https://recursiveai.org/onemodelcontext/cookbook/connect-to-a-server/)
- [Require OAuth sign-in on a server](https://recursiveai.org/onemodelcontext/cookbook/require-sign-in-on-a-server/)

---

## The short answer

To publish an MCP server with Native AOT (Model Context Protocol®), use OneModelContext’s generated `[Tool]` registration and enable `PublishAot` in a .NET 10 project. Publish for the operating system and architecture where the server will run. The downloadable source works both as a native server and as a small client/server demonstration.

## How do I build a Native AOT server in C#?

Start with a console application and install the core package. Mark the tool class `partial`, then add `[Tool]` to its methods. The generator supplies schema and invocation code at build time. The calculator takes two integers and returns their sum; it has no user-defined types requiring additional serialization metadata.

The project file in the download contains the AOT property. `dotnet run` still builds and runs managed code; it is not the command that produces the native deployment artifact. Use `dotnet publish`, as shown above, to produce the native executable. Running that published executable exercises the same protocol exchange and returns `42`.

For an external consuming application, select the `--stdio` branch. The native binary then waits for protocol input instead of starting the demonstration client. Configure the consumer to launch that binary with the argument. A silent console in this mode is expected: application text would corrupt the protocol stream.

## What changes for Native AOT tool registration?

The generated path and reflection quick path are not interchangeable for deployment. OneModelContext’s [tool-schema documentation](https://recursiveai.org/onemodelcontext/docs/tool-schemas/) documents generated invokers that use source-generated System.Text.Json metadata. A missing context for a user-defined record or collection is a build diagnostic, not a runtime switch to reflection.

Keep tool and serializer classes partial when adding more complex signatures. A record result needs a `JsonSerializerContext` that lists it. This is particularly important when code works during development but would otherwise discover types dynamically at runtime. The [structured-output recipe](https://recursiveai.org/onemodelcontext/cookbook/return-structured-output/) shows the complete record-and-context arrangement.

The download uses explicit JSON arguments for its client call. That avoids making its demonstration depend on runtime discovery of an anonymous argument type. It also keeps the native example focused on generated server tools, not on a separate model integration.

## How do I publish for another operating system?

Choose a runtime identifier appropriate to the target, and build in an environment supported by the [.NET Native AOT deployment guide](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/). The command shown here was run for Linux x64. It is not a universal executable for every platform. Microsoft’s guide also lists the native compiler and system prerequisites; installing the .NET SDK alone is not the whole native toolchain.

The project enables invariant globalization for this integer-only example. That is an application choice, not a requirement that every tool server ignore culture. If your tools format dates, compare localized text or use culture-specific rules, review that setting alongside the platform’s globalization requirements before reusing it.

Keep deployment and transport decisions separate. Native compilation changes the artifact you ship. Stdio still means the client starts a process and owns its input and output streams. A native executable does not turn that process into a remotely reachable HTTP service.
