<?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: Léa Moreau</title>
    <description>The latest articles on DEV Community by Léa Moreau (@leamoreau).</description>
    <link>https://dev.to/leamoreau</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4023322%2F0c3933d4-8902-4912-a9fd-c5e23aefdf2c.png</url>
      <title>DEV Community: Léa Moreau</title>
      <link>https://dev.to/leamoreau</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leamoreau"/>
    <language>en</language>
    <item>
      <title>Notes on running an MCP server in production</title>
      <dc:creator>Léa Moreau</dc:creator>
      <pubDate>Fri, 10 Jul 2026 10:52:57 +0000</pubDate>
      <link>https://dev.to/leamoreau/notes-on-running-an-mcp-server-in-production-19lc</link>
      <guid>https://dev.to/leamoreau/notes-on-running-an-mcp-server-in-production-19lc</guid>
      <description>&lt;p&gt;Most MCP tutorials stop at a single stdio tool. These notes cover what I had to change once a server of mine had actual users, in the order the problems appeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging corrupts the protocol if you let it
&lt;/h2&gt;

&lt;p&gt;The stdio transport uses stdout for JSON-RPC frames. A single &lt;code&gt;console.log&lt;/code&gt;, including one buried in a dependency, breaks the stream. The symptom is a client that disconnects for no visible reason, and nothing in the error output points back to the cause.&lt;/p&gt;

&lt;p&gt;The fix is unglamorous. Every log line goes to stderr through a small structured wrapper, and that entire class of bug disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan for the second transport early
&lt;/h2&gt;

&lt;p&gt;Editor clients (Claude Desktop, Claude Code, Cursor) talk stdio to a local process. Hosting the same server for a team requires Streamable HTTP, and the refactor is painful if the code assumes one global server instance.&lt;/p&gt;

&lt;p&gt;I build everything behind a factory now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;McpServer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;McpServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SERVER_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SERVER_VERSION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;registerTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;registerResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;registerPrompts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stdio entrypoint calls it once at startup. The HTTP handler calls it per request and keeps no session state. Request isolation comes from the structure rather than from discipline, and horizontal scaling requires no coordination between replicas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate configuration at boot
&lt;/h2&gt;

&lt;p&gt;A malformed environment variable should stop the process immediately with a readable message, instead of surfacing hours later as strange behavior. I parse all config with Zod at startup.&lt;/p&gt;

&lt;p&gt;Tool inputs get the same treatment. Declare them as Zod schemas and the SDK validates every call before your handler runs, so handlers only ever receive typed, checked arguments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat a fetch tool as a proxy
&lt;/h2&gt;

&lt;p&gt;A tool that fetches URLs is an HTTP proxy driven by a language model, and the model reads untrusted text all day. Mine enforces four limits: http and https only, an optional hostname allowlist, a byte cap on responses, and a timeout. Without the allowlist, a prompted model can probe your internal network from inside your infrastructure. This is a standard SSRF vector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test against the real server, in memory
&lt;/h2&gt;

&lt;p&gt;The SDK provides &lt;code&gt;InMemoryTransport.createLinkedPair()&lt;/code&gt;. A real client connects to the real server inside the test process, with no sockets and no subprocess management. My suite exercises tools, resources and prompts this way and completes in a few seconds on Node 20, 22 and 24.&lt;/p&gt;

&lt;h2&gt;
  
  
  The template
&lt;/h2&gt;

&lt;p&gt;I assembled these pieces into a template I now start every server from: both transports, the guarded fetch tool, the test setup, Docker and CI. It costs $19 and lives &lt;a href="https://leamoreau1988.gumroad.com/l/mcp-quickstart-kit" rel="noopener noreferrer"&gt;on my Gumroad page&lt;/a&gt;. The notes above contain most of the design, so building your own from them is a perfectly reasonable choice too.&lt;/p&gt;

&lt;p&gt;If production has bitten you somewhere I did not list, leave a comment. I am collecting these.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>claude</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
