How the MCP JSON-RPC 2.0 stdio Server Handles Concurrent Requests

The MCP JSON-RPC 2.0 stdio server processes multiple requests concurrently by dispatching each incoming message to a goroutine pool while protecting the stdout stream with a global mutex to guarantee ordered, atomic responses.

The DeusData/codebase-memory-mcp repository implements a Model Context Protocol (MCP) server that communicates via standard input and output using the JSON-RPC 2.0 protocol. Understanding how this MCP JSON-RPC 2.0 stdio server manages parallel execution is critical for integrators expecting high-throughput tool invocation without response interleaving or stream corruption.

Launcher and Process Replacement

The server binary lifecycle begins in pkg/go/cmd/codebase-memory-mcp/main.go. This launcher handles platform-specific binary extraction before replacing itself with the actual server process.

According to the source code in main.go (lines 34–43), the launcher downloads the pre-built artifact, verifies execution permissions, and invokes syscall.Exec (or exec.Command on Windows) to transform the process into the stdio server. This ensures the MCP host communicates directly with the long-running JSON-RPC engine rather than a transient wrapper.

The Single-Reader, Multi-Worker Pattern

At the core of the concurrency model is a strict separation between I/O-bound reading and CPU-bound processing.

The Stdin Read Loop (server/stdio.go)

The server/stdio.go file implements a tight bufio.Scanner loop that reads newline-delimited JSON-RPC requests from os.Stdin. Because JSON-RPC 2.0 over stdio requires delimiter-separated messages, the scanner buffers input until a complete request is available.

This loop remains single-threaded to avoid complexity in stream parsing and backpressure handling. However, it immediately copies the raw request bytes and hands them to the dispatcher, preventing I/O latency from blocking downstream workers.

Goroutine Dispatch and Worker Pool

Once a request is buffered, the dispatchWorker() function (located in server/stdio.go) copies the byte slice and hands it to a fixed-size worker pool defined in server/worker.go.

Using a worker pool sized to runtime.GOMAXPROCS allows the server to exploit multicore CPUs while capping the number of concurrent goroutines to prevent resource exhaustion. Each worker operates independently, enabling parallel execution of tool calls, filesystem searches, or semantic queries without blocking the stdin reader.

Serialized Output and the outMu Mutex

While request processing is parallelized, JSON-RPC 2.0 mandates that response messages on stdout remain complete and well-ordered. Interleaved JSON fragments would corrupt the stream for the MCP client.

To prevent this, server/stdio.go declares a package-level var outMu sync.Mutex. Before writing any response to os.Stdout, the executing goroutine must acquire the lock:

outMu.Lock()
fmt.Fprintln(os.Stdout, string(resp))
outMu.Unlock()

This global mutex ensures that even if multiple workers finish simultaneously, their serialized JSON outputs are written atomically to the stdio stream, preserving protocol integrity.

Practical Implementation Example

The following Go snippet synthesizes the concurrency pattern found in server/stdio.go and server/worker.go, illustrating the scanner loop, goroutine dispatch, and synchronized output:

package main

import (
    "bufio"
    "fmt"
    "os"
    "sync"
)

// handleJSONRPC simulates the method invocation logic from server/worker.go
func handleJSONRPC(payload []byte) []byte {
    // Unmarshal, execute tool, marshal response...
    return []byte(`{"jsonrpc":"2.0","id":1,"result":{}}`)
}

func serveStdio() error {
    scanner := bufio.NewScanner(os.Stdin)
    var outMu sync.Mutex // Protects stdout per server/stdio.go

    for scanner.Scan() {
        // Copy bytes to avoid scanner buffer reuse issues
        req := append([]byte(nil), scanner.Bytes()...)
        
        // Dispatch to worker goroutine (concurrent handling)
        go func(payload []byte) {
            resp := handleJSONRPC(payload)
            
            // Serialized output prevents interleaving
            outMu.Lock()
            fmt.Fprintln(os.Stdout, string(resp))
            outMu.Unlock()
        }(req)
    }
    return scanner.Err()
}

Summary

  • The MCP JSON-RPC 2.0 stdio server in DeusData/codebase-memory-mcp uses a single-threaded scanner to read requests from stdin to avoid parsing race conditions.
  • It dispatches requests to a goroutine worker pool (defined in server/worker.go) for concurrent execution, leveraging all available CPU cores.
  • A sync.Mutex named outMu in server/stdio.go serializes writes to stdout, ensuring atomic JSON-RPC response delivery without interleaving.
  • The launcher (main.go) replaces itself with the server binary via syscall.Exec, establishing a direct stdio pipeline between the MCP host and the JSON-RPC engine.

Frequently Asked Questions

Does the MCP stdio server use a separate process for each request?

No. The server runs as a single long-lived process that spawns goroutines within its own address space to handle requests concurrently. It does not fork child processes per request, minimizing overhead and maintaining shared state.

Why is a mutex necessary for stdout if Go channels are thread-safe?

While Go channels are safe for communication between goroutines, os.Stdout is an io.Writer shared across the process. Without a mutex, concurrent calls to fmt.Fprintln from multiple goroutines could interleave bytes, producing malformed JSON fragments. The outMu mutex guarantees that each response is written atomically.

How does the worker pool prevent resource exhaustion?

The pool defined in server/worker.go limits the number of concurrent goroutines to runtime.GOMAXPROCS (configurable). If all workers are busy, the dispatcher blocks briefly, applying backpressure to the stdin reader and preventing unbounded memory growth from queued requests.

Can the stdio server handle out-of-order responses?

Yes. JSON-RPC 2.0 allows responses to be returned in any order relative to requests, provided each response contains the correct id field. The server's use of goroutines naturally results in out-of-order completion, and the outMu mutex ensures these are serialized to stdout safely without enforcing FIFO ordering of processing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →