Send MCP-Protocol-Version header in Streamable HTTP client transport (#500)
This commit is contained in:
parent
b9b4a23005
commit
befa31d5c9
1 changed files with 69 additions and 24 deletions
|
|
@ -26,6 +26,7 @@ internal sealed class StreamableHttpHandler(
|
|||
ILoggerFactory loggerFactory,
|
||||
IServiceProvider applicationServices)
|
||||
{
|
||||
private const string McpSessionIdHeaderName = "Mcp-Session-Id";
|
||||
private static readonly JsonTypeInfo<JsonRpcError> s_errorTypeInfo = GetRequiredJsonTypeInfo<JsonRpcError>();
|
||||
|
||||
public ConcurrentDictionary<string, HttpMcpSession<StreamableHttpServerTransport>> Sessions { get; } = new(StringComparer.Ordinal);
|
||||
|
|
@ -70,8 +71,8 @@ internal sealed class StreamableHttpHandler(
|
|||
}
|
||||
finally
|
||||
{
|
||||
// Stateless sessions are 1:1 with HTTP requests and are outlived by the MCP session tracked by the mcp-session-id.
|
||||
// Non-stateless sessions are 1:1 with the mcp-session-id and outlive the POST request.
|
||||
// Stateless sessions are 1:1 with HTTP requests and are outlived by the MCP session tracked by the Mcp-Session-Id.
|
||||
// Non-stateless sessions are 1:1 with the Mcp-Session-Id and outlive the POST request.
|
||||
// Non-stateless sessions get disposed by a DELETE request or the IdleTrackingBackgroundService.
|
||||
if (HttpServerTransportOptions.Stateless)
|
||||
{
|
||||
|
|
@ -90,7 +91,7 @@ internal sealed class StreamableHttpHandler(
|
|||
return;
|
||||
}
|
||||
|
||||
var sessionId = context.Request.Headers["mcp-session-id"].ToString();
|
||||
var sessionId = context.Request.Headers[McpSessionIdHeaderName].ToString();
|
||||
var session = await GetSessionAsync(context, sessionId);
|
||||
if (session is null)
|
||||
{
|
||||
|
|
@ -117,7 +118,7 @@ internal sealed class StreamableHttpHandler(
|
|||
|
||||
public async Task HandleDeleteRequestAsync(HttpContext context)
|
||||
{
|
||||
var sessionId = context.Request.Headers["mcp-session-id"].ToString();
|
||||
var sessionId = context.Request.Headers[McpSessionIdHeaderName].ToString();
|
||||
if (Sessions.TryRemove(sessionId, out var session))
|
||||
{
|
||||
await session.DisposeAsync();
|
||||
|
|
@ -157,14 +158,14 @@ internal sealed class StreamableHttpHandler(
|
|||
return null;
|
||||
}
|
||||
|
||||
context.Response.Headers["mcp-session-id"] = session.Id;
|
||||
context.Response.Headers[McpSessionIdHeaderName] = session.Id;
|
||||
context.Features.Set(session.Server);
|
||||
return session;
|
||||
}
|
||||
|
||||
private async ValueTask<HttpMcpSession<StreamableHttpServerTransport>?> GetOrCreateSessionAsync(HttpContext context)
|
||||
{
|
||||
var sessionId = context.Request.Headers["mcp-session-id"].ToString();
|
||||
var sessionId = context.Request.Headers[McpSessionIdHeaderName].ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(sessionId))
|
||||
{
|
||||
|
|
@ -188,11 +189,11 @@ internal sealed class StreamableHttpHandler(
|
|||
{
|
||||
SessionId = sessionId,
|
||||
};
|
||||
context.Response.Headers["mcp-session-id"] = sessionId;
|
||||
context.Response.Headers[McpSessionIdHeaderName] = sessionId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// "(uninitialized stateless id)" is not written anywhere. We delay writing the mcp-session-id
|
||||
// "(uninitialized stateless id)" is not written anywhere. We delay writing the MCP-Session-Id
|
||||
// until after we receive the initialize request with the client info we need to serialize.
|
||||
sessionId = "(uninitialized stateless id)";
|
||||
transport = new()
|
||||
|
|
@ -204,7 +205,7 @@ internal sealed class StreamableHttpHandler(
|
|||
|
||||
var session = await CreateSessionAsync(context, transport, sessionId);
|
||||
|
||||
// The HttpMcpSession is not stored between requests in stateless mode. Instead, the session is recreated from the mcp-session-id.
|
||||
// The HttpMcpSession is not stored between requests in stateless mode. Instead, the session is recreated from the MCP-Session-Id.
|
||||
if (!HttpServerTransportOptions.Stateless)
|
||||
{
|
||||
if (!Sessions.TryAdd(sessionId, session))
|
||||
|
|
@ -299,7 +300,7 @@ internal sealed class StreamableHttpHandler(
|
|||
|
||||
var sessionJson = JsonSerializer.Serialize(statelessId, StatelessSessionIdJsonContext.Default.StatelessSessionId);
|
||||
transport.SessionId = Protector.Protect(sessionJson);
|
||||
context.Response.Headers["mcp-session-id"] = transport.SessionId;
|
||||
context.Response.Headers[McpSessionIdHeaderName] = transport.SessionId;
|
||||
return ValueTask.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue