Mcp.Gateway.Tools
1.8.7
dotnet add package Mcp.Gateway.Tools --version 1.8.7
NuGet\Install-Package Mcp.Gateway.Tools -Version 1.8.7
<PackageReference Include="Mcp.Gateway.Tools" Version="1.8.7" />
<PackageVersion Include="Mcp.Gateway.Tools" Version="1.8.7" />
<PackageReference Include="Mcp.Gateway.Tools" />
paket add Mcp.Gateway.Tools --version 1.8.7
#r "nuget: Mcp.Gateway.Tools, 1.8.7"
#:package Mcp.Gateway.Tools@1.8.7
#addin nuget:?package=Mcp.Gateway.Tools&version=1.8.7
#tool nuget:?package=Mcp.Gateway.Tools&version=1.8.7
π οΈ Mcp.Gateway.Tools
Core library for building MCP servers in .NET 10
Mcp.Gateway.Tools contains the infrastructure for MCP tools, prompts, and resources:
- JSONβRPC models (
JsonRpcMessage,JsonRpcError) - Attributes (
McpToolAttribute,McpPromptAttribute,McpResourceAttribute) - Tool/Prompt/Resource registration (
ToolService) - Invocation and protocol implementation (
ToolInvoker) - Streaming (
ToolConnector,ToolCapabilities) - ASP.NET Core extensions for endpoints (
MapHttpRpcEndpoint,MapWsRpcEndpoint,MapSseRpcEndpoint,AddToolsService)
This README focuses on how to use the library in your own server.
See the root README.md for a highβlevel overview and client integration.
π Documentation
Complete documentation: https://eyjolfurgudnivatne.github.io/mcp.gateway/
- π€ AI Assistant Quickstart - Quick reference for AI assistants
- Getting Started - Installation and first tool
- API Reference - Complete API documentation
- Examples - Working examples
π§ Register tool infrastructure
In your Program.cs (v1.7.0 with MCP 2025-11-25 support):
using Mcp.Gateway.Tools;
var builder = WebApplication.CreateBuilder(args);
// Register ToolService + ToolInvoker + Session Management (v1.7.0)
builder.AddToolsService();
var app = builder.Build();
// Custom: stdio, logging, etc. (see DevTestServer for a full example)
// WebSockets must be enabled before WS/SSE routes
app.UseWebSockets();
// MCP 2025-11-25 Streamable HTTP (v1.7.0 - RECOMMENDED)
app.UseProtocolVersionValidation(); // Protocol version validation
app.MapStreamableHttpEndpoint("/mcp"); // Unified endpoint (POST + GET + DELETE)
// Legacy endpoints (still work, deprecated)
app.MapHttpRpcEndpoint("/rpc"); // HTTP POST only (deprecated)
app.MapWsRpcEndpoint("/ws"); // WebSocket (keep for binary streaming)
app.MapSseRpcEndpoint("/sse"); // SSE only (deprecated, use /mcp GET instead)
app.Run();
AddToolsService registers:
ToolServiceas a singleton (discovers/validates tools)ToolInvokeras scoped (handles JSONβRPC and MCP methods)EventIdGeneratoras singleton (v1.7.0 - SSE event IDs)SessionServiceas singleton (v1.7.0 - session management)SseStreamRegistryas singleton (v1.7.0 - SSE stream management)INotificationSenderβNotificationServiceas singleton (notification infrastructure)
π§© Defining tools
3.1. Simplest Tool (Auto-generated Schema)
The easiest way to create a tool using strongly-typed parameters and automatic schema generation:
using Mcp.Gateway.Tools;
public class MyTools
{
[McpTool("greet")]
public TypedJsonRpc<GreetResponse> Greet(TypedJsonRpc<GreetParams> request)
{
var name = request.Params.Name;
return TypedJsonRpc<GreetResponse>.Success(
request.Id,
new GreetResponse($"Hello, {name}!"));
}
}
public record GreetParams(string Name);
public record GreetResponse(string Message);
Benefits:
- β
No manual JSON Schema - automatically generated from
GreetParams(Input) andGreetResponse(Output) - β Strongly-typed - IntelliSense and compile-time safety
- β Clean code - easy to read and maintain
3.2. Advanced Tool (Custom Schema)
For complex validation or when you need full control over the JSON Schema:
using CalculatorMcpServer.Models;
using Mcp.Gateway.Tools;
public class CalculatorTools
{
[McpTool("add_numbers",
Title = "Add Numbers",
Description = "Adds two numbers and returns the result.",
Icon = "https://example.com/icons/calculator.png", // NEW: Icon (v1.6.5)
InputSchema = @"{
""type"":""object"",
""properties"":
{
""number1"":{""type"":""number"",""description"":""First number""},
""number2"":{""type"":""number"",""description"":""Second number""}
},
""required"": [""number1"",""number2""]
}")]
public JsonRpcMessage AddNumbersTool(JsonRpcMessage request)
{
var args = request.GetParams<AddNumbersRequest>()
?? throw new ToolInvalidParamsException(
"Parameters 'number1' and 'number2' are required and must be numbers.");
return ToolResponse.Success(
request.Id,
new AddNumbersResponse(args.Number1 + args.Number2));
}
}
When to use:
- β Custom validation rules (minLength, maxLength, pattern, etc.)
- β Complex schema features not supported by auto-generation
- β Full control over JSON Schema
Icons (v1.6.5+):
Tools, prompts, and resources can include optional icons for visual representation in MCP clients:
[McpTool("calculator_add",
Icon = "https://example.com/icons/calculator.png")]
[McpPrompt("summarize",
Icon = "https://example.com/icons/document.png")]
[McpResource("file://logs/app.log",
Icon = "https://example.com/icons/log-file.png")]
Icons are serialized as a single-item array in the MCP protocol:
{
"name": "add_numbers",
"icons": [
{
"src": "https://example.com/icons/calculator.png",
"mimeType": null,
"sizes": null
}
]
}
The Icon property accepts:
- β
HTTPS URLs:
"https://example.com/icon.png" - β
Data URIs:
"data:image/svg+xml;base64,..." - βΉοΈ
mimeTypeandsizesare automatically set tonull(client infers from URL)
3.3. With Validation and DI
For tools that need dependency injection and advanced validation (from DevTestServer/Tools/Calculator.cs):
using DevTestServer.MyServices;
using Mcp.Gateway.Tools;
using System.Text.Json;
public class Calculator
{
public sealed record NumbersRequest(double Number1, double Number2);
public sealed record NumbersResponse(double Result);
[McpTool("add_numbers",
Title = "Add Numbers",
Description = "Adds two numbers and return result. Example: 5 + 3 = 8",
InputSchema = @"{
""type"":""object"",
""properties"":{
""number1"":{""type"":""number"",""description"":""First number to add""},
""number2"":{""type"":""number"",""description"":""Second number to add""}
},
""required"": [""number1"",""number2""]
}")]
public async Task<JsonRpcMessage> AddNumbersTool(
JsonRpcMessage request,
CalculatorService calculatorService)
{
await Task.CompletedTask; // placeholder for async work
var @params = request.GetParams();
if (@params.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null ||
!@params.TryGetProperty("number1", out _) ||
!@params.TryGetProperty("number2", out _))
{
throw new ToolInvalidParamsException(
"Parameters 'number1' and 'number2' are required and must be numbers.");
}
var args = request.GetParams<NumbersRequest>()!;
var result = calculatorService.Add(args.Number1, args.Number2);
return ToolResponse.Success(request.Id, new NumbersResponse(result));
}
}
Register the DI service in Program.cs:
builder.Services.AddScoped<CalculatorService>();
TypedJsonRpc<T> (v1.3.0) and Auto-Schema (v1.8.0)
For tools that prefer strongly-typed request models, you can use TypedJsonRpc<T>:
public sealed record AddNumbersRequestTyped(
[property: JsonPropertyName("number1")]
[property: Description("First number to add")] double Number1,
[property: JsonPropertyName("number2")]
[property: Description("Second number to add")] double Number2);
public sealed record AddNumbersResponseTyped(
[property: Description("The result of the addition")] double Result);
[McpTool("add_numbers_typed_ii",
Title = "Add Numbers (typed)",
Description = "Adds two numbers using TypedJsonRpc with auto-generated schema.")]
public TypedJsonRpc<AddNumbersResponseTyped> AddNumbersToolTypedII(TypedJsonRpc<AddNumbersRequestTyped> request)
{
var args = request.GetParams()
?? throw new ToolInvalidParamsException(
"Parameters 'number1' and 'number2' are required and must be numbers.");
return TypedJsonRpc<AddNumbersResponseTyped>.Success(
request.Id,
new AddNumbersResponseTyped(args.Number1 + args.Number2));
}
Input Schema Generation:
If InputSchema is omitted on [McpTool] and the first parameter is TypedJsonRpc<TParams>:
tools/listwill auto-generate a JSON Schema forTParams.
Output Schema Generation (v1.8.0):
If OutputSchema is omitted on [McpTool] and the return type is TypedJsonRpc<TResponse>:
tools/listwill auto-generate a JSON Schema forTResponse.- The response will automatically include
structuredContentmatching the schema.
Schema features:
- Root:
type: "object" propertiesfrom public propertiesrequiredfrom non-nullable properties (nullable β optional)JsonPropertyNamefor JSON field names[property: Description("...")]mapped todescription- enums represented as string enums (
"type": "string", "enum": ["Active", "Disabled"])
If InputSchema or OutputSchema is set on [McpTool], it always wins and auto-generation is skipped for that part.
MCP Prompts (v1.4.0)
You can define reusable prompt templates using [McpPrompt] and the prompt models:
[McpPrompt(Description = "Report to Santa Claus")]
public JsonRpcMessage SantaReportPrompt(JsonRpcMessage request)
{
return ToolResponse.Success(
request.Id,
new PromptResponse(
Name: "santa_report_prompt",
Description: "A prompt that reports to Santa Claus",
Messages: [
new(
PromptRole.System,
"You are a very helpful assistant for Santa Claus."),
new(
PromptRole.User,
"Send a letter to Santa Claus and tell him that {name} has been {behavior}.")
],
Arguments: new {
name = new {
type = "string",
description = "Name of the child"
},
behavior = new {
type = "string",
description = "Behavior of the child (e.g., Good, Naughty)",
@enum = new[] { "Good", "Naughty" }
}
}
));
}
Prompts are exposed via the MCP prompt methods and initialize capabilities:
prompts/listβ returns all discovered prompts withname,description,arguments.prompts/getβ returns the expanded promptmessagesfor a given prompt and arguments.initializeβ includes apromptscapability flag when prompts are registered.
On the wire, prompts are regular JSON-RPC responses whose result contains a prompt object with name,
description, messages (each with role and content), and arguments that clients can use to build LLM calls.
MCP Resources (v1.5.0)
You can expose data and content using [McpResource]:
[McpResource("system://status",
Name = "System Status",
Description = "Current system health metrics",
MimeType = "application/json")]
public JsonRpcMessage SystemStatus(JsonRpcMessage request)
{
var status = new
{
uptime = Environment.TickCount64,
memoryUsed = GC.GetTotalMemory(false) / (1024 * 1024),
timestamp = DateTime.UtcNow
};
var json = JsonSerializer.Serialize(status, JsonOptions.Default);
return ToolResponse.Success(request.Id, new ResourceContent(
Uri: "system://status",
MimeType: "application/json",
Text: json
));
}
Resource URI patterns:
file://logs/app.log- File-based resourcesdb://users/123- Database resourcessystem://status- System metricshttp://api.example.com/data- External APIs
Resources are exposed via the MCP resource methods and initialize capabilities:
resources/listβ returns all discovered resources withuri,name,description,mimeType.resources/readβ returns the content of a specific resource by URI.initializeβ includes aresourcescapability flag when resources are registered.
On the wire, resources are regular JSON-RPC responses whose result contains resource metadata (for list) or
content (for read) with uri, mimeType, and text fields.
π Date/time tools (example)
From Examples/DateTimeMcpServer/Tools/DateTimeTools.cs:
using DateTimeMcpServer.Models;
using Mcp.Gateway.Tools;
public class DateTimeTools
{
[McpTool("get_current_datetime",
Title = "Get current date and time",
Description = "Get current date and time in specified timezone (default: local).",
InputSchema = @"{
""type"":""object"",
""properties"":{
""timezoneName"":{
""type"":""string"",
""description"":""Timezone name (e.g., 'Europe/Oslo', 'UTC')"",
""default"":""UTC""
}
}
}")]
public JsonRpcMessage GetCurrentDateTime(JsonRpcMessage message)
{
var request = message.GetParams<CurrentDateTimeRequest>();
TimeZoneInfo tz;
try
{
tz = string.IsNullOrWhiteSpace(request?.TimezoneName)
? TimeZoneInfo.Local
: TimeZoneInfo.FindSystemTimeZoneById(request.TimezoneName);
}
catch
{
tz = TimeZoneInfo.Local;
}
var now = TimeZoneInfo.ConvertTime(DateTime.Now, tz);
return ToolResponse.Success(
message.Id,
new CurrentDateTimeResponse(
now.ToString("o"),
now.ToString("yyyy-MM-dd"),
now.ToString("HH:mm:ss"),
tz.Id,
now.ToString("dddd"),
System.Globalization.ISOWeek.GetWeekOfYear(now),
now.Year,
now.Month,
now.Day));
}
}
π§΅ Streaming and ToolCapabilities
Capabilities
ToolCapabilities is used to filter tools per transport:
[Flags]
public enum ToolCapabilities
{
Standard = 1,
TextStreaming = 2,
BinaryStreaming = 4,
RequiresWebSocket = 8
}
- HTTP/stdio:
Standardonly - SSE:
Standard+TextStreaming - WebSocket: all (incl.
BinaryStreamingandRequiresWebSocket)
Simple text streaming tool
[McpTool("stream_data",
Description = "Streams incremental data to the client.",
Capabilities = ToolCapabilities.TextStreaming)]
public async Task StreamData(ToolConnector connector)
{
var meta = new StreamMessageMeta(
Method: "stream_data",
Binary: false);
using var handle = (ToolConnector.TextStreamHandle)connector.OpenWrite(meta);
for (int i = 0; i < 5; i++)
{
await handle.WriteAsync(new { chunk = i });
}
await handle.CompleteAsync(new { done = true });
}
See docs/StreamingProtocol.md and docs/examples/toolconnector-usage.md for more details.
π Pagination (v1.6.0)
When you have many tools, prompts, or resources, use cursor-based pagination to avoid overwhelming clients:
Using pagination in tools/list
// Client request with pagination
{
"jsonrpc": "2.0",
"method": "tools/list",
"params": {
"cursor": "eyJvZmZzZXQiOjEwMH0=",
"pageSize": 50
},
"id": 1
}
// Server response with nextCursor
{
"jsonrpc": "2.0",
"result": {
"tools": [ /* 50 tools */ ],
"nextCursor": "eyJvZmZzZXQiOjE1MH0="
},
"id": 1
}
Pagination helper (CursorHelper)
You can use CursorHelper in your own tools to implement pagination:
using Mcp.Gateway.Tools.Pagination;
public class MyTools
{
[McpTool("list_items")]
public JsonRpcMessage ListItems(JsonRpcMessage request)
{
// Get pagination params
var @params = request.GetParams();
string? cursor = null;
int pageSize = 100;
if (@params.TryGetProperty("cursor", out var cursorProp))
cursor = cursorProp.GetString();
if (@params.TryGetProperty("pageSize", out var sizeProp))
pageSize = sizeProp.GetInt32();
// Get your items (e.g., from database)
var allItems = GetAllItems();
// Apply pagination
var paginatedResult = CursorHelper.Paginate(allItems, cursor, pageSize);
// Build response
var response = new Dictionary<string, object>
{
["items"] = paginatedResult.Items
};
if (paginatedResult.NextCursor is not null)
response["nextCursor"] = paginatedResult.NextCursor;
return ToolResponse.Success(request.Id, response);
}
}
Features:
- β
Base64-encoded cursor:
{"offset": 100} - β Default page size: 100 items
- β
Works with any
IEnumerable<T> - β Thread-safe and stateless
See Examples/PaginationMcpServer for a complete example with 120+ tools.
π Notifications (v1.7.0 - MCP 2025-11-25 Compliant!)
Send real-time updates via SSE (Server-Sent Events) when your tools, prompts, or resources change:
Using INotificationSender
Inject INotificationSender into your tools to send notifications:
using Mcp.Gateway.Tools.Notifications;
public class MyTools
{
private readonly INotificationSender _notificationSender;
public MyTools(INotificationSender notificationSender)
{
_notificationSender = notificationSender;
}
[McpTool("reload_tools")]
public async Task<JsonRpcMessage> ReloadTools(JsonRpcMessage request)
{
// Reload your tools (e.g., scan file system, refresh cache)
await ReloadToolsFromFileSystem();
// Notify all active SSE streams (automatic broadcast!)
await _notificationSender.SendNotificationAsync(
NotificationMessage.ToolsChanged());
return ToolResponse.Success(request.Id, new { reloaded = true });
}
[McpTool("update_resource")]
public async Task<JsonRpcMessage> UpdateResource(JsonRpcMessage request)
{
var uri = request.GetParams().GetProperty("uri").GetString();
// Update the resource
await UpdateResourceContent(uri);
// Notify with specific URI
await _notificationSender.SendNotificationAsync(
NotificationMessage.ResourcesUpdated(uri));
return ToolResponse.Success(request.Id, new { updated = uri });
}
}
Notification types
Three notification methods are available:
// Tools changed
await notificationSender.SendNotificationAsync(
NotificationMessage.ToolsChanged());
// Prompts changed
await notificationSender.SendNotificationAsync(
NotificationMessage.PromptsChanged());
// Resources updated (optional URI)
await notificationSender.SendNotificationAsync(
NotificationMessage.ResourcesUpdated("file://config/settings.json"));
How it works (v1.7.0)
- Client connects via
GET /mcpwithMCP-Session-Idheader - Server opens SSE stream with keep-alive pings
- Server detects change (tool added, resource updated, etc.)
- Server broadcasts notification to all active SSE streams:
id: 42 event: message data: {"jsonrpc":"2.0","method":"notifications/tools/list_changed","params":{}} - Client re-fetches tools/list, prompts/list, or resources/list
Message buffering and resumption (v1.7.0)
Notifications are automatically buffered per session (max 100 messages) for Last-Event-ID resumption:
GET /mcp HTTP/1.1
MCP-Session-Id: abc123
Last-Event-ID: 42 # Resume from event 42
# Server replays events 43, 44, 45, ... then streams new events
Notification capabilities
When NotificationService is registered (automatic via AddToolsService()), the initialize response includes:
{
"capabilities": {
"tools": {},
"prompts": {},
"resources": {},
"notifications": {
"tools": {},
"prompts": {},
"resources": {}
}
}
}
Note: Only capabilities for registered function types are included. If your server has no prompts, notifications.prompts will not be present.
Migration from v1.6.x (v1.7.0)
Good news: No code changes needed! Notifications automatically work via SSE in v1.7.0.
// v1.6.x - WebSocket only (still works!)
notificationService.AddSubscriber(webSocket);
// v1.7.0 - SSE automatic (recommended)
// Client opens: GET /mcp with MCP-Session-Id
// Server automatically broadcasts via SSE
// No code changes needed!
WebSocket notifications are deprecated but still functional for backward compatibility.
See Examples/NotificationMcpServer for a complete example with manual notification triggers.
π§± JSON models
Core models live in ToolModels.cs:
JsonRpcMessageβ JSONβRPC 2.0 messagesJsonRpcErrorβ structured errorsJsonOptions.Defaultβ sharedJsonSerializerOptions(camelCase, etc.)
Typical usage:
public JsonRpcMessage Echo(JsonRpcMessage message)
{
var raw = message.GetParams();
return JsonRpcMessage.CreateSuccess(message.Id, raw);
}
π§ͺ Verification and tests
The library itself is tested via DevTestServer + Mcp.Gateway.Tests:
- Protocol tests:
Mcp.Gateway.Tests/Endpoints/Http/McpProtocolTests.cs - Streaming tests:
Mcp.Gateway.Tests/Endpoints/Ws/*,.../Sse/* - stdio tests:
Mcp.Gateway.Tests/Endpoints/Stdio/*
For your own development:
dotnet test
π Summary
To use Mcp.Gateway.Tools in your project:
- Add the NuGet package
- Call
builder.AddToolsService()inProgram.cs - Map
MapHttpRpcEndpoint,MapWsRpcEndpoint,MapSseRpcEndpointas needed - Define tools by annotating methods with
[McpTool] - Connect the server to an MCP client (GitHub Copilot, Claude, etc.)
For complete examples, see:
Examples/CalculatorMcpServer- Calculator toolsExamples/DateTimeMcpServer- Date/time utilitiesExamples/PromptMcpServer- Prompt templatesExamples/ResourceMcpServer- File, system, and database resourcesExamples/PaginationMcpServer- Pagination with 120+ mock tools (v1.6.0)Examples/NotificationMcpServer- WebSocket notifications demo (v1.6.0)DevTestServer- Full-featured reference (used by tests)
License: MIT β see root LICENSE.
| Product | Versions 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. |
-
net10.0
- Mcp.Gateway.Models (>= 1.8.7)
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.8.7 | 119 | 1/11/2026 |
| 1.8.6 | 110 | 1/10/2026 |
| 1.8.5 | 108 | 1/9/2026 |
| 1.8.4 | 113 | 1/9/2026 |
| 1.8.3 | 108 | 1/3/2026 |
| 1.8.2 | 107 | 1/3/2026 |
| 1.8.1 | 111 | 1/3/2026 |
| 1.8.0 | 181 | 12/19/2025 |
| 1.7.3 | 278 | 12/19/2025 |
| 1.7.2 | 275 | 12/19/2025 |
| 1.7.1 | 280 | 12/19/2025 |
| 1.7.0 | 280 | 12/18/2025 |
| 1.6.0 | 279 | 12/16/2025 |
| 1.5.0 | 278 | 12/16/2025 |
| 1.4.0 | 282 | 12/16/2025 |
| 1.3.0 | 220 | 12/14/2025 |
| 1.2.0 | 133 | 12/12/2025 |
| 1.1.0 | 179 | 12/5/2025 |
| 1.0.1 | 211 | 12/5/2025 |
v1.8.7: Resource API Refinements and Ping Compliance. BREAKING: Client ReadResourceAsync now returns ReadResourceResult. Added Client UnsubscribeResourceAsync. Moved system/ping to ping for spec compliance. Added resource metadata support.