Repository navigation
Expand file tree
/
Copy pathProgram.cs
More file actions
102 lines (86 loc) · 4.26 KB
/
Copy pathProgram.cs
File metadata and controls
102 lines (86 loc) · 4.26 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
102
using Bit.Brouter.Demo.Server.Components;
using Bit.Brouter.Demo.Server.Controllers;
using Bit.Brouter.Demo.Server.Services;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.HttpOverrides;
using ModelContextProtocol.Protocol;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
// The prerender pass instantiates the client's components in this container, so it has to
// register the very same services the WebAssembly container does.
builder.Services.AddDemoServices();
// The MCP server (Controllers/McpController.cs) and the plain HTTP endpoints that mirror it. A
// member with nothing in it is left out of the JSON, exactly as the MCP transport leaves it out, so
// the browser view of a tool is the answer an agent gets rather than that answer plus a column of
// nulls - and a test comparing the two surfaces compares like with like.
builder.Services.AddControllers()
.AddJsonOptions(options => options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull);
builder.Services.AddMcpServer(options =>
{
// The name a person sees in their client's server list, and the version they would quote in a
// bug report - the library's, not this web app's, because the library is what the answers are about.
options.ServerInfo = new Implementation
{
Name = "bit-brouter",
Title = "Bit.Brouter",
Version = BrouterServerInstructions.BrouterVersion,
Description = "Documentation, public API and route-template analysis for the Bit.Brouter router for Blazor.",
WebsiteUrl = "https://github.com/bitfoundation/bitplatform/tree/develop/src/Brouter"
};
// Returned from `initialize` and put in front of the model before it has called anything, which
// makes it the only text here that reaches an agent confident enough not to look. See the type.
options.ServerInstructions = BrouterServerInstructions.Text;
})
.WithHttpTransport()
.WithToolsFromAssembly()
.WithResourcesFromAssembly()
.WithPromptsFromAssembly()
// Every argument this server takes is a key into a closed set, and none of those sets is
// guessable from outside: without this, choosing a docs slug in a client means going and
// calling a listing tool first, just to learn what a slug looks like.
.WithCompleteHandler((request, cancellationToken) => ValueTask.FromResult(new CompleteResult
{
Completion = BrouterCompletions.Complete(request.Params?.Argument?.Name, request.Params?.Argument?.Value)
}))
// Additive to the attributed resources, not a replacement for them: a template cannot be listed
// as something to click, so the documentation pages are enumerated one by one as well.
.WithListResourcesHandler((request, cancellationToken) => ValueTask.FromResult(new ListResourcesResult
{
Resources = [.. McpResources.ListDocumentationPages()]
}));
// Renders a docs page outside of a request's component hierarchy, so its content can be handed to
// an MCP client as text. Scoped: a renderer belongs to the request that asked for the page.
builder.Services.AddScoped<HtmlRenderer>();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.All;
options.ForwardedHostHeaderName = "X-Host";
options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
options.ForwardLimit = 1;
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
// Both are declared before the catch-all host page below: /api/... and /mcp are literal routes, so
// they win over "/{*path}" regardless of order, but keeping them first says so out loud.
app.MapControllers();
app.MapMcp("/mcp");
// Components/Pages/Host.razor is a catch-all page, so every deep link matches an endpoint and
// gets the prerendered app back - Brouter then resolves the real route (including its own 404).
app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode();
app.Run();