MCP 가 역시 핫하군요.
몇일전 EKS에 MCP Server를 올리는데 고생했던 부분을 공유합니다.
열림 10:09AM - 30 Jul 25 UTC
닫힘 06:52AM - 01 Aug 25 UTC
bug
**Describe the bug**
Deploying a simple McpServer on multiple hosts results in e… rrors despite Stateless option being active.
Concrete description of setup:
2 Application Servers behind a haproxy reverse proxy load balancer in round ribbon mode (no sticky sessions).
Each application server has the the same docker container running the asp.net mcpServer application (code below).
```csharp
namespace McpServerTesting;
using System.ComponentModel;
using Serilog;
using ModelContextProtocol.Server;
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Starting web host...");
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();
ConfigureServices(builder.Services, builder.Configuration, builder.Environment);
var app = builder.Build();
ConfigureMiddleware(app, builder.Configuration);
app.Run();
Log.Information("Web host stopped");
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
private static void ConfigureServices(IServiceCollection services, IConfiguration configuration, IHostEnvironment environment)
{
services.AddMcpServer()
.WithHttpTransport(opt => {
opt.Stateless = true;
})
.WithToolsFromAssembly();
}
private static void ConfigureMiddleware(WebApplication app, IConfiguration configuration)
{
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.MapMcp();
}
}
[McpServerToolType]
public class TestTool
{
[McpServerTool, Description("Retrieves the current UTC DateTime")]
public DateTime GetUtcDateTime()
{
return DateTime.UtcNow;
}
}
```
**To Reproduce**
Steps to reproduce the behavior:
1. Run the app as described on 2 separate application servers behind a loadbalancer.
2. Connect to the mcp server using mcp inspector (https://github.com/modelcontextprotocol/inspector) with httpStreaming transport
3. List the tools (this already works only every second time) -> when request goes to server inspector originally connected to
4. Execute the DateTime Tool (only works every second try) -> when request goes to server inspector originally connected to
**Expected behavior**
Using the stateless option when configuring the the mcpServer
` services.AddMcpServer()
.WithHttpTransport(opt => {
opt.Stateless = true;
})
.WithToolsFromAssembly();`
i expected no issues with the setup, as it should not matter which of the application servers the list or tool execution request goes to.
**Logs**
I can see this exception in my logs on the server the inspector did not originally connect to:
Message:
```
The key {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} was not found in the key ring. For more information go to https://aka.ms/aspnet/dataprotectionwarning
```
ErrorStack:
```
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData)
at Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions.Unprotect(IDataProtector protector, String protectedData)
at ModelContextProtocol.AspNetCore.StreamableHttpHandler.GetSessionAsync(HttpContext context, String sessionId)
at ModelContextProtocol.AspNetCore.StreamableHttpHandler.GetOrCreateSessionAsync(HttpContext context)
at ModelContextProtocol.AspNetCore.StreamableHttpHandler.HandlePostRequestAsync(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
```
**Additional context**
Am i missing some additional configuration to make the server truly stateless?
I also tried with a single deployed instance and have no issues there.
MCP Server .net sdk 는 stateless 옵션을 제공하지만 Microsoft.AspNetCore.DataProtection에 의존하여 세션을 관리합니다. POD이 2개 이상일 경우 오류가 발생하는데요. 백본으로 redis나 db가 반드시 있어야 보안 세션 key ring을 공유할 수 있도록 되어 있어서 stateless란 명칭이 올바른지 논쟁적인 부분이 존재하는 걸로 보입니다.
열림 07:01AM - 05 Apr 18 UTC
닫힘 11:43AM - 19 Apr 18 UTC
question
I just tried to spin up two instances of the identity sever. (yes load balancing… is setup) The documentation says that Identity server 4 is stateless so this should work.
> An unhandled exception occurred while processing the request.
> CryptographicException: The key {ec55dd66-7caf-4423-9dd6-74768e80675d} was not found in the key ring.
> Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, bool allowOperationsOnRevokedKeys, out UnprotectStatus status)
>
> InvalidOperationException: The antiforgery token could not be decrypted.
> Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(string serializedToken)
>
This makes me think the token was created on one instance and trying to be decrypted on the other.
https를 사용하고 간단하게 올려서 혼자 쓸거라 DataProtection을 끄는 방법을 GPT5에 물어보니 다음과 같은 유효한 방법을 알려 주었습니다.
...
builder.Services.AddSingleton<IDataProtectionProvider, NoOpDataProtectionProvider>();
...
public sealed class NoOpDataProtectionProvider : IDataProtectionProvider
{
private static readonly NoOpDataProtector _protector = new();
public IDataProtector CreateProtector(string purpose) => _protector;
private sealed class NoOpDataProtector : IDataProtector
{
public IDataProtector CreateProtector(string purpose) => this;
// 바이트 단위 Protect/Unprotect: 있는 그대로 반환
public byte[] Protect(byte[] plaintext) => plaintext ?? [];
public byte[] Unprotect(byte[] protectedData) => protectedData ?? [];
}
}
5개의 좋아요