Adding Ithil to Your API
Ithil.Hosting is a NuGet package you add to your existing ASP.NET Web API. It registers the tool schema endpoint the gateway reads, and gives you two ways to mark endpoints as tools:
- Controllers — the
[AgentTool]attribute on action methods, discovered at compile time - Minimal APIs —
.WithAgentTool()on mapped routes, discovered at startup (0.3.0+)
Use either or both; they end up in a single schema.
Your existing API code, routes, and business logic are unchanged. Ithil is additive only.
Install the package
dotnet add package Ithil.HostingWire up in Program.cs (controllers)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddIthilHosting(); // registers Ithil services
var app = builder.Build();
app.MapControllers();
app.MapIthilSchema(SchemaRegistry.Tools); // exposes GET /ithil/schema
app.Run();SchemaRegistry is a static class emitted by the Ithil source generator at compile time. It contains a Tools collection of all [AgentTool]-decorated methods found in the project.
Wire up in Program.cs (minimal APIs)
Chain .WithAgentTool() onto each route you want agents to call, then pass includeMappedEndpoints: true to MapIthilSchema:
using Ithil.Generated;
using Ithil.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddIthilHosting();
var app = builder.Build();
app.MapGet("/api/inventory/{sku}", (string sku, bool? includeReserved) => ...)
.WithAgentTool("GetInventory", "Returns current stock on hand for a SKU.");
app.MapPost("/api/inventory/adjust", (StockAdjustment adjustment) => ...)
.WithAgentTool("AdjustInventory", "Adjusts stock for a SKU.", allowWrite: true);
// Unmarked, so agents never see it.
app.MapGet("/healthz", () => Results.Ok("healthy"));
app.MapIthilSchema(SchemaRegistry.Tools, includeMappedEndpoints: true);
app.Run();Unlike [AgentTool], WithAgentTool() takes the tool name as its first argument — lambdas have no meaningful method name to borrow. Marking is opt-in, so adding a route never exposes it by accident.
Apps that mix controllers and minimal APIs use the same call: keep app.MapControllers() and switch to MapIthilSchema(SchemaRegistry.Tools, includeMappedEndpoints: true). If a controller action and a mapped endpoint share the same verb and route, the [AgentTool] entry wins.
See the WithAgentTool() Reference for every parameter, how handler parameters are classified, and how to pick a verb for Map / MapMethods endpoints.
How the source generator works
At compile time, the Ithil Roslyn source generator:
- Scans every class in the project for
[AgentTool]attributes - For each decorated method, reads the HTTP verb (from
[HttpGet],[HttpPost], etc.) and the route template - Emits a
SchemaRegistryclass with a statically-typedToolscollection containing the full MCP tool schema for each method
No reflection occurs at runtime. The schema is fully resolved at build time, which means startup is fast and there are no surprises if an attribute is misconfigured — the build fails instead.
What MapIthilSchema does
Every form of MapIthilSchema registers a single endpoint:
GET /ithil/schemaMapIthilSchema(SchemaRegistry.Tools) serves your [AgentTool] tools. MapIthilSchema(SchemaRegistry.Tools, includeMappedEndpoints: true) serves those plus every WithAgentTool() endpoint, reading the routing table on the first request and caching the result.
The gateway polls this endpoint at startup to discover which tools are available. The response is a JSON array of tool definitions:
[
{
"name": "GetOrderStatus",
"description": "Returns the current status of an order given its order ID",
"httpMethod": "GET",
"routePattern": "orders/{id}/status",
"inputSchema": {
"type": "object",
"properties": {
"id": { "type": "string" }
},
"required": ["id"]
},
"allowWrite": false,
"maxResponseTokens": 2000
}
]The gateway must be able to reach GET /ithil/schema on your API. Ensure there are no network or firewall rules blocking this path. The endpoint does not require authentication — it is an internal service-to-service call between the gateway and your API.
Verify it worked
After starting your API, confirm the schema endpoint is responding:
curl http://localhost:5200/ithil/schemaYou should see a JSON array containing one entry for every [AgentTool]-decorated method and every WithAgentTool() endpoint. If a tool is missing, check that:
- The source generator ran — rebuild the project with
dotnet build MapIthilSchema(...)is called inProgram.cs- At least one method has
[AgentTool]applied, or one route has.WithAgentTool() - For minimal-API tools, you passed
includeMappedEndpoints: true
If the endpoint returns 500, a mapped endpoint has an ambiguous HTTP verb — the error message names the tool and the fix. See Choosing a verb.
Targeting specific endpoints
[AgentTool] is applied to individual controller action methods:
[AgentTool("Returns inventory count for a SKU")]
[HttpGet("inventory/{sku}")]
public IActionResult GetInventory(string sku) { ... }
[AgentTool("Creates a purchase order", AllowWrite = true)]
[HttpPost("orders")]
public IActionResult CreateOrder([FromBody] OrderRequest req) { ... }Minimal-API routes are targeted the same way, one call per route:
app.MapGet("/inventory/{sku}", (string sku) => ...)
.WithAgentTool("GetInventory", "Returns inventory count for a SKU");
app.MapPost("/orders", (OrderRequest req) => ...)
.WithAgentTool("CreateOrder", "Creates a purchase order", allowWrite: true);See the [AgentTool] Reference and the WithAgentTool() Reference for all parameters.