---
title: "Ask the user to confirm a tool call in C#"
url: https://recursiveai.org/onemodelcontext/cookbook/ask-the-user-to-confirm/
description: "Ask for yes/no input during a Model Context Protocol® tool call. Includes a runnable C# client that approves or declines without an account."
version: "1.0.1"
---

# Ask the user to confirm a tool call 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/ask-the-user-to-confirm/ask-the-user-to-confirm.zip)

Model Context Protocol® (MCP) elicitation lets a tool ask the user for input. Here it asks for confirmation before continuing.

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

public partial class DraftTools
{
    [Tool]
    public async Task<string> Publish(
        RequestContext ctx)
        => await ctx.ConfirmAsync("Publish the draft?")
            ? "Published." : "Canceled.";
}
```

Unzip [the example](https://recursiveai.org/onemodelcontext/cookbook/ask-the-user-to-confirm/ask-the-user-to-confirm.zip), then `dotnet run`.

The client passes `onConfirm` to `ModelContext.ConnectAsync` to answer the snippet’s yes/no question. The snippet at the top waits for a client; it prints no application text. The client must support elicitation. The core package generates registration code for the `partial` tool class.

The download runs a server and client inside the same program, passing messages directly in memory. Its client prints the question and automatically approves it; it does not display a real consent dialog or publish a file. In your application, the `onConfirm` callback should show the question and return the user’s answer. If the client did not declare elicitation support, the request fails with the missing capability rather than silently approving.

## The full program

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

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()), onConfirm: (question, _) =>
    { Console.WriteLine(question); return ValueTask.FromResult(!args.Contains("--decline")); });
Console.WriteLine((await client.CallToolAsync("publish")).Text);
await stop.CancelAsync();
try { await serving; }
catch (OperationCanceledException) { }

public partial class ExampleTools
{
    [Tool]
    public async Task<string> Publish(RequestContext context)
        => await context.ConfirmAsync("Publish the draft?")
            ? "Published." : "Canceled.";
}
```

`dotnet run` prints:

```text
Publish the draft?
Published.
```

## Variations

Decline instead of approving. Run the same download with:

```shell
dotnet run -- --decline
```

```text
Publish the draft?
Canceled.
```

The callback returns `false`; the tool’s conditional expression selects `Canceled.`. No exception is needed for an ordinary refusal.

## 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

For MCP elicitation in C# (Model Context Protocol®), put a `RequestContext` parameter on the tool and await `ConfirmAsync`. On the client, supply `onConfirm` when connecting. The callback receives the question and returns a Boolean. The library handles the protocol exchange; your application handles the user interface.

## How do I use elicitation in a C# tool?

The tool method in the snippet at the top is the complete server-side decision. `RequestContext` is injected into the call and does not become an input property that a model must supply. `ConfirmAsync` returns whether the client approved the question. The tool then returns either success text or cancellation text.

Keep the continuation explicit. Asking a question is not the same as performing an action. This example returns a string; to publish a real draft, put that operation after an affirmative answer, not before the question. A refusal is a normal branch of application logic, not a malformed protocol request.

The client-side callback in the full program is deliberately automatic so the download finishes unattended. Replace that callback with your host’s dialog or command-line interaction when a person is using the application. Setting the callback also advertises the client’s ability to handle confirmation. It does not make another client support the feature.

## How does a C# client answer an elicitation request?

Pass the callback to `ModelContext.ConnectAsync`, as the full program does. The callback gets the message and cancellation token. It can asynchronously wait for your UI and then return `true` or `false`. Keep that wait connected to the cancellation token so a disconnected caller does not leave a dialog waiting indefinitely.

The download demonstrates both outcomes through the same tool. The default run approves; `--decline` refuses. Those messages are application text chosen by the tool, not special protocol status strings. A client that lacks the capability cannot complete this flow: the [multi-round-trip guide](https://recursiveai.org/onemodelcontext/docs/multi-round-trip/) documents the capability error.

## Does elicitation run a tool more than once?

On protocol revision 2026-07-28, yes. When an answer is missing, the server returns an input request and protected request state. The client collects the answer and retries; the handler runs again from the beginning. Code before the ask must therefore be safe to repeat. Reads are suitable there; appending a file, sending a message or charging a card is not.

On 2025-11-25 and earlier revisions, the ask is a request over the held connection and the handler awaits its answer without replay. Code that respects the replay rule works in both cases. Put values that must survive the retry in `ctx.State`, as described in the same guide; do not treat client-held request state as an authorization decision of your own.

A yes/no question is the narrow case. OneModelContext also documents `ElicitAsync<T>` for forms and `ElicitUrlAsync` for URL interactions. Use confirmation when the only answer you need is approval, rather than inventing a form just to carry a Boolean.
