Beto-Bot Part 2: Bridging the Gap with MCP and Docker

Umberto Righetti

Umberto Righetti

2026-04-14

JavaDockerMCPInfrastructure

using_dockerusing_docker

In the first part of this series, I introduced the high-level goals of Beto-Bot. Today, we’re getting our hands dirty with the infrastructure.

Specifically: how do you get a Java application to talk to a GitHub MCP server that was designed for Node.js?

🌉 The SSE-to-STDIO Challenge

Our local GitHub MCP server communicates via STDIO (Standard Input/Output). However, modern Java clients, particularly those using Spring AI, often prefer SSE (Server-Sent Events) or even newer : Streamable HTTP ( more on that in a later post )

To bridge this gap without rewriting the server, I implemented a proxy architecture using Supergateway.

Communication

The setup works like this:

  • Beto-Bot: Sends and receives messages via SSE.
  • Supergateway Proxy: Acts as the translator.
  • GitHub MCP Server: Operates in its native STDIO environment.

Basically:

[beto-bot] <---SSE---> [proxy] <---STDIO---> [github mcp server]

🐳 Dockerizing the Tools

To keep the environment clean and reproducible, I wrapped the entire stack in Docker Compose. This ensures that the MCP server, the proxy, and the Java bot all live in the same network and can find each other easily.

The docker-compose.yml logic

One key highlight is the github-mcp service, which handles the command to start the server via npx while exposing it through the proxy:

services:
  github-mcp:
    image: node:20
    environment:
      - GITHUB_PERSONAL_ACCESS_TOKEN=${GITHUB_PERSONAL_ACCESS_TOKEN}
    command: >
      sh -c "npx -y supergateway 
      --stdio 'docker run -i --rm ... ghcr.io/github/github-mcp-server:latest' 
      --port 3000 --ssePath /sse"

With this DinD (Docker inside Docker, or next to Docker, since it doesnt really run inside it) we dont really see a performance hit, apart from the supergateway ( but thats negligible in my opinion )

By using the node:20 image, we provide the necessary environment for Supergateway to run without requiring to install Node.js or npx on our local machine. By giving the container access to the Docker socket, it can spin up the official GitHub MCP image as a sibling container, keeping our environment clean and isolated.

☕ The Java Configuration

On the Spring Boot side, I configured a McpAsyncClient. One critical lesson learned: Don't let the application start before the MCP server is ready. I added a .block() with a 30-second timeout to the initialization. This forces the application to wait for the "handshake" before it tries to process any GitHub tasks.

Note: You're free to still use this, but i have since moved to a simplified approach using Spring AI's native MCP Client which can be configured through application.yml or properties using the snippet below.

spring:
  ai:
    mcp:
      client:
        type: ASYNC
        enabled: true
@Bean
    @Primary
    public McpAsyncClient githubMcpClient() {
        String mcpUrl = "http://localhost:9090/sse";
 
        var transport = HttpClientSseClientTransport.builder(mcpUrl)
                .build();
 
        var client = McpClient.async(transport)
                .requestTimeout(Duration.ofMinutes(5)).build();
        try {
            logger.info("--- Connecting to GitHub MCP Proxy ---");
            client.initialize()
                    .retryWhen(Retry.fixedDelay(10, Duration.ofSeconds(2)))
                    .block(Duration.ofSeconds(30));
            logger.info("--- GITHUB MCP READY ---");
        } catch (Exception e) {
            logger.error("Failed to init with MCP Proxy: {}", e.getMessage());
        }
        return client;
    }
 
    @Bean
    public List<McpAsyncClient> customMcpAsyncClientList(McpAsyncClient githubMcpClient) {
        return List.of(githubMcpClient);
    }

What’s Next?

Now that the bridge is built and the pipes are connected, we have a Java app that can use GitHub's tools. In the next post, we’ll look at The Fetcher—the scheduled service that actually goes out and finds work for our bot to do.