Skip to content

Add URL Routing Support for MCP HTTP Transport #622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ModelContextProtocol.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Project Path="samples/QuickstartClient/QuickstartClient.csproj" />
<Project Path="samples/QuickstartWeatherServer/QuickstartWeatherServer.csproj" />
<Project Path="samples/TestServerWithHosting/TestServerWithHosting.csproj" />
<Project Path="samples/UrlRoutingSseServer/UrlRoutingSseServer.csproj" />
</Folder>
<Folder Name="/Solution Items/">
<File Path="Directory.Build.props" />
Expand Down
31 changes: 31 additions & 0 deletions samples/UrlRoutingSseServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using ModelContextProtocol.AspNetCore;
using UrlRoutingSseServer.Tools;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMcpServer()
.WithHttpTransportAndRouting()
.WithTools<EchoTool>()
.WithTools<SampleLlmTool>()
.WithTools<AdminTool>()
.WithTools<WeatherTool>()
.WithTools<MathTool>();

builder.Services.AddOpenTelemetry()
.WithTracing(b => b.AddSource("*")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation())
.WithMetrics(b => b.AddMeter("*")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation())
.WithLogging()
.UseOtlpExporter();

var app = builder.Build();

app.MapMcpWithRouting("mcp");

app.Run();
23 changes: 23 additions & 0 deletions samples/UrlRoutingSseServer/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"OTEL_SERVICE_NAME": "sse-server",
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7133;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"OTEL_SERVICE_NAME": "sse-server",
}
}
}
}
133 changes: 133 additions & 0 deletions samples/UrlRoutingSseServer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# ASP.NET Core MCP Server with Routing

This sample demonstrates route-aware tool filtering in MCP servers, allowing different tool sets to be exposed at different HTTP endpoints based on the `[McpServerToolRoute]` attribute.

## Overview

The routing feature enables you to:

- Expose different tools at different HTTP endpoints
- Create context-specific tool collections (admin, utilities, etc.)
- Maintain global tools available on all routes
- Filter tool visibility based on the requested route

## Route Configuration

### Available Routes

| Route | Available Tools | Description |
|-------|----------------|-------------|
| `/mcp` (global) | All tools | Default route with complete tool set |
| `/mcp/admin` | Admin tools + Global tools | Administrative functions |
| `/mcp/weather` | Weather tools + Global tools | Weather-related operations |
| `/mcp/math` | Math tools + Global tools | Mathematical calculations |
| `/mcp/utilities` | Utility tools + Global tools | General utility functions |
| `/mcp/echo` | Echo tools + Global tools | Echo and text operations |

### Tool Categories

- **Global Tools**: `SampleLLM` (available on all routes)
- **Admin Tools**: `GetSystemStatus`, `RestartService` (admin route only)
- **Weather Tools**: `GetWeather`, `GetForecast` (weather + utilities routes)
- **Math Tools**: `Add`, `Factorial` (math + utilities routes)
- **Echo Tools**: `Echo`, `EchoAdvanced` (echo + utilities routes)

## Running the Sample

1. Start the server:
```bash
cd samples/UrlRoutingSseServer
dotnet run
```

2. The server will start at `http://localhost:5000` (or port shown in console)

## Testing Different Routes

You can test the routing behavior using curl or any HTTP client:

### List All Tools (Global Route)
```bash
curl -X POST http://localhost:5000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

### List Admin Tools Only
```bash
curl -X POST http://localhost:5000/mcp/admin \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

### List Weather Tools Only
```bash
curl -X POST http://localhost:5000/mcp/weather \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

### Call a Tool
```bash
curl -X POST http://localhost:5000/mcp/weather \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"Seattle"}}}'
```

## Expected Results

- **Global route** (`/mcp`): Returns all 9 tools
- **Admin route** (`/mcp/admin`): Returns 3 tools (2 admin + 1 global)
- **Weather route** (`/mcp/weather`): Returns 3 tools (2 weather + 1 global)
- **Math route** (`/mcp/math`): Returns 3 tools (2 math + 1 global)
- **Utilities route** (`/mcp/utilities`): Returns 4 tools (3 utility + 1 global)

## Implementation Details

### Key Configuration Changes

The routing feature requires two configuration changes:

```csharp
// Use routing-enabled transport
builder.Services.AddMcpServer()
.WithHttpTransportAndRouting() // Instead of .WithHttpTransport()

// Map with routing support
app.MapMcpWithRouting("mcp"); // Instead of app.MapMcp()
```

### Route Attribute Usage

```csharp
[McpServerTool, Description("Admin-only tool")]
[McpServerToolRoute("admin")] // Single route
public static string AdminTool() { ... }

[McpServerTool, Description("Multi-route tool")]
[McpServerToolRoute("weather", "utilities")] // Multiple routes
public static string UtilityTool() { ... }

[McpServerTool, Description("Global tool")]
// No [McpServerToolRoute] = available everywhere
public static string GlobalTool() { ... }
```

## Use Cases

This routing feature enables scenarios like:

- **Multi-agent system coordination**: Different agent types access specialized tool sets (research agents get web search, execution agents get file operations)
- **Context-aware tool separation**: Specialized agents with distinct purposes and capabilities working within the same MCP server
- **Agent workflow orchestration**: Route-specific tools for different phases of multi-step agent workflows
- **Specialized agent environments**: Domain-specific agents (coding, research, planning) each with their appropriate toolset
- **Agent collaboration patterns**: Enabling agent-to-agent handoffs with context-appropriate tools at each stage

## Key Files

- `Program.cs`: Server configuration with routing enabled
- `Tools/AdminTool.cs`: Administrative tools (admin route only)
- `Tools/EchoTool.cs`: Basic echo tools with route filtering
- `Tools/MathTool.cs`: Mathematical calculation tools
- `Tools/SampleLlmTool.cs`: Global tool (no route restriction)
- `Tools/WeatherTool.cs`: Weather-related tools
22 changes: 22 additions & 0 deletions samples/UrlRoutingSseServer/Tools/AdminTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace UrlRoutingSseServer.Tools;

[McpServerToolType]
public sealed class AdminTool
{
[McpServerTool, Description("Gets system status information - admin only.")]
[McpServerToolRoute("admin")]
public static string GetSystemStatus()
{
return $"System Status: Running | Uptime: {Environment.TickCount64 / 1000}s | Memory: {GC.GetTotalMemory(false) / 1024 / 1024}MB";
}

[McpServerTool, Description("Restarts a service - admin only.")]
[McpServerToolRoute("admin")]
public static string RestartService([Description("Name of the service to restart")] string serviceName)
{
return $"Service '{serviceName}' restart initiated (simulated)";
}
}
25 changes: 25 additions & 0 deletions samples/UrlRoutingSseServer/Tools/EchoTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace UrlRoutingSseServer.Tools;

[McpServerToolType]
public sealed class EchoTool
{
[McpServerTool, Description("Echoes the input back to the client.")]
[McpServerToolRoute("echo")]
public static string Echo(string message)
{
return "hello " + message;
}

[McpServerTool(Name = "echo_advanced"), Description("Advanced echo with formatting options.")]
[McpServerToolRoute("echo", "utilities")]
public static string EchoAdvanced(
[Description("The message to echo")] string message,
[Description("Whether to convert to uppercase")] bool uppercase = false)
{
var result = $"Advanced echo: {message}";
return uppercase ? result.ToUpper() : result;
}
}
32 changes: 32 additions & 0 deletions samples/UrlRoutingSseServer/Tools/MathTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace UrlRoutingSseServer.Tools;

[McpServerToolType]
public sealed class MathTool
{
[McpServerTool, Description("Adds two numbers together.")]
[McpServerToolRoute("math", "utilities")]
public static int Add(
[Description("First number")] int a,
[Description("Second number")] int b)
{
return a + b;
}

[McpServerTool, Description("Calculates factorial of a number.")]
[McpServerToolRoute("math")]
public static long Factorial([Description("Number to calculate factorial for")] int n)
{
if (n < 0) return -1;
if (n == 0 || n == 1) return 1;

long result = 1;
for (int i = 2; i <= n; i++)
{
result *= i;
}
return result;
}
}
36 changes: 36 additions & 0 deletions samples/UrlRoutingSseServer/Tools/SampleLlmTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.Extensions.AI;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace UrlRoutingSseServer.Tools;

/// <summary>
/// This tool uses dependency injection and async method
/// </summary>
[McpServerToolType]
public sealed class SampleLlmTool
{
[McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
public static async Task<string> SampleLLM(
IMcpServer thisServer,
[Description("The prompt to send to the LLM")] string prompt,
[Description("Maximum number of tokens to generate")] int maxTokens,
CancellationToken cancellationToken)
{
ChatMessage[] messages =
[
new(ChatRole.System, "You are a helpful test server."),
new(ChatRole.User, prompt),
];

ChatOptions options = new()
{
MaxOutputTokens = maxTokens,
Temperature = 0.7f,
};

var samplingResponse = await thisServer.AsSamplingChatClient().GetResponseAsync(messages, options, cancellationToken);

return $"LLM sampling result: {samplingResponse}";
}
}
26 changes: 26 additions & 0 deletions samples/UrlRoutingSseServer/Tools/WeatherTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace UrlRoutingSseServer.Tools;

[McpServerToolType]
public sealed class WeatherTool
{
[McpServerTool, Description("Gets current weather for a location.")]
[McpServerToolRoute("weather", "utilities")]
public static string GetWeather([Description("City name")] string city)
{
var temps = new[] { 72, 68, 75, 80, 77 };
var conditions = new[] { "Sunny", "Cloudy", "Rainy", "Partly Cloudy", "Clear" };
var random = new Random(city.GetHashCode()); // Deterministic based on city

return $"Weather in {city}: {temps[random.Next(temps.Length)]}°F, {conditions[random.Next(conditions.Length)]}";
}

[McpServerTool, Description("Gets 5-day weather forecast.")]
[McpServerToolRoute("weather")]
public static string GetForecast([Description("City name")] string city)
{
return $"5-day forecast for {city}: Mon 75°F, Tue 73°F, Wed 78°F, Thu 72°F, Fri 76°F";
}
}
21 changes: 21 additions & 0 deletions samples/UrlRoutingSseServer/UrlRoutingSseServer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>true</PublishAot>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ModelContextProtocol.AspNetCore\ModelContextProtocol.AspNetCore.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
</ItemGroup>

</Project>
8 changes: 8 additions & 0 deletions samples/UrlRoutingSseServer/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/UrlRoutingSseServer/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Loading