<?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: Teng</title>
    <description>The latest articles on DEV Community by Teng (@tengbyte).</description>
    <link>https://dev.to/tengbyte</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%2F4034846%2F02e3dd95-564f-416e-ac40-e80271f27cbb.jpg</url>
      <title>DEV Community: Teng</title>
      <link>https://dev.to/tengbyte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tengbyte"/>
    <language>en</language>
    <item>
      <title>My eval said a perfect MCP server was broken. It was the eval that was lying.</title>
      <dc:creator>Teng</dc:creator>
      <pubDate>Wed, 29 Jul 2026 00:43:20 +0000</pubDate>
      <link>https://dev.to/tengbyte/my-eval-said-a-perfect-mcp-server-was-broken-it-was-the-eval-that-was-lying-4fbm</link>
      <guid>https://dev.to/tengbyte/my-eval-said-a-perfect-mcp-server-was-broken-it-was-the-eval-that-was-lying-4fbm</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://tengli.dev/posts/calibrating-mcp-evals.html" rel="noopener noreferrer"&gt;tengli.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I added an LLM-powered eval to &lt;a href="https://github.com/TengByte/mcpgrade" rel="noopener noreferrer"&gt;mcpgrade&lt;/a&gt;, the first real run produced a result that looked like a scoop: context7 — a server with a &lt;em&gt;perfect&lt;/em&gt; static score — failed tool selection 62% of the time. A model shown its two-tool catalog picked the "wrong" tool on 5 of 8 tasks.&lt;/p&gt;

&lt;p&gt;If I had shipped that number, it would have been wrong. Not slightly wrong — systematically, unfairly wrong. This post is about how I caught it, because the failure mode generalizes to most agent benchmarks people are building right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;mcpgrade's &lt;code&gt;--eval&lt;/code&gt; mode works like this: it reads a server's tool catalog, synthesizes realistic single-step tasks ("find the Slack channel where the incident was discussed"), shows a model the full catalog, and measures three things — does it pick the right tool, does it fill valid arguments, and does it correctly &lt;em&gt;refuse&lt;/em&gt; tasks that no tool can handle.&lt;/p&gt;

&lt;p&gt;Round 1, on three real servers, cost about twelve cents and produced this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Static score&lt;/th&gt;
&lt;th&gt;Tool selection&lt;/th&gt;
&lt;th&gt;Args&lt;/th&gt;
&lt;th&gt;Refusal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;context7 (2 tools)&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;server-memory (9 tools)&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;server-slack (8 tools)&lt;/td&gt;
&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;54%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two servers with excellent static scores, apparently failing live. Either static analysis was worthless, or the eval was broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eval was broken
&lt;/h2&gt;

&lt;p&gt;Every "miss" traced to one cause. Slack's &lt;code&gt;post_message&lt;/code&gt; needs a &lt;code&gt;thread_ts&lt;/code&gt; — a value you can only get from a &lt;em&gt;previous&lt;/em&gt; call to &lt;code&gt;get_channel_history&lt;/code&gt;. context7's &lt;code&gt;get-library-docs&lt;/code&gt; needs a library ID that comes from &lt;code&gt;resolve-library-id&lt;/code&gt;. These are &lt;strong&gt;pipelined tools&lt;/strong&gt;: their required arguments are produced by other tools.&lt;/p&gt;

&lt;p&gt;My task synthesizer didn't know that. It generated tasks like "reply to the thread about the outage" — without a thread timestamp. The model, quite sensibly, picked &lt;code&gt;get_channel_history&lt;/code&gt; first (to find the thread), or declined. My grader marked both choices wrong.&lt;/p&gt;

&lt;p&gt;The model wasn't confused. The model was &lt;em&gt;right&lt;/em&gt;. The benchmark was grading correct multi-step reasoning as failure — and memory's 93% was the tell all along: its tools are single-step, so it scored fine.&lt;/p&gt;

&lt;p&gt;The fix was one constraint in the synthesis prompt: &lt;strong&gt;every task must embed concrete values for every required parameter.&lt;/strong&gt; "Reply to thread 1721581200.123456 in #incidents" — now single-shot selection is a fair question. Round 2: context7 38% → 100%, slack 54% → 100%.&lt;/p&gt;

&lt;p&gt;If your agent benchmark shows a capable model failing on tools that real users navigate fine, check whether you're asking one-step questions about multi-step tools. In my experience most home-grown "tool selection accuracy" numbers have this bug quietly inflating their failure rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 3: does it discriminate?
&lt;/h2&gt;

&lt;p&gt;A benchmark that gives everyone 100% is decoration. So round 3 pointed the fixed eval at firecrawl — 26 tools, the lowest static score in my &lt;a href="https://tengli.dev/posts/mcp-servers-failing-agents.html" rel="noopener noreferrer"&gt;36-server scan&lt;/a&gt;. If the eval is measuring something real, a messy catalog should score worse. It did, in two specific ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Selection misses landed exactly on the naming collisions static rules had flagged.&lt;/strong&gt; 84% selection accuracy, and the 16% wasn't random: &lt;code&gt;extract&lt;/code&gt;↔&lt;code&gt;scrape&lt;/code&gt;, &lt;code&gt;agent_status&lt;/code&gt;↔&lt;code&gt;check_crawl_status&lt;/code&gt;, &lt;code&gt;feedback&lt;/code&gt;↔&lt;code&gt;search_feedback&lt;/code&gt; — the same confusable pairs the static rules (N002, C001) had already flagged from names and descriptions alone. That's the result I most wanted: &lt;strong&gt;static lint predicts live model confusion.&lt;/strong&gt; The cheap, free, ten-second scan finds the same failure points as the LLM eval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Refusal collapsed.&lt;/strong&gt; Given deliberately out-of-scope tasks, models refused correctly 100% of the time on small, well-documented catalogs — and &lt;strong&gt;50%&lt;/strong&gt; of the time on firecrawl's 26 fuzzy tools. Half the time, the model "found" a plausible-sounding tool and called it anyway. Big vague catalogs don't just cause wrong picks; they cause &lt;em&gt;action when inaction is correct&lt;/em&gt;, which in production is the scariest failure mode there is. Nobody reviews the agent that confidently did something.&lt;/p&gt;

&lt;h2&gt;
  
  
  What three rounds bought me
&lt;/h2&gt;

&lt;p&gt;The whole calibration — three rounds, four servers — cost about $0.60 on a small model. For that I got answers to the three questions any eval must survive:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is it fair?&lt;/strong&gt; After the synthesis fix, well-designed servers score 100%. Failures now mean something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it discriminate?&lt;/strong&gt; Messy catalogs score measurably worse, in interpretable ways.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it affordable?&lt;/strong&gt; ~$0.04–0.2 per server. Running it on every PR is a rounding error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most benchmark builders skip straight to leaderboards. The calibration step — deliberately trying to prove your own metric is lying — is cheap, unglamorous, and the only thing separating a measurement from a random number generator with axes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The calibration isn't finished — a reader proved it
&lt;/h2&gt;

&lt;p&gt;Within a day of the launch post, a reader (&lt;a href="https://dev.to/mads_hansen_27b33ebfee4c9/comment/3bjlc"&gt;Mads Hansen, in the dev.to comments&lt;/a&gt;) pointed out two flaws I hadn't caught, and he's right on both.&lt;/p&gt;

&lt;p&gt;First: my outcome taxonomy is still too coarse. "Refusal" currently lumps together a model that &lt;em&gt;asks a clarifying question&lt;/em&gt; and a model that &lt;em&gt;declines outright&lt;/em&gt; — and neither is separated from the truly dangerous outcome, confidently calling a plausible-but-wrong tool. Four buckets (correct call / correct refusal / correct clarification / unsafe plausible action) with different weights is strictly better, because their production costs are wildly different.&lt;/p&gt;

&lt;p&gt;Second, and subtler: &lt;strong&gt;my synthetic tasks can flatter the schemas that generated them.&lt;/strong&gt; The synthesizer reads the catalog to write tasks — so a badly-written catalog produces tasks phrased in its own bad vocabulary. The fix is held-out authoring: derive intents from real integration failures, paraphrase them through a step that never sees tool names, and freeze the test split before touching any descriptions.&lt;/p&gt;

&lt;p&gt;Both are now &lt;a href="https://github.com/TengByte/mcpgrade/issues" rel="noopener noreferrer"&gt;tracked issues on the repo&lt;/a&gt;. Which is the point of publishing your methodology instead of just your leaderboard: readers debug your benchmark the way they'd debug your code.&lt;/p&gt;

&lt;p&gt;Full raw numbers and methodology live in the repo: &lt;a href="https://github.com/TengByte/mcpgrade/blob/main/docs/eval-calibration.md" rel="noopener noreferrer"&gt;docs/eval-calibration.md&lt;/a&gt;. If you build agent benchmarks and have found other systematic unfairness patterns, I want to hear about them — open an issue.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build production AI agent integrations at a large tech company; mcpgrade is a personal project. The eval runs on any OpenAI-compatible endpoint — bring your own key.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>I lint-scanned 36 popular MCP servers. A third of them are failing your agent.</title>
      <dc:creator>Teng</dc:creator>
      <pubDate>Wed, 22 Jul 2026 06:22:03 +0000</pubDate>
      <link>https://dev.to/tengbyte/i-lint-scanned-36-popular-mcp-servers-a-third-of-them-are-failing-your-agent-102d</link>
      <guid>https://dev.to/tengbyte/i-lint-scanned-36-popular-mcp-servers-a-third-of-them-are-failing-your-agent-102d</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://tengli.dev/posts/mcp-servers-failing-agents.html" rel="noopener noreferrer"&gt;tengli.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your MCP server can be 100% spec-compliant and still be unusable by an agent.&lt;/p&gt;

&lt;p&gt;The Model Context Protocol spec tells you how to &lt;em&gt;transport&lt;/em&gt; tools: JSON-RPC framing, capability negotiation, schema shapes. It says nothing about whether a model can actually &lt;em&gt;use&lt;/em&gt; what you serve — whether it picks the right tool out of your catalog, fills the arguments correctly, or burns 8k tokens parsing your schemas on every single request.&lt;/p&gt;

&lt;p&gt;I integrate first- and third-party MCP connectors into a production AI agent for a living, and I kept seeing the same failure: servers that pass every compliance check, yet the model calls the wrong tool, hallucinates arguments, or ignores the tool entirely. The problems were never in the protocol layer. They were in the parts no one lints: descriptions, naming, schema design.&lt;/p&gt;

&lt;p&gt;So I wrote &lt;a href="https://github.com/TengByte/mcpgrade" rel="noopener noreferrer"&gt;mcpgrade&lt;/a&gt; — a Lighthouse-style scorecard for MCP servers. One command, no API key, report in seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mcpgrade &lt;span class="nt"&gt;--stdio&lt;/span&gt; &lt;span class="s2"&gt;"npx -y your-mcp-server"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I pointed it at 36 popular servers. It did not go great.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;Full sortable table: &lt;a href="https://tengli.dev/mcp-leaderboard.html" rel="noopener noreferrer"&gt;https://tengli.dev/mcp-leaderboard.html&lt;/a&gt;. The short version (static analysis, point-in-time snapshot; servers marked &lt;em&gt;(archived)&lt;/em&gt; are unmaintained reference implementations, included because they're still widely installed and copied):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Top of the class (A):&lt;/strong&gt; brave-search &lt;em&gt;(archived)&lt;/em&gt;, exa, google-maps &lt;em&gt;(archived)&lt;/em&gt;, slack &lt;em&gt;(archived)&lt;/em&gt;, perplexity-ask, @shopify/dev-mcp, @apify/actors-mcp-server, airbnb, figma-developer-mcp, tavily, gitlab &lt;em&gt;(archived)&lt;/em&gt;, elastic, shrimp-task-manager, and more — 15 of 36.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bottom of the class (D/F), 11 of 36 — and it's not hobby projects:&lt;/strong&gt; MongoDB's official server (66, with 66 errors), Notion's official server (62), Airtable (69, 66 errors), todoist-mcp-server (67, &lt;strong&gt;110 errors&lt;/strong&gt;), GitHub's archived reference server (67, 44 errors), and firecrawl-mcp at the very bottom (57, &lt;strong&gt;134 errors&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Two more servers (Stripe, Supabase) couldn't be scanned with dummy credentials and were excluded rather than graded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: the ecosystem has an undocumented-parameter epidemic
&lt;/h2&gt;

