Add server-side Streamable HTTP transport support (#330)
- Changes IJsonRpcMessage to an abstract base class so RelatedTransport will always be available - Streamable HTTP supports multiple concurrent HTTP request with their own indpendent SSE response streams - RelatedTransport indicates the source or destination of the JsonRpcMessage - Changes the default RequestId to a JSON number for better compatibility with MCP servers in the wild
This commit is contained in:
parent
7d77be68cf
commit
7dcba357d4
5 changed files with 1637 additions and 395 deletions
|
|
@ -106,11 +106,11 @@ public class McpClientFactoryTests
|
|||
|
||||
private class NopTransport : ITransport, IClientTransport
|
||||
{
|
||||
private readonly Channel<IJsonRpcMessage> _channel = Channel.CreateUnbounded<IJsonRpcMessage>();
|
||||
private readonly Channel<JsonRpcMessage> _channel = Channel.CreateUnbounded<JsonRpcMessage>();
|
||||
|
||||
public bool IsConnected => true;
|
||||
|
||||
public ChannelReader<IJsonRpcMessage> MessageReader => _channel.Reader;
|
||||
public ChannelReader<JsonRpcMessage> MessageReader => _channel.Reader;
|
||||
|
||||
public Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default) => Task.FromResult<ITransport>(this);
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ public class McpClientFactoryTests
|
|||
|
||||
public string Name => "Test Nop Transport";
|
||||
|
||||
public virtual Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
public virtual Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
|
|
@ -148,7 +148,7 @@ public class McpClientFactoryTests
|
|||
{
|
||||
public const string ExpectedMessage = "Something failed";
|
||||
|
||||
public override Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
public override Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new InvalidOperationException(ExpectedMessage);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class NotificationHandlerTests : ClientServerTestBase
|
|||
}
|
||||
}
|
||||
|
||||
Assert.Equal(Iterations, counter);
|
||||
Assert.Equal(Iterations, counter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -619,7 +619,7 @@ public class McpServerTests : LoggedTest
|
|||
public McpServerOptions ServerOptions => throw new NotImplementedException();
|
||||
public IServiceProvider? Services => throw new NotImplementedException();
|
||||
public LoggingLevel? LoggingLevel => throw new NotImplementedException();
|
||||
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default) =>
|
||||
public Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
public Task RunAsync(CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ public class SseClientTransportTests : LoggedTest
|
|||
|
||||
await session.DisposeAsync();
|
||||
|
||||
Assert.False(session.IsConnected);
|
||||
var transportBase = Assert.IsAssignableFrom<TransportBase>(session);
|
||||
Assert.False(transportBase.IsConnected);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,19 +9,19 @@ namespace ModelContextProtocol.Tests.Utils;
|
|||
|
||||
public class TestServerTransport : ITransport
|
||||
{
|
||||
private readonly Channel<IJsonRpcMessage> _messageChannel;
|
||||
private readonly Channel<JsonRpcMessage> _messageChannel;
|
||||
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
public ChannelReader<IJsonRpcMessage> MessageReader => _messageChannel;
|
||||
public ChannelReader<JsonRpcMessage> MessageReader => _messageChannel;
|
||||
|
||||
public List<IJsonRpcMessage> SentMessages { get; } = [];
|
||||
public List<JsonRpcMessage> SentMessages { get; } = [];
|
||||
|
||||
public Action<IJsonRpcMessage>? OnMessageSent { get; set; }
|
||||
public Action<JsonRpcMessage>? OnMessageSent { get; set; }
|
||||
|
||||
public TestServerTransport()
|
||||
{
|
||||
_messageChannel = Channel.CreateUnbounded<IJsonRpcMessage>(new UnboundedChannelOptions
|
||||
_messageChannel = Channel.CreateUnbounded<JsonRpcMessage>(new UnboundedChannelOptions
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = true,
|
||||
|
|
@ -36,7 +36,7 @@ public class TestServerTransport : ITransport
|
|||
return default;
|
||||
}
|
||||
|
||||
public async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
public async Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
SentMessages.Add(message);
|
||||
if (message is JsonRpcRequest request)
|
||||
|
|
@ -77,7 +77,7 @@ public class TestServerTransport : ITransport
|
|||
}, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task WriteMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
private async Task WriteMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _messageChannel.Writer.WriteAsync(message, cancellationToken);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue