Skip to content

Commit 8414766

Browse files
committed
WIP Sample
1 parent 8ee8b5b commit 8414766

File tree

8 files changed

+164
-1
lines changed

8 files changed

+164
-1
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Collections.Concurrent;
2+
3+
namespace AspNetCoreMcpServer;
4+
5+
public class ApiKeyStore
6+
{
7+
private readonly ConcurrentDictionary<string, string> _keys = new();
8+
9+
public void SaveKey(string id, string key)
10+
{
11+
_keys[id] = key;
12+
}
13+
14+
public string? GetKey(string id)
15+
{
16+
return _keys.TryGetValue(id, out var key) ? key : null;
17+
}
18+
}

samples/AspNetCoreMcpServer/AspNetCoreMcpServer.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
<TargetFramework>net9.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
7-
<PublishAot>true</PublishAot>
87
</PropertyGroup>
98

109
<ItemGroup>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<base href="/" />
8+
<title>AspNetCoreMcpServer</title>
9+
<HeadOutlet />
10+
</head>
11+
12+
<body>
13+
<Routes />
14+
<script src="_framework/blazor.web.js"></script>
15+
</body>
16+
17+
</html>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@page "/apikey"
2+
@inject ApiKeyStore KeyStore
3+
4+
<h3>Enter API Key</h3>
5+
6+
@if (Submitted)
7+
{
8+
<p>API Key saved. You can close this window and return to the client.</p>
9+
}
10+
else
11+
{
12+
<form method="post" @formname="apikey-form" @onsubmit="Submit">
13+
<AntiforgeryToken />
14+
<input type="hidden" name="Id" value="@Id" />
15+
<div>
16+
<label>
17+
API Key:
18+
<input type="text" name="Key" @bind="Key" />
19+
</label>
20+
</div>
21+
<button type="submit">Submit</button>
22+
</form>
23+
}
24+
25+
@code {
26+
[SupplyParameterFromQuery]
27+
public string? Id { get; set; }
28+
29+
[SupplyParameterFromForm]
30+
public string? Key { get; set; }
31+
32+
public bool Submitted { get; set; }
33+
34+
private void Submit()
35+
{
36+
if (!string.IsNullOrEmpty(Id) && !string.IsNullOrEmpty(Key))
37+
{
38+
KeyStore.SaveKey(Id, Key);
39+
Submitted = true;
40+
}
41+
}
42+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Router AppAssembly="typeof(Program).Assembly">
2+
<Found Context="routeData">
3+
<RouteView RouteData="routeData" />
4+
</Found>
5+
</Router>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@using System.Net.Http
2+
@using System.Net.Http.Json
3+
@using Microsoft.AspNetCore.Components.Forms
4+
@using Microsoft.AspNetCore.Components.Routing
5+
@using Microsoft.AspNetCore.Components.Web
6+
@using Microsoft.JSInterop
7+
@using AspNetCoreMcpServer
8+
@using AspNetCoreMcpServer.Components

samples/AspNetCoreMcpServer/Program.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@
44
using AspNetCoreMcpServer.Tools;
55
using AspNetCoreMcpServer.Resources;
66
using System.Net.Http.Headers;
7+
using AspNetCoreMcpServer;
8+
using AspNetCoreMcpServer.Components;
9+
using ModelContextProtocol.Protocol;
710

811
var builder = WebApplication.CreateBuilder(args);
12+
13+
builder.Services.AddSingleton<ApiKeyStore>();
14+
builder.Services.AddRazorComponents();
15+
builder.Services.AddHttpContextAccessor();
16+
917
builder.Services.AddMcpServer()
1018
.WithHttpTransport()
19+
.WithTools<UrlElicitationTool>()
1120
.WithTools<EchoTool>()
1221
.WithTools<SampleLlmTool>()
1322
.WithTools<WeatherTools>()
@@ -30,8 +39,21 @@
3039
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
3140
});
3241

42+
builder.Services.AddCors(options =>
43+
{
44+
options.AddDefaultPolicy(policy =>
45+
{
46+
policy.AllowAnyOrigin()
47+
.AllowAnyHeader()
48+
.AllowAnyMethod();
49+
});
50+
});
51+
3352
var app = builder.Build();
3453

54+
app.UseAntiforgery();
55+
app.UseCors();
56+
app.MapRazorComponents<App>();
3557
app.MapMcp();
3658

3759
app.Run();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using ModelContextProtocol;
2+
using ModelContextProtocol.Protocol;
3+
using ModelContextProtocol.Server;
4+
using System.ComponentModel;
5+
6+
namespace AspNetCoreMcpServer.Tools;
7+
8+
[McpServerToolType]
9+
public class UrlElicitationTool(ApiKeyStore apiKeyStore, IHttpContextAccessor httpContextAccessor, ILogger<UrlElicitationTool> logger)
10+
{
11+
[McpServerTool, Description("Requests an API Key from the user via out-of-band elicitation.")]
12+
public async Task<string> NeedsApiKey(McpServer server, CancellationToken cancellationToken)
13+
{
14+
var httpContext = httpContextAccessor.HttpContext;
15+
if (httpContext == null)
16+
{
17+
throw new InvalidOperationException("No HttpContext available.");
18+
}
19+
20+
var elicitationId = Guid.NewGuid().ToString();
21+
var request = httpContext.Request;
22+
var baseUrl = $"{request.Scheme}://{request.Host}";
23+
var url = $"{baseUrl}/apikey?id={elicitationId}";
24+
25+
if (server.ClientCapabilities?.Elicitation?.Url is null)
26+
{
27+
//throw new McpException("Client does not support URL elicitation.");
28+
}
29+
30+
var result = await server.ElicitAsync(new ElicitRequestParams
31+
{
32+
Mode = "url",
33+
ElicitationId = elicitationId,
34+
Url = url,
35+
Message = "Please provide your API Key to continue."
36+
}, cancellationToken);
37+
38+
if (!result.IsAccepted)
39+
{
40+
throw new McpException("User declined to provide API Key.");
41+
}
42+
43+
var key = apiKeyStore.GetKey(elicitationId);
44+
if (key is null)
45+
{
46+
throw new McpException("User accepted but no key found.");
47+
}
48+
49+
logger.LogInformation("Received API Key for session: {SessionId}", server.SessionId);
50+
return "API Key received and stored.";
51+
}
52+
}

0 commit comments

Comments
 (0)