@@ -20,7 +20,7 @@ use server::McpServer;
2020use serde:: Deserialize ;
2121use serde_json:: json;
2222use std:: { collections:: HashMap , convert:: Infallible , sync:: Arc } ;
23- use tokio:: io:: { AsyncBufReadExt , AsyncWriteExt , BufReader } ;
23+ use tokio:: io:: { AsyncBufReadExt , AsyncReadExt , AsyncWriteExt , BufReader } ;
2424use tokio:: sync:: { Mutex , mpsc} ;
2525use tokio_stream:: { StreamExt , wrappers:: UnboundedReceiverStream } ;
2626use uuid:: Uuid ;
@@ -229,6 +229,49 @@ async fn health_check() -> impl IntoResponse {
229229 ( StatusCode :: OK , "OK" )
230230}
231231
232+ /// Whether the line is a Content-Length or Content-Type header (case-insensitive).
233+ fn is_stdio_header_line ( line : & str ) -> bool {
234+ let lower = line. to_ascii_lowercase ( ) ;
235+ lower. starts_with ( "content-length:" ) || lower. starts_with ( "content-type:" )
236+ }
237+
238+ /// Parse content-length value from a header line.
239+ fn parse_content_length ( line : & str ) -> Option < usize > {
240+ let lower = line. to_ascii_lowercase ( ) ;
241+ if lower. starts_with ( "content-length:" ) {
242+ line[ 15 ..] . trim ( ) . parse :: < usize > ( ) . ok ( )
243+ } else {
244+ None
245+ }
246+ }
247+
248+ /// Whether we used Content-Length framing or line-delimited.
249+ #[ derive( Copy , Clone ) ]
250+ enum StdioFrame {
251+ LineDelimited ,
252+ ContentLength ,
253+ }
254+
255+ async fn write_stdio_response (
256+ stdout : & mut tokio:: io:: Stdout ,
257+ response_json : & str ,
258+ frame : StdioFrame ,
259+ ) -> anyhow:: Result < ( ) > {
260+ match frame {
261+ StdioFrame :: LineDelimited => {
262+ stdout. write_all ( response_json. as_bytes ( ) ) . await ?;
263+ stdout. write_all ( b"\n " ) . await ?;
264+ }
265+ StdioFrame :: ContentLength => {
266+ let header = format ! ( "Content-Length: {}\r \n \r \n " , response_json. len( ) ) ;
267+ stdout. write_all ( header. as_bytes ( ) ) . await ?;
268+ stdout. write_all ( response_json. as_bytes ( ) ) . await ?;
269+ }
270+ }
271+ stdout. flush ( ) . await ?;
272+ Ok ( ( ) )
273+ }
274+
232275async fn run_stdio ( client : OpenPrClient ) -> anyhow:: Result < ( ) > {
233276 tracing:: info!( "MCP stdio transport started" ) ;
234277
@@ -245,41 +288,73 @@ async fn run_stdio(client: OpenPrClient) -> anyhow::Result<()> {
245288 break ;
246289 }
247290 Ok ( _) => {
248- let line = line. trim ( ) ;
249- if line . is_empty ( ) {
291+ let trimmed = line. trim ( ) ;
292+ if trimmed . is_empty ( ) {
250293 continue ;
251294 }
252295
253- tracing:: debug!( request = %line, "Received request" ) ;
296+ // Detect Content-Length framing (used by Codex, Claude Desktop)
297+ let ( payload, frame) = if is_stdio_header_line ( trimmed) {
298+ // Read headers until empty line
299+ let mut content_length: Option < usize > = parse_content_length ( trimmed) ;
300+ loop {
301+ let mut header_line = String :: new ( ) ;
302+ match reader. read_line ( & mut header_line) . await {
303+ Ok ( 0 ) => break ,
304+ Ok ( _) => {
305+ let ht = header_line. trim ( ) ;
306+ if ht. is_empty ( ) {
307+ break ; // End of headers
308+ }
309+ if content_length. is_none ( ) {
310+ content_length = parse_content_length ( ht) ;
311+ }
312+ }
313+ Err ( _) => break ,
314+ }
315+ }
316+ let cl = content_length. unwrap_or ( 0 ) ;
317+ if cl == 0 {
318+ continue ;
319+ }
320+ let mut body = vec ! [ 0u8 ; cl] ;
321+ if let Err ( e) = reader. read_exact ( & mut body) . await {
322+ tracing:: error!( error = %e, "Failed to read Content-Length body" ) ;
323+ continue ;
324+ }
325+ ( body, StdioFrame :: ContentLength )
326+ } else {
327+ // Line-delimited JSON
328+ ( trimmed. as_bytes ( ) . to_vec ( ) , StdioFrame :: LineDelimited )
329+ } ;
254330
255- let request: JsonRpcRequest = match serde_json:: from_str ( line ) {
331+ let request: JsonRpcRequest = match serde_json:: from_slice ( & payload ) {
256332 Ok ( req) => req,
257333 Err ( e) => {
258334 tracing:: error!( error = %e, "Failed to parse request" ) ;
259335 let error_response = JsonRpcResponse :: error (
260336 None ,
261337 protocol:: JsonRpcError :: parse_error ( format ! ( "Invalid JSON: {}" , e) ) ,
262338 ) ;
263- if let Ok ( response_json) = serde_json:: to_string ( & error_response) {
264- stdout. write_all ( response_json. as_bytes ( ) ) . await ?;
265- stdout. write_all ( b"\n " ) . await ?;
266- stdout. flush ( ) . await ?;
339+ if let Ok ( rj) = serde_json:: to_string ( & error_response) {
340+ let _ = write_stdio_response ( & mut stdout, & rj, frame) . await ;
267341 }
268342 continue ;
269343 }
270344 } ;
271345
346+ tracing:: debug!( method = %request. method, "Received request" ) ;
347+
272348 let response = server. handle_request ( request) . await ;
273349 let Some ( response) = response else {
274350 continue ;
275351 } ;
276352
277353 match serde_json:: to_string ( & response) {
278354 Ok ( response_json) => {
279- tracing:: debug!( response = %response_json, "Sending response" ) ;
280- stdout. write_all ( response_json. as_bytes ( ) ) . await ?;
281- stdout. write_all ( b"\n " ) . await ?;
282- stdout. flush ( ) . await ?;
355+ if let Err ( e) = write_stdio_response ( & mut stdout, & response_json, frame) . await {
356+ tracing:: error!( error = %e, "Failed to write response" ) ;
357+ }
283358 }
284359 Err ( e) => {
285360 tracing:: error!( error = %e, "Failed to serialize response" ) ;
0 commit comments