micro/go-micro · error

failed to read request: %w

Error message

failed to read request: %w

What it means

The stdio transport reads newline-delimited JSON-RPC requests from stdin. ReadBytes returns nil for a clean EOF (client closed stdin) but any other read error — broken pipe on stdin, I/O error from the attached process — is wrapped as "failed to read request: %w" and returned from Serve, terminating the transport loop.

Source

Thrown at gateway/mcp/stdio.go:95

// Serve starts the stdio transport and processes JSON-RPC requests
func (t *StdioTransport) Serve() error {
	t.server.opts.Logger.Printf("[mcp] MCP server started (stdio transport)")

	// Read and process requests from stdin
	for {
		select {
		case <-t.ctx.Done():
			return nil
		default:
		}

		// Read one line (JSON-RPC request)
		line, err := t.reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return fmt.Errorf("failed to read request: %w", err)
		}

		// Parse JSON-RPC request
		var req JSONRPCRequest
		if err := json.Unmarshal(line, &req); err != nil {
			t.sendError(nil, ParseError, "Parse error", err.Error())
			continue
		}

		// Validate JSON-RPC version
		if req.JSONRPC != "2.0" {
			t.sendError(req.ID, InvalidRequest, "Invalid request", "jsonrpc must be '2.0'")
			continue
		}

		// Handle request
		go t.handleRequest(&req)
	}

View on GitHub (pinned to 24529f1404)

Solutions

  1. Inspect the wrapped %w error to identify the OS-level cause (e.g. 'file already closed', EPIPE).
  2. Ensure the parent process keeps the stdin pipe open for the lifetime of the MCP server and closes it cleanly to signal shutdown (clean EOF returns nil).
  3. Check that your process supervisor isn't closing or re-binding stdin while the server runs.
  4. Return/handle the error in the caller of Serve and restart the transport if the failure is transient.

Example fix

// before
if err := transport.Serve(); err != nil {
    log.Printf("mcp: %v", err) // failed to read request: file already closed
}
// after
if err := transport.Serve(); err != nil {
    if errors.Is(err, os.ErrClosed) {
        log.Printf("mcp: stdin closed unexpectedly, restarting")
        go func() { _ = transport.Serve() }()
    } else {
        log.Printf("mcp: fatal: %v", err)
    }
}
Defensive patterns

Strategy: try-catch

Try / catch

if err := transport.Serve(); err != nil {
    if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, os.ErrClosed) || errors.Is(err, syscall.EPIPE) {
        // stdin stream broken: log, reconnect from parent, or restart transport
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: StdioTransport.Serve() encounters a non-EOF read error on the stdin reader: the parent process's stdin pipe breaks, the fd is closed unexpectedly, or an OS-level I/O error occurs while blocking on ReadBytes('\n').

Common situations: Parent process (e.g. an MCP client like an editor/agent) crashes or closes the pipe abruptly; running the binary where stdin is a closed/invalid fd; container runtime killing the input stream; malformed pipe handling in a supervisor that restarts the child mid-read.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/378ada3dd1d8c23d. Report an issue: GitHub.