AzureAICommunity.Agent.Middleware.ApprovalMiddleware 1.1.0

dotnet add package AzureAICommunity.Agent.Middleware.ApprovalMiddleware --version 1.1.0
                    
NuGet\Install-Package AzureAICommunity.Agent.Middleware.ApprovalMiddleware -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AzureAICommunity.Agent.Middleware.ApprovalMiddleware" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AzureAICommunity.Agent.Middleware.ApprovalMiddleware" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="AzureAICommunity.Agent.Middleware.ApprovalMiddleware" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AzureAICommunity.Agent.Middleware.ApprovalMiddleware --version 1.1.0
                    
#r "nuget: AzureAICommunity.Agent.Middleware.ApprovalMiddleware, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AzureAICommunity.Agent.Middleware.ApprovalMiddleware@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AzureAICommunity.Agent.Middleware.ApprovalMiddleware&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=AzureAICommunity.Agent.Middleware.ApprovalMiddleware&version=1.1.0
                    
Install as a Cake Tool

<div align="center">

๐Ÿ” AzureAICommunity - Agent - Approval Middleware

Add a human-in-the-loop approval gate to AI agent tool calls with a single method call.

NuGet Version NuGet Downloads License .NET GitHub Repo GitHub Follow YouTube Channel YouTube Subscribers LinkedIn

Getting Started ยท Callback Contract ยท How It Works ยท Contributing

</div>


Overview

AzureAICommunity.Agent.Middleware.ApprovalMiddleware adds an approval gate directly into the AIAgentBuilder function-invocation pipeline. You pass only the tools that need approval โ€” all other tools registered on the agent execute freely without interruption. Before any gated tool executes, your callback is called with the pending FunctionCallContent; you decide whether to approve or deny using any UI: console, desktop dialog, HTTP call to a remote approver, etc. The middleware itself contains no UI code.


โœจ Features

Feature
๐ŸŽฏ Selective gating โ€” only the tools you specify are intercepted; others run freely
๐ŸŒ Gate-all overload โ€” omit approvalTools to intercept every tool on the agent
๐Ÿ”” Simple callback โ€” Func<FunctionCallContent, Task<bool?>> receives the tool name and arguments
๐Ÿ’ฌ LLM-aware denial โ€” when denied, a descriptive message is returned to the model so it can reason about the refusal
โœ๏ธ Custom denial message โ€” optional denialMessageFactory to return per-call denial text to the LLM
๐Ÿ”Œ MEA integration โ€” drops directly into any Microsoft.Agents.AI pipeline via UseApprovalMiddleware()
๐Ÿ–ฅ๏ธ UI agnostic โ€” use any approval UI: console, GUI, HTTP, SignalR, webhooks
๐Ÿ”— Method chaining โ€” returns the builder for fluent composition with other middleware

๐Ÿ“ฆ Installation

dotnet add package AzureAICommunity.Agent.Middleware.ApprovalMiddleware

๐Ÿš€ Quick Start

using AzureAICommunity.Agent.Middleware.ApprovalMiddleware;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

// Read-only tool โ€” no approval needed
var getStatusTool = AIFunctionFactory.Create(GetDeviceStatus);

// Destructive tools โ€” require approval before executing
var turnOnTool  = AIFunctionFactory.Create(TurnOnDevice);
var turnOffTool = AIFunctionFactory.Create(TurnOffDevice);

// All tools are registered on the agent
AIAgent baseAgent = new ChatClientAgent(
    chatClient,
    instructions: "You are a helpful smart-home assistant.",
    tools: [getStatusTool, turnOnTool, turnOffTool]);

// Only the destructive tools are passed to UseApprovalMiddleware.
// GetDeviceStatus is intentionally omitted โ€” it bypasses the approval gate.
AIAgent agent = new AIAgentBuilder(baseAgent)
    .UseApprovalMiddleware(
        [turnOnTool, turnOffTool],
        async fc =>
        {
            // Return true          โ†’ approved
            // Return false or null โ†’ denied, a denial message is returned to the LLM
            Console.Write($"Allow '{fc.Name}'? (y/n): ");
            bool approved = Console.ReadLine()?.Trim() == "y";
            return (bool?)approved;
        },
        denialMessageFactory: fc => fc.Name switch
        {
            "TurnOnDevice"  => $"User refused to turn ON '{fc.Arguments?["deviceName"]}'. Do not retry.",
            "TurnOffDevice" => $"User refused to turn OFF '{fc.Arguments?["deviceName"]}'. Do not retry.",
            _               => $"User refused '{fc.Name}'. Do not retry this action."
        })
    .Build();

