---
title: "Serve prompt templates in C#"
url: https://recursiveai.org/onemodelcontext/cookbook/serve-prompt-templates/
description: "Serve prompt templates in C# with OneModelContext. Run a complete .NET server and client example with no external service."
version: "1.0.1"
---

# Serve prompt templates 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/serve-prompt-templates/serve-prompt-templates.zip)

A prompt template turns named arguments into reusable messages that a client can retrieve for a conversation.

```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 Prompts());

public partial class Prompts
{
    [Prompt("review")]
    public string Review(string topic) =>
        $"Review {topic}.";
}
```

Serves `review`; requesting topic `error handling` returns the user message `Review error handling.`.

Unzip [the example](https://recursiveai.org/onemodelcontext/cookbook/serve-prompt-templates/serve-prompt-templates.zip), then `dotnet run`.

The download starts a child server, lists the available prompt, and retrieves its messages with a required topic and an optional tone. No model call or API key is involved. The package’s source generator supplies registration for the `partial` class. A missing required argument makes `GetPromptAsync` throw `ProtocolException` with invalid-params code `-32602`.

## The full program

```csharp
using RecursiveAI.OneModelContext;
using RecursiveAI.OneModelContext.Protocol;
using System.Reflection;

if (args.Contains("--stdio"))
{
    await ModelContext.ServeAsync(new Demo());
    return;
}
var assembly = Assembly.GetExecutingAssembly().Location;
await using var client = await ModelContext.ConnectAsync($"dotnet \"{assembly}\" --stdio");
foreach (var prompt in await client.ListPromptsAsync())
    Console.WriteLine("Prompt: " + prompt.Name);
var result = await client.GetPromptAsync("review",
    new Dictionary<string, string> { ["topic"] = "error handling" });
foreach (var message in result.Messages)
    Console.WriteLine($"{message.Role}: {((TextContent)message.Content).Text}");

public partial class Demo
{
    [Prompt("review")]
    public string Review(string topic, string tone = "concise") =>
        $"Review {topic}. Keep the feedback {tone}.";
}
```

`dotnet run` prints:

```text
Prompt: review
user: Review error handling. Keep the feedback concise.
```

## Variations

Set the optional tone. Replace the dictionary in the `GetPromptAsync` call with:

```csharp
new Dictionary<string, string>
{
    ["topic"] = "error handling",
    ["tone"] = "detailed"
}
```

The message becomes:

```text
user: Review error handling. Keep the feedback detailed.
```

## Related recipes

- [Serve resource URI templates in C#](https://recursiveai.org/onemodelcontext/cookbook/serve-resource-templates/)
- [Report server tool progress in C#](https://recursiveai.org/onemodelcontext/cookbook/report-tool-progress/)
- [Cancel a long-running server tool call in C#](https://recursiveai.org/onemodelcontext/cookbook/cancel-long-running-tools/)

---

## The short answer

Declare a `[Prompt]` method on a non-static `partial` class and serve an instance with OneModelContext. A method returning a string becomes one user message. Its parameters become named prompt arguments. The client retrieves the messages through `GetPromptAsync`, passing a dictionary of argument names and string values.

The example is a prompt server, not a text-generation service. It returns instructions for a conversation. It does not send those instructions to a model or produce the review itself. This makes the downloadable example runnable without credentials or a model provider.

## Can you provide an example of a server prompt?

The `review` prompt asks for a topic and optionally a tone. The complete program passes `error handling` for `topic` and omits `tone`, so the method uses its C# default, `concise`. The output includes `user:` because a returned string becomes a user-role message, not an assistant answer.

The snippet at the top shows the smallest version, with only the required topic. The full program adds the default and a client so you can inspect the result. Both use the generator included in the package. The containing class must remain `partial`; `[Prompt("review")]` gives the prompt a stable public name independent of the C# method name.

The [protocol prompt specification](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts) separates listing from retrieval. A listing describes a prompt and its arguments. Retrieval fills in those arguments and returns messages. Calling `ListPromptsAsync` alone does not execute the template or return a completed conversation.

## How do I use prompt arguments in C#?

Match the dictionary keys to the method’s parameter names. Prompt arguments travel as text. OneModelContext’s [documented mapping](https://recursiveai.org/onemodelcontext/docs/tool-schemas/#resources-and-prompts) accepts strings and types that parse from strings, including numbers, Booleans, GUIDs, dates and times. Parameters are required unless nullable or given a default value. A value that cannot be parsed produces an invalid-params error.

Choose defaults for arguments that have a sensible omitted behavior. In this example the tone can default, but the topic cannot: an empty choice of what to review is not the same as an omitted style preference. The variation sends both keys to show how a caller overrides a default without changing the server.

A string return is enough for a single instruction. When you need roles or several messages, the documented return choices also include `PromptMessage` sequences and `GetPromptResult`. Keep roles explicit when constructing those messages; do not concatenate role labels into one string and expect the client to interpret them as separate turns.

## What is the difference between a prompt and a tool?

A prompt supplies reusable conversation messages. A tool executes an operation and returns its result. The [specification describes prompts as user-controlled](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#user-interaction-model): hosts may expose them through commands or other selection interfaces. It does not require any particular interface, so serving `review` does not guarantee that every client displays a slash command.

Use a prompt for a reusable review instruction that a user selects. Use a tool if the caller needs an operation to perform a review and return findings. Retrieving prompt messages is not a substitute for invoking a model. The client owns the next step: it may show the messages, edit them, or include them in a conversation according to its own workflow.
