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

> Learn how the MCP JSON-RPC 2.0 stdio server handles concurrent requests using goroutines and a global mutex for ordered, atomic responses.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: internals
- Published: 2026-07-18

---

**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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/server/stdio.go))

The [`server/stdio.go`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/server/stdio.go)) copies the byte slice and hands it to a fixed-size worker pool defined in [`server/worker.go`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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:

```go
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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/server/stdio.go) and [`server/worker.go`](https://github.com/DeusData/codebase-memory-mcp/blob/main/server/worker.go), illustrating the scanner loop, goroutine dispatch, and synchronized output:

```go
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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/server/worker.go)) for concurrent execution, leveraging all available CPU cores.
- A **`sync.Mutex` named `outMu`** in [`server/stdio.go`](https://github.com/DeusData/codebase-memory-mcp/blob/main/server/stdio.go) serializes writes to `stdout`, ensuring atomic JSON-RPC response delivery without interleaving.
- The launcher ([`main.go`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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.