Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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