Improved samples: added weather tool in AspNetCoreSseServer and option to connect via http to AspNetCoreSseServer. (#469)
This commit is contained in:
parent
f9129f5b28
commit
0cf449eaca
4 changed files with 117 additions and 16 deletions
|
|
@ -3,12 +3,14 @@ using OpenTelemetry.Metrics;
|
||||||
using OpenTelemetry.Trace;
|
using OpenTelemetry.Trace;
|
||||||
using AspNetCoreMcpServer.Tools;
|
using AspNetCoreMcpServer.Tools;
|
||||||
using AspNetCoreMcpServer.Resources;
|
using AspNetCoreMcpServer.Resources;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport()
|
.WithHttpTransport()
|
||||||
.WithTools<EchoTool>()
|
.WithTools<EchoTool>()
|
||||||
.WithTools<SampleLlmTool>()
|
.WithTools<SampleLlmTool>()
|
||||||
|
.WithTools<WeatherTools>()
|
||||||
.WithResources<SimpleResourceType>();
|
.WithResources<SimpleResourceType>();
|
||||||
|
|
||||||
builder.Services.AddOpenTelemetry()
|
builder.Services.AddOpenTelemetry()
|
||||||
|
|
@ -21,6 +23,13 @@ builder.Services.AddOpenTelemetry()
|
||||||
.WithLogging()
|
.WithLogging()
|
||||||
.UseOtlpExporter();
|
.UseOtlpExporter();
|
||||||
|
|
||||||
|
// Configure HttpClientFactory for weather.gov API
|
||||||
|
builder.Services.AddHttpClient("WeatherApi", client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri("https://api.weather.gov");
|
||||||
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
|
||||||
|
});
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.MapMcp();
|
app.MapMcp();
|
||||||
|
|
|
||||||
73
samples/AspNetCoreMcpServer/Tools/WeatherTools.cs
Normal file
73
samples/AspNetCoreMcpServer/Tools/WeatherTools.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
using ModelContextProtocol;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace AspNetCoreMcpServer.Tools;
|
||||||
|
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class WeatherTools
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
|
public WeatherTools(IHttpClientFactory httpClientFactory)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool, Description("Get weather alerts for a US state.")]
|
||||||
|
public async Task<string> GetAlerts(
|
||||||
|
[Description("The US state to get alerts for. Use the 2 letter abbreviation for the state (e.g. NY).")] string state)
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient("WeatherApi");
|
||||||
|
using var responseStream = await client.GetStreamAsync($"/alerts/active/area/{state}");
|
||||||
|
using var jsonDocument = await JsonDocument.ParseAsync(responseStream)
|
||||||
|
?? throw new McpException("No JSON returned from alerts endpoint");
|
||||||
|
|
||||||
|
var alerts = jsonDocument.RootElement.GetProperty("features").EnumerateArray();
|
||||||
|
|
||||||
|
if (!alerts.Any())
|
||||||
|
{
|
||||||
|
return "No active alerts for this state.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join("\n--\n", alerts.Select(alert =>
|
||||||
|
{
|
||||||
|
JsonElement properties = alert.GetProperty("properties");
|
||||||
|
return $"""
|
||||||
|
Event: {properties.GetProperty("event").GetString()}
|
||||||
|
Area: {properties.GetProperty("areaDesc").GetString()}
|
||||||
|
Severity: {properties.GetProperty("severity").GetString()}
|
||||||
|
Description: {properties.GetProperty("description").GetString()}
|
||||||
|
Instruction: {properties.GetProperty("instruction").GetString()}
|
||||||
|
""";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[McpServerTool, Description("Get weather forecast for a location.")]
|
||||||
|
public async Task<string> GetForecast(
|
||||||
|
[Description("Latitude of the location.")] double latitude,
|
||||||
|
[Description("Longitude of the location.")] double longitude)
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient("WeatherApi");
|
||||||
|
var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
|
||||||
|
|
||||||
|
using var locationResponseStream = await client.GetStreamAsync(pointUrl);
|
||||||
|
using var locationDocument = await JsonDocument.ParseAsync(locationResponseStream);
|
||||||
|
var forecastUrl = locationDocument?.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
|
||||||
|
?? throw new McpException($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
|
||||||
|
|
||||||
|
using var forecastResponseStream = await client.GetStreamAsync(forecastUrl);
|
||||||
|
using var forecastDocument = await JsonDocument.ParseAsync(forecastResponseStream);
|
||||||
|
var periods = forecastDocument?.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray()
|
||||||
|
?? throw new McpException("No JSON returned from forecast endpoint");
|
||||||
|
|
||||||
|
return string.Join("\n---\n", periods.Select(period => $"""
|
||||||
|
{period.GetProperty("name").GetString()}
|
||||||
|
Temperature: {period.GetProperty("temperature").GetInt32()}°F
|
||||||
|
Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
|
||||||
|
Forecast: {period.GetProperty("detailedForecast").GetString()}
|
||||||
|
"""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Hosting;
|
||||||
using ModelContextProtocol.Client;
|
using ModelContextProtocol.Client;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
var builder = Host.CreateApplicationBuilder(args);
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
|
|
||||||
|
|
@ -12,16 +13,27 @@ builder.Configuration
|
||||||
.AddEnvironmentVariables()
|
.AddEnvironmentVariables()
|
||||||
.AddUserSecrets<Program>();
|
.AddUserSecrets<Program>();
|
||||||
|
|
||||||
|
IClientTransport clientTransport;
|
||||||
var (command, arguments) = GetCommandAndArguments(args);
|
var (command, arguments) = GetCommandAndArguments(args);
|
||||||
|
|
||||||
var clientTransport = new StdioClientTransport(new()
|
if (command == "http")
|
||||||
{
|
{
|
||||||
Name = "Demo Server",
|
// make sure AspNetCoreMcpServer is running
|
||||||
Command = command,
|
clientTransport = new SseClientTransport(new()
|
||||||
Arguments = arguments,
|
{
|
||||||
});
|
Endpoint = new Uri("http://localhost:3001")
|
||||||
|
});
|
||||||
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clientTransport = new StdioClientTransport(new()
|
||||||
|
{
|
||||||
|
Name = "Demo Server",
|
||||||
|
Command = command,
|
||||||
|
Arguments = arguments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport!);
|
||||||
|
|
||||||
var tools = await mcpClient.ListToolsAsync();
|
var tools = await mcpClient.ListToolsAsync();
|
||||||
foreach (var tool in tools)
|
foreach (var tool in tools)
|
||||||
|
|
@ -46,6 +58,9 @@ Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.WriteLine("MCP Client Started!");
|
Console.WriteLine("MCP Client Started!");
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
|
|
||||||
|
var messages = new List<ChatMessage>();
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
PromptForInput();
|
PromptForInput();
|
||||||
while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
|
while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
@ -55,11 +70,17 @@ while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparis
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
|
messages.Add(new ChatMessage(ChatRole.User, query));
|
||||||
|
await foreach (var message in anthropicClient.GetStreamingResponseAsync(messages, options))
|
||||||
{
|
{
|
||||||
Console.Write(message);
|
Console.Write(message);
|
||||||
|
sb.Append(message.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
sb.AppendLine();
|
||||||
|
messages.Add(new ChatMessage(ChatRole.Assistant, sb.ToString()));
|
||||||
|
sb.Clear();
|
||||||
|
|
||||||
PromptForInput();
|
PromptForInput();
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +109,7 @@ static (string command, string[] arguments) GetCommandAndArguments(string[] args
|
||||||
{
|
{
|
||||||
return args switch
|
return args switch
|
||||||
{
|
{
|
||||||
|
[var mode] when mode.Equals("http", StringComparison.OrdinalIgnoreCase) => ("http", args),
|
||||||
[var script] when script.EndsWith(".py") => ("python", args),
|
[var script] when script.EndsWith(".py") => ("python", args),
|
||||||
[var script] when script.EndsWith(".js") => ("node", args),
|
[var script] when script.EndsWith(".js") => ("node", args),
|
||||||
[var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script]),
|
[var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script]),
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,8 @@ builder.Logging.AddConsole(options =>
|
||||||
options.LogToStandardErrorThreshold = LogLevel.Trace;
|
options.LogToStandardErrorThreshold = LogLevel.Trace;
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddSingleton(_ =>
|
using var httpClient = new HttpClient { BaseAddress = new Uri("https://api.weather.gov") };
|
||||||
{
|
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
|
||||||
var client = new HttpClient { BaseAddress = new Uri("https://api.weather.gov") };
|
builder.Services.AddSingleton(httpClient);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
|
|
||||||
return client;
|
|
||||||
});
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue