<?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: The Dollar Craft</title>
    <description>The latest articles on DEV Community by The Dollar Craft (@thedollarcraft).</description>
    <link>https://dev.to/thedollarcraft</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%2F4158799%2Feaf2084b-7c0f-4499-ae4a-761f6dfaff4d.jpg</url>
      <title>DEV Community: The Dollar Craft</title>
      <link>https://dev.to/thedollarcraft</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thedollarcraft"/>
    <language>en</language>
    <item>
      <title>Debugging Claude MCP: Proxies, Auth, and Rate Limits</title>
      <dc:creator>The Dollar Craft</dc:creator>
      <pubDate>Thu, 15 Oct 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/thedollarcraft/debugging-claude-mcp-proxies-auth-and-rate-limits-jl4</link>
      <guid>https://dev.to/thedollarcraft/debugging-claude-mcp-proxies-auth-and-rate-limits-jl4</guid>
      <description>&lt;h2&gt;
  
  
  The failure that doesn't show up until week three
&lt;/h2&gt;

&lt;p&gt;Three hundred forty qualified leads went into a CRM that never recorded them. The agent kept reporting success on every call. A bearer token behind the proxy had expired six days earlier, and the proxy was swallowing the real error and returning a generic 502 instead.&lt;/p&gt;

&lt;p&gt;That is the pattern with MCP agents in production. They do not crash. They report success while quietly dropping data, and the damage shows up in a pipeline report weeks later, not in a stack trace today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your proxy is lying to your agent
&lt;/h2&gt;

&lt;p&gt;Most MCP connection failures are not protocol bugs. They are infrastructure defaults that worked in local dev and broke the moment a proxy or load balancer got involved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkpef2pik00evqiyr38u0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkpef2pik00evqiyr38u0.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The common failure: a proxy or API gateway normalizes every upstream error into a generic status code, usually a 502 or 500. Your agent's retry logic is written to catch a 401 and trigger a token refresh. It never sees the 401. It sees a 502, assumes the service is down, and either retries blindly or gives up.&lt;/p&gt;

&lt;p&gt;Fix the proxy config first, before touching agent code. Pass through the real upstream status code and headers instead of normalizing them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/mcp/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://mcp_backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_intercept_errors&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass_header&lt;/span&gt; &lt;span class="s"&gt;WWW-Authenticate&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;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg9esh85e61shfh4kch4k.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg9esh85e61shfh4kch4k.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;code&gt;proxy_intercept_errors off&lt;/code&gt; stops your gateway from rewriting upstream error pages. Once the real 401 reaches the client, your existing refresh logic will actually fire.&lt;/p&gt;
&lt;h2&gt;
  
  
  Handle 429s like a server talking to you, not like a crash
&lt;/h2&gt;

&lt;p&gt;A 429 is the server asking you to slow down, not telling you something is broken. Treat it as a crash and your agent either retries so aggressively it gets blocked, or gives up on work it should have finished.&lt;/p&gt;

&lt;p&gt;Use exponential backoff with jitter, not a fixed interval. A fixed interval means every client throttled at the same moment retries at the same moment again, and you get a second wave of 429s right on schedule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Max retries exceeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cap concurrency below the documented rate limit, not at it. A single agent firing twelve parallel tool calls against one MCP server hits the ceiling faster than the same twelve calls routed through a queue with a concurrency cap of three or four. Other traffic on the same key eats into your headroom even when your own code behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  One agent, one job
&lt;/h2&gt;

&lt;p&gt;A single agent that qualifies leads, drafts outreach, updates the CRM, and books meetings will fail at the handoffs between those tasks. Split it into narrow agents with a defined contract between them. A lead-qualification agent that outputs a strict JSON schema is trivial to debug. A monolithic agent touching five systems turns every bug into an afternoon of log archaeology, because you cannot isolate which responsibility broke without tracing the whole chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put a human between the agent and the send button
&lt;/h2&gt;

&lt;p&gt;Automate drafting. Gate the actual send, whether that's an email, a CRM update, or a calendar invite, behind human approval for at least the first few hundred executions. Track false positive and false negative rates on what the agent flags, and pull the checkpoint back only as that data earns it. Removing the gate on day one because the demo looked good is how a bad prompt burns through a real list before anyone notices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch tokens per qualified lead, not total spend
&lt;/h2&gt;

&lt;p&gt;Total token spend tells you nothing actionable. Token spend per qualified lead does. A spike usually means one of two things: a prompt is retrying on malformed output, or your context window is accumulating irrelevant history across turns instead of getting trimmed. Check the per-call token count before you check the model. Latency spikes above your SLA on one specific tool call almost always trace back to an external dependency, not the model itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skip the synthetic testing shortcut on edge cases
&lt;/h2&gt;

&lt;p&gt;Synthetic user testing is a fast, cheap pre-filter. It is not a replacement for a support ticket queue. Models tuned to be helpful default to agreeable scoring, so five synthetic personas testing five different flows will often cluster near the top of the scale regardless of which flow is actually worse. Use it to catch obvious breaks before a real user does, not as final validation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>webdev</category>
      <category>api</category>
    </item>
  </channel>
</rss>
