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:
Stephen Halter 2025-04-18 17:07:23 -07:00 • committed by GitHub
commit 7dcba357d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 1637 additions and 395 deletions

View file

@ -27,7 +27,11 @@ public class KestrelInMemoryTest : LoggedTest
var connection = _inMemoryTransport.CreateConnection();
return new(connection.ClientStream);
},
});
})
{
BaseAddress = new Uri("http://localhost:5000/"),
Timeout = TimeSpan.FromSeconds(10),
};
}
public WebApplicationBuilder Builder { get; }

View file

@ -8,19 +8,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,
@ -35,7 +35,7 @@ public class TestServerTransport : ITransport
return ValueTask.CompletedTask;
}
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)
@ -76,7 +76,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);
}