4141 */
4242public class StdioServerTransportProvider implements McpServerTransportProvider {
4343
44+ private static final int DEFAULT_INPUT_MAX_SIZE = 16 * 1024 * 1024 ; // 16MB
45+
4446 private static final Logger logger = LoggerFactory .getLogger (StdioServerTransportProvider .class );
4547
4648 private final McpJsonMapper jsonMapper ;
@@ -49,6 +51,8 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
4951
5052 private final OutputStream outputStream ;
5153
54+ private final int inputMaxSize ;
55+
5256 private McpServerSession session ;
5357
5458 private final AtomicBoolean isClosing = new AtomicBoolean (false );
@@ -72,13 +76,29 @@ public StdioServerTransportProvider(McpJsonMapper jsonMapper) {
7276 * @param outputStream The output stream to write to
7377 */
7478 public StdioServerTransportProvider (McpJsonMapper jsonMapper , InputStream inputStream , OutputStream outputStream ) {
79+ this (jsonMapper , inputStream , outputStream , DEFAULT_INPUT_MAX_SIZE );
80+ }
81+
82+ /**
83+ * Creates a new StdioServerTransportProvider.
84+ * @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
85+ * @param inputStream The input stream to read from
86+ * @param outputStream The output stream to write to
87+ * @param inputMaxSize The maximum number of characters read for a single inbound
88+ * message. A peer that sends a longer message (or never terminates a line) has its
89+ * message rejected instead of forcing the transport to buffer it in memory.
90+ */
91+ public StdioServerTransportProvider (McpJsonMapper jsonMapper , InputStream inputStream , OutputStream outputStream ,
92+ int inputMaxSize ) {
7593 Assert .notNull (jsonMapper , "The JsonMapper can not be null" );
7694 Assert .notNull (inputStream , "The InputStream can not be null" );
7795 Assert .notNull (outputStream , "The OutputStream can not be null" );
96+ Assert .isTrue (inputMaxSize > 0 , "inputMaxSize must be positive" );
7897
7998 this .jsonMapper = jsonMapper ;
8099 this .inputStream = inputStream ;
81100 this .outputStream = outputStream ;
101+ this .inputMaxSize = inputMaxSize ;
82102 }
83103
84104 @ Override
@@ -211,7 +231,7 @@ private void startInboundProcessing() {
211231 reader = new BufferedReader (new InputStreamReader (inputStream , StandardCharsets .UTF_8 ));
212232 while (!isClosing .get ()) {
213233 try {
214- String line = reader . readLine ();
234+ String line = readLine (reader , inputMaxSize );
215235 if (line == null || isClosing .get ()) {
216236 break ;
217237 }
@@ -232,6 +252,10 @@ private void startInboundProcessing() {
232252 break ;
233253 }
234254 }
255+ catch (MaxSizeExceededException e ) {
256+ logIfNotClosing ("Inbound message exceeds the maximum allowed size" , e );
257+ break ;
258+ }
235259 catch (IOException e ) {
236260 logIfNotClosing ("Error reading from stdin" , e );
237261 break ;
@@ -304,6 +328,36 @@ else if (isClosing.get()) {
304328 outboundConsumer .apply (outboundSink .asFlux ()).subscribe ();
305329 } // @formatter:on
306330
331+ /**
332+ * Read line with a max size.
333+ */
334+ private static String readLine (BufferedReader reader , int maxSize )
335+ throws IOException , MaxSizeExceededException {
336+ StringBuilder sb = new StringBuilder ();
337+ int c ;
338+ while ((c = reader .read ()) != -1 ) {
339+ if (c == '\n' ) {
340+ return sb .toString ();
341+ }
342+ if (c == '\r' ) {
343+ // Consume an optional trailing '\n' so that "\r\n" is treated as a
344+ // single terminator, mirroring BufferedReader#readLine().
345+ reader .mark (1 );
346+ int next = reader .read ();
347+ if (next != '\n' && next != -1 ) {
348+ reader .reset ();
349+ }
350+ return sb .toString ();
351+ }
352+ if (sb .length () >= maxSize ) {
353+ throw new MaxSizeExceededException (
354+ "Inbound message exceeds the maximum allowed size of " + maxSize + " characters" );
355+ }
356+ sb .append ((char ) c );
357+ }
358+ return sb .isEmpty () ? null : sb .toString ();
359+ }
360+
307361 private void logIfNotClosing (String message , Exception e ) {
308362 if (!isClosing .get ()) {
309363 logger .error (message , e );
0 commit comments