Pass session id's to MCP endpoints. (#466)
* Pass session id's to MCP endpoints. * Update src/ModelContextProtocol/Client/McpClient.cs Co-authored-by: Stephen Halter <halter73@gmail.com> * Update src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs Co-authored-by: Stephen Halter <halter73@gmail.com> * Make init callback asynchronous. * Address feedback. --------- Co-authored-by: Stephen Halter <halter73@gmail.com>
This commit is contained in:
parent
fa017c04c3
commit
d7d9eab3f8
5 changed files with 166 additions and 21 deletions
|
|
@ -6,6 +6,7 @@ namespace ModelContextProtocol.Server;
|
|||
internal sealed class DestinationBoundMcpServer(McpServer server, ITransport? transport) : IMcpServer
|
||||
{
|
||||
public string EndpointName => server.EndpointName;
|
||||
public string? SessionId => transport?.SessionId ?? server.SessionId;
|
||||
public ClientCapabilities? ClientCapabilities => server.ClientCapabilities;
|
||||
public Implementation? ClientInfo => server.ClientInfo;
|
||||
public McpServerOptions ServerOptions => server.ServerOptions;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,10 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
InitializeSession(transport);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? SessionId => _sessionTransport.SessionId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ServerCapabilities ServerCapabilities { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ namespace ModelContextProtocol.Server;
|
|||
/// These messages should be passed to <see cref="OnMessageReceivedAsync(JsonRpcMessage, CancellationToken)"/>.
|
||||
/// Defaults to "/message".
|
||||
/// </param>
|
||||
public sealed class SseResponseStreamTransport(Stream sseResponseStream, string? messageEndpoint = "/message") : ITransport
|
||||
/// <param name="sessionId">The identifier corresponding to the current MCP session.</param>
|
||||
public sealed class SseResponseStreamTransport(Stream sseResponseStream, string? messageEndpoint = "/message", string? sessionId = null) : ITransport
|
||||
{
|
||||
private readonly SseWriter _sseWriter = new(messageEndpoint);
|
||||
private readonly Channel<JsonRpcMessage> _incomingChannel = Channel.CreateBounded<JsonRpcMessage>(new BoundedChannelOptions(1)
|
||||
|
|
@ -49,6 +50,9 @@ public sealed class SseResponseStreamTransport(Stream sseResponseStream, string?
|
|||
/// <inheritdoc/>
|
||||
public ChannelReader<JsonRpcMessage> MessageReader => _incomingChannel.Reader;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? SessionId { get; } = sessionId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ internal sealed class StreamableHttpPostTransport(StreamableHttpServerTransport
|
|||
|
||||
public ChannelReader<JsonRpcMessage> MessageReader => throw new NotSupportedException("JsonRpcMessage.RelatedTransport should only be used for sending messages.");
|
||||
|
||||
string? ITransport.SessionId => parentTransport.SessionId;
|
||||
|
||||
/// <returns>
|
||||
/// True, if data was written to the respond body.
|
||||
/// False, if nothing was written because the request body did not contain any <see cref="JsonRpcRequest"/> messages to respond to.
|
||||
|
|
@ -79,10 +81,11 @@ internal sealed class StreamableHttpPostTransport(StreamableHttpServerTransport
|
|||
{
|
||||
_pendingRequest = request.Id;
|
||||
|
||||
// Store client capabilities so they can be serialized by "stateless" callers for use in later requests.
|
||||
if (parentTransport.Stateless && request.Method == RequestMethods.Initialize)
|
||||
// Invoke the initialize request callback if applicable.
|
||||
if (parentTransport.OnInitRequestReceived is { } onInitRequest && request.Method == RequestMethods.Initialize)
|
||||
{
|
||||
parentTransport.InitializeRequest = JsonSerializer.Deserialize(request.Params, McpJsonUtilities.JsonContext.Default.InitializeRequestParams);
|
||||
var initializeRequest = JsonSerializer.Deserialize(request.Params, McpJsonUtilities.JsonContext.Default.InitializeRequestParams);
|
||||
await onInitRequest(initializeRequest).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,15 +46,18 @@ public sealed class StreamableHttpServerTransport : ITransport
|
|||
public bool Stateless { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initialize request if it was received by <see cref="HandlePostRequest(IDuplexPipe, CancellationToken)"/> and <see cref="Stateless"/> is set to <see langword="true"/>.
|
||||
/// Gets or sets a callback to be invoked before handling the initialize request.
|
||||
/// </summary>
|
||||
public InitializeRequestParams? InitializeRequest { get; internal set; }
|
||||
public Func<InitializeRequestParams?, ValueTask>? OnInitRequestReceived { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReader<JsonRpcMessage> MessageReader => _incomingChannel.Reader;
|
||||
|
||||
internal ChannelWriter<JsonRpcMessage> MessageWriter => _incomingChannel.Writer;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles an optional SSE GET request a client using the Streamable HTTP transport might make by
|
||||
/// writing any unsolicited JSON-RPC messages sent via <see cref="SendMessageAsync"/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue