A prompt template turns named arguments into reusable messages that a client can retrieve for a conversation.
dotnet add package RecursiveAI.OneModelContextRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
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, 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
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:
Prompt: review user: Review error handling. Keep the feedback concise.
Variations
Set the optional tone. Replace the dictionary in the GetPromptAsync call with:
new Dictionary<string, string> { ["topic"] = "error handling", ["tone"] = "detailed" }
The message becomes:
user: Review error handling. Keep the feedback detailed.Related recipes
- Serve resource URI templates in C#
- Report server tool progress in C#
- Cancel a long-running server tool call in C#
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 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 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: 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.