modelcontextprotocol / modelcontextprotocol/java-sdk

StdioServerTransportProvider should support a stdin-close callback without interrupting shutdown

Abierto
#936 0 comentarios 2 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

waiting for triage
Lenguaje dominante
Java
Estrellas
3.7k
Forks
1.1k
Merge medio
1 d 15 h
PR fusionados (30 d)
9

Descripción

For stdio-based MCP servers, stdin closing is effectively the client disconnect signal. This is especially important when an MCP server runs in a container: once the MCP client disconnects or closes stdin, the server should be able to trigger application shutdown and clean up background resources.

Today, StdioServerTransportProvider closes the MCP session when stdin reaches EOF, but it does not expose a public transport-level callback that applications can use to run shutdown logic.

Downstream, we had to vendor/copy StdioServerTransportProvider to add such a callback.

Current behavior

When the stdin read loop exits, the SDK transport does roughly this:

finally {
  isClosing.set(true);
  if (session != null) {
    session.close();
  }
  inboundSink.tryEmitComplete();
}

The session is closed, but the application has no direct hook to react to the stdio client disconnect.

There is also a subtle ordering issue. handleIncomingMessages() currently disposes the inbound scheduler from doOnTerminate():

this.inboundSink.asFlux()
  .flatMap(message -> session.handle(message))
  .doOnTerminate(() -> {
    this.outboundSink.tryEmitComplete();
    this.inboundScheduler.dispose();
  })
  .subscribe();

When stdin closes, the inbound read loop completes inboundSink. The termination callback can run on the same inbound thread. Disposing the scheduler there may call shutdownNow(), interrupting that same thread before downstream shutdown work has completed.

We observed this downstream: shutdown logic could run with the current thread interrupt flag already set, causing graceful shutdown to fail or exit early.

Expected behavior

StdioServerTransportProvider should allow applications to register an optional callback for stdin EOF / stdio client disconnect.

That callback should run:

  1. After the session is closed.
  2. After the inbound sink is completed.
  3. Before the inbound scheduler is disposed.
  4. Without the current thread being interrupted by scheduler disposal.
Proposed fix

Add an optional callback to StdioServerTransportProvider, for example as a Runnable:

public StdioServerTransportProvider(
    McpJsonMapper jsonMapper,
    InputStream inputStream,
    OutputStream outputStream,
    Runnable closeCallback
)

Or, if a reactive API is preferred:

Supplier<Mono<Void>> closeCallback

Then move inboundScheduler.dispose() out of handleIncomingMessages().doOnTerminate(...) and into the inbound read-loop finally, after the callback has completed.

The inbound read-loop cleanup would look conceptually like this:

finally {
  isClosing.set(true);
  if (session != null) {
    session.close();
  }
  inboundSink.tryEmitComplete();

  if (closeCallback != null) {
    closeCallback.run();
  }

  inboundScheduler.dispose();
}

handleIncomingMessages() should still complete the outbound sink, but should not dispose the inbound scheduler from the termination callback.

Suggested regression test

Add a test that:

  1. Creates a StdioServerTransportProvider with a piped input stream.
  2. Registers a callback.
  3. Starts the transport with a mock session.
  4. Closes the piped output stream to simulate stdin EOF.
  5. Verifies the callback runs.
  6. Verifies Thread.currentThread().isInterrupted() is false inside the callback.

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza con StdioServerTransportProvider, especialmente con el bucle de lectura de stdin y el manejo de la terminación de handleIncomingMessages(). Añade la prueba de regresión sugerida para piped-stream y verifica después que el callback se ejecute tras la limpieza de session y inbound-sink, antes de liberar el scheduler, y que el hilo del callback no se interrumpa.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
java
Área
backend
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Tranquilo
Claridad
Bien especificado
Aptitud para principiantes
68/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.