-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
101 lines (85 loc) · 3.68 KB
/
Copy pathProgram.cs
File metadata and controls
101 lines (85 loc) · 3.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using Sample.Mcp;
using Shiny.Net.HttpServer;
using Shiny.Net.HttpServer.Mcp;
// ---------------------------------------------------------------------------
// An MCP server, hosted on Shiny.Net.HttpServer rather than ASP.NET Core.
//
// The point of the exercise: this same file runs unchanged inside a .NET MAUI app, where
// ASP.NET Core cannot go. The tools below then have something worth exposing — the device the
// app is running on.
//
// dotnet run --project samples/Sample.Mcp
// npx @modelcontextprotocol/inspector # then connect to http://localhost:8181/mcp
// ---------------------------------------------------------------------------
var builder = HttpServer.CreateBuilder();
builder.Configure(o =>
{
o.Port = 8181;
o.HideExceptionDetails = false;
});
builder.Services.AddLogging(l => l.AddSimpleConsole(c => c.SingleLine = true).SetMinimumLevel(LogLevel.Information));
// State the tools mutate, to make the difference between a session and a stateless call visible.
builder.Services.AddSingleton<Thermostat>();
builder.Services
.AddMcpServer(o =>
{
o.ServerInfo = new Implementation { Name = "sample-thermostat", Version = "1.0.0" };
o.ServerInstructions = "Reads and adjusts a thermostat. Temperatures are in Celsius.";
})
.WithTools<ThermostatTools>()
.WithHttpTransport(o =>
{
// Nothing else is allowed to reach this from a browser. Without an allow-list, any page the
// user happens to have open could drive a server bound to their own machine.
o.AllowedOrigins.Add("http://localhost:6274"); // the MCP Inspector's dev server
o.IdleSessionTimeout = TimeSpan.FromMinutes(10);
});
var app = builder.Build();
app.MapMcp();
// The MCP endpoint is just another route: everything else the server does still works alongside it.
app.MapGet("/health", ctx => ctx.Response.WriteAsync("ok"));
Console.WriteLine("MCP endpoint: http://localhost:8181/mcp");
Console.WriteLine("Press Ctrl+C to stop.");
using var stopping = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
stopping.Cancel();
};
await app.RunAsync(stopping.Token);
namespace Sample.Mcp
{
/// <summary>The thing being exposed. In a real app this is the device, not a field.</summary>
public sealed class Thermostat
{
public double Target { get; set; } = 20.5;
public double Current => this.Target - 0.4;
}
/// <summary>
/// Tools are ordinary methods. Constructor and parameter injection work exactly as they do in
/// an endpoint class, because the MCP server is created from the HTTP server's own container.
/// </summary>
[McpServerToolType]
public sealed class ThermostatTools
{
[McpServerTool(Name = "get_temperature"), Description("Reads the current and target temperature in Celsius.")]
public static string GetTemperature(Thermostat thermostat)
=> $"Currently {thermostat.Current:0.0}°C, set to {thermostat.Target:0.0}°C.";
[McpServerTool(Name = "set_temperature"), Description("Sets the target temperature in Celsius.")]
public static string SetTemperature(
Thermostat thermostat,
[Description("Target temperature, between 5 and 30 degrees Celsius.")] double celsius
)
{
if (celsius is < 5 or > 30)
return "Refused: pick something between 5 and 30°C.";
thermostat.Target = celsius;
return $"Target set to {celsius:0.0}°C.";
}
}
}