If you use Paper as your MCP server and develop inside a Docker-based dev container (VS Code Dev Containers, Cursor, or any devcontainer-compatible editor), you've probably hit this frustrating wall:
Failed to reconnect to plugin:paper-desktop:paper: ECONNRESET
Your MCP client inside the container tries to reach 127.0.0.1:29979, but that address points to the container's own loopback — not your host machine where Paper is actually running. This article walks through the exact solution.
Why This Happens
When you run a dev container, your development environment lives inside a Docker container. Your .mcp.json file correctly points to http://127.0.0.1:29979/mcp — and that works fine when running tools directly on the host. But inside the container:
-
127.0.0.1refers to the container's loopback, not the host's - Paper MCP server is bound to
127.0.0.1on the host (localhost only) - Docker containers communicate with the host via the bridge network gateway (e.g.,
172.20.0.1) — and Paper rejects connections from that IP
Why ports: "29979:29979" doesn't solve it
A common first instinct is to add this to docker-compose.yaml:
ports:
- "29979:29979"
This does the opposite of what you need. It exposes a container port to the host, not the other way around. The traffic direction you need is: container → host.
The Solution: A Two-Hop socat Relay
The fix requires two socat relays working in tandem:
MCP Client (container)
→ socat (container: 0.0.0.0:29979)
→ socat (host: 172.20.0.1:29979)
→ Paper MCP (host: 127.0.0.1:29979)
Why two hops? The host relay is necessary because Paper only accepts connections from 127.0.0.1. When container socat connects to the host via the bridge gateway IP, the host sees it as an external connection and Paper rejects it. The host-side socat acts as a local proxy that makes the connection appear to come from localhost.
Step 1 — Install socat in the Dev Container
Add socat to your .devcontainer/Dockerfile:
# Install socat for MCP host port forwarding
USER root
RUN apt-get update && apt-get install -y --no-install-recommends socat && rm -rf /var/lib/apt/lists/*
Step 2 — Start the In-Container Relay via Docker Compose
In .devcontainer/compose.yaml, replace the default command: sleep infinity with a command that starts socat before keeping the container alive. The $$ syntax escapes $ for Docker Compose so the shell receives it correctly:
services:
rails-app: # or whatever your service is named
command: >
/bin/sh -c "socat TCP-LISTEN:29979,fork,reuseaddr
TCP:$$(ip route show default | awk '{print $$3}'):29979
& sleep infinity"
This socat process listens on port 29979 inside the container and forwards traffic to the Docker gateway IP (the host), where the second relay will be waiting.
Step 3 — Set Up the Host-Side Relay
On your host machine, create a relay script that dynamically resolves the Docker bridge gateway and starts socat bound to it:
# ~/.local/bin/paper-mcp-bridge
#!/bin/bash
NETWORK_NAME="your_project_default" # adjust to your compose project name
echo "[paper-mcp-bridge] Waiting for Docker network '$NETWORK_NAME'..."
while true; do
DOCKER_GW=$(docker network inspect "$NETWORK_NAME" \
--format '{{range .IPAM.Config}}{{.Gateway}}{{end}}' 2>/dev/null)
if [ -n "$DOCKER_GW" ]; then
break
fi
sleep 3
done
echo "[paper-mcp-bridge] Gateway: $DOCKER_GW — starting relay..."
exec socat TCP-LISTEN:29979,fork,reuseaddr,bind="$DOCKER_GW" TCP:127.0.0.1:29979
Finding your network name: Run
docker network lsand look for the network associated with your compose project. It's typically<project_name>_default.
Make it executable:
chmod +x ~/.local/bin/paper-mcp-bridge
Step 4 — Persist with a systemd User Service
Create ~/.config/systemd/user/mcp-bridge.service:
[Unit]
Description=MCP Bridge — Relay Docker container access to Paper MCP server (port 29979)
After=default.target
[Service]
Type=simple
ExecStartPre=-/usr/bin/pkill -f "socat TCP-LISTEN:29979"
ExecStart=%h/.local/bin/paper-mcp-bridge
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
Enable and start it:
systemctl --user daemon-reload
systemctl --user enable mcp-bridge.service
systemctl --user start mcp-bridge.service
The service will:
- Start automatically when you log in
- Wait for the Docker network to be available before binding
- Restart automatically if it crashes
- Kill any leftover socat processes before starting (
ExecStartPre=-)
Step 5 — Keep Your .mcp.json Unchanged
No changes needed to your MCP config:
{
"mcpServers": {
"paper": {
"url": "http://127.0.0.1:29979/mcp"
}
}
}
127.0.0.1:29979 works on the host directly, and inside the container the in-container socat intercepts it transparently.
Testing It
After rebuilding the container, verify from inside it:
# Check socat is listening
ss -tlnp | grep 29979
# Test the full chain reaches Paper MCP
bash -c "echo > /dev/tcp/127.0.0.1/29979 && echo OK || echo FAILED"
A successful OK means the two-hop relay is working end-to-end.
On the host, verify Paper MCP is responding:
curl -v http://127.0.0.1:29979/mcp
# Expect: HTTP 404 {"error":"not_found","error_description":"Session not found"}
# (This is normal — it means the server is up and responding)
Summary
| Component | What it does |
|---|---|
socat in Dockerfile |
Installs the relay tool in the container image |
compose.yaml command |
Starts container-side socat on port 29979 → host gateway |
paper-mcp-bridge script |
Host-side socat bound to Docker bridge IP → localhost:29979 |
mcp-bridge.service |
Persists the host relay across reboots via systemd |
The key insight is that Paper MCP only accepts localhost connections, so you need a relay on the host itself — not just port forwarding. The container relay handles the 127.0.0.1 address from MCP clients; the host relay makes those connections appear local to Paper.
Top comments (0)