55package mcp
66
77import (
8+ "bufio"
9+ "bytes"
810 "context"
911 "encoding/json"
1012 "errors"
@@ -111,11 +113,15 @@ type serverConnection interface {
111113
112114// A StdioTransport is a [Transport] that communicates over stdin/stdout using
113115// newline-delimited JSON.
114- type StdioTransport struct {}
116+ type StdioTransport struct {
117+ // MaxMessageBytes, if positive, rejects incoming JSON-RPC message payloads
118+ // larger than this many bytes. The line ending is not counted.
119+ MaxMessageBytes int64
120+ }
115121
116122// Connect implements the [Transport] interface.
117- func (* StdioTransport ) Connect (context.Context ) (Connection , error ) {
118- return newIOConn (rwc {os .Stdin , nopCloserWriter {os .Stdout }}), nil
123+ func (t * StdioTransport ) Connect (context.Context ) (Connection , error ) {
124+ return newIOConnWithOptions (rwc {os .Stdin , nopCloserWriter {os .Stdout }}, t . MaxMessageBytes ), nil
119125}
120126
121127// nopCloserWriter is an io.WriteCloser with a trivial Close method.
@@ -130,11 +136,14 @@ func (nopCloserWriter) Close() error { return nil }
130136type IOTransport struct {
131137 Reader io.ReadCloser
132138 Writer io.WriteCloser
139+ // MaxMessageBytes, if positive, rejects incoming JSON-RPC message payloads
140+ // larger than this many bytes. The line ending is not counted.
141+ MaxMessageBytes int64
133142}
134143
135144// Connect implements the [Transport] interface.
136145func (t * IOTransport ) Connect (context.Context ) (Connection , error ) {
137- return newIOConn (rwc {t .Reader , t .Writer }), nil
146+ return newIOConnWithOptions (rwc {t .Reader , t .Writer }, t . MaxMessageBytes ), nil
138147}
139148
140149// An InMemoryTransport is a [Transport] that communicates over an in-memory
@@ -405,6 +414,10 @@ type msgOrErr struct {
405414}
406415
407416func newIOConn (rwc io.ReadWriteCloser ) * ioConn {
417+ return newIOConnWithOptions (rwc , 0 )
418+ }
419+
420+ func newIOConnWithOptions (rwc io.ReadWriteCloser , maxMessageBytes int64 ) * ioConn {
408421 var (
409422 incoming = make (chan msgOrErr )
410423 closed = make (chan struct {})
@@ -416,24 +429,9 @@ func newIOConn(rwc io.ReadWriteCloser) *ioConn {
416429 // but that is unavoidable since AFAIK there is no (easy and portable) way to
417430 // guarantee that reads of stdin are unblocked when closed.
418431 go func () {
419- dec := json . NewDecoder (rwc )
432+ reader := bufio . NewReader (rwc )
420433 for {
421- var raw json.RawMessage
422- err := dec .Decode (& raw )
423- // If decoding was successful, check for trailing data at the end of the stream.
424- if err == nil {
425- // Read the next byte to check if there is trailing data.
426- var tr [1 ]byte
427- if n , readErr := dec .Buffered ().Read (tr [:]); n > 0 {
428- // If read byte is not a newline, it is an error.
429- // Support both Unix (\n) and Windows (\r\n) line endings.
430- if tr [0 ] != '\n' && tr [0 ] != '\r' {
431- err = fmt .Errorf ("invalid trailing data at the end of stream" )
432- }
433- } else if readErr != nil && readErr != io .EOF {
434- err = readErr
435- }
436- }
434+ raw , err := readFrame (reader , maxMessageBytes )
437435 select {
438436 case incoming <- msgOrErr {msg : raw , err : err }:
439437 case <- closed :
@@ -451,6 +449,66 @@ func newIOConn(rwc io.ReadWriteCloser) *ioConn {
451449 }
452450}
453451
452+ func readFrame (reader * bufio.Reader , maxMessageBytes int64 ) (json.RawMessage , error ) {
453+ var frame []byte
454+ for {
455+ part , err := reader .ReadSlice ('\n' )
456+ frame = append (frame , part ... )
457+ switch {
458+ case err == nil :
459+ frame = trimLineEnding (frame )
460+ if maxMessageBytes > 0 && int64 (len (frame )) > maxMessageBytes {
461+ return nil , fmt .Errorf ("JSON-RPC message exceeds maximum size of %d bytes" , maxMessageBytes )
462+ }
463+ if err := validateJSONFrame (frame ); err != nil {
464+ return nil , err
465+ }
466+ return json .RawMessage (frame ), nil
467+ case errors .Is (err , bufio .ErrBufferFull ):
468+ if maxMessageBytes > 0 && int64 (len (frame )) > maxMessageBytes {
469+ return nil , fmt .Errorf ("JSON-RPC message exceeds maximum size of %d bytes" , maxMessageBytes )
470+ }
471+ continue
472+ case errors .Is (err , io .EOF ):
473+ if len (frame ) == 0 {
474+ return nil , io .EOF
475+ }
476+ if maxMessageBytes > 0 && int64 (len (frame )) > maxMessageBytes {
477+ return nil , fmt .Errorf ("JSON-RPC message exceeds maximum size of %d bytes" , maxMessageBytes )
478+ }
479+ if err := validateJSONFrame (frame ); err != nil {
480+ return nil , err
481+ }
482+ return json .RawMessage (frame ), nil
483+ default :
484+ return nil , err
485+ }
486+ }
487+ }
488+
489+ func trimLineEnding (frame []byte ) []byte {
490+ if n := len (frame ); n > 0 && frame [n - 1 ] == '\n' {
491+ frame = frame [:n - 1 ]
492+ }
493+ if n := len (frame ); n > 0 && frame [n - 1 ] == '\r' {
494+ frame = frame [:n - 1 ]
495+ }
496+ return frame
497+ }
498+
499+ func validateJSONFrame (frame []byte ) error {
500+ dec := json .NewDecoder (bytes .NewReader (frame ))
501+ var raw json.RawMessage
502+ if err := dec .Decode (& raw ); err != nil {
503+ return err
504+ }
505+ var extra json.RawMessage
506+ if err := dec .Decode (& extra ); err != io .EOF {
507+ return fmt .Errorf ("invalid trailing data at the end of stream" )
508+ }
509+ return nil
510+ }
511+
454512func (c * ioConn ) SessionID () string { return "" }
455513
456514func (c * ioConn ) sessionUpdated (state ServerSessionState ) {
0 commit comments