实现安全的MCP服务器
本文展示了如何使用OAuth和Entra ID实现安全的模型上下文协议(MCP)服务器。MCP服务器使用ASP.NET Core实现,并使用Microsoft Entra ID来保护API。使用Azure OpenAI和语义内核的ASP.NET Core应用程序用于实现代理访问的MCP客户端。
代码库:https://github.com/damienbod/McpSecurity
设置
解决方案在两个独立的ASP.NET Core服务中实现,并使用Azure OpenAI来实现代理和LLM处理。MCP服务器使用ModelContextProtocol.AspNetCore Nuget包与Microsoft.Identity.Web一起实现,以保护API(MCP服务器)。MCP客户端可以在ASP.NET Core Web应用程序中实现和使用。该客户端使用Microsoft Entra ID和Microsoft.Identity.Web Nuget包进行身份验证。客户端使用Azure OpenAI来处理提示。
实现安全的MCP服务器
使用ModelContextProtocol.AspNetCore Nuget包在.NET中设置MCP服务器相当简单。这提供了扩展方法,您只需要定义MCP服务器提供的MCP工具。MCP服务器是一个API,可以使用来自Microsoft Entra ID的委托访问令牌进行保护。
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
|
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);
var httpMcpServerUrl = builder.Configuration["HttpMcpServerUrl"];
builder.Services.AddAuthentication().AddMcp(options =>
{
options.ResourceMetadata = new()
{
Resource = new Uri(httpMcpServerUrl),
ResourceDocumentation = new Uri("https://mcpoauthsecurity-hag0drckepathyb6.westeurope-01.azurewebsites.net/health"),
ScopesSupported = ["mcp:tools"],
};
});
builder.Services.AddAuthorization();
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithTools<RandomNumberTools>()
.WithTools<DateTools>()
.WithTools<WeatherTools>();
// 添加CORS以支持浏览器中的HTTP传输
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddHttpClient();
// 更改作用域验证以确保仅使用委托访问令牌
builder.Services.AddAuthorizationBuilder()
.AddPolicy("mcp_tools", policy =>
policy.RequireClaim("http://schemas.microsoft.com/identity/claims/scope", "mcp:tools"));
|
身份验证和授权中间件可以添加到MCP端点,并要求具有所需scp声明和值的有效Microsoft Entra ID令牌才能使用此服务。“mcp_tools"策略用于强制执行此操作。
1
2
3
4
5
6
7
8
9
10
11
12
|
// 配置HTTP请求管道
app.UseHttpsRedirection();
// 启用CORS
app.UseCors();
app.MapGet("/health", () => $"Secure MCP server running deployed: UTC: {DateTime.UtcNow}, use /mcp path to use the tools");
app.UseAuthentication();
app.UseAuthorization();
app.MapMcp("/mcp").RequireAuthorization("mcp_tools");
|
在ASP.NET Core中实现MCP客户端
现在我们有了安全的MCP服务器,我们可以创建一个客户端来使用API。我使用ASP.NET Core和Azure OpenAI实现了第一个客户端。ASP.NET Core应用程序使用OpenID Connect代码流与PKCE进行保护。
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
|
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(["api://96b0f495-3b65-4c8f-a0c6-c3767c3365ed/mcp:tools"])
.AddInMemoryTokenCaches();
builder.Services.AddAuthorization(options =>
{
// 默认情况下,所有传入请求将根据默认策略进行授权
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI();
builder.Services.AddScoped<ChatService>();
var app = builder.Build();
// 配置HTTP请求管道
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapStaticAssets();
app.MapRazorPages()
.WithStaticAssets();
app.MapControllers();
app.Run();
|
ChatService
使用Microsoft.SemanticKernel Nuget包通过Azure OpenAI实现代理逻辑。使用HttpClient访问MCP服务器。委托访问令牌可以用作Bearer令牌,MCP服务器功能可以在代理逻辑中调用。
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
|
public class ChatService
{
private readonly IConfiguration _configuration;
private readonly ElicitationCoordinator _elicitationCoordinator;
private Kernel _kernel;
private IMcpClient _mcpClient = null!;
private bool _initialized;
private ApprovalMode _mode = ApprovalMode.Manual;
private readonly ITokenAcquisition _tokenAcquisition;
private PromptingService? _promptingService;
public ChatService(IConfiguration configuration, ElicitationCoordinator elicitationCoordinator, ITokenAcquisition tokenAcquisition)
{
_configuration = configuration;
_elicitationCoordinator = elicitationCoordinator;
var config = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
_kernel = SemanticKernelHelper.GetKernel(config);
_tokenAcquisition = tokenAcquisition;
}
public async Task EnsureSetupAsync(IHttpClientFactory clientFactory)
{
if (_initialized) return;
var accessToken = await _tokenAcquisition
.GetAccessTokenForUserAsync([_configuration["McpScope"]!]);
_mcpClient = await McpClientFactory.CreateAsync(CreateMcpTransport(clientFactory, accessToken), GetMcpOptions());
await _kernel.ImportMcpClientToolsAsync(_mcpClient);
_promptingService = new PromptingService(_kernel, autoInvoke: _mode == ApprovalMode.Elicitation);
_initialized = true;
}
private IClientTransport CreateMcpTransport(IHttpClientFactory clientFactory, string accessToken)
{
var httpClient = clientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var httpMcpServerUrl = _configuration["HttpMcpServerUrl"] ?? throw new ArgumentNullException("Configuration missing for HttpMcpServerUrl");
return new SseClientTransport(new() { Endpoint = new Uri(httpMcpServerUrl), Name = "Secure Client" }, httpClient);
}
}
|
可以从UI发送提示,并使用MCP服务器。我使用了一个简单的Razor页面应用程序来处理UI逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return OnGet();
}
_chatService.SetMode(SelectedMode);
await _chatService.EnsureSetupAsync(_clientFactory);
// 使用提示开始新的聊天
var response = await _chatService.BeginChatAsync(GetUserKey(), Prompt);
PromptResults = response.FinalAnswer;
PendingFunctions = response.PendingFunctions;
return Page();
}
|
注意事项
这种方法运行稳定,MCP服务器的作用域限制为用户身份,即委托访问令牌。Entra ID适用于没有云限制的企业类型解决方案。使用提供的Nuget包可以轻松实现Azure OpenAI。未使用OAuth DCR。在MCP服务器中验证作用域以防止客户端使用应用程序身份非常重要。