From ag-ui-dotnet
Exposes server-side backend tools for AG-UI agents using the .NET SDK. Define AIFunctions, register on IChatClient, and let the model call C# functions running on the server.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ag-ui-dotnet:agui-dotnet-server-toolsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Goal: let the model call a C# function that runs on your server, with the server executing the call and returning the result so the run continues to a final answer.
Goal: let the model call a C# function that runs on your server, with the server executing the call and returning the result so the run continues to a final answer.
A server tool is an ordinary Microsoft.Extensions.AI AIFunction registered on the server's IChatClient. The FunctionInvokingChatClient executes it inside the run; the client sends no tools and needs no tool code, and receives the final answer. Tool-call and result events still cross the wire — the client just never executes anything.
dotnet add package AGUI.Server
dotnet add package AGUI.Formatting
Microsoft.Extensions.AI supplies AIFunctionFactory, AddChatClient, and UseFunctionInvocation. Run dotnet package search AGUI.Server --exact-match for the current version.
Create the function with AIFunctionFactory.Create, add it to the chat client's tools, and enable function invocation:
using System.ComponentModel;
using Microsoft.Extensions.AI;
[Description("Search for restaurants in a location.")]
static RestaurantSearchResponse SearchRestaurants(
[Description("Where to search and what cuisine.")] RestaurantSearchRequest request)
{
// ... real lookup ...
}
var builder = WebApplication.CreateBuilder(args);
var searchRestaurants = AIFunctionFactory.Create(
SearchRestaurants,
serializerOptions: SampleJsonSerializerContext.Default.Options);
builder.Services.AddChatClient(
new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsIChatClient())
.ConfigureOptions(o => (o.Tools ??= []).Add(searchRestaurants))
.UseFunctionInvocation(fic => fic.TerminateOnUnknownCalls = true);
The [Description] attributes become the tool and parameter schema the model sees. Any Microsoft.Extensions.AI provider works in place of Azure OpenAI.
When a tool takes or returns a complex type (anything beyond primitives), its schema and (de)serialization must be source-generated, not reflection-based. Put the parameter and result types in a JsonSerializerContext, register it on the host's JSON options, and pass it to AIFunctionFactory.Create:
[JsonSerializable(typeof(RestaurantSearchRequest))]
[JsonSerializable(typeof(RestaurantSearchResponse))]
internal sealed partial class SampleJsonSerializerContext : JsonSerializerContext;
builder.Services.ConfigureHttpJsonOptions(o =>
o.SerializerOptions.TypeInfoResolverChain.Add(SampleJsonSerializerContext.Default));
var searchRestaurants = AIFunctionFactory.Create(
SearchRestaurants,
serializerOptions: SampleJsonSerializerContext.Default.Options);
The same context is registered on the host (so the wire RunAgentInput round-trips the types) and passed to the function (so its argument binding uses source-gen).
The endpoint streams the registered IChatClient — function calls are resolved server-side inside the stream before any text reaches the client:
using AGUI.Abstractions;
using AGUI.Formatting;
using AGUI.Server;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Options;
using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions;
builder.Services.AddSingleton<IAGUIEventStreamFormatter, SseEventStreamFormatter>();
var app = builder.Build();
app.MapPost("/", async (
[FromBody] RunAgentInput input,
IChatClient chatClient,
IAGUIEventStreamFormatter formatter,
IOptions<JsonOptions> jsonOptions,
HttpContext http,
CancellationToken ct) =>
{
var ctx = input.ToChatRequestContext(jsonOptions.Value.SerializerOptions);
var updates = chatClient.GetStreamingResponseAsync(ctx.Messages, ctx.ChatOptions, ct);
var events = updates.AsAGUIEventStreamAsync(ctx, ct);
http.Response.ContentType = formatter.MediaType;
http.Response.Headers.CacheControl = "no-cache";
await formatter.WriteAsync(events, http.Response.Body, ct);
});
app.Run();
When the model requests several tool calls in one turn, let the function-invoking client run them concurrently:
.UseFunctionInvocation(fic =>
{
fic.TerminateOnUnknownCalls = true;
fic.AllowConcurrentInvocation = true;
});
The model decides whether to batch calls; AllowConcurrentInvocation only controls whether the already-requested calls execute in parallel instead of serially.
JsonSerializerContext entry, registered on the host and passed to AIFunctionFactory.Create.TerminateOnUnknownCalls at its default when client tools are also in play. With it set, a tool the server doesn't own (one the client declared) ends the server run cleanly so the client can execute it; without it the unknown call surfaces as an error mid-run.TOOL_CALL_START / TOOL_CALL_ARGS / TOOL_CALL_END followed by a TOOL_CALL_RESULT, then assistant text that uses the result — all within one run that ends RUN_FINISHED.dotnet publish produces no trim/AOT warnings for the tool types.npx claudepluginhub ag-ui-protocol/ag-ui --plugin ag-ui-dotnetExposes client-side tools to an AG-UI agent via the .NET SDK: C# functions running in the client app (GPS, local state, device APIs) that the model can call and get results back automatically.
Creates and manages persistent AI agents in .NET using Azure SDK for threads, messages, runs, tools, function calling, file search, and code interpreter.
Low-level .NET SDK for creating and managing persistent Azure AI agents with threads, messages, runs, and tools.