// Gate ALL tools โ€” no list needed
AIAgent agentGateAll = new AIAgentBuilder(baseAgent)
    .UseApprovalMiddleware(async fc =>
    {
        Console.Write($"Allow '{fc.Name}'? (y/n): ");
        return (bool?)(Console.ReadLine()?.Trim() == "y");
    })
    .Build();

AgentResponse response = await agent.RunAsync("Check the lights, then turn them on.");
Console.WriteLine(response);

๐Ÿ”” Callback Contract

The approvalCallback receives a FunctionCallContent with the tool name and arguments.

Return value Effect
true Approved โ€” the tool executes and its real result is returned to the LLM
false or null Denied โ€” a denial message is returned to the LLM as the tool result so it can reason about the refusal

The callback is Func<FunctionCallContent, Task<bool?>>, so it can be async and use any UI or remote call.


โš™๏ธ How It Works

LLM decides to call a tool
    โ””โ”€โ–บ FunctionInvocationContext intercepted by ApprovalMiddleware
            โ”‚
            โ”œโ”€ gate-all mode OR tool name in approvalTools?
            โ”‚       โ”œโ”€ YES โ†’ approvalCallback(FunctionCallContent) called
            โ”‚       โ”‚           โ”œโ”€ returns true         โ†’ next(context, ct)  โ† tool executes normally
            โ”‚       โ”‚           โ””โ”€ returns false/null   โ†’ denialMessageFactory(fc) โ†’ text returned to LLM
            โ”‚       โ””โ”€ NO  โ†’ next(context, ct)    โ† tool executes freely, no approval prompt
            โ”‚
            โ””โ”€ agent continues with the result

๐Ÿ“‹ Type Reference

Type Description
ApprovalMiddlewareExtensions Provides the UseApprovalMiddleware extension method on AIAgentBuilder
FunctionCallContent Passed to the callback โ€” contains Name and Arguments of the pending tool call

Extension methods

// Overload 1 โ€” gate specific tools
builder.UseApprovalMiddleware(
    IEnumerable<AITool> approvalTools,                        // Tools that require approval
    Func<FunctionCallContent, Task<bool?>> approvalCallback,  // Approval decision callback
    Func<FunctionCallContent, string>? denialMessageFactory = null  // Optional: custom denial text
);

// Overload 2 โ€” gate ALL tools
builder.UseApprovalMiddleware(
    Func<FunctionCallContent, Task<bool?>> approvalCallback,  // Approval decision callback
    Func<FunctionCallContent, string>? denialMessageFactory = null  // Optional: custom denial text
);
denialMessageFactory

Optional factory that produces the text returned to the LLM as the tool result when a call is denied. Receives the FunctionCallContent of the denied call. If omitted, defaults to "User denied the call to '{name}'.".

denialMessageFactory: fc => fc.Name switch
{
    "TurnOnDevice"  => $"User refused to turn ON '{fc.Arguments?["deviceName"]}'. Do not retry.",
    "TurnOffDevice" => $"User refused to turn OFF '{fc.Arguments?["deviceName"]}'. Do not retry.",
    _               => $"User refused '{fc.Name}'. Do not retry this action."
}

๐Ÿค Contributing

Contributions are welcome! Please open an issue to discuss what you'd like to change before submitting a pull request.

๐Ÿ“ Repository: https://github.com/rvinothrajendran/AgentFramework

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes (git commit -m 'Add my feature')
  4. Push to the branch (git push origin feature/my-feature)
  5. Open a Pull Request

๐Ÿ‘ค Author

Built and maintained by Vinoth Rajendran.


๐Ÿ“„ License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 92 5/1/2026
1.0.0 85 5/1/2026