ApricotFramework.Agentic.Tools.Grpc.Client 0.1.2

Prefix Reserved
dotnet add package ApricotFramework.Agentic.Tools.Grpc.Client --version 0.1.2
                    
NuGet\Install-Package ApricotFramework.Agentic.Tools.Grpc.Client -Version 0.1.2
                    
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="ApricotFramework.Agentic.Tools.Grpc.Client" Version="0.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ApricotFramework.Agentic.Tools.Grpc.Client" Version="0.1.2" />
                    
Directory.Packages.props
<PackageReference Include="ApricotFramework.Agentic.Tools.Grpc.Client" />
                    
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 ApricotFramework.Agentic.Tools.Grpc.Client --version 0.1.2
                    
#r "nuget: ApricotFramework.Agentic.Tools.Grpc.Client, 0.1.2"
                    
#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 ApricotFramework.Agentic.Tools.Grpc.Client@0.1.2
                    
#: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=ApricotFramework.Agentic.Tools.Grpc.Client&version=0.1.2
                    
Install as a Cake Addin
#tool nuget:?package=ApricotFramework.Agentic.Tools.Grpc.Client&version=0.1.2
                    
Install as a Cake Tool

ApricotFramework.Agentic

NuGet CI License

Declare an AI tool once, beside the code that runs it, and surface it anywhere. A tool carries its own name, the prompt a model selects on, JSON schemas generated from ordinary .NET types, and the behaviour a caller's policy depends on.

A tool is a Microsoft.Extensions.AI.AIFunction, not something adapted into one — so the same declaration goes into ChatOptions.Tools for an in-process loop, through McpServerTool.Create for an MCP server, and over gRPC to another service, with no adapter in any direction. Tools arriving from those same directions come back the other way with none either.

Nothing here knows how a tool is reached. Local or remote is a property of the caller, not of the operation.

Nothing here knows what a permission is. What a caller must hold is declared the way the host already declares it — an attribute — and decided by the host's own pipeline.

Status: preview. The shape is still moving. Expect breaking changes until 1.0.

Install

Nothing implies anything else. Take the surfaces this host actually has.

dotnet add package ApricotFramework.Agentic.Tools              # registry, executor, registration
dotnet add package ApricotFramework.Agentic.Tools.Abstractions # a library that only declares tools
dotnet add package ApricotFramework.Agentic.Tools.AspNetCore   # authorization, and a caller from the request
dotnet add package ApricotFramework.Agentic.Tools.Mcp.Client   # tools from upstream MCP servers
dotnet add package ApricotFramework.Agentic.Tools.Mcp.Server   # this host's tools, over MCP
dotnet add package ApricotFramework.Agentic.Tools.Grpc.Server  # this host's tools, over gRPC
dotnet add package ApricotFramework.Agentic.Tools.Grpc.Client  # tools from another service, over gRPC
dotnet add package ApricotFramework.Agentic.Tools.ErrorDefinitions  # ErrorDefinitions errors as tool failures

A tool

A tool is an ordinary method on an ordinary class.

[AgentToolType]
[Authorize(Policy = "tickets.read")]        // your policy, your pipeline
public sealed class TicketTools(TicketStore tickets)   // scoped, and resolved per call
{
    [AgentTool("support_tickets_get", Title = "Get a ticket", ReadOnly = true)]
    [Description("Reads one ticket in full, given its identifier. Use this after " +
                 "support_tickets_list has narrowed to a particular ticket.")]
    public async Task<TicketDetail> Get(
        [Description("The identifier of the ticket.")] long id,
        CancellationToken cancellationToken)
        => Map(await tickets.GetById(id, cancellationToken));
}

The parameter list is the argument shape and the schemas come from the types. [Description] becomes what a model reads; nullability becomes the difference between required and optional. A parameter typed AgentToolContext, CancellationToken or IProgress<T> is bound from the call and left out of the schema.

Binding and schema generation are AIFunctionFactory's — the same machinery [McpServerTool] uses — which is why there is no class-per-tool base to derive. The one case that needs a class is a result that arrives as a sequence, because an AIFunction returns one value: derive AgentTool and implement InvokeStreamingAsync. A consumer that cannot stream collects the items and sees the same result.

Registration

builder.Services.AddAgentToolsWeb()                // registry, executor, tripwire, and the
                                                   // caller taken from the request
    .WithAuthorization();                          // gate tools on the attributes they declare

builder.Services.AddAgentToolType<TicketTools>();
builder.Services.AddAgentTool<TicketsListTool>();  // the hand-written one, for a sequence
builder.Services.AddAgentToolValidator<ToolNamingConvention>();   // your rules, checked at startup

Anything else to say about a tool is said inside the call, so nothing is handed back to be mutated later:

builder.Services.AddAgentToolType<TicketAdminTools>(tool => tool.RequireAuthorization("tickets.admin"));

Invoking

There is no context parameter. Opening a scope and deciding who the caller is are one question a host answers once, in an IAgentToolContextFactory — not something every endpoint, MCP handler and gRPC method re-derives.

var offered = await executor.GetAvailableToolsAsync(cancellationToken);
var json    = await executor.InvokeCompleteAsync(name, argumentsJson, cancellationToken);

await foreach (var chunk in executor.InvokeAsync(name, argumentsJson, cancellationToken)) { … }

A scope is opened per call and lives until the last item has been yielded, so a tool takes its dependencies through its constructor the way an endpoint does.

For a chat client:

var options = new ChatOptions { Tools = [.. await executor.GetAvailableFunctionsAsync(cancellationToken)] };

Each of those re-enters the executor when called, so a conversation that listed twenty minutes ago still runs in a fresh scope — and is filtered again, because a listing is not a permission.

Tools from somewhere else

builder.Services.AddAgentToolsCore()
    .WithStaticMcpClients(builder.Configuration)               // servers named in configuration
    .WithMcpTools(options =>
{
    options.Prefix = "up_";                                    // a name space of your own
    options.Metadata.Add(new AuthorizeAttribute("tools.external"));
});

An MCP client's tool is already an AIFunction, so nothing wraps it: what this host claims about it — a name, a pinned description, a gate — sits beside it in the descriptor, and a consumer reaching through it for the real thing still finds it. The registry asks on every listing rather than once at start-up, so a server that adds a tool is reflected without a restart, and a host that connects servers per person gets a surface per person.

The same shape over gRPC brings in another service's tools, point to point.

Design notes

The registry insists on almost nothing. A tool needs a name, and no two may share one. A title, a description, who may call a tool and which surface it belongs on are validators and filters you add, because a library that insists on its own judgement is one you eventually work around.

A tool that declares authorization nothing enforces stops the host. That is the tripwire, and the reason there is no setting to turn authorization on.

One decision covers the listing and the call. A listing exists to avoid offering what the call would refuse, so a filter applies to both.

Results are JSON text. Not a format that carries numbers as doubles — an Int64 identifier past 2^53 has to survive the round trip.

Full documentation: https://projectapricot.dev/docs/agentic

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
0.1.2 54 10/1/2026
0.1.1 45 10/1/2026
0.1.0 48 9/30/2026