&lt;p&gt;Almost every D/F server has a &lt;strong&gt;descriptions score of zero&lt;/strong&gt; while its schema, naming, and token scores are fine. One rule dominates: &lt;code&gt;D004 — parameter has no description&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;firecrawl: 132 of its 134 errors are undocumented parameters. &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;formats&lt;/code&gt;, &lt;code&gt;jsonOptions&lt;/code&gt; — the model gets a name and a type, nothing else. todoist: 110. MongoDB and Airtable: 66 each.&lt;/p&gt;

&lt;p&gt;The root cause is visible in the source of nearly all of them: &lt;strong&gt;schemas are generated from zod or OpenAPI definitions, and nobody adds &lt;code&gt;.describe()&lt;/code&gt;&lt;/strong&gt;. The type system knows &lt;code&gt;url: string&lt;/code&gt;. The model needs to know &lt;em&gt;which&lt;/em&gt; URL, in what format, with what constraints. Your schema generator is quietly stripping the single most important signal your tools have.&lt;/p&gt;

&lt;p&gt;If you take one thing from this post: open your server, count the parameters without a &lt;code&gt;description&lt;/code&gt;, and fix them. It's the highest-leverage hour you can spend on agent reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: it's documentation discipline, not catalog size — but size makes discipline harder
&lt;/h2&gt;

&lt;p&gt;My first pass at this data suggested "small catalogs win": most 95+ scorers have few tools, and the 24–26 tool servers cluster at D/F. Then shrimp-task-manager scored &lt;strong&gt;A/96 with 15 tools&lt;/strong&gt; — carefully documented, tightly named, every description distinct.&lt;/p&gt;

&lt;p&gt;So the honest version: &lt;strong&gt;well-documented big catalogs are possible; they're just rare.&lt;/strong&gt; Every tool you add is another description to write, another name that can collide, another schema to keep tight. Discipline doesn't scale by default. (Size still taxes you either way: the full catalog is serialized into every request.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: compliance and usability are different axes
&lt;/h2&gt;

&lt;p&gt;The most-updated servers aren't the most usable ones. The &lt;em&gt;archived&lt;/em&gt; Slack reference server — code nobody maintains — scores A/97, because someone once documented every tool and every parameter by hand. Meanwhile several actively-developed commercial servers ship parameters with no descriptions at all.&lt;/p&gt;

&lt;p&gt;Agent usability is a &lt;em&gt;writing&lt;/em&gt; problem more than an engineering problem. Compliance checkers can't measure it. That's the gap mcpgrade fills.&lt;/p&gt;

&lt;p&gt;(One hopeful counterpoint: while writing this, context7 shipped a new version that fixed all its missing parameter descriptions — jumping from C to a perfect static score. The ecosystem can move fast when the gap is visible.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: I checked the static scores against a real model. The scary number is refusal.
&lt;/h2&gt;

&lt;p&gt;Static lint is a proxy, so I built &lt;code&gt;--eval&lt;/code&gt;: it synthesizes realistic single-step tasks (each embedding concrete values for every required parameter), shows a model the full catalog, and measures whether it picks the right tool and fills valid arguments. Calibration details and methodology: &lt;a href="https://github.com/TengByte/mcpgrade/blob/main/docs/eval-calibration.md" rel="noopener noreferrer"&gt;docs/eval-calibration.md&lt;/a&gt;. Cost: pennies per server on a small model.&lt;/p&gt;

&lt;p&gt;Two results worth your attention:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static findings predict live confusion.&lt;/strong&gt; On well-documented servers, tool-selection accuracy was 100%. On firecrawl it dropped to 84% — and the misses land &lt;em&gt;exactly&lt;/em&gt; on the naming collisions static rules flag: &lt;code&gt;extract&lt;/code&gt;↔&lt;code&gt;scrape&lt;/code&gt;, &lt;code&gt;agent_status&lt;/code&gt;↔&lt;code&gt;check_crawl_status&lt;/code&gt;, &lt;code&gt;feedback&lt;/code&gt;↔&lt;code&gt;search_feedback&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Big fuzzy catalogs break refusal.&lt;/strong&gt; Given deliberately out-of-scope tasks, the model correctly declined 100% of the time on small, well-documented catalogs — but only &lt;strong&gt;50% of the time&lt;/strong&gt; on firecrawl's 26 fuzzy tools. Half the time it "found" a plausible tool and called it. In production, that's an agent doing &lt;em&gt;something&lt;/em&gt; when it should do &lt;em&gt;nothing&lt;/em&gt; — arguably the most dangerous failure mode there is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "good" looks like
&lt;/h2&gt;

&lt;p&gt;From the top scorers, a checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every tool description answers three questions: what it does, when to use it, what it returns.&lt;/li&gt;
&lt;li&gt;Every parameter has a description with format and one example value.&lt;/li&gt;
&lt;li&gt;Fixed value sets live in &lt;code&gt;enum&lt;/code&gt;, not in prose.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;required&lt;/code&gt; is declared explicitly — even when it's empty.&lt;/li&gt;
&lt;li&gt;One naming convention, verb_object style, no generic verbs, no near-twin names.&lt;/li&gt;
&lt;li&gt;Errors name the missing/invalid parameter so the model can self-correct in one turn.
## Try it on your server
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mcpgrade &lt;span class="nt"&gt;--stdio&lt;/span&gt; &lt;span class="s2"&gt;"node ./my-server.js"&lt;/span&gt;   &lt;span class="c"&gt;# local stdio&lt;/span&gt;
npx mcpgrade https://my-server.example/mcp    &lt;span class="c"&gt;# streamable HTTP&lt;/span&gt;
npx mcpgrade &amp;lt;target&amp;gt; &lt;span class="nt"&gt;--fail-on&lt;/span&gt; error         &lt;span class="c"&gt;# CI gate&lt;/span&gt;
npx mcpgrade &amp;lt;target&amp;gt; &lt;span class="nt"&gt;--eval&lt;/span&gt;                  &lt;span class="c"&gt;# live model test (BYO key; any OpenAI-compatible endpoint works)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;24 rules, each with a concrete fix and a rationale you're welcome to dispute in the issues — the ruleset is opinionated by design, and I'd rather have the argument in public. (How this differs from mcp-lint and other MCP QA tools — with side-by-side outputs: &lt;a href="https://github.com/TengByte/mcpgrade/blob/main/docs/comparison.md" rel="noopener noreferrer"&gt;docs/comparison.md&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;If you maintain one of the servers above and fix your score, open a &lt;code&gt;rescan&lt;/code&gt; issue — I'll happily re-run and update the table. PRs to your own servers beat arguments with my ruleset.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build production AI agent integrations at a large tech company; mcpgrade is a personal project and reflects scars from integrating dozens of MCP connectors. No affiliation with any server ranked above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>mcp</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
