1
- using System . Text . Json ;
1
+ using System . Net ;
2
+ using System . Text . Json ;
2
3
using Microsoft . Extensions . Logging ;
3
4
using ModelContextProtocol . Protocol . Messages ;
4
5
using ModelContextProtocol . Utils . Json ;
9
10
namespace ModelContextProtocol . Protocol . Transport ;
10
11
11
12
/// <summary>
12
- /// Implements the MCP transport protocol over standard input/output streams .
13
+ /// Implements the MCP transport protocol using <see cref="HttpListener"/> .
13
14
/// </summary>
14
15
public sealed class HttpListenerSseServerTransport : TransportBase , IServerTransport
15
16
{
16
17
private readonly string _serverName ;
17
18
private readonly HttpListenerServerProvider _httpServerProvider ;
18
19
private readonly ILogger < HttpListenerSseServerTransport > _logger ;
19
- private readonly JsonSerializerOptions _jsonOptions ;
20
- private CancellationTokenSource ? _shutdownCts ;
21
-
20
+ private SseResponseStreamTransport ? _sseResponseStreamTransport ;
21
+
22
22
private string EndpointName => $ "Server (SSE) ({ _serverName } )";
23
23
24
24
/// <summary>
@@ -43,28 +43,23 @@ public HttpListenerSseServerTransport(string serverName, int port, ILoggerFactor
43
43
{
44
44
_serverName = serverName ;
45
45
_logger = loggerFactory . CreateLogger < HttpListenerSseServerTransport > ( ) ;
46
- _jsonOptions = McpJsonUtilities . DefaultOptions ;
47
- _httpServerProvider = new HttpListenerServerProvider ( port ) ;
46
+ _httpServerProvider = new HttpListenerServerProvider ( port )
47
+ {
48
+ OnSseConnectionAsync = OnSseConnectionAsync ,
49
+ OnMessageAsync = OnMessageAsync ,
50
+ } ;
48
51
}
49
52
50
53
/// <inheritdoc/>
51
54
public Task StartListeningAsync ( CancellationToken cancellationToken = default )
52
55
{
53
- _shutdownCts = new CancellationTokenSource ( ) ;
54
-
55
- _httpServerProvider . InitializeMessageHandler ( HttpMessageHandler ) ;
56
- _httpServerProvider . StartAsync ( cancellationToken ) ;
57
-
58
- SetConnected ( true ) ;
59
-
60
- return Task . CompletedTask ;
56
+ return _httpServerProvider . StartAsync ( cancellationToken ) ;
61
57
}
62
58
63
-
64
59
/// <inheritdoc/>
65
60
public override async Task SendMessageAsync ( IJsonRpcMessage message , CancellationToken cancellationToken = default )
66
61
{
67
- if ( ! IsConnected )
62
+ if ( ! IsConnected || _sseResponseStreamTransport is null )
68
63
{
69
64
_logger . TransportNotConnected ( EndpointName ) ;
70
65
throw new McpTransportException ( "Transport is not connected" ) ;
@@ -78,10 +73,13 @@ public override async Task SendMessageAsync(IJsonRpcMessage message, Cancellatio
78
73
79
74
try
80
75
{
81
- var json = JsonSerializer . Serialize ( message , _jsonOptions . GetTypeInfo < IJsonRpcMessage > ( ) ) ;
82
- _logger . TransportSendingMessage ( EndpointName , id , json ) ;
76
+ if ( _logger . IsEnabled ( LogLevel . Debug ) )
77
+ {
78
+ var json = JsonSerializer . Serialize ( message , McpJsonUtilities . DefaultOptions . GetTypeInfo < IJsonRpcMessage > ( ) ) ;
79
+ _logger . TransportSendingMessage ( EndpointName , id , json ) ;
80
+ }
83
81
84
- await _httpServerProvider . SendEvent ( json , "message" ) . ConfigureAwait ( false ) ;
82
+ await _sseResponseStreamTransport . SendMessageAsync ( message , cancellationToken ) . ConfigureAwait ( false ) ;
85
83
86
84
_logger . TransportSentMessage ( EndpointName , id ) ;
87
85
}
@@ -99,49 +97,61 @@ public override async ValueTask DisposeAsync()
99
97
GC . SuppressFinalize ( this ) ;
100
98
}
101
99
102
- private async Task CleanupAsync ( CancellationToken cancellationToken )
100
+ private Task CleanupAsync ( CancellationToken cancellationToken )
103
101
{
104
102
_logger . TransportCleaningUp ( EndpointName ) ;
105
103
106
- if ( _shutdownCts != null )
107
- {
108
- await _shutdownCts . CancelAsync ( ) . ConfigureAwait ( false ) ;
109
- _shutdownCts . Dispose ( ) ;
110
- _shutdownCts = null ;
111
- }
112
-
113
104
_httpServerProvider . Dispose ( ) ;
114
-
115
105
SetConnected ( false ) ;
106
+
116
107
_logger . TransportCleanedUp ( EndpointName ) ;
108
+ return Task . CompletedTask ;
109
+ }
110
+
111
+ private async Task OnSseConnectionAsync ( Stream responseStream , CancellationToken cancellationToken )
112
+ {
113
+ await using var sseResponseStreamTransport = new SseResponseStreamTransport ( responseStream ) ;
114
+ _sseResponseStreamTransport = sseResponseStreamTransport ;
115
+ SetConnected ( true ) ;
116
+ await sseResponseStreamTransport . RunAsync ( cancellationToken ) ;
117
117
}
118
118
119
119
/// <summary>
120
120
/// Handles HTTP messages received by the HTTP server provider.
121
121
/// </summary>
122
122
/// <returns>true if the message was accepted (return 202), false otherwise (return 400)</returns>
123
- private bool HttpMessageHandler ( string request , CancellationToken cancellationToken )
123
+ private async Task < bool > OnMessageAsync ( Stream requestStream , CancellationToken cancellationToken )
124
124
{
125
- _logger . TransportReceivedMessage ( EndpointName , request ) ;
125
+ string request ;
126
+ IJsonRpcMessage ? message = null ;
127
+
128
+ if ( _logger . IsEnabled ( LogLevel . Information ) )
129
+ {
130
+ using var reader = new StreamReader ( requestStream ) ;
131
+ request = await reader . ReadToEndAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
132
+ message = JsonSerializer . Deserialize ( request , McpJsonUtilities . DefaultOptions . GetTypeInfo < IJsonRpcMessage > ( ) ) ;
133
+
134
+ _logger . TransportReceivedMessage ( EndpointName , request ) ;
135
+ }
136
+ else
137
+ {
138
+ request = "(Enable information-level logs to see the request)" ;
139
+ }
126
140
127
141
try
128
142
{
129
- var message = JsonSerializer . Deserialize ( request , _jsonOptions . GetTypeInfo < IJsonRpcMessage > ( ) ) ;
143
+ message ??= await JsonSerializer . DeserializeAsync ( requestStream , McpJsonUtilities . DefaultOptions . GetTypeInfo < IJsonRpcMessage > ( ) ) ;
130
144
if ( message != null )
131
145
{
132
- // Fire-and-forget the message to the message channel
133
- Task . Run ( async ( ) =>
146
+ string messageId = "(no id)" ;
147
+ if ( message is IJsonRpcMessageWithId messageWithId )
134
148
{
135
- string messageId = "(no id)" ;
136
- if ( message is IJsonRpcMessageWithId messageWithId )
137
- {
138
- messageId = messageWithId . Id . ToString ( ) ;
139
- }
140
-
141
- _logger . TransportReceivedMessageParsed ( EndpointName , messageId ) ;
142
- await WriteMessageAsync ( message , cancellationToken ) . ConfigureAwait ( false ) ;
143
- _logger . TransportMessageWritten ( EndpointName , messageId ) ;
144
- } , cancellationToken ) ;
149
+ messageId = messageWithId . Id . ToString ( ) ;
150
+ }
151
+
152
+ _logger . TransportReceivedMessageParsed ( EndpointName , messageId ) ;
153
+ await WriteMessageAsync ( message , cancellationToken ) . ConfigureAwait ( false ) ;
154
+ _logger . TransportMessageWritten ( EndpointName , messageId ) ;
145
155
146
156
return true ;
147
157
}
0 commit comments