MCP Tools
This framework allows to provide tools to AI agents such as Claude via the Model Context Protocol (MCP), in the same way web services are provided with the webservice or controller frameworks. The methods you implement are offered to the agent as tools, which it can decide to call while working on a task.
Hosting Tools
To provide tools, create an endpoint with Tools.Create() and add the classes or functions
implementing your tools. Methods marked with the [Tool] attribute will be offered to the agent,
described by the summary you provide.
using System.ComponentModel.DataAnnotations;
using GenHTTP.Api.Content;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Mcp;
var tools = Tools.Create()
.Server("library", "1.0.0", "Library")
.Instructions("Search for books before lending them to a reader.")
.Add<LibraryTools>()
.Add("get_time", "Returns the current time of the library (UTC)", [Tool(ReadOnly = true)] () => DateTime.UtcNow);
var app = Layout.Create()
.Add("mcp", tools);
await Host.Create()
.Handler(app)
.Development()
.RunAsync();
public record Book(int Id, string Title, string? LentTo);
public class LibraryTools
{
private static readonly List<Book> Books = [new(1, "SPQR: A History of Ancient Rome", null)];
[Tool(ReadOnly = true)]
[Summary("Searches the books of the library by title")]
public List<Book> FindBooks([Summary("Part of the title, e.g. 'Rome'")] string query, [Range(1, 50)] int limit = 10)
=> Books.Where(b => b.Title.Contains(query, StringComparison.OrdinalIgnoreCase)).Take(limit).ToList();
[Tool]
[Summary("Lends a book to a reader")]
public Book LendBook([Summary("The id of the book, as returned by find_books")] int bookId, [Summary("The name of the reader")] string reader)
{
var book = Books.FirstOrDefault(b => b.Id == bookId)
?? throw new ProviderException(ResponseStatus.NotFound, $"There is no book with id {bookId}.");
if (book.LentTo != null)
{
throw new ProviderException(ResponseStatus.Conflict, $"'{book.Title}' is already lent to {book.LentTo}.");
}
var lent = book with { LentTo = reader };
Books[Books.IndexOf(book)] = lent;
return lent;
}
}This example provides the tools find_books, lend_book and get_time via http://localhost:8080/mcp.
You can add the endpoint to Claude Code and ask questions such as
“Which books about Rome are in the library?”:
claude mcp add --transport http library http://localhost:8080/mcpAny other client supporting the Streamable HTTP transport of MCP can connect to the endpoint the same way.
Defining Tools
Tools can be added in several ways, which can be mixed within a single endpoint:
| Method | Description |
|---|---|
Add<T>() | Adds the methods marked with [Tool] of the given class. A single instance is created via its parameterless constructor. |
Add(instance) | Adds the methods marked with [Tool] of the given instance. |
Add(name, description, function) | Adds a single tool implemented by the given function. |
The name of a tool defaults to the name of the method in snake case, so FindBooksAsync becomes
find_books. You can pass a different name to the attribute ([Tool("search")]). Every tool needs
a description that tells the model what it does, which is read from the [Summary] (or [Description])
attribute on the method. Additional [Remarks] are appended to the description.
Tool classes need to be dedicated to tools - classes that also provide webservice or controller methods are rejected.
Hints
Besides the name, the [Tool] attribute allows to describe the behavior of the tool, so
clients can decide whether to ask the user before invoking it:
| Property | Description |
|---|---|
Title | A human-readable name of the tool, shown by clients. |
ReadOnly | The tool does not modify its environment. |
Destructive | The tool may perform destructive updates. |
Idempotent | Calling the tool repeatedly with the same arguments has no additional effect. |
OpenWorld | The tool interacts with external entities (such as the web). |
On functions, the attribute can be used to set those hints as well, but not the name, which
is passed to Add instead.
Arguments
The parameters of a tool become its arguments. The framework generates a JSON schema for them, so the model knows what to pass, and validates the arguments sent by the client before the tool is invoked. Arguments with a default value and nullable members of input records are optional.
Validation attributes from System.ComponentModel.DataAnnotations (such as [Range], [Length],
[StringLength], [RegularExpression], [EmailAddress], [Url] or [AllowedValues]) are added
to the schema and checked on every call, also for the properties of records passed as arguments.
If an argument is invalid, the model receives an error listing all violations, so it can correct
the call.
[Tool]
[Summary("Registers a new reader")]
public Reader Register([Length(2, 50)] string name, [EmailAddress] string mail, [Range(16, 120)] int? age = null) => /* ... */;Values provided by the framework, such as the IRequest or the authenticated user,
are injected as they are for web services and are not
visible to the model. Streams and binary data are not supported as arguments.
Results
The return value of a tool is converted into the result read by the model:
| Return Type | Result |
|---|---|
void, Task | An empty, successful result. |
string | The text, as it is (e.g. Markdown). |
| Any other type | Serialized to JSON. Objects are also sent as structured content, described by an output schema. |
ToolResult | Full control over the content sent to the client. |
ToolResult allows to return text, JSON, images, audio or links to resources the client can fetch:
[Tool(ReadOnly = true)]
[Summary("Returns the cover of a book")]
public ToolResult GetCover(int bookId)
{
var image = File.ReadAllBytes($"covers/{bookId}.png");
return ToolResult.From(new TextContent($"The cover of book {bookId}"),
new ImageContent(image, "image/png"));
}Asynchronous methods (Task<T>, ValueTask<T>) are awaited, and the
result types of the service frameworks are unwrapped.
Errors
If a tool throws a ProviderException, the model receives an error with the message of the
exception, so you can tell it what went wrong and how to fix it. Alternatively, you can return
ToolResult.Error("..."). Other exceptions are reported to the model as an unexpected failure and
are logged by the server.
Errors are reported to the model as the result of the tool call, so it can react to them - the HTTP request itself succeeds.
Security
The endpoint is a regular handler, so you can add concerns such as
authentication to it. Combined with the [RequireRole] interceptor,
access can be restricted per tool. The roles required by a tool are added to its description, so
the model knows about them. To inject the authenticated user into your tools, add a
user injector.
using GenHTTP.Api.Content.Authentication;
using GenHTTP.Api.Content.Documentation;
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.Authentication;
using GenHTTP.Modules.Authentication.ApiKey;
using GenHTTP.Modules.Mcp;
using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Reflection.Injectors;
var auth = ApiKeyAuthentication.Create()
.Authenticator(AuthenticateAsync);
var tools = Tools.Create()
.Add<AdminTools>()
.Injectors(Injection.Default().Add(new UserInjector<ApiKeyUser>()))
.Authentication(auth);
static ValueTask<IUser?> AuthenticateAsync(IRequest request, string key)
=> new(key == "abc" ? new ApiKeyUser(key, "ADMIN") : null);
public class AdminTools
{
[Tool(Destructive = true)]
[Summary("Removes all books from the library")]
[RequireRole("ADMIN")]
public string Purge(ApiKeyUser user) => $"Purged by {user.DisplayName}";
}The API key can then be passed by the client, e.g. with Claude Code:
claude mcp add --transport http library http://localhost:8080/mcp --header "X-API-Key: abc"Agents do not send an Origin header. Requests from web browsers do, and are rejected by default
to prevent malicious web pages from accessing a server running on your machine or within your network
(e.g. via DNS rebinding). If you would like to use the endpoint from a web page, allow its origin:
var tools = Tools.Create()
.AllowOrigin("https://example.com");Dependency Injection
With the dependency injection module enabled on your host, tool classes are created per call with their dependencies resolved, and services can be injected into the parameters of your tools:
using GenHTTP.Modules.DependencyInjection;
using GenHTTP.Modules.Mcp;
var tools = DependentTools.Create()
.Add<LibraryTools>();
public class LibraryTools(BookRepository books)
{
[Tool(ReadOnly = true)]
[Summary("Searches the books of the library by title")]
public List<Book> FindBooks(string query, AuditLog log) => /* ... */;
}To create the instances of your tool classes in another way, pass a factory to InstanceProvider(...).
Configuration
The endpoint provides the following options:
| Method | Description |
|---|---|
Server(name, version, title) | Identifies the server to clients (defaults to the name and version of the entry assembly). |
Instructions(text) | Explains to the model how to use the tools, e.g. which tool to call first. Keep them short, as clients may shorten them. |
CacheDuration(duration) | How long clients may cache the list of tools (defaults to 5 minutes). |
Serialization(options) | The JsonSerializerOptions used to read arguments, write results and generate the schemas. Pass options with a source generated context to avoid reflection. |
Injectors(registry) | The injectors providing framework values to tools. |
AllowOrigin(origin) | Allows a web page served from the given origin to use the endpoint. |
Protocol Support
The endpoint implements the Streamable HTTP transport of MCP for the stateless protocol revision
2026-07-28 as well as the earlier revisions 2025-03-26 to 2025-11-25, so both current and older
clients can connect. As the endpoint does not keep sessions, it can be scaled horizontally without
further configuration.
The framework provides tools only. Prompts and resources are not supported yet - if you need them, feel free to get in touch via GitHub or Discord.
Every operation is logged with the category GenHTTP.Modules.Mcp,
including the tools called, their duration and the errors they reported.