<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Thales Augusto</title>
    <description>The latest articles on DEV Community by Thales Augusto (@tarv7).</description>
    <link>https://dev.to/tarv7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2497858%2Fe3c2718a-76f3-448f-a57c-d7aeea550fff.png</url>
      <title>DEV Community: Thales Augusto</title>
      <link>https://dev.to/tarv7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tarv7"/>
    <language>en</language>
    <item>
      <title>How to Use Paper MCP Server Inside a Dev Container</title>
      <dc:creator>Thales Augusto</dc:creator>
      <pubDate>Fri, 15 May 2026 20:17:11 +0000</pubDate>
      <link>https://dev.to/tarv7/how-to-use-paper-mcp-server-inside-a-dev-container-2324</link>
      <guid>https://dev.to/tarv7/how-to-use-paper-mcp-server-inside-a-dev-container-2324</guid>
      <description>&lt;p&gt;If you use &lt;a href="https://paperhq.io/" rel="noopener noreferrer"&gt;Paper&lt;/a&gt; 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:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Failed to reconnect to plugin:paper-desktop:paper: ECONNRESET&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your MCP client inside the container tries to reach &lt;code&gt;127.0.0.1:29979&lt;/code&gt;, 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Happens
&lt;/h2&gt;

&lt;p&gt;When you run a dev container, your development environment lives inside a Docker container. Your &lt;code&gt;.mcp.json&lt;/code&gt; file correctly points to &lt;code&gt;http://127.0.0.1:29979/mcp&lt;/code&gt; — and that works fine when running tools directly on the host. But inside the container:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;127.0.0.1&lt;/code&gt; refers to the &lt;strong&gt;container's loopback&lt;/strong&gt;, not the host's&lt;/li&gt;
&lt;li&gt;Paper MCP server is bound to &lt;strong&gt;&lt;code&gt;127.0.0.1&lt;/code&gt; on the host&lt;/strong&gt; (localhost only)&lt;/li&gt;
&lt;li&gt;Docker containers communicate with the host via the &lt;strong&gt;bridge network gateway&lt;/strong&gt; (e.g., &lt;code&gt;172.20.0.1&lt;/code&gt;) — and Paper rejects connections from that IP&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;ports: "29979:29979"&lt;/code&gt; doesn't solve it
&lt;/h3&gt;

&lt;p&gt;A common first instinct is to add this to &lt;code&gt;docker-compose.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;29979:29979"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does the &lt;strong&gt;opposite&lt;/strong&gt; 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: A Two-Hop socat Relay
&lt;/h2&gt;

&lt;p&gt;The fix requires two &lt;code&gt;socat&lt;/code&gt; relays working in tandem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why two hops?&lt;/strong&gt; The host relay is necessary because Paper only accepts connections from &lt;code&gt;127.0.0.1&lt;/code&gt;. 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Install socat in the Dev Container
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;socat&lt;/code&gt; to your &lt;code&gt;.devcontainer/Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install socat for MCP host port forwarding&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; socat &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Start the In-Container Relay via Docker Compose
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;.devcontainer/compose.yaml&lt;/code&gt;, replace the default &lt;code&gt;command: sleep infinity&lt;/code&gt; with a command that starts socat before keeping the container alive. The &lt;code&gt;$$&lt;/code&gt; syntax escapes &lt;code&gt;$&lt;/code&gt; for Docker Compose so the shell receives it correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rails-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# or whatever your service is named&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;/bin/sh -c "socat TCP-LISTEN:29979,fork,reuseaddr&lt;/span&gt;
      &lt;span class="s"&gt;TCP:$$(ip route show default | awk '{print $$3}'):29979&lt;/span&gt;
      &lt;span class="s"&gt;&amp;amp; sleep infinity"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This socat process listens on port &lt;code&gt;29979&lt;/code&gt; inside the container and forwards traffic to the Docker gateway IP (the host), where the second relay will be waiting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Set Up the Host-Side Relay
&lt;/h2&gt;

&lt;p&gt;On your &lt;strong&gt;host machine&lt;/strong&gt;, create a relay script that dynamically resolves the Docker bridge gateway and starts socat bound to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.local/bin/paper-mcp-bridge&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;NETWORK_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_project_default"&lt;/span&gt;  &lt;span class="c"&gt;# adjust to your compose project name&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[paper-mcp-bridge] Waiting for Docker network '&lt;/span&gt;&lt;span class="nv"&gt;$NETWORK_NAME&lt;/span&gt;&lt;span class="s2"&gt;'..."&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;DOCKER_GW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker network inspect &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NETWORK_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{range .IPAM.Config}}{{.Gateway}}{{end}}'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_GW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;break
  &lt;/span&gt;&lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;3
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[paper-mcp-bridge] Gateway: &lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_GW&lt;/span&gt;&lt;span class="s2"&gt; — starting relay..."&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;socat TCP-LISTEN:29979,fork,reuseaddr,bind&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_GW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; TCP:127.0.0.1:29979
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Finding your network name:&lt;/strong&gt; Run &lt;code&gt;docker network ls&lt;/code&gt; and look for the network associated with your compose project. It's typically &lt;code&gt;&amp;lt;project_name&amp;gt;_default&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Make it executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/.local/bin/paper-mcp-bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4 — Persist with a systemd User Service
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;~/.config/systemd/user/mcp-bridge.service&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;MCP Bridge — Relay Docker container access to Paper MCP server (port 29979)&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPre&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-/usr/bin/pkill -f "socat TCP-LISTEN:29979"&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%h/.local/bin/paper-mcp-bridge&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable and start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; daemon-reload
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;mcp-bridge.service
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start mcp-bridge.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start automatically when you log in&lt;/li&gt;
&lt;li&gt;Wait for the Docker network to be available before binding&lt;/li&gt;
&lt;li&gt;Restart automatically if it crashes&lt;/li&gt;
&lt;li&gt;Kill any leftover socat processes before starting (&lt;code&gt;ExecStartPre=-&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 5 — Keep Your &lt;code&gt;.mcp.json&lt;/code&gt; Unchanged
&lt;/h2&gt;

&lt;p&gt;No changes needed to your MCP config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paper"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:29979/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;127.0.0.1:29979&lt;/code&gt; works on the host directly, and inside the container the in-container socat intercepts it transparently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing It
&lt;/h2&gt;

&lt;p&gt;After rebuilding the container, verify from &lt;strong&gt;inside&lt;/strong&gt; it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check socat is listening&lt;/span&gt;
ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;29979

&lt;span class="c"&gt;# Test the full chain reaches Paper MCP&lt;/span&gt;
bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"echo &amp;gt; /dev/tcp/127.0.0.1/29979 &amp;amp;&amp;amp; echo OK || echo FAILED"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful &lt;code&gt;OK&lt;/code&gt; means the two-hop relay is working end-to-end.&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;host&lt;/strong&gt;, verify Paper MCP is responding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; http://127.0.0.1:29979/mcp
&lt;span class="c"&gt;# Expect: HTTP 404 {"error":"not_found","error_description":"Session not found"}&lt;/span&gt;
&lt;span class="c"&gt;# (This is normal — it means the server is up and responding)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;socat&lt;/code&gt; in Dockerfile&lt;/td&gt;
&lt;td&gt;Installs the relay tool in the container image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;compose.yaml&lt;/code&gt; command&lt;/td&gt;
&lt;td&gt;Starts container-side socat on port 29979 → host gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;paper-mcp-bridge&lt;/code&gt; script&lt;/td&gt;
&lt;td&gt;Host-side socat bound to Docker bridge IP → localhost:29979&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp-bridge.service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Persists the host relay across reboots via systemd&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key insight is that &lt;strong&gt;Paper MCP only accepts localhost connections&lt;/strong&gt;, so you need a relay on the host itself — not just port forwarding. The container relay handles the &lt;code&gt;127.0.0.1&lt;/code&gt; address from MCP clients; the host relay makes those connections appear local to Paper.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>mcp</category>
      <category>tutorial</category>
      <category>vscode</category>
    </item>
  </channel>
</rss>
