Skip to content

Conversation

@netztaucher
Copy link

Bug Report / Proposed Fix: Persistent "EOF" in MCP Mode

Problem Description

The codemap-mcp server consistently returns an EOF error (disconnection) upon any tool call that generates output. This makes the tool unusable in environments like Claude Desktop or other MCP clients that rely on a stable JSON-RPC stream over Stdio.

Root Cause Analysis

The current implementation of the MCP server in mcp/main.go uses a captureOutput function that hijacks the global os.Stdout.

// From legacy mcp/main.go
func captureOutput(f func()) string {
    old := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w
    f()
    // ...
}

While this works for a simple CLI, it is a protocol violation in an MCP server using mcp.StdioTransport. The MCP transport relies on os.Stdout to send JSON-RPC messages. By redirecting os.Stdout globally, the server inadvertently corrupts or interrupts the communication stream, leading to an immediate EOF on the client side.

Proposed Solution: Refactor to io.Writer

Instead of global output hijacking, the rendering engine should be refactored to support dependency injection of the output stream.

1. Refactor Render Signatures

Update all rendering functions in render/ (Tree, Depgraph, Skyline) to accept an io.Writer.

- func Tree(project scanner.Project)
+ func Tree(w io.Writer, project scanner.Project)

2. Replace direct os.Stdout writes

Replace all instances of fmt.Printf and fmt.Println with fmt.Fprintf(w, ...) and fmt.Fprintln(w, ...).

3. Update MCP Handlers

In mcp/main.go, replace the captureOutput logic with a local bytes.Buffer. This ensures the rendering happens in memory and the resulting string is safely returned as part of the JSON-RPC response without ever touching the actual os.Stdout stream.

var buf bytes.Buffer
render.Tree(&buf, project)
output := buf.String()
return textResult(output)

Benefits

  • Protocol Stability: Guaranteed isolation between tool output and JSON-RPC communication.
  • Thread Safety: Multiple handlers can theoretically render output concurrently without global state conflicts.
  • Architectural Cleanup: Moves away from global state mutation towards clean interface-based design.

Verification

The fix was verified by manually sending JSON-RPC initialize and tools/call requests to the patched binary. The server now remains connected and returns correctly formatted output across multiple calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant