<?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: Jonathan Langens</title>
    <description>The latest articles on DEV Community by Jonathan Langens (@langensjonathan).</description>
    <link>https://dev.to/langensjonathan</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%2F4048437%2F1f6862d0-d482-459d-8e39-18cce1a92117.jpg</url>
      <title>DEV Community: Jonathan Langens</title>
      <link>https://dev.to/langensjonathan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/langensjonathan"/>
    <language>en</language>
    <item>
      <title>MAVERIK: Why MCP Agents Need Their Own JMeter</title>
      <dc:creator>Jonathan Langens</dc:creator>
      <pubDate>Sun, 02 Aug 2026 12:03:26 +0000</pubDate>
      <link>https://dev.to/langensjonathan/maverik-why-mcp-agents-need-their-own-jmeter-27cn</link>
      <guid>https://dev.to/langensjonathan/maverik-why-mcp-agents-need-their-own-jmeter-27cn</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 3 of 3 — building and testing MCP agents&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you've done any performance testing, you know the JMeter mindset: "is my system fast enough" is a question you answer by measuring, not guessing. Define a test plan, throw load at the system, read off the numbers, and now you know whether a change made things better or worse — not "it felt faster."&lt;/p&gt;

&lt;p&gt;MCP agents don't have an equivalent, and they badly need one. Most agent development I've seen — including my own, until recently — looks like: tweak the system prompt, run it a few times by hand, eyeball whether the answers seem better, ship it. That's not measurement. That's vibes with extra steps.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  What I built to fix that
&lt;/h1&gt;

&lt;p&gt;MAVERIK, the MCP host from the earlier parts of this series, has grown a built-in testing framework that treats agent definitions the way JMeter treats endpoints: something you define once, run systematically, and compare on hard numbers instead of impressions.&lt;/p&gt;

&lt;p&gt;The mapping is almost one-to-one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JMeter&lt;/th&gt;
&lt;th&gt;MAVERIK&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test plan&lt;/td&gt;
&lt;td&gt;Test suite — a set of questions, each with a pass criterion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sampler firing one request&lt;/td&gt;
&lt;td&gt;A question firing one prompt at an agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assertion on the response&lt;/td&gt;
&lt;td&gt;A criterion: exact match, substring, regex, or an LLM judge with a rubric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread group running N times&lt;/td&gt;
&lt;td&gt;Repetitions — because LLM answers aren't deterministic, and one lucky run tells you nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One detail on judge criteria worth calling out: the judge is a second model, so it burns tokens of its own. MAVERIK tracks the judge's cost &lt;em&gt;separately&lt;/em&gt;, so it never pollutes the consumed budget of the agent being tested. If you don't do that, every rubric-scored suite silently makes your agents look more expensive than they are.&lt;/p&gt;

&lt;p&gt;Point a suite at a set of agent definitions and MAVERIK fires every question at every one of them, spinning up a fresh agent instance per case, and records what Part 2 called the outcome parameters: whether the instance passed, how long it took (the whole turn — LLM and tool time both), how many input and output tokens it burned across every round trip, and how many tool calls it made — which tools, specifically, since some tools are far more expensive to call than others.&lt;/p&gt;

&lt;h1&gt;
  
  
  The two-parameter split, operationalized
&lt;/h1&gt;

&lt;p&gt;This is where the framing from Part 2 actually earns its keep: you tune the definition, and you measure the instances. Before you run anything, you write the outcome parameters down as criteria — what counts as correct, what rubric a judge should apply. That's the task contract made executable. Then you sweep the tunable parameters, one definition field at a time: a different system context, a cheaper model, a parallel-tool-calling strategy instead of sequential, a trimmed MCP server set. MAVERIK tells you, in the units you defined up front, whether the change helped or just moved the cost somewhere else.&lt;/p&gt;

&lt;p&gt;That's the actual payoff. Not "did the agent answer the question" in isolation, but "does the v2 system context answer just as well for a third of the tokens" or "is the cheap model actually good enough for this class of question, or does it only look that way on the easy cases."&lt;/p&gt;

&lt;p&gt;If that sounds abstract, I published a worked example last week: two agent definitions differing only in their MCP server set, one question, and a 28% cost difference that had nothing to do with answer quality — the whole experiment was a config duplication and a one-question suite. That post &lt;em&gt;is&lt;/em&gt; this loop, run once by hand. This part is about the loop itself.&lt;/p&gt;

&lt;h1&gt;
  
  
  Built the hard way, on purpose
&lt;/h1&gt;

&lt;p&gt;The reason any of this is possible is the design choice from Part 1: the tool-calling loop is driven by hand, not by an SDK's auto-invocation mode. Every tool call is visible, every round trip is countable, every token is attributable — and critically, the exact same loop code runs whether you're chatting with an agent interactively or running it through a full benchmark suite. Test results only mean something if they predict real behavior, and that's only true if the test path and the production path are the same code, not a simulation of it.&lt;/p&gt;

&lt;p&gt;There's more to build: cost prediction that accounts for which tools are expensive to call, and parameter sweeps that generate a matrix of definitions from ranges instead of hand-authoring each one. But the core loop already works: define what "good" means, run it against real agent definitions, and let the numbers do the arguing instead of intuition.&lt;/p&gt;

&lt;p&gt;The next step after that is the obvious one for anyone with a CI/CD reflex: if a suite can tell you whether a change helped, it can also tell you whether a change &lt;em&gt;broke something&lt;/em&gt; — which means it belongs in the pipeline, running on every commit, not just when someone remembers to check. That's the next article.&lt;/p&gt;

&lt;p&gt;If you're building on MCP and you've ever shipped a prompt change on vibes, I'd love to hear how you're currently deciding whether it actually helped. The project is open source — &lt;a href="https://github.com/langens-jonathan/maverik" rel="noopener noreferrer"&gt;https://github.com/langens-jonathan/maverik&lt;/a&gt;.&lt;/p&gt;

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

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

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

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
    </item>
    <item>
      <title>Do unused MCP tools cost you money?</title>
      <dc:creator>Jonathan Langens</dc:creator>
      <pubDate>Thu, 30 Jul 2026 18:18:36 +0000</pubDate>
      <link>https://dev.to/langensjonathan/do-unused-mcp-tools-cost-you-money-2gnb</link>
      <guid>https://dev.to/langensjonathan/do-unused-mcp-tools-cost-you-money-2gnb</guid>
      <description>&lt;p&gt;&lt;em&gt;A short case study from my "building and testing MCP agents" series — it stands on its own, but the method behind it is laid out in &lt;a href="https://dev.to/langensjonathan/the-parameters-that-actually-matter-when-youre-tuning-an-ai-agent-2agd"&gt;https://dev.to/langensjonathan/the-parameters-that-actually-matter-when-youre-tuning-an-ai-agent-2agd&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I benchmarked two agents that are identical except for one thing — how many MCP servers they're connected to — on the exact same question. Both got the right answer, both called the same single tool. The one with more MCP servers attached still cost &lt;strong&gt;28% more per question&lt;/strong&gt;, purely from the extra tool schemas the model has to be told about on every single call, whether it uses them or not.&lt;/p&gt;

&lt;h1&gt;
  
  
  The setup
&lt;/h1&gt;

&lt;p&gt;MAVERIK is my open-source MCP test bench: define a suite of questions with pass criteria, run it against one or more agent configurations, and compare the results on hard numbers. This post is one deliberately tiny experiment with it: change exactly one thing about an agent, hold everything else fixed, and see what the numbers attribute to that one change.&lt;/p&gt;

&lt;p&gt;I have a small "GitHub summarizer" agent: one system prompt, one job — answer questions about my GitHub account by calling the &lt;a href="https://github.com/github/github-mcp-server" rel="noopener noreferrer"&gt;GitHub MCP server&lt;/a&gt;. I duplicated its configuration (MAVERIK supports this directly — same model, same prompt, same everything) and changed one field on the copy: the set of attached MCP servers, adding &lt;code&gt;deepwiki&lt;/code&gt;, &lt;code&gt;microsoft-learn&lt;/code&gt;, and &lt;code&gt;context7&lt;/code&gt;. Neither agent needs any of those three for the question I was about to ask; they were attached because that's what the "kitchen sink" version of this agent had accumulated over a few sessions of general-purpose use.&lt;/p&gt;

&lt;p&gt;Then I wrote the simplest possible test suite — one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How many repositories do I have?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;with a &lt;code&gt;contains&lt;/code&gt; criterion checking the answer includes the correct count. No judge model, no subjectivity — it either says the right number or it doesn't. I ran both agents against it, 2 repetitions each, same model (&lt;code&gt;claude-haiku&lt;/code&gt;) for both, and pulled up MAVERIK's &lt;strong&gt;Agent Comparison&lt;/strong&gt; report.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Agent A — &lt;code&gt;github&lt;/code&gt; only&lt;/th&gt;
&lt;th&gt;Agent B — &lt;code&gt;github&lt;/code&gt; + 3 more MCP servers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MCP servers attached&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pass rate&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;Tool calls per question&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;get_me&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;get_me&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iterations per question&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg input tokens&lt;/td&gt;
&lt;td&gt;16,628&lt;/td&gt;
&lt;td&gt;21,467&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg output tokens&lt;/td&gt;
&lt;td&gt;138&lt;/td&gt;
&lt;td&gt;144.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg peak context tokens&lt;/td&gt;
&lt;td&gt;8,494&lt;/td&gt;
&lt;td&gt;10,919.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg duration&lt;/td&gt;
&lt;td&gt;3,659 ms&lt;/td&gt;
&lt;td&gt;3,061 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token cost / question&lt;/td&gt;
&lt;td&gt;$0.034636&lt;/td&gt;
&lt;td&gt;$0.044379&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool cost / question&lt;/td&gt;
&lt;td&gt;$0.0000&lt;/td&gt;
&lt;td&gt;$0.0000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overall cost / question&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.034636&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.044379&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(Raw CSV, straight out of the report's export button, is at the bottom of this post if you want to check my arithmetic.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually moved
&lt;/h2&gt;

&lt;p&gt;Three numbers moved together, consistently, across both repetitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input tokens: +29.1%&lt;/strong&gt; (16,628 → 21,467) — and it's &lt;em&gt;exactly&lt;/em&gt; the same number on both repetitions for each agent. That's not noise; that's the tool catalog being serialized into every request, deterministically, regardless of which tools actually get called.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peak context tokens: +28.6%&lt;/strong&gt; (8,494 → 10,919.5) — same story, since peak context is dominated by that same input payload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost per question: +28.1%&lt;/strong&gt; ($0.034636 → $0.044379) — tracking the input-token increase almost 1:1, because output tokens barely moved (138 vs. 144.5) and no priced tools were called by either agent. The three unused MCP servers weren't free — they were billed on every single request, whether the model reached for them or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One caveat I want to make before someone else does: 28% is a function of how small this task is. The tax itself is absolute — ~4,839 extra input tokens on every request — so on a task with 50k tokens of real context the &lt;em&gt;percentage&lt;/em&gt; would look much smaller. But that's the wrong comfort: the absolute tax is paid on every iteration of every run this agent ever makes, forever. Short tasks are where the tax is most &lt;em&gt;visible&lt;/em&gt;, not where it's worst.&lt;/p&gt;

&lt;h1&gt;
  
  
  What &lt;em&gt;didn't&lt;/em&gt; move — and why that's the more interesting part
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pass rate: identical (100%/100%).&lt;/strong&gt; Extra tools didn't confuse the model into a worse answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool calls and iterations: identical (1 call, 2 iterations, same tool — &lt;code&gt;get_me&lt;/code&gt; — both times).&lt;/strong&gt; The model didn't waste a call poking at an irrelevant server "just in case." Having more options didn't make it indecisive here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duration: actually &lt;em&gt;lower&lt;/em&gt; for the agent with more servers attached&lt;/strong&gt; (3,061 ms vs. 3,659 ms) — the opposite of what the token numbers would predict. Looking at the individual repetitions, agent A's first call was a slow outlier (4,402 ms) that dragged its average up; its second call (2,916 ms) was actually faster than either of agent B's. With only 2 repetitions this is provider-side latency noise, not signal — which is exactly why MAVERIK has a &lt;code&gt;repetitions&lt;/code&gt; knob in the first place. I'd want a much larger &lt;code&gt;n&lt;/code&gt; before drawing any conclusion about latency here. Token counts, by contrast, needed zero repetitions to trust — they were bit-for-bit identical across both runs of the same agent, because they're a function of the prompt and tool schemas, not the network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That asymmetry is the real lesson: &lt;strong&gt;cost is a much cleaner signal than latency.&lt;/strong&gt; If I'd only looked at duration, I'd have concluded the bigger agent was &lt;em&gt;better&lt;/em&gt;. It took breaking the run down by token count — something a stopwatch can't tell you — to see the actual, deterministic tax of the extra servers.&lt;/p&gt;

&lt;h1&gt;
  
  
  The takeaway
&lt;/h1&gt;

&lt;p&gt;It's a reminder that &lt;strong&gt;"which MCP servers does this agent have access to" is a tunable parameter with a real, measurable cost, not a one-time architectural decision you make and forget.&lt;/strong&gt; Every tool a server exposes gets its name, description, and JSON schema stuffed into the model's context on every call. A server with a large or verbose tool catalog is a standing tax on every request an agent makes, independent of whether that agent ever calls it.&lt;/p&gt;

&lt;p&gt;There's a design consequence hiding in that, too. Restricting which tools an agent may use only saves you money if the host enforces the restriction &lt;strong&gt;before serialization&lt;/strong&gt;: a disallowed tool has to be left out of the request entirely, not merely rejected when the model tries to call it. Enforce it at call time and you keep paying the token tax for tools the agent was never allowed to touch. The same trimming shrinks your actual attack surface, in the security sense — every unused tool schema in context is one more thing a prompt injection can try to steer the model toward.&lt;/p&gt;

&lt;p&gt;The practical version of this: if you're building agents against MCP servers, don't default to "attach everything, it might be useful." Give each agent only the servers its job actually requires, and — if you're not sure whether a broader toolset is worth its overhead — measure it instead of guessing. That's a two-line duplication of an agent config and a one-question test suite, and the answer was sitting in a CSV five minutes later.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reproducing this
&lt;/h1&gt;

&lt;p&gt;This whole thing was: duplicate an agent config, write a one-question suite with a &lt;code&gt;contains&lt;/code&gt; criterion, run both agents against it a couple of times, open the built-in &lt;strong&gt;Agent Comparison&lt;/strong&gt; report (or export it to CSV, like I did above). If you're evaluating your own MCP servers or agent configs, the project is open source — &lt;a href="https://github.com/langens-jonathan/maverik" rel="noopener noreferrer"&gt;https://github.com/langens-jonathan/maverik&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Raw CSV (Reporting → Agent Comparison → Export → To CSV)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;suiteId,agentId,timestamp,sourceRunId,passRate,avgDurationMs,avgInputTokens,avgOutputTokens,avgToolCalls,avgPeakContextTokens,tokenCost,toolCost,overallCost
github-summarizer,github-test-agent,2026-07-26T10:07:18.9720785+00:00,github-summarizer-20260726-100718,1,3659,16628,138,1,8494,0.034636,0,0.034636
github-summarizer,github-test-agent-all-mcp-servers,2026-07-26T10:07:18.9720785+00:00,github-summarizer-20260726-100718,1,3061,21467,144.5,1,10919.5,0.044379,0,0.044379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>The Parameters That Actually Matter When You're Tuning an AI Agent</title>
      <dc:creator>Jonathan Langens</dc:creator>
      <pubDate>Thu, 30 Jul 2026 07:40:28 +0000</pubDate>
      <link>https://dev.to/langensjonathan/the-parameters-that-actually-matter-when-youre-tuning-an-ai-agent-2agd</link>
      <guid>https://dev.to/langensjonathan/the-parameters-that-actually-matter-when-youre-tuning-an-ai-agent-2agd</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 2 of 3 — building and testing MCP agents&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every AI agent is a bundle of decisions, most of which get made once, informally, and never revisited: which model, what system prompt, which tools it's allowed to touch, how many steps it gets before you give up on it. Nobody writes these down as a config. They just happen, in code, and then everyone forgets they were choices at all.&lt;/p&gt;

&lt;p&gt;In Part 1 I argued those decisions have a shape: an &lt;strong&gt;agent definition&lt;/strong&gt; (the class — loop, system context, capability boundary, model, limits) and an &lt;strong&gt;agent instance&lt;/strong&gt; (a running occurrence — goal, context window, credentials, consumed budget). That split turns out to be exactly what you need for tuning, because the two kinds of parameters people mix up when "tuning an agent" live on opposite sides of it: you tune the definition, and you measure the instances.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tunable parameters: the fields of the agent definition
&lt;/h1&gt;

&lt;p&gt;These are the levers — change any one and you have a different agent, even if everything else stays the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;system context&lt;/strong&gt; — how the agent is instructed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM&lt;/strong&gt; — which model answers, and at what price&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP server set and capability boundary&lt;/strong&gt; — which tool sources exist, and which of their tools this agent may actually reach for&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the host loop&lt;/strong&gt; — sequential vs parallel tool execution, retry behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;termination limits&lt;/strong&gt; — how many iterations and tokens it gets before you cut it off&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;context-size strategy&lt;/strong&gt; — how it manages a growing conversation history as a task gets longer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the agent definition from Part 1, field for field — with one deliberate absence. The &lt;strong&gt;task contract&lt;/strong&gt; is the only definition field that isn't a lever. You don't tune it; you tune everything else &lt;em&gt;against&lt;/em&gt; it. Hold that thought.&lt;/p&gt;

&lt;p&gt;The consequence of seeing these as fields is simple but real: if you don't track &lt;em&gt;which&lt;/em&gt; field you changed, you can't attribute &lt;em&gt;why&lt;/em&gt; the results changed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Outcome parameters: what you measure on the instances
&lt;/h1&gt;

&lt;p&gt;This is the half people skip. An agent isn't "good" in the abstract — it's good or bad against criteria you define yourself, and every one of them is a measurement taken from agent instances after they run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Does it reach an acceptable answer?&lt;/strong&gt; Correctness isn't binary — sometimes it's "contains the right fact," sometimes it needs a rubric and a second model to judge it. Notice what this is: the task contract made testable. The definition says what the agent must return; the outcome criterion checks whether an instance actually did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How long does it take?&lt;/strong&gt; Wall-clock time, including every tool call, not just the model's "thinking."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many tokens does it burn?&lt;/strong&gt; Input and output, summed across every round trip, not just the final message. This is the instance's consumed budget, read back out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which tools does it reach for, and how often?&lt;/strong&gt; Underrated. Some tools are cheap to call, others are expensive — rate-limited APIs, slow external services, tools with real dollar cost per call. An agent that gets the right answer by using an expensive tool once is worse than one that gets there in five cheap calls, even if both "pass."&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The order matters more than the list
&lt;/h1&gt;

&lt;p&gt;Here's the part that's easy to get backwards: you have to define the outcome parameters &lt;em&gt;before&lt;/em&gt; you start sweeping the tunable ones. Decide what a correct answer looks like, what latency is acceptable, what token budget you're willing to spend, and which tools should be used sparingly — write it down as a criterion, a rubric, a budget. Only then start trying a tighter system context, a cheaper model, a different loop strategy.&lt;/p&gt;

&lt;p&gt;Skip that step and "tuning" becomes vibes — you nudge a prompt, the answer looks a little better in the three examples you tried, you ship it, and you have no idea whether it's actually better or just different. Do it in the right order and every change becomes a testable hypothesis: does this specific change to one definition field move the numbers I said mattered, in the direction I wanted, without moving the ones I didn't touch?&lt;/p&gt;

&lt;h1&gt;
  
  
  Why this matters: an agent definition is data, not code
&lt;/h1&gt;

&lt;p&gt;The reason this framing pays off in practice is that once the system context, model, capability boundary, and loop settings are &lt;em&gt;configuration&lt;/em&gt; rather than something baked into code — an &lt;code&gt;agents.json&lt;/code&gt;, in my host — comparing two agents stops being a rewrite and becomes a diff. Point the same set of test questions at two definitions that differ in exactly one field, run a batch of instances against each, and any difference in the measured outcomes is attributable to that one field.&lt;/p&gt;

&lt;p&gt;That's the whole game: tune the class, measure the instances. And it's exactly the setup you need for what comes next — running these comparisons systematically instead of by hand.&lt;/p&gt;

&lt;p&gt;The link to part 1 of this series: &lt;a href="https://dev.to/langensjonathan/mcp-agents-explained-what-actually-makes-an-llm-an-agent-12a0"&gt;https://dev.to/langensjonathan/mcp-agents-explained-what-actually-makes-an-llm-an-agent-12a0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>agents</category>
    </item>
    <item>
      <title>MCP Agents, Explained: What Actually Makes an LLM an "Agent"</title>
      <dc:creator>Jonathan Langens</dc:creator>
      <pubDate>Wed, 29 Jul 2026 11:24:27 +0000</pubDate>
      <link>https://dev.to/langensjonathan/mcp-agents-explained-what-actually-makes-an-llm-an-agent-12a0</link>
      <guid>https://dev.to/langensjonathan/mcp-agents-explained-what-actually-makes-an-llm-an-agent-12a0</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 1 of 3 — building and testing MCP agents&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Agent" has become one of those words that means everything and nothing. So let's ground it.&lt;/p&gt;

&lt;p&gt;On its own, a large language model does one thing: it turns text into text. Ask it a question, it predicts an answer. It can't look anything up, check a fact against a live system, or take an action in the world. Everything it "knows" is frozen at training time.&lt;/p&gt;

&lt;p&gt;An agent changes that by wiring the model into a loop with tools. Give it a set of functions it can call — read a file, hit an API, query a database — and a loop that feeds each tool's result back to it, and something different happens. The model can now decide it needs information, ask for a tool, read what comes back, decide whether that's enough or whether it needs another tool, and only then answer.&lt;/p&gt;

&lt;p&gt;That loop — &lt;em&gt;call a tool, or answer, repeat until done&lt;/em&gt; — is the core of every system people call an agent, dressed up in different frameworks. But to me it seems the loop alone is not the whole definition. A loop with no stop condition is a runaway process; a loop with no tool policy is a security incident waiting to happen. When I tried to pin down what I actually configure when I set up an agent, I ended up with two separate things.&lt;/p&gt;

&lt;h1&gt;
  
  
  Agent definition and agent instance
&lt;/h1&gt;

&lt;p&gt;I split the concept in two parts, like a class and its instances. An &lt;strong&gt;agent definition&lt;/strong&gt; is the template; an &lt;strong&gt;agent instance&lt;/strong&gt; is one running occurrence of it. (I wrote a separate article going deeper into this definition and how the newest MCP spec stress-tests it — link at the bottom.)&lt;/p&gt;

&lt;p&gt;The agent definition consists of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;the host loop&lt;/strong&gt; — the core, as above&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;system context&lt;/strong&gt; — persona, instructions. Static and authored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP server set&lt;/strong&gt; — which tool sources exist for this agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;capability boundary / tool policy&lt;/strong&gt; — which servers and tools this agent &lt;em&gt;may&lt;/em&gt; use, and what needs approval. This is a role, not a login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM&lt;/strong&gt; — the actual model we are using&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;termination limits&lt;/strong&gt; — max iterations, token budget, stop conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;task contract&lt;/strong&gt; — what this agent accepts and what it must return&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;context-size strategy&lt;/strong&gt; — how it keeps the conversation inside the model's window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent instance is what you get when a definition picks up real work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;a goal&lt;/strong&gt; — what the user actually asked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a working context window&lt;/strong&gt; — conversation history, tool results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;credentials&lt;/strong&gt; — whose tokens, on whose behalf&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;consumed budget&lt;/strong&gt; — iterations elapsed, tokens spent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note the symmetry: the definition holds &lt;em&gt;limits&lt;/em&gt;, &lt;em&gt;contracts&lt;/em&gt; and &lt;em&gt;capabilities&lt;/em&gt;; the instance holds &lt;em&gt;counters&lt;/em&gt;, &lt;em&gt;goals&lt;/em&gt; and &lt;em&gt;credentials&lt;/em&gt;. Static in the class, dynamic in the instance. Keep this split in mind — the rest of the series leans on it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where MCP fits in
&lt;/h1&gt;

&lt;p&gt;The Model Context Protocol (MCP) standardizes one half of the picture: how a host discovers and calls tools in the first place. Before MCP, every "agent plus data source" pairing needed its own bespoke integration — a Slack agent, a GitHub agent, a database agent, each wired by hand. MCP turns that into a protocol: any MCP server exposes typed, discoverable tools over a standard interface, and any MCP-aware host can connect to any MCP server without custom glue code.&lt;/p&gt;

&lt;p&gt;Think of it as the plug standardizing, not the appliance. The agent — definition, instance, and the loop between them — still has to exist on the host side. MCP just means the host doesn't have to reinvent "how do I talk to this particular tool" every single time.&lt;/p&gt;

&lt;h1&gt;
  
  
  What this looks like in practice
&lt;/h1&gt;

&lt;p&gt;I've been building a small MCP host that does exactly this: it connects to one or more MCP servers, aggregates whatever tools they expose, and drives that call-a-tool-or-answer loop on behalf of an LLM. The interesting design decision wasn't the happy path — it was resisting the temptation to let the SDK auto-run tools invisibly. Most agent frameworks offer a mode where you hand the model a tool list and it just handles the rest, opaquely, in one call.&lt;/p&gt;

&lt;p&gt;I deliberately don't do that. The host drives the loop by hand: send the conversation, inspect the response for tool-call requests, dispatch each one, feed the results back, repeat — capped by the termination limits from the agent definition, so nothing spirals. It's a few dozen more lines of code than the "just let the SDK do it" version. What it buys you is visibility: every tool call, every round trip, every token is something you can actually see and measure, instead of a black box between "here's a question" and "here's an answer."&lt;/p&gt;

&lt;p&gt;That visibility turns out to matter a lot once you start asking harder questions. Not just "does the agent work," but "is this system prompt actually better than that one," "is the cheaper model good enough," "what will this cost at scale." Those are measurement questions, not build questions — and they're what the rest of this series is about.&lt;/p&gt;

&lt;p&gt;Next up: the levers you have when configuring an agent — which, not coincidentally, are exactly the fields of the agent definition above — and the metrics that tell you whether pulling one made things better or worse.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Deeper dive into the definition itself: &lt;a href="https://dev.to/langensjonathan/what-is-an-agent-a-classinstance-definition-stress-tested-against-the-2026-07-28-mcp-spec-5akp"&gt;https://dev.to/langensjonathan/what-is-an-agent-a-classinstance-definition-stress-tested-against-the-2026-07-28-mcp-spec-5akp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>What is an 'agent'? A class/instance definition, stress-tested against the 2026-07-28 MCP spec</title>
      <dc:creator>Jonathan Langens</dc:creator>
      <pubDate>Tue, 28 Jul 2026 07:28:30 +0000</pubDate>
      <link>https://dev.to/langensjonathan/what-is-an-agent-a-classinstance-definition-stress-tested-against-the-2026-07-28-mcp-spec-5akp</link>
      <guid>https://dev.to/langensjonathan/what-is-an-agent-a-classinstance-definition-stress-tested-against-the-2026-07-28-mcp-spec-5akp</guid>
      <description>&lt;h1&gt;
  
  
  Industry standards
&lt;/h1&gt;

&lt;p&gt;In the Gen AI / LLM space, two standards have emerged as the de facto choice in their domains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A2A&lt;/strong&gt; (v1.0, Linux Foundation) defines how agents integrate and communicate with their peers. It defines the &lt;em&gt;interface&lt;/em&gt; of an agent, but not the agent itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; (2026-07-28 spec, finalizes today) defines how an agent uses tools. It defines the &lt;em&gt;internal plumbing&lt;/em&gt; of an agent, but not the agent itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One nuance to be fair to both: MCP can also act as an agent's public interface, when an agent is exposed as an MCP server inside a single trust domain. So A2A covers the cross-organization contract, and MCP covers everything inside it. Either way, the conclusion stands: neither protocol defines what an agent &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To me it seems the terms 'agent' and 'agentic' are still not well defined, and this article is my attempt at an operational definition: one precise enough that you could build a host around it. That claim is not hypothetical — I am building an MCP host, and this definition is what fell out of that work.&lt;/p&gt;

&lt;h1&gt;
  
  
  Prior art (and why it is not enough)
&lt;/h1&gt;

&lt;p&gt;"An agent is an LLM using tools in a loop" (Anthropic's framing) is directionally right, but it is not operational: it does not tell you what belongs in your &lt;code&gt;agents.json&lt;/code&gt;. The academic definitions (rational agents, BDI, FIPA) predate LLMs and do not map to context windows, token budgets or MCP servers. I want something in between: a definition you can serialize.&lt;/p&gt;

&lt;p&gt;Scope: this is a definition for agents that live in an MCP host. Frameworks like LangGraph organize things differently, but I believe the same components show up under different names.&lt;/p&gt;

&lt;h1&gt;
  
  
  The definitions
&lt;/h1&gt;

&lt;p&gt;I split the concept in two parts, like a class and its instances. Both live in the MCP host. From here on: an &lt;strong&gt;agent definition&lt;/strong&gt; is the class, an &lt;strong&gt;agent&lt;/strong&gt; is the instance.&lt;/p&gt;

&lt;p&gt;The agent definition consists of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;the host loop&lt;/strong&gt; — the core. The LLM deciding actions, observing results and iterating. Without a loop you have a chatbot with tools, not an agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;system context&lt;/strong&gt; — persona, instructions. Static and authored.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP server set&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;capability boundary / tool policy&lt;/strong&gt; — which servers and tools this agent &lt;em&gt;may&lt;/em&gt; use, and what needs approval. This is a role, not a login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM&lt;/strong&gt; — the actual model we are using&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;termination limits&lt;/strong&gt; — max iterations, token budget, stop conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;task contract&lt;/strong&gt; — the schema of what this agent accepts and what it must return&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;context-size strategy&lt;/strong&gt; — compaction vs top-x&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent is an instance of that class. In C# terms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;AgentDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;HostLoop&lt;/span&gt; &lt;span class="n"&gt;Loop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SystemContext&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;McpServerSet&lt;/span&gt; &lt;span class="n"&gt;Servers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CapabilityBoundary&lt;/span&gt; &lt;span class="n"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="n"&gt;Llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TerminationLimits&lt;/span&gt; &lt;span class="n"&gt;Limits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TaskContract&lt;/span&gt; &lt;span class="n"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextStrategy&lt;/span&gt; &lt;span class="n"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;AgentDefinition&lt;/span&gt; &lt;span class="n"&gt;Definition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AgentTask&lt;/span&gt; &lt;span class="n"&gt;Goal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextWindow&lt;/span&gt; &lt;span class="n"&gt;WorkingContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Principal&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Budget&lt;/span&gt; &lt;span class="n"&gt;Consumed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;goal&lt;/strong&gt; — what the user actually asked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;working context window&lt;/strong&gt; — conversation history, tool results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;credentials&lt;/strong&gt; — the principal: whose OAuth tokens, on whose behalf. Two instances of the same definition, running for two different users, differ only here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;consumed budget&lt;/strong&gt; — iterations elapsed, tokens spent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note the symmetry: the definition holds limits, contracts and capabilities; the instance holds counters, goals and credentials. Static in the class, dynamic in the instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the 2026-07-28 spec, or more precisely: the stateless core
&lt;/h2&gt;

&lt;p&gt;The new MCP spec removes protocol sessions entirely. A server no longer remembers you between calls. All state now travels in explicit handles that the model itself can see: task handles for long-running work, workflow ids, and the &lt;code&gt;requestState&lt;/code&gt; blob a paused call hands back.&lt;/p&gt;

&lt;p&gt;This breaks my 'working context window' as defined above. Those handles land in the context window, which means compacting — or worse, top-x-ing — becomes a &lt;em&gt;correctness&lt;/em&gt; concern instead of a cost concern. Summarize away a task handle and the agent has orphaned remote work it can never resume, because the stateless server has no session through which to remind it.&lt;/p&gt;

&lt;p&gt;So the instance needs a split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;compressible context&lt;/strong&gt; — conversation history, safe to compact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;load-bearing context&lt;/strong&gt; — outstanding handles and request state, never compacted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The host loop also stops being free-form. It must now support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the multi-round-trip pause — a tool call returns input-required plus request state; the loop surfaces the question and re-issues the call with the answer&lt;/li&gt;
&lt;li&gt;task polling (&lt;code&gt;tasks/get&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;unsolicited task handles arriving in results&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Serializable Agents
&lt;/h1&gt;

&lt;p&gt;Here is where the stateless core points. If instance state is exactly {goal, context, handles, credentials, consumed budget}, and the protocol holds no hidden session on your behalf, then an agent is a &lt;em&gt;serializable value&lt;/em&gt;. You can suspend it, persist it, and resume it in a different host process. Not a running process you must keep alive — a record you can store.&lt;/p&gt;

&lt;p&gt;Production reality will take a while to catch up (connection reuse, servers caches, ...), but the direction is set by the spec itself.&lt;/p&gt;

&lt;p&gt;I'm looking for feedback on this — especially from people running MCP hosts in production: what is in your agent config that this definition misses?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
