<?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: Miguel Angelo Sepulveda</title>
    <description>The latest articles on DEV Community by Miguel Angelo Sepulveda (@miguelangelo).</description>
    <link>https://dev.to/miguelangelo</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%2F3712492%2F113a80e6-95ce-4427-85f8-80497230f1dd.jpg</url>
      <title>DEV Community: Miguel Angelo Sepulveda</title>
      <link>https://dev.to/miguelangelo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/miguelangelo"/>
    <language>en</language>
    <item>
      <title>Something is squatting on port 3000</title>
      <dc:creator>Miguel Angelo Sepulveda</dc:creator>
      <pubDate>Mon, 10 Aug 2026 16:12:33 +0000</pubDate>
      <link>https://dev.to/miguelangelo/something-is-squatting-on-port-3000-5gnj</link>
      <guid>https://dev.to/miguelangelo/something-is-squatting-on-port-3000-5gnj</guid>
      <description>&lt;p&gt;I run my AI agent in a terminal split inside my IDE. Most of the time, it's a good arrangement. The DX beats the dedicated editor extensions, since the AI companies release console-first and the CLI is where new features land. The agent edits files, I watch the diffs and see what it's doing and why, and everyone stays in their lane.&lt;/p&gt;

&lt;p&gt;Then the agent decides it needs a dev server. It runs &lt;code&gt;pnpm dev&lt;/code&gt; with its shell tool, the way it runs everything else, and at that moment a process comes into existence that my editor knows nothing about. It's not in my task list. &lt;code&gt;&amp;lt;leader&amp;gt;ow&lt;/code&gt; shows my overseer tasks, humming along with their output buffers and their proper lifecycle — and this thing is not among them. I can't stop it from the editor. I can't tail it. If the agent's session dies, the process doesn't die with it; it just loses its parent and keeps listening on port 3000, along with whatever child processes it spawned along the way.&lt;/p&gt;

&lt;p&gt;I found this out the usual way: a fresh &lt;code&gt;pnpm dev&lt;/code&gt; refusing to bind, a &lt;code&gt;ps aux | grep node&lt;/code&gt; full of processes whose sessions had ended an hour ago, and me killing PIDs at some hour when I should have known better.&lt;/p&gt;

&lt;p&gt;The irritating part is the asymmetry. The tasks &lt;em&gt;I&lt;/em&gt; start go through &lt;a href="https://github.com/stevearc/overseer.nvim" rel="noopener noreferrer"&gt;overseer.nvim&lt;/a&gt;, which is the piece of my config I'd defend in a fight. My tasks appear in a list. They have output I can open. When I stop one, its whole process tree gets torn down properly. The agent, sitting in a terminal buffer &lt;em&gt;inside the same editor&lt;/em&gt;, gets none of that. It has a shell and a prayer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, stated plainly
&lt;/h2&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/impossiblecode/overseer-nvim-mcp" rel="noopener noreferrer"&gt;overseer-nvim-mcp&lt;/a&gt;, an MCP server that gives the agent the same view of overseer that I have. Seven tools: list tasks, list templates, run, tail, restart, stop, dispose. When the agent needs a dev server now, it starts an overseer task — a real one, in the same list as mine, with the same teardown semantics. I can watch it. I can stop it. When the session ends, nothing is orphaned, because overseer owns the process, not the agent's shell.&lt;/p&gt;

&lt;p&gt;That's the whole idea. The rest of this post is the details I cared about getting right, because the idea is obvious and the details are where it either works or annoys you forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transport is just an environment variable
&lt;/h2&gt;

&lt;p&gt;My least favorite genre of developer tool is the one that "discovers" things. It greps process lists, hashes working directories, scans for sockets, and works right up until it confidently connects to the wrong instance.&lt;/p&gt;

&lt;p&gt;This server does none of that, because Neovim already solved the problem. Every process spawned inside a Neovim terminal buffer inherits &lt;code&gt;$NVIM&lt;/code&gt; — the address of that editor's RPC socket. The MCP client (Claude Code, in my case) inherits it from the terminal. The server is a child of the client, so it inherits it too. Everything the server does is &lt;code&gt;nvim_exec_lua&lt;/code&gt; over msgpack-RPC to that one address.&lt;/p&gt;

&lt;p&gt;There is no configuration for this. There couldn't be — there's nothing to configure. The variable either points at the Neovim you're sitting in, or it isn't set, in which case the server registers zero tools and gets out of the way. Run the agent outside Neovim and the server simply has nothing to say, which is the correct amount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing a task list without stepping on it
&lt;/h2&gt;

&lt;p&gt;The part that took actual thought: the agent's tasks and my tasks live in the same list. That's the point — one list, one view. But it means an agent deciding to "clean up" could kill the dev server I started, and an agent restarting "its" task could restart mine.&lt;/p&gt;

&lt;p&gt;So every task is tagged with who started it. &lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;restart&lt;/code&gt;, and &lt;code&gt;dispose&lt;/code&gt; refuse to touch a running task the agent didn't start, unless the agent explicitly passes a force flag. The reasoning is about failure costs: a wrong refusal costs the agent one extra tool call to say "yes, really." A wrong stop kills a process I was using. Those aren't symmetric, so the default isn't either.&lt;/p&gt;

&lt;p&gt;The tasks the agent starts, it can manage freely. Mine, it has to ask about. This has so far produced exactly the behavior I wanted: the agent restarts its own watcher without asking and leaves my servers alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents are terrible at waiting
&lt;/h2&gt;

&lt;p&gt;An agent that starts a dev server immediately wants to know when it's ready. The naive pattern is polling — tail the output, sleep, tail again, burn tokens reading the same Turbopack banner five times.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;overseer_tail&lt;/code&gt; takes a &lt;code&gt;wait_for&lt;/code&gt; parameter instead: a regex, and the call blocks until a line matches it. The agent says "wake me when you see &lt;code&gt;ready - listening&lt;/code&gt;" and gets exactly one response, when it matters. The call returns on a match, on task exit, or at the timeout — and the result says which of the three happened, so a timeout can't be misread as a server that came up fine. The blocking happens in the server process, outside Neovim, so the editor never freezes while somebody waits on a webpack build.&lt;/p&gt;

&lt;p&gt;This is a small feature that changes how the whole thing feels. The agent starts a server, waits on the ready line, and moves on, instead of guessing at sleep durations in a loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Templates, or: your repo already knows its commands
&lt;/h2&gt;

&lt;p&gt;Overseer discovers runnable tasks through providers — npm scripts, make targets, just recipes, go-task, VS Code task definitions. Whatever your setup discovers, the agent sees through &lt;code&gt;overseer_list_templates&lt;/code&gt;, so instead of reconstructing &lt;code&gt;npm run test:e2e -- --headed&lt;/code&gt; from a README, it runs the task by name, the same one I'd pick from the menu.&lt;/p&gt;

&lt;p&gt;Plenty of repos return an empty template list, and that's fine. A raw command is the primary path, not a fallback; templates are a bonus when the repo declares them.&lt;/p&gt;

&lt;h2&gt;
  
  
  And when the task doesn't exist yet, it can create one
&lt;/h2&gt;

&lt;p&gt;A raw command is a one-off, though. It lives in the task list until the session ends, and next time the agent has to reconstruct it. Some commands deserve better — every project has that one incantation you run daily that never made it into an npm script, a Makefile, or a Taskfile.&lt;/p&gt;

&lt;p&gt;For those, the server publishes a slash command (&lt;code&gt;/mcp__overseer__directory_local_task&lt;/code&gt; in Claude Code) that has the agent write the task into the project itself, using overseer's own &lt;code&gt;register_template&lt;/code&gt; in a &lt;code&gt;.nvim.lua&lt;/code&gt; at the project root. It's generated against live editor state rather than being a canned recipe: it checks whether &lt;code&gt;exrc&lt;/code&gt; is actually enabled and whether a &lt;code&gt;.nvim.lua&lt;/code&gt; already exists, and it warns about the two things that make a correct setup look broken — Neovim will ask you to &lt;code&gt;:trust&lt;/code&gt; the file, and the new task won't appear until a restart.&lt;/p&gt;

&lt;p&gt;The part I find interesting: the agent isn't limited to running the tasks my project already declares. It can author new ones — real overseer templates that show up in &lt;code&gt;&amp;lt;leader&amp;gt;ow&lt;/code&gt; next to everything else and outlive the session that created them. The AI stops being a guest that borrows my task runner and starts leaving useful things behind in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;Any MCP client running inside a Neovim terminal works: Claude Code, Codex CLI, Gemini CLI, opencode. If you're on codecompanion or avante, &lt;a href="https://github.com/ravitemer/mcphub.nvim" rel="noopener noreferrer"&gt;mcphub.nvim&lt;/a&gt; gets you there.&lt;/p&gt;

&lt;p&gt;If you're on LazyVim, you're one &lt;code&gt;:LazyExtras&lt;/code&gt; toggle away — enable the overseer extra, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add overseer &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; overseer-nvim-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire setup. The package is a single file on npm with zero runtime dependencies, which I mention because the median MCP server seems determined to install half of npm before it says hello.&lt;/p&gt;

&lt;p&gt;There's a demo gif and the full tool reference in the &lt;a href="https://github.com/impossiblecode/overseer-nvim-mcp" rel="noopener noreferrer"&gt;README&lt;/a&gt;, and the server is in the official MCP registry as &lt;code&gt;io.github.impossiblecode/overseer-nvim-mcp&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general problem, briefly
&lt;/h2&gt;

&lt;p&gt;Stepping back: the orphaned dev server is one instance of something more general. Agents run in our editors now, but they interact with our machines like SSH guests — a shell, a working directory, no awareness of the tooling we've spent years arranging around ourselves. Every long-running process an agent starts through a bare shell is state that our editor can't see and the agent can't reliably clean up.&lt;/p&gt;

&lt;p&gt;The answer, at least here, wasn't to build the agent its own parallel infrastructure. It was to hand it mine. Overseer already knew how to run tasks, show them, and tear them down. The missing piece was a protocol adapter and some manners.&lt;/p&gt;

&lt;p&gt;It works. My task list finally tells the truth about what's running on my machine, including the parts an AI started. And the next time something is squatting on port 3000, at least it will have a name.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>neovim</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Reading "Ham on Rye" and designing an MCP ecosystem</title>
      <dc:creator>Miguel Angelo Sepulveda</dc:creator>
      <pubDate>Thu, 15 Jan 2026 12:32:28 +0000</pubDate>
      <link>https://dev.to/miguelangelo/reading-ham-on-rye-and-designing-an-mcp-ecosystem-3bam</link>
      <guid>https://dev.to/miguelangelo/reading-ham-on-rye-and-designing-an-mcp-ecosystem-3bam</guid>
      <description>&lt;p&gt;I've been reading &lt;em&gt;Ham on Rye&lt;/em&gt; again. Bukowski's semi-autobiographical novel about growing up poor in Los Angeles during the Depression, about a kid who finds himself at odds with every system designed to help him — school, family, social services. The book isn't about triumph. It's about survival through accumulated small dignities. Making something work that was never designed to work for you.&lt;/p&gt;

&lt;p&gt;I kept thinking about this while staring at the platform's documentation problem — and while building the MCP ecosystem that would eventually solve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest shape of the problem
&lt;/h2&gt;

&lt;p&gt;Here's what nobody talks about at conferences: most enterprise codebases are archaeological sites. Layers of decisions made by people who aren't here anymore, documented by systems that no longer exist, maintained by developers who learned the codebase through oral tradition and strategic &lt;code&gt;grep&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;This platform had the usual symptoms. Documentation lived in Confluence, in README files, in Swagger specs — some generated, some handwritten, none current — and in the heads of a few senior engineers who'd been there since the beginning.&lt;/p&gt;

&lt;p&gt;When LLM agents started to be integrated into workflows, the promise was autonomy. Point an agent at a task, let it figure out what tools exist, let it call them. It didn't work that way. Every conversation started with a bunch of pasted context. "Here's the function signature. Here's what this parameter actually means — ignore the docs, they're wrong. Here's the service that owns this data."&lt;/p&gt;

&lt;p&gt;The agents worked. But they worked like I work when my GPS is broken — many times stopping to ask for directions or deciding to guess on a road that leads nowhere safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Model Context Protocol actually solves
&lt;/h2&gt;

&lt;p&gt;MCP is Anthropic's answer to a question that sounds simple: how do you give an LLM structured access to external capabilities?&lt;/p&gt;

&lt;p&gt;The naive answer is "put everything in the system prompt". This works until it doesn't. Context windows have limits. Prompts become unwieldy. Worse, you're making the LLM do the parsing work &lt;strong&gt;every time&lt;/strong&gt; — figuring out which of the fifty functions you've described is actually relevant to this specific query.&lt;/p&gt;

&lt;p&gt;MCP inverts this. Instead of describing everything upfront, you expose a discovery layer. Tools, resources, prompts — all described in a schema the LLM can query dynamically. The agent asks "what can I do?" and gets back a structured list. It asks "what's this tool's interface?" and gets a JSON schema. Then it calls what it needs.&lt;/p&gt;

&lt;p&gt;The protocol is elegant. What isn't elegant is getting your actual codebase into that shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generator: teaching a parser to read your mind
&lt;/h2&gt;

&lt;p&gt;The first package I built was &lt;code&gt;@mxconsulting/mcp-gen&lt;/code&gt;, a manifest generator that reads source code and extracts tool definitions from JSDoc annotations.&lt;/p&gt;

&lt;p&gt;The simple version would just look for &lt;code&gt;@mcp&lt;/code&gt; tags and scrape whatever's nearby. But the actual problem is harder. JSDoc doesn't have a standard for MCP tools. Developers write comments inconsistently. Sometimes the function name is on the next line. Sometimes it's an arrow function assigned to a const. Sometimes it's exported directly, sometimes through a re-export.&lt;/p&gt;

&lt;p&gt;So the parser needed to be forensic:&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;private&lt;/span&gt; &lt;span class="nf"&gt;extractFunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;commentEndLine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;lineNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;commentEndLine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commentEndLine&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="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&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;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Pattern 1: function functionName(&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functionDeclaration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;export&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;function&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z_$&lt;/span&gt;&lt;span class="se"&gt;][&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_$&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;functionDeclaration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lineNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;functionDeclaration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Pattern 2: const functionName = function(&lt;/span&gt;
    &lt;span class="c1"&gt;// Pattern 3: const functionName = (&lt;/span&gt;
    &lt;span class="c1"&gt;// Pattern 4: const functionName = async (&lt;/span&gt;
    &lt;span class="c1"&gt;// Pattern 5: exports.functionName =&lt;/span&gt;
    &lt;span class="c1"&gt;// Pattern 6: functionName: function(&lt;/span&gt;
    &lt;span class="c1"&gt;// ... and more&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;This isn't glamorous code. It's code that handles reality — the reality that your codebase has six different ways of defining functions, and you need to find them all.&lt;/p&gt;

&lt;p&gt;The generator also validates what it finds. JSDoc parameters with type names in the wrong position, PHP-style variable prefixes copied by accident, descriptions that contain syntax rather than explanations — all caught before they become malformed tool definitions that confuse your agent at runtime.&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;private&lt;/span&gt; &lt;span class="nf"&gt;validateJSDocParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="c1"&gt;// Check for type names being used as parameter names&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsDocTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;jsDocTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Parameter &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' appears to be a type name `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;`rather than a parameter name. Malformed JSDoc syntax.`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ... more validation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When validation fails, you get line numbers. You get the original function name. You get a specific error that tells you what to fix. Because debugging "tool call failed" at 2 AM is not how anyone should spend their time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kubernetes problem within the problem
&lt;/h2&gt;

&lt;p&gt;Generating manifests is one thing. Getting them to the right place, keeping them current, and doing this across a cluster — that's the infrastructure layer.&lt;/p&gt;

&lt;p&gt;Our platform runs on Kubernetes. The manifests needed to be ConfigMaps. The MCP server needed to mount those ConfigMaps. And when documentation changed (because it does change, if you've built a system where changing it is easy), the server needed to pick up the new version without manual intervention.&lt;/p&gt;

&lt;p&gt;The traditional approach is to annotate your Deployment with ConfigMap checksums, then use something like &lt;em&gt;Stakater Reloader&lt;/em&gt; to watch for changes and trigger rolling updates. This works for static configurations. It's insufficient when your ConfigMaps are generated, numerous, and can change independently of your code deployments.&lt;/p&gt;

&lt;p&gt;So I built two components: a mutating admission webhook and a reloader controller.&lt;/p&gt;

&lt;h3&gt;
  
  
  The webhook: surgery at admission time
&lt;/h3&gt;

&lt;p&gt;A mutating admission webhook intercepts pod creation and modifies the spec before Kubernetes acts on it. Mine looks for pods with specific annotations, finds matching ConfigMaps by label selector, and injects them as volumes.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mutateHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RequestHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;pod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;admissionReview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;annotations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;annotations&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="c1"&gt;// Check for opt-in annotation&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mountConfigMapsByLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mcp.mxconsulting.net/mount-configmaps-by-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mountConfigMapsByLabel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Not our concern, allow without modification&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Process indexed label selectors&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&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;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`mcp.mxconsulting.net/configmap-label-selector.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;mountDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`mcp.mxconsulting.net/configmap-base-mount-directory.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configMaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;k8sApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listConfigMapForAllNamespaces&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;for &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;cm&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;configMaps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Calculate checksum for change detection&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// Inject volume and mount via JSON Patch&lt;/span&gt;
      &lt;span class="nx"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/metadata/annotations/checksum~1configmap-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="c1"&gt;// ... volume injection&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="o"&gt;++&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 checksum calculation is the key detail. By storing a hash of each ConfigMap's contents as a pod annotation, we create a declarative trigger for the reloader. When the content changes, the checksum changes. When the checksum changes, the reloader notices.&lt;/p&gt;

&lt;h3&gt;
  
  
  The reloader: smart reconciliation
&lt;/h3&gt;

&lt;p&gt;The reloader controller watches Deployments and ConfigMaps. When a relevant ConfigMap changes, it needs to update the corresponding Deployment's annotations so that Kubernetes sees a spec change and triggers a rollout.&lt;/p&gt;

&lt;p&gt;The naive approach is brute force: on any ConfigMap change, check every Deployment, recalculate all checksums. This doesn't scale. With hundreds of ConfigMaps and dozens of Deployments, you spend all your time reconciling.&lt;/p&gt;

&lt;p&gt;So the reloader implements smart reconciliation:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;kubernetesController&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;KubernetesController&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;deploymentReconciler&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;DeploymentReconciler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;kubernetesController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAppsApi&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;kubernetesController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCoreApi&lt;/span&gt;&lt;span class="p"&gt;()&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;smartReconciler&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;SmartReconciler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;kubernetesController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAppsApi&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;deploymentReconciler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;memoryManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;metricsManager&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;bruteForceReconciler&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;BruteForceReconciler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Smart reconciliation handles targeted updates&lt;/span&gt;
  &lt;span class="c1"&gt;// Brute force runs periodically as a safety net&lt;/span&gt;
  &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ENABLE_SMART_RECONCILIATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bruteForceReconciler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconcileAllRelevantDeployments&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;FULL_RECONCILIATION_INTERVAL_MS&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 smart reconciler tracks which Deployments care about which ConfigMaps. When ConfigMap X changes, it updates only Deployments that mount X. The brute force reconciler runs every few minutes as a safety net — catching any drift that smart reconciliation missed.&lt;/p&gt;

&lt;p&gt;Both reconcilers share infrastructure: rate limiting (so we don't hammer the API server), caching (so we don't refetch unchanged data), and memory management (so long-running controllers don't leak).&lt;/p&gt;

&lt;h2&gt;
  
  
  The server: where it all comes together
&lt;/h2&gt;

&lt;p&gt;The MCP server itself — &lt;code&gt;@mxconsulting/mcp&lt;/code&gt; — loads manifest files from its mounted ConfigMap volumes, parses them, and exposes everything over HTTP (or stdio for local development).&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;setupServerWithManifests&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;McpServer&lt;/span&gt;&lt;span class="p"&gt;)&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;manifests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadManifests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;for &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;manifest&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;manifests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Register manifest-level resource (summary of what's in this manifest)&lt;/span&gt;
    &lt;span class="nf"&gt;registerManifestResource&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;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Register individual tool resources (JSON descriptions)&lt;/span&gt;
    &lt;span class="nf"&gt;registerToolResources&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;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Register actual callable tools&lt;/span&gt;
    &lt;span class="nf"&gt;registerMcpTools&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;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Register static resources and templates&lt;/span&gt;
    &lt;span class="nf"&gt;registerManifestResources&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;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;registerManifestResourceTemplates&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;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Category-based organization&lt;/span&gt;
  &lt;span class="nf"&gt;registerCategoryResources&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;registerOverviewResources&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="c1"&gt;// Prompts and completion handlers&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="nf"&gt;registerCompletionHandler&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server exposes not just tools, but resources &lt;em&gt;about&lt;/em&gt; tools. An LLM can fetch a category overview to understand what's available before diving into specifics. It can read a manifest summary to understand the shape of a subsystem. The protocol supports this naturally; the server makes it useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Bukowski understood about systems
&lt;/h2&gt;

&lt;p&gt;In &lt;em&gt;Ham on Rye&lt;/em&gt;, young Henry Chinaski is assigned to write about President Hoover's visit to Los Angeles. He can't attend — Saturdays are consumed by his father's lawn ritual, where missing a single blade of grass means a beating. So he invents the whole thing: the motorcade, the secret service agents, the sun breaking through clouds as the President speaks. His teacher reads the essay to the class, calls it remarkable. Later she asks if he was actually there. He admits he wasn't. "That makes it all the more remarkable," she says. Walking home, Henry realizes: "So, that's what they wanted: lies. Beautiful lies. That's what they needed."&lt;/p&gt;

&lt;p&gt;Enterprise documentation is full of beautiful lies. Swagger files that look complete but haven't been updated in eighteen months. README files that describe an architecture from three versions ago. Confluence pages that exist to prove documentation exists. Everyone pretends the system works because admitting otherwise means admitting nobody has time to fix it.&lt;/p&gt;

&lt;p&gt;The system I built doesn't solve these problems through policy or discipline. It solves them through automation. Write a JSDoc comment above your function — the manifest generator finds it. Deploy a new ConfigMap — the webhook mounts it and the reloader propagates changes. Point an LLM at the server — it discovers everything automatically.&lt;/p&gt;

&lt;p&gt;There's no dignity in a broken documentation system. There's also no dignity in pretending you can fix it through willpower. The dignity is in building something that survives contact with reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Our LLM agents now discover available tools at runtime. They understand parameter schemas because the schemas are generated from actual type annotations. When we add a new capability, we add a JSDoc comment and redeploy — the agents pick it up automatically.&lt;/p&gt;

&lt;p&gt;Was it worth four weeks of TypeScript compilation errors, webhook debugging, and Kubernetes API mocking? The honest answer is: ask me in a year, when I've forgotten the pain and only remember the outcomes.&lt;/p&gt;

&lt;p&gt;But right now, at 3 AM, watching an agent autonomously discover and use tools written years ago without any extra prompting... Yeah, I think it was worth it.&lt;/p&gt;

&lt;p&gt;Some systems are designed to be elegant. Some are designed to survive. The best ones manage both. I don't know which category this falls into yet.&lt;/p&gt;

&lt;p&gt;But it works. And that's something.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>kubernetes</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
