<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>David Golverdingen — Insights</title>
    <link>https://davidgolverdingen.nl/en/insights</link>
    <description>Practitioner writing on MCP architecture, context engineering, and AI systems that survive contact with enterprise data. By the author of The Missing Layer.</description>
    <language>en</language>
    <lastBuildDate>Fri, 14 Aug 2026 09:00:00 GMT</lastBuildDate>
    <atom:link href="https://davidgolverdingen.nl/feed.xml" rel="self" type="application/rss+xml" />
  <item>
    <title>We Replaced Jira With Markdown Files</title>
    <link>https://davidgolverdingen.nl/en/insights/replaced-jira-with-markdown-files</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/replaced-jira-with-markdown-files</guid>
    <pubDate>Tue, 11 Aug 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[One team, ten repositories, four tech stacks, and a Jira licence nobody enjoyed. We moved every ticket into markdown files next to the code. Seven months later, 15 projects and 12 people are on a board that costs nothing per seat.]]></description>
    <content:encoded><![CDATA[<p>Early this year I was wiring Claude into Jira through an MCP server. It worked, and every session it felt slightly wrong: slow round trips, a schema I did not control, structure sitting somewhere the agent could not see while it was reading the code.</p>
<p>Which makes it a context problem rather than a tooling preference. Context engineering is not about pushing more context into the model; it is about putting the context where the agent is already looking. Ours could read every line of the codebase directly, and the ticket telling them what to change only through a tool call.</p>
<p>The fix was almost embarrassingly simple. <strong>Put the ticket in the repo, as markdown.</strong> I pitched it to a colleague, and off we went.</p>
<p>Seven months later: 15 projects across 10 repositories, 165 live tickets, 12 people on the board including non-developers, and no Jira licence.</p>
<p>This post is why we left and what we built. Two follow-ups cover the rest: <a href="https://davidgolverdingen.nl/en/insights/ticket-is-the-context-window">the skill and the loop that let agents work these tickets</a>, and <a href="https://davidgolverdingen.nl/en/insights/review-the-plan-not-just-the-code">the three review layers that keep the output honest</a>.</p>
<h2>What was actually wrong with Jira</h2>
<p>The cost was easy to name: roughly €2,000 a year for something we used maybe 5% of. It was not the reason we left.</p>
<p>Every user had to be paid for, so the board was implicitly rationed. Performance degraded as projects grew. The features we wanted sat behind paid plugins. Automations were clumsy enough that we mostly did not write them. And the board was close to what we wanted without ever being it, because that last gap lived in someone else&#39;s product roadmap. None of that is fatal alone. Together it means the tool shapes the team instead of the other way around.</p>
<h2>The constraint that ruled out the obvious answers</h2>
<p>We are one team maintaining ten separate repositories that ship independently of one another, across TypeScript, C#, Java and PowerShell. A monorepo was never realistic.</p>
<p>That kills the usual alternatives. GitHub Issues comes closest and misses twice: issues are scoped to one repository, so cross-repo visibility becomes somebody&#39;s weekly spreadsheet, and despite feeling like part of the repo they are not in it. They live in a database behind an API. Not files, not on the branch, not in the diff, and not something an agent editing the code can read without a round trip. Every hosted alternative moves the work further away still.</p>
<p>So we inverted it: <strong>distribute the tickets, centralise the view.</strong></p>
<h2>One file per ticket</h2>
<p>Every repository carries a <code>tickets/</code> folder. A ticket is one markdown file:</p>
<pre><code class="language-yaml">---
id: APP-42
type: story
title: &#39;[Profile] Add user avatar upload&#39;
status: in-progress
priority: high
size: m
assignee: developer@example.com
reported_by: colleague@example.com
tasks:
  - name: Implementation plan
    status: done
  - name: Upload component
    status: in-progress
---
## Objective
## Context
## Acceptance Criteria
## Scope
## Design
## Technical Notes
## Implementation Plan
## Diagram
## Work Log
</code></pre>
<p>The frontmatter is the machine-readable half, and none of it is free-form. Every field is schema-validated: the status vocabulary, priorities, t-shirt sizes, the ID matching its filename, timestamps in one format. A pre-commit hook and a CI step both run that validation, so a malformed ticket never reaches the default branch.</p>
<p>The body is the human half, with fixed headings. That is what makes a section addressable: a tool can replace <em>Acceptance Criteria</em> by name without touching anything around it, which you cannot do reliably to a free-form description field. <code>## Diagram</code> holds Mermaid, and most substantial tickets have one, because a state machine or a data flow is faster to check than the paragraph describing it. The same pre-commit hook parses every Mermaid block with the real Mermaid parser, so a diagram that would not render cannot be committed.</p>
<p>Which leads to the rule holding it together: <strong>nobody edits these files by hand.</strong> Writes go through a CLI of typed operations (set a status, claim a task, tick a criterion, append a work log entry, replace a section) that stamps timestamps, refuses invalid transitions, and keeps the file valid by construction. Hand-editing markdown is how fields drift and a board stops being trustworthy. The format is open to read and closed to casual writing.</p>
<p>Each repository also holds a small derived <code>.tickets.config.json</code> with its prefix, default branch and allowed values, so validation is local and calls nothing external. An admin edits project settings in the portal; the file is written out to every affected repository.</p>
<h2>The board is a projection</h2>
<p>A viewer application (Angular, Firebase, a GitHub App for commits) scans every configured repository and aggregates the tickets into one Kanban board. Edits commit back to the correct branch: the ticket&#39;s feature branch if one exists, the default branch otherwise. Because every change is a commit, git history <em>is</em> the audit trail.</p>
<p>The one piece of ticket <em>content</em> not in git is board ordering, which lives in Firestore next to the things that were never content: profiles, saved filters, role lists, the project registry. Dragging a card to reprioritise would otherwise rewrite files across ten repositories and produce merge conflicts carrying no information. Content is git-canonical; ordering is not content.</p>
<p>Two decisions I would keep in any rebuild. <strong>The lanes are asymmetric:</strong> <code>in-progress</code> is twice the width of the others and shows the task checklist and the live branch inline, which matters far more than I expected once several tickets run at once. <strong>Workflow steps are subtasks, not columns:</strong> code review, manual test and E2E are entries in the ticket&#39;s task list. For a team of two to five, a column per step produces a board of nearly-empty columns, while task-level detail puts the standup answer on the card.</p>
<h2>Nobody pays to be on the board</h2>
<p>We already had Microsoft Entra SSO, so authentication cost nothing and no seat has a price on it. That reads like a footnote, and it is the change I value most: when visibility stops being metered, you stop deciding who deserves it.</p>
<p>It also took the system somewhere I did not plan. Five of the fifteen projects have no repository at all. They are folders in a shared host repo covering application management, business automation, engineering optimisation, AI enablement and general company work. What is in them now: onboarding a new colleague, enabling SSO, formalising a technical quick-scan process, getting the team through a Claude course. None of it is code, all of it sits on the same board, next to the software work it competes with for time.</p>
<h2>Two ways in, one authoring guide</h2>
<p>WBTickets has its own MCP server alongside the eleven others we run: 18 tools for reads, writes, milestones and attachments, hitting the same repositories through the same commit gateway as the board.</p>
<p>That is how the projects without a codebase work. A colleague in claude.ai, with no checkout and no git, describes a problem in their own words and ends up with a schema-valid ticket committed to a repository. <code>create_ticket</code> is deliberately two-step: called with no arguments it creates nothing and returns the authoring guide, which tells the agent what to ask and to get sign-off before writing. That last part is instruction rather than enforcement. The server will happily create a ticket on a first call that arrives with arguments; what the two-step buys is that the guide is in front of the agent before it has anything to commit.</p>
<p>For code repositories we prefer the other route, running on that same guide. A ticket for a real codebase is written from a checkout by an agent that reads the code first: what exists, what the change touches, which older ticket decided the design that is there now. That is the difference between a Scope with real boundaries and one that merely sounds plausible. The write still lands on the default branch through the same path; only the analysis is local.</p>
<p>Attachments get their own trick. Screenshots are how non-developers explain a bug, but pushing image bytes through a model&#39;s context is pure waste. So <code>view_attachments</code> returns metadata only and opens a small app inside the chat; the app fetches and uploads the actual bytes through separate tools that never put them in the model&#39;s context at all. You see the screenshot, drag a new one in, and the agent&#39;s context stays clean.</p>
<p>The loop closes at the far end: <code>reported_by</code> records who asked, and they are notified when the ticket reaches <code>done</code>. They never have to open the board.</p>
<h2>The board matches the work that was delivered</h2>
<p>I have never seen a ticket board outside the codebase that still matched what was actually built. Not a failure of any particular team: it is distance. Scope changes, deviations and the reasoning behind them get settled in a pull request or a call, and updating the ticket is a separate action somebody has to remember to take, later, from memory. Some of it lands days late as a summary. Most of it never lands at all, which is why the interesting question about any board is not what it says but when it last agreed with reality.</p>
<p>Here the agent doing the work is what writes it down, in the same commit as the code. A deviation gets a work log entry when it happens, with the reason, while the reason is still in context rather than reconstructed afterwards. So by the time the pull request opens, the ticket describes what was built and not only what was planned, and where the two disagree that disagreement is on the record with its argument attached.</p>
<p>That is the part I would miss most if we went back. A board that is merely tidy tells you how disciplined the team is about bookkeeping. A board that is written by the work tells you what happened.</p>
<h2>What came nearly free afterwards</h2>
<p>Once the substrate was markdown in git, the adjacent things stopped being projects. Milestones as roadmap items with horizons instead of dates. Release notes generated from git tags with ticket IDs resolved to board links. Design artifacts: a self-contained HTML mockup committed beside the ticket, reviewed in the same pull request as the code and rendered in a sandboxed iframe on the board.</p>
<p>The next one is in build: a knowledge base to replace Confluence and its ~300 articles. What made it worth starting is that almost none of it is new. It inherits the auth, the attachments, the markdown rendering and the same git-canonical instinct, so the architecture argument was about content workflow rather than infrastructure.</p>
<p>Same primitive every time. That is the compounding return of a format every tool already understands.</p>
<h2>The build-versus-buy line moved</h2>
<p>We are a small in-house IT department. Warmtebouw installs heating, cooling and ventilation; we are the handful of people who build the software around that. This is not a heroic story, and that is the part worth noticing.</p>
<p>You rarely bought a tool because its feature list was unbeatable. You bought it because building and maintaining something that fit your team cost far more than the licence did. That arithmetic has changed. Writing a bespoke system, and more importantly keeping it correct and documented while you use it, is now cheap enough that a small team can do it alongside the actual work. We are one of many teams quietly discovering that the licence was buying convenience we can now produce ourselves, and better, because ours fits.</p>
<p>It does not generalise to everything. The tools worth replacing are the ones where your own workflow is the product and the vendor&#39;s genericity is the tax you pay. Nobody sane is rebuilding their ERP this way.</p>
<p>Which brings me to the vendors. Atlassian is now positioning itself as the layer in the middle of agentic work, and I would not want to be selling that. The middle is precisely the position an agent routes around. If the work lives in the repository and the agent is already there, a hosted system of record stops being where the work happens and becomes a place you synchronise to. That is a difficult thing to charge per seat for, and a harder one to defend once teams notice the alternative is a folder of markdown files.</p>
<h2>The honest ledger</h2>
<p>You own the maintenance. Jira&#39;s operating burden is zero and ours is not. This began as a side project and grew in the gaps between real work. The core worked from the moment there were tickets on the board; everything since has been sharpening it.</p>
<p>What makes that sustainable is who maintains it: the team that uses it every day. A rough edge gets filed down by the person it just annoyed, usually in the same session they hit it, and the fix is filed as a ticket in the system it fixes. There is no maintenance budget to defend, because the work is spread thinly across all the other work.</p>
<p>What we bought is a system that fits our workflow exactly rather than 60% of it, a board open to everyone in the company, and every remaining failure mode being ours to fix. The licence saving is real and it is the least interesting part.</p>
<p>The board was the deliverable. The consequence was that the description of the work now lives where the agent is already looking, and that changed how the work gets done far more than any board could. <a href="https://davidgolverdingen.nl/en/insights/ticket-is-the-context-window">Here is what that looks like in practice.</a></p>
]]></content:encoded>
    <category>Architecture</category>
    <category>Tooling</category>
    <category>AI</category>
    <media:content url="https://davidgolverdingen.nl/images/og/replaced-jira-with-markdown-files.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>The Ticket Is the Context Window</title>
    <link>https://davidgolverdingen.nl/en/insights/ticket-is-the-context-window</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/ticket-is-the-context-window</guid>
    <pubDate>Mon, 10 Aug 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Once tickets were markdown in the repo, the format turned out to be the easy part. The real deliverable was the process, written down in a form the agent executes: one skill, two write paths, and a loop you can tell not to stop.]]></description>
    <content:encoded><![CDATA[<p>I thought the deliverable was the file format and the board. It was not. <strong>The deliverable is the process, written down in a form the agent executes.</strong></p>
<p>That is the part I did not see coming when we <a href="https://davidgolverdingen.nl/en/insights/replaced-jira-with-markdown-files">replaced Jira with markdown files in our repositories</a>. That post covers why we left and what the system is. This one is about what the system turned out to be for.</p>
<h2>The skill is the product</h2>
<p>Our workflow lives in a Claude Code skill: 452 lines covering how a ticket is created, refined, planned, picked up, worked, reviewed and closed, plus a few reference files it pulls in on demand so the always-loaded part stays readable. Not a style guide. Instructions with gates in them, which the agent is required to load before touching any ticket.</p>
<p>The create flow does not start by scaffolding a file. It starts by making the agent ask two to four targeted questions when a request is thin, then present the drafted Objective, Context and Acceptance Criteria for sign-off before anything is written. Planning runs in read-only mode, so no ticket write can happen before the plan is approved. Work happens one task at a time, each claimed on the board before a line of code is written.</p>
<h2>The routing rule</h2>
<p>The most useful rule in it fits in a sentence. There are two ways to write a ticket, and <strong>the diff decides which one.</strong></p>
<p><strong>Ticket-only changes</strong> go through the MCP server, which commits straight to the default branch: creating a ticket, refining it, flipping status before the code work or after the pull request lands. No checkout, no branch, no pull request, no review bot. The analysis still happens locally where the code is; only the write is remote.</p>
<p><strong>Code work</strong> happens on a ticket branch with the CLI and git, where the plan, status changes, task completions and work log entries ride along with the code commits and merge through the same pull request.</p>
<p>Before the rule, every status change during a coding session was a small decision with no good answer: open a throwaway pull request for a one-line frontmatter edit, or commit ticket noise onto the feature branch. Now the diff answers it.</p>
<h2>One implementation, four consumers</h2>
<p>Underneath both paths sits one npm package holding the schema, the parser, the serializer, and every edit operation as a pure function.</p>
<p>Four things import it: the CLI that agents and humans run in a checkout, the Angular viewer behind every board edit, the Cloud Functions commit gateway, and the MCP server for the remote path. Nobody reimplements ticket semantics. The MCP server has no authoring logic of its own; it resolves the project, allocates an ID, runs the shared transform, and lets the gateway validate before committing.</p>
<p>That is what makes a guardrail real. When we added a rule that a ticket cannot be closed while its acceptance criteria are unticked, it went into the package once and now fires identically whether you type a CLI command, call the MCP tool from your phone, or drag a card across the board. There is no back door, because there is only one door.</p>
<p>The override is the part I like most. You can force past that gate, but it costs a written reason, and the same operation that flips the status appends that reason to the work log. A bypass is always visible and attributable. A gate people quietly route around reads as enforcement while providing none.</p>
<h2>The loop</h2>
<p>The thing that changed daily work most is one line:</p>
<pre><code>/goal complete tasks according to the wbtickets skill until the PR is mergeable
</code></pre>
<p>That is the whole prompt. What follows runs on its own, and it can run on its own because the ticket carries enough state to make it possible:</p>
<ol>
<li>Pull, re-read the ticket, take the first task that is not done.</li>
<li>Claim it: mark it in-progress, commit, push. Before any code, so the board and any parallel session see the claim.</li>
<li>Implement exactly that task. Build it, test it.</li>
<li>Present the pending diff and wait for approval.</li>
<li>Complete the task, write the work log entry, commit code and bookkeeping together, push.</li>
<li>Next task, back to step 2.</li>
</ol>
<p>Step 4 is the default, and the goal is what waives it. A goal is a hook that blocks the session from ending until its condition holds, so typing that line is the approval: instead of stopping at each task, the agent finishes one, pushes, and walks straight into the next. The gate is not removed from the skill and it is not disabled for the repository. It is authorised once, for this run, by the person who started it. Point it at a ticket with seven tasks and it works the ticket, not the task.</p>
<p>Which makes it a judgement call rather than a setting I leave on. Waiving the per-task check is right when the tasks are small, the plan is well understood and a wrong turn costs one revert. It is wrong on anything where I would want to see the shape of task three before task four is built on top of it. The goal trades review granularity for throughput, and low-complexity, low-risk tickets are where that trade is clearly worth making.</p>
<p>Two things make the unattended version acceptable rather than reckless.</p>
<p><strong>The claim happens before the code.</strong> It is bookkeeping with nothing attached and it must reach the board first, or a second session picks up the same task. The recurring failure was sliding from one task&#39;s finished commit into the next task&#39;s edits without claiming, which looks harmless until two agents are working in parallel.</p>
<p><strong>One task is one commit.</strong> The loop never batches. Every step lands as a separate, conventionally named commit carrying its code, its task completion and its work log entry together. An unattended run is reviewable afterwards, commit by commit, and the pull request reads as a sequence of decisions instead of one wall of diff.</p>
<p>And the gates the goal cannot touch sit at the edges: an approved plan going in, and <a href="https://davidgolverdingen.nl/en/insights/review-the-plan-not-just-the-code">three layers of review</a> coming out. Autonomy inside the ticket, scrutiny at the boundaries. Skipping the per-task check is defensible precisely because those two are not skippable.</p>
<h2>Planning ends by throwing the context away</h2>
<p>The shape this replaces is one I used for years. Read the ticket, make a plan from it, let the plan drive the code. That plan lived in the chat or in a scratch file, outside both the ticket and the repository, and nothing merged it back. When the work deviated, the plan stayed as written and the reasoning for the deviation went wherever the conversation went. Now the plan is a section of the ticket, so it travels with the code through the same pull request, and a deviation lands next to the plan it deviated from.</p>
<p>Planning is the one phase that deliberately stops, and it stops twice: once for approval, then again at the very end. The session writes the plan into the ticket, splits the work into tasks, adds a Mermaid diagram when a flow or state machine is clearer drawn than described, commits, and then clears its own context.</p>
<p>The rule that makes it work: if the reasoning exists only in the chat, the plan was not self-contained, and that is a bug to fix before clearing. The decisions, the rejected alternatives, the sequencing rationale and the per-task verification all go into the ticket.</p>
<p><strong>The ticket is the context window.</strong> A fresh session picks up task three of seven without any of the conversation that produced the plan. It still syncs the branch and re-reads the ticket, but it never has to reconstruct why the work is shaped the way it is, because that is in a file it can read. The exploration transcript is not context, it is exhaust.</p>
<p>Getting that plan right is the highest-leverage step in the whole workflow, which is why it gets <a href="https://davidgolverdingen.nl/en/insights/review-the-plan-not-just-the-code">a review of its own before any code exists</a>.</p>
<p>This is also why old tickets stay readable. They are time-locked decision records. Before planning work on a subsystem, the flow has the agent mine the history of the files it will touch and search older tickets on the same component, so a decision made in March is available in August without anyone remembering it exists.</p>
<h2>Distribution is the quiet multiplier</h2>
<p>A process written down helps one repository. A process that installs itself helps all of them.</p>
<p>The skill ships inside the same package as the CLI, with a <code>sync</code> command wired to a session-start hook, so every repository picks up the current version at the start of every session. Change the instructions and the change reaches every agent session in the company by the next session start. No migration, no announcement, no repository left on last month&#39;s process. The same package serves the guidance the MCP server hands to colleagues authoring tickets from claude.ai, so the two cannot drift apart.</p>
<p>It is not one skill riding along either. Ten travel that channel now, from the review process to the per-stack rubrics, reaching a Java repository and an Angular one alike. That was not the plan. It is what happens when you build a way to ship one process and discover the pipe does not care how much you put in it.</p>
<h2>Watching four agents work</h2>
<p>One ticket, one checkout, one session. That is forced rather than chosen: the bookkeeping auto-commits, so two sessions cannot share a working copy without fighting over it. A <code>parallel</code> command does the rest, provisioning a git worktree per ticket with its own port and dependencies, flagging any files two tickets would both touch, and handing back one kickoff prompt per session.</p>
<p>That constraint produced the best moment I have had with this system.</p>
<p>Three ingredients, none designed for it. Ticket bookkeeping auto-commits <strong>and pushes</strong> the instant it happens, the one deliberate exception to our never-auto-commit rule, because the board has to see it. The board is branch-aware, reading each ticket from its own feature branch rather than the stale copy on the default branch, so it shows work in flight. And the <code>in-progress</code> lane is double width with the task checklist inline.</p>
<p>Run four sessions at once and the board becomes a live view: four tickets in progress, four checklists ticking themselves off within seconds of each push. I had it open on a second screen and watched the work move.</p>
<figure class="blog-figure">
  <img src="https://davidgolverdingen.nl/images/blog/ticket-is-the-context-window-board.jpg" alt="Kanban board with four tickets in the in-progress lane, each showing its own task checklist and branch" width="1464" height="647" loading="lazy" decoding="async">
  <figcaption>The in-progress lane at double width, mid-run, four sessions pushing into it</figcaption>
</figure>
<p>The caveat is honest: the board is exactly as current as the last push, so unpushed work does not exist to it. In practice that is fine, because the bookkeeping pushes itself, and the bookkeeping is the part worth watching.</p>
<p>I did not build this to be watchable. But it is the first time a project management tool has shown me something I did not already know from being the person doing the work.</p>
<h2>What this actually changed</h2>
<p>Our tickets are now the thing an agent reads before it writes code, instead of the thing someone updates after the code is written.</p>
<p>The board did not get smarter. The tickets did.</p>
<p>Which leaves the uncomfortable question. If an agent writes most of the code, and on a good day you tell it not to pause between tasks, what stops it from confidently shipping the wrong thing? Three layers of review, and <a href="https://davidgolverdingen.nl/en/insights/review-the-plan-not-just-the-code">the one that does the most work runs before a single line of code exists</a>.</p>
]]></content:encoded>
    <category>AI</category>
    <category>Agentic Workflow</category>
    <category>Context Engineering</category>
    <category>Architecture</category>
    <media:content url="https://davidgolverdingen.nl/images/og/ticket-is-the-context-window.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>The Cheapest Time to Be Wrong</title>
    <link>https://davidgolverdingen.nl/en/insights/review-the-plan-not-just-the-code</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/review-the-plan-not-just-the-code</guid>
    <pubDate>Sun, 09 Aug 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Every ticket passes three automatic review layers before a human reads a line of it: a second model on the plan, a multi-lens self-review before the PR, and an agent-driven bot review after. The earliest one is the one that matters.]]></description>
    <content:encoded><![CDATA[<p>If an agent writes most of the code, what stops it from confidently shipping the wrong thing?</p>
<p>Our answer is three review layers, firing at different times for different reasons. What surprised me is which one carries the weight. It is not the review of the code. It is the one that runs while there is no code to review. That is the two earlier posts&#39; loose end: <a href="https://davidgolverdingen.nl/en/insights/replaced-jira-with-markdown-files">why we replaced Jira with markdown tickets</a>, and <a href="https://davidgolverdingen.nl/en/insights/ticket-is-the-context-window">the skill and the loop</a> that let an agent work those tickets on its own.</p>
<h2>Before review: authoring against the real code</h2>
<p>A review can only be as good as the thing it measures against, so the acceptance criteria have to be worth measuring against.</p>
<p>That starts with <a href="https://davidgolverdingen.nl/en/insights/replaced-jira-with-markdown-files">how the ticket was authored</a>, from a checkout, against the real code. The skill then enforces a shape on the result. Objective in one or two sentences. Context under 120 words. Around five acceptance criteria, one requirement each, written for behaviour rather than deliverables, so &quot;tests pass&quot; and &quot;version bumped&quot; are excluded by definition.</p>
<p>Behavioural criteria use EARS form: <em>When <code>&lt;trigger&gt;</code>, the system shall <code>&lt;observable outcome&gt;</code></em>. That is not ceremony. The trigger is the test setup, and writing it forces out which criteria only a running application can confirm. Those become the manual test later, and they are the ones nobody would otherwise think to check.</p>
<p>The section budgets exist for the same reason. A ticket is read in seconds during refinement, so it captures what and why and never how. If Context is overflowing, analysis has leaked into a document that is supposed to be a specification, and the fix is to defer it to the plan.</p>
<h2>The plan is the highest-leverage artifact</h2>
<p>Then the ticket gets planned, and this is the step worth spending real effort on.</p>
<p>Planning runs read-only, <a href="https://davidgolverdingen.nl/en/insights/ticket-is-the-context-window">as the previous post describes</a>. The agent explores: the scope, the acceptance criteria, the code it will touch, the history of those files, older tickets on the same component. Then it drafts a plan into a scratch file, one block per task:</p>
<pre><code>### Task 1 — &lt;name&gt;
- Goal: what this task delivers
- Approach: concise steps
- Decisions: choices made + rejected alternatives + why this sequencing
- Touches: files / components
- Verification: build/tests/manual check that proves it&#39;s done
</code></pre>
<p>Each task maps to one self-contained commit, so a fresh session can resume from the ticket alone. Anything hinging on an unproven integration gets an early-validation task sequenced first: a throwaway harness that proves the wiring before real work is built on top of it.</p>
<p>A Mermaid diagram goes in when a flow or state machine is clearer drawn than written. The pre-commit hook runs the real Mermaid parser over every block, so a diagram that would not render cannot be committed.</p>
<p>The <code>Decisions</code> line is the one that matters. It carries the rejected alternatives and the reason for the sequencing, which is exactly what disappears when a plan lives only in a chat window.</p>
<p>Here is why this artifact outranks the others: <strong>a wrong decision is cheapest to fix before any code exists.</strong> Bad sequencing caught in a plan costs a paragraph. The same mistake caught in review costs a day of rework, and caught after merge it costs a follow-up ticket. Not every defect is a design defect, and no plan review will catch a null check. But design and sequencing errors are the expensive class, and this is the only layer that gets at them while they are still cheap.</p>
<h2>Layer one: a second model reads the plan</h2>
<p>So the plan gets reviewed before anyone implements it.</p>
<p>The draft goes to Codex, from a different model family, running read-only and in the background. The instruction is deliberately narrow: list findings for Claude. Gaps, missed edge cases, risky sequencing, wrong assumptions. Not a rewrite.</p>
<p>That constraint is the whole trick. A second model asked to improve a plan will produce its own plan, and you are left comparing two documents with no way to judge. A second model asked to attack a plan produces a list you can act on item by item. The agent folds in what is worth acting on and notes what it consciously rejected, so the disagreements are visible rather than silently resolved.</p>
<p>Model diversity is the point, not a vote. Two instances of the same model share the same blind spots, and averaging them just gives you a more confident version of the same mistake.</p>
<h2>The human gate</h2>
<p>Then it stops and asks.</p>
<p>This is the approval that always exists, on every ticket, no matter how autonomous the rest of the run is. Nothing has been written to the ticket yet, so the plan is still free to change. The reviewer is looking at a page of decisions rather than a thousand lines of diff, which is the cheapest possible moment for a human to disagree.</p>
<p>After approval the plan is <a href="https://davidgolverdingen.nl/en/insights/ticket-is-the-context-window">written into the ticket and the session throws its own context away</a>.</p>
<h2>Layer two: multi-lens self-review before the PR</h2>
<p>Implementation happens task by task. Then, before the pull request is opened or readied, a full self-review runs, and the first thing it does is throw away the context that produced the code.</p>
<p>That clear is unconditional. A reviewer holding the authoring transcript inherits the assumptions the author used to justify the work, and inherited assumptions are exactly what a review is supposed to catch. The branch diff and the ticket carry everything a reviewer needs.</p>
<p>The review then fans out into read-only lenses running in parallel, each with a different brief. A code-heavy diff gets all four:</p>
<ul>
<li><strong>Correctness and edge cases</strong>: logic errors, null and undefined, async and promise handling, swallowed failures, boundary inputs.</li>
<li><strong>Security and boundaries</strong>: authentication and authorisation, validation at trust boundaries, secrets, injection, and for Firebase the ownership and App Check specifics.</li>
<li><strong>Conventions and architecture</strong>: the repo&#39;s own <code>CLAUDE.md</code> and language rubric, over-engineering, and scope drift measured against the ticket.</li>
<li><strong>Test adequacy</strong>: whether changed branches and error paths are actually exercised, and whether assertions would fail if the code broke.</li>
</ul>
<p>A docs-only or config-only diff does not need four code lenses, so the set is sized to what actually changed and the skipped lenses are named in the output. Same discipline as the bot review below: a skip is a visible decision, not a silent gap.</p>
<p>They share one pre-computed scratch file with the diff, the ticket and the standards, so four reviewers are not four times re-reading the same thing. The win there is token cost rather than wall-clock, which is the sort of thing that decides whether a review runs on every pull request or only on the ones you remember. Codex reviews independently again alongside them.</p>
<p>Two things make the output trustworthy rather than voluminous.</p>
<p><strong>Adversarial verification.</strong> Every candidate finding goes to a skeptic whose job is to disprove it, and never the lens that raised it, because a finder grading its own work is not a check. The skeptic scores confidence from 0 to 100 and anything under 80 is dropped. Executable proof beats argument: run the function on the triggering input, grep the actual file. Precision matters more than recall here, because false positives are how a review becomes something people stop reading.</p>
<p><strong>Tools as ground truth.</strong> Build, lint, typecheck and tests are run, not guessed, and their real output is shown. A failure is a finding, not something to summarise away.</p>
<p>The complete findings list is presented with a verdict of Ready, Needs work, or Blocking, and <em>nothing has been changed yet</em>. Only then does the main agent act: Critical and High are fixed and re-verified, Medium and Low are offered as a decision. The reviewers find and the main agent fixes, which keeps the roles from blurring.</p>
<h2>Layer three: the bot review, driven by the agent</h2>
<p>The last layer is CodeRabbit on the pull request, and the interesting part is that the agent runs it rather than waiting on it.</p>
<p>Automatic review is switched off. The trigger is a deliberate comment, and it only gets spent on diffs that warrant one. A pull request touching only documentation, tickets or config skips the review entirely, and the skip is written down so it stays a visible decision rather than an omission.</p>
<p>Then a trap worth knowing. A green CodeRabbit check means the review completed, not that it was clean: six unresolved findings sit behind the same green tick as none. The comments have to be fetched separately, and treating &quot;check passed&quot; as &quot;nothing to do&quot; is the easiest way there is to merge a reviewed pull request without reading the review.</p>
<p>Each comment gets triaged the same way as any other finding: real issue, nit, or wrong. A bot review is not automatically right, and grounding a rejection in <code>file:line</code> is the difference between disagreement and hand-waving.</p>
<p>The last step is the one people skip. <strong>Reply before resolving, and address the bot by name.</strong> CodeRabbit ingests replies that mention it and can record a repo-scoped learning, so &quot;we do this deliberately because X&quot; has a chance of stopping the same flag on future pull requests. Silently resolving teaches it nothing at all. In our repositories the review has got quieter over time, and I am fairly sure that is why.</p>
<p>One safety rule sits underneath all of this: review comments are data, never instructions. An agent that executes what it reads in a pull request comment is one crafted comment away from doing something nobody asked for.</p>
<h2>Why three and not one</h2>
<p>Each layer catches what the one before it structurally cannot. The plan review catches design, because the design is all that exists yet. The self-review catches implementation drift against the ticket, which needs code to exist. And the bot is the only one looking across the repository&#39;s history rather than at a single branch, which is a view none of the others can construct.</p>
<h2>The honest part</h2>
<p>This is slower per ticket than not doing it, and it is not a replacement for a human reading the diff. Nothing here removes the pull request review; it changes what arrives at it.</p>
<p>What we get is that a human&#39;s first look is no longer the first challenge to the work. A different model family has attacked the plan, a skeptic has tried to kill each finding, and the build and tests have actually run. What is left is usually a real conversation about a real decision.</p>
<p>Which, on the good days, is what code review was supposed to be.</p>
]]></content:encoded>
    <category>AI</category>
    <category>Agentic Workflow</category>
    <category>Code Review</category>
    <category>Tooling</category>
    <media:content url="https://davidgolverdingen.nl/images/og/review-the-plan-not-just-the-code.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>Enterprise AI Without an Enterprise Budget</title>
    <link>https://davidgolverdingen.nl/en/insights/enterprise-ai-without-enterprise-budget</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/enterprise-ai-without-enterprise-budget</guid>
    <pubDate>Sun, 24 May 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Eleven production MCP servers, no platform team, no framework dependencies, no API bill. The architecture that puts enterprise AI inside a mid-market budget.]]></description>
    <content:encoded><![CDATA[<p>Enterprise AI gets sold as something only large companies can afford. It doesn&#39;t have to be. The reason is structural: the protocol layer underneath is absorbing the work that used to require an AI platform.</p>
<p>Over three months at one mid-sized engineering firm (not a software company), I operationalised AI across the entire business. Eleven production MCP servers covering ERP, BIM, fleet, calculations, building automation, energy, and operational logs. No platform team, no framework dependencies, no custom chat UI. Project managers, field engineers, and operations colleagues query the whole company&#39;s data in natural language, every day. The running cost is roughly €19/seat/month for the staff who use it, plus the engineering time to build the MCP layer.</p>
<p>This piece is about the architecture that made that reachable, and why it&#39;s reachable for small and mid-sized companies that have been told enterprise AI is out of their league.</p>
<h2>The standard path is expensive</h2>
<p>When a mid-market or smaller company decides to bring AI in, the path they&#39;re usually pointed toward looks the same: build a branded chat UI on the API, wire it to internal auth, manage prompts in-house, maybe add a RAG pipeline against company documents, optionally an orchestration framework for <em>&quot;agent workflows,&quot;</em> and increasingly an observability platform to monitor it all. That path is real, and at sufficient scale it may be the right call. I haven&#39;t run this architecture at the scale of a large multinational with hundreds of heterogeneous systems and complex tenancy requirements, so I can&#39;t tell you whether the same posture holds there. My guess is that the MCP layer itself stretches further than the framework industry assumes (more servers, deeper hierarchies of tools, more careful schemas) rather than needing a different architecture entirely. But that&#39;s a hypothesis from one scale, not a report from another.</p>
<p>For a company with twenty important systems and a few hundred employees, the standard path doesn&#39;t pay back. It&#39;s expensive in three coupled ways, and pulling the three apart reveals a simpler architecture underneath.</p>
<p><strong>The chat client.</strong> A branded chat UI is a frontend team, a design effort, conversation history infrastructure, attachment handling, multi-modal input support, an admin panel, model routing, prompt management. The vendor (Anthropic, OpenAI, Google) ships all of this as part of the subscription and continues to ship new affordances every quarter. Building it in-house means investing engineering hours into a commodity layer where the vendor has structural advantages no internal team can match.</p>
<p><strong>The model.</strong> A custom chat UI almost always pins to a specific model version for stability. Six months later the frontier moves, and the pinned model is now a generation behind. Upgrading means re-validating every prompt and every tool, so most teams don&#39;t. Meanwhile every Claude Team subscriber got Opus 4.7 the morning it shipped, with zero engineering work.</p>
<p><strong>The billing.</strong> API billing is per-token, which means the cost is a function of user behaviour that hasn&#39;t happened yet. In a workforce of 50-500 people, roughly 20% of users drive 80% of consumption once adoption stabilises. Published analyses of heavy usage put the API-vs-subscription cost ratio at 15-30x; for average users the multiple is smaller (3-10x), and for light users API can come out ahead. The relevant number isn&#39;t the average. It&#39;s the variance.</p>
<p>These three costs aren&#39;t independent. They flow from the same root decision: build our own chat UI, or use the vendor&#39;s. Building locks all three together. The standard path commits a smaller company to a build budget, a maintenance team, an aging model, and an unpredictable bill, all at once.</p>
<h2>The simpler path</h2>
<p>Use the vendor&#39;s chat client. Pay per seat. Connect your existing identity provider. Then put your engineering hours where the value compounds: in MCP servers that <a href="https://davidgolverdingen.nl/en/insights/97-percent-mcp-tool-descriptions-broken">encode your domain</a>.</p>
<p>This is the architecture in production. Employees use Claude through the standard client (web, desktop, mobile). The client authenticates against the corporate identity provider, same as every other corporate system. Through that client, employees have access to eleven MCP servers covering the full operational chain. Each MCP tool is RBAC-gated against the same IdP roles that govern every other system. A field engineer sees their own time bookings; a controller sees aggregated financials; a guest user sees nothing.</p>
<p>What I didn&#39;t build: a chat UI. A model gateway. A prompt management platform. A vector database. A retrieval pipeline. An agent observability stack. An <a href="https://davidgolverdingen.nl/en/insights/mcp-is-the-ai-platform"><em>&quot;AI platform&quot;</em></a> of any kind. None of those layers exist in the stack, because the subscription client provides everything above the MCP layer, and the IdP provides everything around it.</p>
<p>A concrete example. The most common AI project a mid-sized company is pitched is <em>&quot;RAG over our documents&quot;:</em> chunk all the SharePoint or Google Drive content, embed it, build a vector store, wire it to a retrieval layer, host and maintain the whole pipeline. Even when that pipeline is cheap to build, it&#39;s a layer you have to keep alive. Re-index when documents change, re-tune when retrieval quality drops, re-permission when access rules shift, re-host when the embedding model is deprecated. Meanwhile, the Microsoft 365 and Google Workspace integrations that ship with Claude, ChatGPT, and Gemini are themselves MCP servers, published by the vendors, maintained by the vendors, with permissions inherited from the existing IdP and freshness handled upstream. The <em>&quot;document search&quot;</em> capability that makes a custom RAG pipeline sound necessary is already an MCP server you can turn on in your admin console. The choice isn&#39;t cheap vs. expensive. It&#39;s an extra layer you maintain vs. no extra layer at all. And once you stop blaming the data and <a href="https://davidgolverdingen.nl/en/insights/your-data-is-fine">start fixing the meaning layer</a>, the case for a custom RAG pipeline gets thinner still.</p>
<p>That sharpens the architectural rule. MCP isn&#39;t a category that lives only inside your perimeter; it&#39;s the protocol the entire ecosystem speaks, and vendors are already publishing servers for the commodity layer: productivity suites, code hosts, ticketing systems, design tools. Your engineering investment goes into the MCP servers that <em>only you</em> can write: your ERP, your BIM data, your operational logs, your calculation history. The systems unique to your business that no vendor has, or will ever have, a connector for. Everything else, you consume. That&#39;s where the investment compounds, and where it doesn&#39;t.</p>
<p>The cost picture inverts. Claude Team is about €19/seat/month on the annual plan; ChatGPT Enterprise and Gemini for Workspace are in similar territory. The vendor eats the billing variance. Every seat gets the latest frontier model the day it ships. No frontend to maintain, no platform team to staff, no orchestration platform to buy.</p>
<h2>What this opens up</h2>
<p>The interesting part of this architecture, and the part that makes it reachable for small companies, is the shape of <a href="https://davidgolverdingen.nl/en/insights/six-levels-of-mcp-servers">the MCP layer</a>.</p>
<p><strong>The domain layer is model-agnostic.</strong> MCP servers are typed interfaces with tool descriptions and schemas. The same servers work with Claude today, Gemini tomorrow, GPT next quarter. None of the domain logic is locked to a model vendor. The engineering investment is portable across the entire frontier-model market.</p>
<p><strong>Identity lives at the tool boundary.</strong> RBAC is enforced inside the MCP server, against your existing IdP. A model that&#39;s been prompt-injected can only call tools the authenticated user is already authorised to call. The security model is the same one your company already runs for every other system. No AI-specific identity layer, no prompt firewall, no model gateway. The tool boundary is the trust boundary.</p>
<p><strong>The engineering shape is small.</strong> An MCP server, in my experience, is one engineer working with one domain expert for a few weeks per domain. That&#39;s the whole staffing model. Nine production servers in the first three months, eleven now. No platform team, no specialists. The people who own the underlying business systems can do most of the work themselves, with engineering support.</p>
<p>This is what makes the approach reachable. A small or mid-sized company doesn&#39;t need to hire an AI platform team to run this stack. The chat client is rented from a vendor at a price comparable to a productivity-suite seat. The MCP layer is built incrementally by the engineers and domain experts already on the payroll. There&#39;s no procurement cycle for an <em>&quot;AI platform,&quot;</em> no consulting engagement to size the rollout, no infrastructure to provision.</p>
<h2>What you rent, what you own</h2>
<table>
<thead>
<tr>
<th>What you rent</th>
<th>What you own</th>
</tr>
</thead>
<tbody><tr>
<td>The chat client (Claude, ChatGPT, Gemini, your choice)</td>
<td>The MCP servers that encode your domain</td>
</tr>
<tr>
<td>Conversation history, attachments, multi-modal UI</td>
<td>Tool descriptions, query strategies, business logic</td>
</tr>
<tr>
<td>Enterprise SSO, audit logs, retention policies</td>
<td>Identity-gated tool access through your existing IdP</td>
</tr>
<tr>
<td>Frontend updates, model upgrades, security patches</td>
<td>Domain knowledge written into schemas</td>
</tr>
<tr>
<td>The model itself, latest version on day one</td>
<td>The integration with ERP, BIM, fleet, energy, calc</td>
</tr>
<tr>
<td>Predictable per-seat billing</td>
<td>One engineer, one domain expert, per server</td>
</tr>
</tbody></table>
<p>What you rent is the commodity layer: the stuff vendors compete on and ship continuously. What you own is the part no vendor can build for you, because no vendor knows what your data means.</p>
<h2>The same pattern, one floor down</h2>
<p>The reason this is reachable goes one layer deeper than the chat client. The same logic applies to the framework layer underneath: LangChain, LangGraph, CrewAI, RAG pipelines, vector DBs, agent observability stacks. Building on top of those is one shape of architecture; building MCP servers directly against a frontier model is the same architectural posture without the additional layer.</p>
<p>Each MCP spec release closes another category of problem that previously required a framework layer. Tool calling, resource management, prompts, sampling, elicitation, UI primitives via MCP Apps, and enterprise identity integration on the 2026 roadmap. Each one used to live in a framework above MCP, and each one is now in the protocol itself. The framework layer isn&#39;t being argued against; it&#39;s being absorbed. Companies building on top of frameworks today are building on top of a layer the protocol is in the process of swallowing.</p>
<p>Both layers reward the same answer: rent the surface, own the domain. Use the vendor&#39;s client. Use the vendor&#39;s identity integrations. Use the vendor&#39;s model upgrades. Skip the framework layer. Spend your engineering on the part that&#39;s actually yours: the MCP servers that turn your operational data into something an agent can reason about.</p>
<p>That&#39;s what <em>&quot;MCP is the platform&quot;</em> actually looks like in practice. Not a stack you build, a perimeter you draw. Inside the perimeter: your domain, your tools, your IdP, your data. Outside: the model, the client, the vendor. The line between them is MCP.</p>
<h2>If you&#39;re starting</h2>
<p>Subscribe to Claude Team, ChatGPT Enterprise, or Gemini for Workspace. Connect your existing identity provider. Then pick the one system whose data your colleagues most want to query in natural language, and write one MCP server against it. Ship that. Watch them use it. Build the next one. The <a href="https://davidgolverdingen.nl/en/insights/production-mcp-practitioners-guide">practitioner&#39;s guide</a> walks the full seven-step recipe.</p>
<p>That&#39;s the entire starting move. No vendor selection process for an AI platform. No headcount plan for a platform team. No procurement cycle for orchestration software. A subscription, an identity wire-up, and one MCP server is enough to be in production with real users by the end of a month.</p>
<p>Three months of that across a real business produced nine production servers, and steady additions since have taken it to eleven, with no framework dependencies, no custom chat UI, no API bill, no platform team, and a company that gets every frontier model upgrade for free the day it ships.</p>
<p>This architecture isn&#39;t a clever workaround for the moment. It&#39;s an early version of what enterprise AI is going to look like once the protocol layer finishes absorbing the platform layer above it. The companies that build this way now are getting a head start on a stack that won&#39;t look unusual in two years. It will look obvious.</p>
<p>Enterprise AI doesn&#39;t require an enterprise budget. It requires picking the right perimeter to draw.</p>
<hr>
<p>Go deeper: read the full practitioner report, <a href="https://davidgolverdingen.nl/en/the-missing-layer"><em>The Missing Layer</em></a>, or explore the working code in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> server.</p>
]]></content:encoded>
    <category>MCP</category>
    <category>AI</category>
    <category>Architecture</category>
    <category>Cost</category>
    <media:content url="https://davidgolverdingen.nl/images/og/enterprise-ai-without-enterprise-budget.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>MCP Is the AI Platform</title>
    <link>https://davidgolverdingen.nl/en/insights/mcp-is-the-ai-platform</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/mcp-is-the-ai-platform</guid>
    <pubDate>Sat, 23 May 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Eleven production MCP servers, one mid-sized firm. No agent framework, no RAG pipeline, no AI-platform vendor. MCP plus enterprise identity is the whole stack.]]></description>
    <content:encoded><![CDATA[<p>Stop building around the model. Build for MCP.</p>
<p>After shipping eleven production MCP servers across one mid-sized engineering firm (bound to Claude in our case, though everything below applies equally to GPT, Gemini, or any frontier model that speaks MCP), here&#39;s what I didn&#39;t build: agent frameworks, RAG pipelines, vector databases, orchestration platforms, or any of the other layers the AI industry insists you need.</p>
<p>What I have instead: a frontier model, MCP servers, and well-written tool descriptions. That&#39;s it. And it works: measurably, daily, across the whole business.</p>
<p>By 2026 MCP isn&#39;t a niche bet. It crossed roughly 97 million monthly SDK downloads, and OpenAI, Google, Microsoft, and Anthropic have all integrated it across their products. The protocol question is settled. <strong>And because every frontier model now consumes MCP through the same interface, the model question is settled too.</strong> Pick any of them and the architecture doesn&#39;t change. What&#39;s still being argued is whether you need anything else on top.</p>
<h2>The model is the agent. The framework is overhead.</h2>
<p>The framing <em>&quot;you need an agent framework&quot;</em> obscures a simpler truth: the model <em>is</em> the agent. It reads tool descriptions. It chooses which tool to call. It sequences the calls. It interprets the results. That&#39;s textbook agentic behaviour, built into every frontier model that speaks MCP.</p>
<p>What companies sell you on top of that is <em>framework around the agent</em>: orchestration logic, retrieval pipelines, prompt managers, observability layers. Each of those products is solving a real problem in some context. But in a mid-sized business with a knowable set of important data sources, those contexts mostly don&#39;t apply.</p>
<table>
<thead>
<tr>
<th>What I use</th>
<th>What I skip</th>
</tr>
</thead>
<tbody><tr>
<td>A frontier model (Claude in my case; swap as needed)</td>
<td>LangChain + LangGraph + LangSmith stack</td>
</tr>
<tr>
<td>MCP servers (custom, well-typed)</td>
<td>Multi-agent orchestration (CrewAI, Microsoft Agent Framework, OpenAI Agents SDK)</td>
</tr>
<tr>
<td>Tool descriptions as the interface</td>
<td>Vector DBs, embedding pipelines, agentic-retrieval frameworks (LlamaIndex)</td>
</tr>
<tr>
<td>Domain knowledge written into schemas</td>
<td>Hand-curated knowledge graphs sitting next to the tool</td>
</tr>
<tr>
<td>Cross-source composition through tool design</td>
<td>Workflow orchestration platforms</td>
</tr>
<tr>
<td>Production telemetry as the feedback loop</td>
<td>Agent observability stacks (LangSmith, LangFuse, Arize Phoenix)</td>
</tr>
</tbody></table>
<p>Each row on the right is a paid product category. Each row on the left is the model, the protocol, or work I did once and reuse.</p>
<h2>What &quot;build for MCP&quot; looks like in practice</h2>
<p>A well-designed MCP server doesn&#39;t ask the model to <em>figure out</em> what the data means. It tells the model what the data means <a href="https://davidgolverdingen.nl/en/insights/six-levels-of-mcp-servers">in the tool description itself</a>.</p>
<p>The contrast plays out at every level. A bad tool says: <em>&quot;query data from the ERP.&quot;</em> A good tool says: <em>&quot;always start with <code>summaryOnly=true</code>. Active projects accumulate thousands of records. Type codes determine which fields are populated. Use <code>get_budget</code> for planned costs, this tool for actuals.&quot;</em></p>
<p>The first version forces the model to invent a query strategy on every call. The second hands the model a query strategy on every call. The difference between those two servers, in production, is whether business users actually use the result.</p>
<p>This is the work teams skip when they reach for frameworks. The complexity is mostly self-inflicted. It exists because nobody wrote down what the tools mean. The same gap is why <a href="https://davidgolverdingen.nl/en/insights/97-percent-mcp-tool-descriptions-broken">97% of MCP tool descriptions analysed in production contain at least one critical smell</a>.</p>
<h2>One server can compose many sources</h2>
<p>The most common objection (<em>&quot;but you need orchestration to combine data from multiple systems&quot;</em>) assumes orchestration has to live in a separate framework. It doesn&#39;t.</p>
<p>One of my MCP servers combines five heterogeneous sources behind one interface: a third-party meter-data aggregator, a public weather API, a government building registry, the company ERP, and an IoT building-automation platform. No agent dance. No retrieval pipeline. Just typed tools with descriptions explaining when each source applies and how they relate.</p>
<p>The model figures out the composition because the tool descriptions tell it the relationships. The pattern works across eleven production servers covering ERP, BIM, fleet, calculations, building automation, energy, and operational logs. Effectively the entire operational surface of one business, addressable through tool descriptions, consumable by any MCP-speaking model.</p>
<h2>Tools abstract everything below them</h2>
<p>The model never sees how the data is fetched. Inside one MCP server, individual tools call whatever the underlying system speaks: REST for SaaS products, GraphQL for internal APIs, direct SQL against the data warehouse, JSON files on disk, SOAP envelopes for legacy systems. The tool returns typed records; the protocol heterogeneity stays inside the tool.</p>
<p>That&#39;s the abstraction layer the data-fabric and iPaaS industries charge premium prices to build. MCP tools already do it, one tool at a time, in whatever language the data actually lives in, with no central pipeline. The choice of backend doesn&#39;t propagate; pick whatever fits the underlying system, and the model interface stays identical.</p>
<p>Even SQL becomes tractable. Direct SQL from an LLM is dangerous because the model can be tricked into destructive queries. But SQL inside a typed MCP tool (where the server builds SQL from schema-validated parameters, the connection runs as a read-only role, and the corporate IdP gates who can call it) is just a regular function call. Any value that doesn&#39;t match the tool&#39;s JSON schema is rejected at the protocol boundary before the tool&#39;s code runs at all.</p>
<p>This isn&#39;t theoretical. One of my servers is 100% SQL behind a query endpoint, covering calculation data. Every tool translates a typed agent request into a SQL query: the model passes schema-validated parameters in, the server constructs and runs the query, typed records come back out. The model never sees SQL. The connection runs as a read-only role, and access is gated by the same IdP roles that govern every other system.</p>
<p>I built it in one day. The calculation expert who owns the underlying data validated it, and it already exposes every dataset that team needs. Once you&#39;ve internalised the pattern, applying it to a new domain is a day&#39;s work, not a quarter&#39;s project.</p>
<h2>Why mid-market is the sweet spot</h2>
<p>This argument has bounds. At Fortune 500 scale (hundreds of heterogeneous systems, multi-tenant SaaS, mountains of unstructured documents) you might need retrieval pipelines and orchestration. The complexity is real because the scope is real.</p>
<p>But mid-sized businesses have something Fortune 500 doesn&#39;t: a knowable set of important data sources. Five to twenty key systems. A handful of domain experts who can sit with you for an afternoon and tell you what the data actually means. That&#39;s the entire prerequisite for a well-built MCP server.</p>
<p>If your company fits in one office building and has fewer than twenty important systems, you&#39;re who this is for. You almost certainly don&#39;t need most of what the AI industry is trying to sell you.</p>
<h2>What &quot;the platform&quot; actually is</h2>
<p>Notice what&#39;s missing from the table above: a platform. There&#39;s no &quot;AI platform&quot; in the stack. Just a frontier model, MCP servers, and the corporate identity layer the business already pays for. Swap the model (Claude today, Gemini tomorrow, GPT next quarter) and the rest of the stack stays identical.</p>
<p>That last piece is what turns eleven MCP servers into something an enterprise can run. Not an AI-specific identity layer. The one the company already operates. In a Microsoft shop, that&#39;s Entra ID with RBAC. In other shops, Okta, Google Cloud Identity, or any OAuth 2.1 provider. MCP servers authenticate against the existing IdP, scope tool access by role, and log every call to the same audit trail every other corporate system already uses.</p>
<p>The implications:</p>
<ul>
<li>A field engineer sees only their own time bookings and assigned projects.</li>
<li>A controller sees aggregated financials, not raw payroll.</li>
<li>A guest user sees nothing.</li>
</ul>
<p>Every action is attributable to a named identity in the same audit log as every other system. That&#39;s the entire enterprise AI security model. No prompt-firewall vendor. No AI-specific governance platform. No model gateway. Just the access-control infrastructure the company already runs, enforced at the MCP-tool boundary. Even prompt injection becomes bounded. A tricked model can only call tools the authenticated user is already authorised to call.</p>
<p>Anthropic&#39;s 2026 MCP roadmap leads with enterprise authentication and identity-provider integration. The protocol is moving in this direction because the pattern works: tool-level RBAC against the corporate IdP turns <em>&quot;the AI security problem&quot;</em> into a solved authentication problem. Which it always was.</p>
<p>Wire MCP servers through enterprise identity and MCP isn&#39;t <em>connected to</em> the platform. MCP <em>is</em> the platform.</p>
<h2>The work that matters</h2>
<p>What the framework industry sells is scaffolding. What actually moves outcomes is the specific, domain-bound work of writing good tool descriptions: choosing the right granularity, documenting which tool fits which question, adding query strategies, capturing failure modes from production usage and feeding them back.</p>
<p>None of that can be outsourced to a vendor, because <a href="https://davidgolverdingen.nl/en/insights/your-data-is-fine">nobody outside your business knows what your data means</a>. But all of it is reachable in a few weeks per domain, with one engineer and one domain expert. That&#39;s the trade you&#39;re being told doesn&#39;t exist.</p>
<h2>The question that replaces &quot;which framework should I pick?&quot;</h2>
<p>If you&#39;re starting MCP work today, the most useful question isn&#39;t <em>&quot;which framework do I need?&quot;</em> It&#39;s <em>&quot;what&#39;s the smallest useful tool I can write for the one person whose week would get better tomorrow if it worked?&quot;</em></p>
<p>Build that. Watch them use it. Write down what they tried that didn&#39;t work. Update the tool description. Ship the next one.</p>
<p>Three months of that across a real business produced nine production servers (eleven since) and zero framework dependencies. That&#39;s the story: not that frameworks are bad, but that for most mid-sized companies, they&#39;re the wrong problem to be solving first.</p>
<p>The model is the agent. The IdP is the security boundary. MCP is the platform. Build for the platform, not around it.</p>
<hr>
<p>Go deeper: read the full practitioner report, <a href="https://davidgolverdingen.nl/en/the-missing-layer"><em>The Missing Layer</em></a>, or explore the working code in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> server.</p>
]]></content:encoded>
    <category>MCP</category>
    <category>AI</category>
    <category>Architecture</category>
    <media:content url="https://davidgolverdingen.nl/images/og/mcp-is-the-ai-platform.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>Six Things the MCP Spec Should Fix</title>
    <link>https://davidgolverdingen.nl/en/insights/six-things-mcp-spec-should-fix</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/six-things-mcp-spec-should-fix</guid>
    <pubDate>Fri, 24 Apr 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Six fixes the MCP spec needs after 90+ tools and eleven APIs in production: from Resources no client surfaces to enums that break agent reasoning.]]></description>
    <content:encoded><![CDATA[<p>After building eleven MCP servers with more than 90 tools across eleven external APIs, the protocol&#39;s limitations become sharp. MCP excels at connecting agents to tools. It does not yet help agents understand what those tools mean, or safely act on what they understand.</p>
<p>These are the six changes, grounded in production experience rather than speculation, that would raise the floor for every server in the ecosystem.</p>
<h2>1. Smarter tool discovery</h2>
<p>Today, every connected MCP server dumps all its tools into the agent&#39;s context at once. For a single server with 10 tools, that&#39;s fine. For a developer in an IDE with 10 servers connected simultaneously? That&#39;s 100+ tool descriptions competing for context.</p>
<p>This has a chilling effect on metadata investment: why write 200 lines of rich domain knowledge per tool if the client is going to flood the context with everything?</p>
<p>The spec needs a discovery layer: DNS for tools. Let servers declare tool groups with short summaries. The client presents groups to the model, the model selects the relevant group, and only those tool definitions are injected. Resolve first, load second.</p>
<h2>2. Extensible metadata without context flooding</h2>
<p>All metadata currently goes into tool descriptions and input schema descriptions, both consuming context tokens on every call. There&#39;s no mechanism for metadata the agent can request on demand.</p>
<p>A tool could declare 200 lines of field-level documentation, cross-reference tables, and query strategy guides as structured metadata. The client loads the tool&#39;s name and short description by default, but injects the full metadata only when the model indicates it wants to call that tool.</p>
<p>This would eliminate the trade-off between rich documentation and context efficiency, a trade-off that currently forces server authors to compress critical domain knowledge into artificially short descriptions.</p>
<h2>3. Resources that actually work</h2>
<p>MCP Resources are the spec&#39;s answer to reference documentation: static or dynamic content that agents can request to inform their reasoning. In theory, perfect for domain guides and field dictionaries.</p>
<p>In practice, no tested client reliably surfaces resources to agents. Claude Desktop lists them in a sidebar but agents don&#39;t request them. Claude Code and Cursor ignore them entirely. I built resources, tested them, and removed them after validation showed zero agent-initiated requests.</p>
<p>For resources to work, clients need to either: automatically inject relevant resource content when a related tool is selected, allow servers to mark resources as required context for specific tools, or give the model an explicit <code>get_resource</code> primitive that it&#39;s trained to use.</p>
<h2>4. First-class feedback loops</h2>
<p>The current spec has no concept of agent-to-server feedback. Every interaction is request-response: call a tool, get data, move on. No standard way for agents to report confusion, flag data quality issues, or indicate which calls were unhelpful.</p>
<p>I solved this with a custom <code>report_problem</code> tool and <code>queryIntent</code> parameters on every input schema. This works, but it&#39;s a workaround. The spec should support feedback as a first-class primitive: a standardized way for agents to annotate tool calls with intent, satisfaction, and issues encountered. Server authors could subscribe to feedback events and improve metadata iteratively, closing the loop that currently requires custom infrastructure.</p>
<h2>5. Agent training on MCP patterns</h2>
<p>Perhaps the most impactful change would happen not in the spec but in model training. Current models are not trained on rich MCP interactions. They don&#39;t know that a WHEN TO USE block should be parsed differently than a generic API description. They don&#39;t understand that <code>.describe()</code> annotations on output schema fields are there to be read and used.</p>
<p>If model providers included rich MCP server interactions in their training data (tool descriptions with domain metadata, multi-tool sequences with cross-references, feedback loops with intent parameters), agents would naturally leverage the metadata that server authors invest in writing. The metadata is already machine-readable. The models just need to be taught to read it.</p>
<h2>6. Scoped write permissions for MCP Apps</h2>
<p>As servers evolve from read-only to interactive applications, write operations become inevitable. But the spec offers no mechanism to restrict a write tool to a specific interaction context. If a server exposes a POST tool, any connected agent can call it at any time.</p>
<p>I designed a workaround: the WriteIntent pattern. The agent calls a model-visible &quot;open&quot; tool that mints a server-side intent. The actual mutation is performed by a separate &quot;commit&quot; tool registered with <code>visibility: [&quot;app&quot;]</code>, hidden from the agent, callable only by the MCP App form. The intent is user-bound, resource-scoped, one-shot, time-limited, and validated with typed schemas.</p>
<p>The spec should formalize this: a way to scope write tools to specific MCP apps, so that a tool marked as write-only-via-app can only be invoked through a validated form interaction, not through freeform agent reasoning.</p>
<p>All six recommendations share a common theme: the spec is designed for transport, but the real challenge is knowledge delivery and safe interaction. Addressing these gaps at the framework level would raise the floor for every server, not just the ones built by teams willing to invest 40 hours in metadata engineering.</p>
<hr>
<p>Go deeper: read the full practitioner report, <a href="https://davidgolverdingen.nl/en/the-missing-layer"><em>The Missing Layer</em></a>, or explore the working code in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> server.</p>
]]></content:encoded>
    <category>MCP</category>
    <category>Architecture</category>
    <media:content url="https://davidgolverdingen.nl/images/og/six-things-mcp-spec-should-fix.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>Your MCP Server Should Get Smarter Every Week</title>
    <link>https://davidgolverdingen.nl/en/insights/mcp-server-smarter-every-week</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/mcp-server-smarter-every-week</guid>
    <pubDate>Fri, 17 Apr 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Three calls to the same tool with tightening filters. Without queryIntent: opaque retries. With it: the exact metadata gap, fixable in minutes.]]></description>
    <content:encoded><![CDATA[<p>Writing good tool descriptions is the start. Keeping them good is the real work.</p>
<p>The description blocks I covered in <a href="https://davidgolverdingen.nl/en/insights/97-percent-mcp-tool-descriptions-broken">a previous post</a> are the initial knowledge layer. But how do you know what&#39;s missing? How do you find the gaps the agent silently works around without telling you? That&#39;s where the feedback architecture comes in.</p>
<p>After running eleven MCP servers in production, I settled on three layers (weighted roughly 70/20/10 by value) with one design choice that ties everything together.</p>
<h2>Layer 1: Tool call logs (70% of the value)</h2>
<p>Log every tool call: request parameters, response summary, session ID, timestamp. No conversation text, no user questions, no agent reasoning. Just the MCP layer.</p>
<p>This is the foundation. Three consecutive calls with tightening filters on the same table means the agent is struggling with something the metadata should have covered. You don&#39;t need the agent to tell you it&#39;s confused. The call pattern tells you.</p>
<p>In the production implementation, every tool call writes to a persistent store with: tool name, user, queryIntent, filter fields and operators (not values, for privacy), summaryOnly flag, row count, duration, and error type. Session IDs correlate multi-tool sequences.</p>
<h2>Layer 2: Pattern analysis (20% of the value)</h2>
<p>Weekly analysis of the raw logs. Look for repeated call sequences, redundant calls, unused tools that should have been used, tools called in unexpected order.</p>
<p>Session IDs make this possible. You can trace an entire multi-tool interaction from start to finish and ask: where did the agent take a detour? Where did it call tool A when the answer was in tool B? Where did it retry with different filters because the first call didn&#39;t return what it expected?</p>
<p>This finds silent failures: cases where the agent got a wrong answer without realizing it.</p>
<h2>Layer 3: QueryIntent + report tool (10% of the value)</h2>
<p>A queryIntent parameter on every tool call: one sentence describing the business question being answered. Plus a report tool the agent can use when genuinely stuck.</p>
<p>Here&#39;s where it becomes concrete. The same three-call sequence, with and without queryIntent:</p>
<p><strong>Without:</strong></p>
<blockquote>
<p><code>09:14:03 | get_service_records | summaryOnly=true, project=P-7056</code></p>
<p><code>09:14:07 | get_service_records | type=13, project=P-7056</code></p>
<p><code>09:14:09 | get_service_records | type=13, project=P-7056, completed=true</code></p>
</blockquote>
<p>Three calls. The last one added a filter. Why? Unknown.</p>
<p><strong>With:</strong></p>
<blockquote>
<p><code>09:14:03 | intent: Overview of all service records for project P-7056</code></p>
<p><code>09:14:07 | intent: Which maintenance orders exist for this project</code></p>
<p><code>09:14:09 | intent: Only completed orders, previous call also returned open items</code></p>
</blockquote>
<p>Now you see exactly what happened: the agent expected only completed items but the metadata didn&#39;t make clear that a completion filter is needed. That&#39;s a targeted metadata fix requiring minutes to implement.</p>
<h2>Nudge patterns: making feedback happen</h2>
<p>Agents don&#39;t self-report reliably. Academic research shows mixed results on LLM metacognition. So instead of relying on the agent&#39;s initiative, the production implementation injects contextual nudges into tool responses:</p>
<ul>
<li><strong>Empty results:</strong> &quot;No records matched your filters. If unexpected, consider calling report_problem.&quot;</li>
<li><strong>Pagination detected:</strong> &quot;You&#39;re paginating (skip=200). If you&#39;re doing this to manually aggregate data, call report_problem. We may be able to add that summary dimension.&quot;</li>
<li><strong>Every response:</strong> A feedback reminder as the last alert: &quot;If this query took multiple attempts or returned confusing data, call report_problem before your next step.&quot;</li>
</ul>
<p>These nudges are injected by the server&#39;s handler factory, not by the agent&#39;s judgment. They target specific friction patterns and give the agent a low-effort path to report issues it would otherwise silently work around.</p>
<h2>Why this compounds</h2>
<p>Each resolved issue makes the system permanently smarter. A metadata update isn&#39;t a patch. It eliminates an entire category of uncertainty for every future session, every connected agent. The metadata layer grows richer over time while the reasoning layer stays the same. Cheap, permanent, compounding progress.</p>
<hr>
<p>Go deeper: read the full practitioner report, <a href="https://davidgolverdingen.nl/en/the-missing-layer"><em>The Missing Layer</em></a>, or explore the working code in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> server.</p>
]]></content:encoded>
    <category>MCP</category>
    <category>Architecture</category>
    <media:content url="https://davidgolverdingen.nl/images/og/mcp-server-smarter-every-week.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>How to Write MCP Tool Descriptions: The 8-Block Pattern from 11 Production Servers</title>
    <link>https://davidgolverdingen.nl/en/insights/97-percent-mcp-tool-descriptions-broken</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/97-percent-mcp-tool-descriptions-broken</guid>
    <pubDate>Fri, 10 Apr 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[An analysis of 856 tools across 103 MCP servers found 97.1% have at least one description smell. After shipping 90+ tools in production, here's the eight-block pattern that fixed ours.]]></description>
    <content:encoded><![CDATA[<p>An academic analysis of 856 tools across 103 MCP servers (<a href="https://arxiv.org/abs/2602.14878"><em>MCP Tool Descriptions Are Smelly!</em> (arXiv:2602.14878, 2026)</a>) found that 97.1% of tool descriptions contain at least one &quot;smell&quot;: unstated limitations, missing usage guidelines, opaque parameters. That&#39;s not a fringe problem. That&#39;s the baseline.</p>
<p>In <a href="https://davidgolverdingen.nl/en/insights/six-levels-of-mcp-servers">The Six Levels of MCP Servers</a>, I described what each maturity level looks like. Here&#39;s what the jump from Level 1 to Level 4 actually takes.</p>
<h2>A tool description is not a sentence</h2>
<p>The MCP specification&#39;s own best-practice proposal considers this a good description: <em>&quot;Read the contents of multiple files simultaneously. More efficient than reading files individually.&quot;</em> That&#39;s the ceiling the community is aiming for.</p>
<p>After building more than 90 tools across eleven production servers, I arrived at a different standard. A tool description is an operational manual, structured into blocks, each added because the agent failed without it.</p>
<h2>The eight blocks</h2>
<table>
<thead>
<tr>
<th>Block</th>
<th>Purpose</th>
<th>Without it...</th>
</tr>
</thead>
<tbody><tr>
<td><strong>RETURNS</strong></td>
<td>Agent knows which fields come back</td>
<td>Can&#39;t determine if this tool has the data it needs</td>
</tr>
<tr>
<td><strong>WHEN TO USE</strong></td>
<td>Agent knows when this tool fits</td>
<td>Picks wrong tool or misses this one</td>
</tr>
<tr>
<td><strong>WHEN NOT TO USE</strong></td>
<td>Prevents wrong tool selection</td>
<td>Tries this tool for queries that belong elsewhere</td>
</tr>
<tr>
<td><strong>QUERY STRATEGY</strong></td>
<td>Teaches summary-first, then drill down</td>
<td>Fetches full records when a summary would suffice</td>
</tr>
<tr>
<td><strong>INTERPRETATION</strong></td>
<td>Cross-field rules, type-to-field mappings</td>
<td>Returns raw numbers without conclusions</td>
</tr>
<tr>
<td><strong>RELATED TOOLS</strong></td>
<td>Agent chains queries via join keys</td>
<td>Stops after first tool call</td>
</tr>
<tr>
<td><strong>FEEDBACK</strong></td>
<td>Agent reports friction</td>
<td>Issues go undetected, descriptions never improve</td>
</tr>
<tr>
<td><strong>ALERTS</strong></td>
<td>Agent surfaces server-generated warnings</td>
<td>Ignores domain-specific warnings in the response</td>
</tr>
</tbody></table>
<p>These blocks are not theoretical. Each was added in response to a specific failure mode observed in production. The agent picked the wrong tool, so add WHEN NOT TO USE. The agent fetched 2,000 records instead of a summary, so add QUERY STRATEGY. The agent ignored that a status code meant something entirely different for a different record type, so add INTERPRETATION.</p>
<h2>What actually works as a metadata channel</h2>
<p>Not everything you write reaches the agent. After testing across Claude Desktop, Claude Code, and Cursor, a clear hierarchy emerged:</p>
<p><strong>Tier 1: Always works (~95% of the value).</strong> Tool descriptions and input/output schema <code>.describe()</code> annotations. The agent reads these on every call. This is where all domain knowledge must live.</p>
<p><strong>Tier 2: Works sometimes.</strong> Server instructions, cross-tool behavioral rules injected at session start. Some clients inject them, others don&#39;t. Useful for global rules, not a substitute for per-tool descriptions.</p>
<p><strong>Tier 3: Does not work in practice.</strong> MCP Resources and meta-tools for reference lookups. I built both, deployed them, tested them. Agents never requested them across any tested client. Both were removed.</p>
<p>The key insight: everything the agent needs must live in the tool description and input/output schema. That&#39;s the only reliable delivery channel, for now.</p>
<h2>Before and after</h2>
<p>The difference in practice. A building profile tool, same API, same data:</p>
<p><strong>Level 1:</strong> <code>&quot;Look up building information by postcode and house number.&quot;</code> Two untyped parameters. No field documentation. The agent guesses what an energy label means, whether the building year is reliable, and which postcode format the API accepts.</p>
<p><strong>Level 4:</strong> Structured WHEN TO USE / WHEN NOT TO USE blocks. A QUERY STRATEGY that warns the agent not to trust smart meter registration addresses as physical building addresses. An INTERPRETATION block that explains three different energy certification standards: NTA 8800 returns kWh/m2, Nader Voorschrift returns MJ total building energy. Without that block, the agent compares them as if they&#39;re the same unit and gives confidently wrong advice. Input schema with regex validation. Output fields with <code>.describe()</code> annotations documenting null patterns, cross-tool join keys, and which surface area field to use for which benchmark.</p>
<p>Same tool. Different category. The Level 1 version exposes an API. The Level 4 version teaches the agent how a domain expert thinks about this data.</p>
<p>An open-source extract of this tool, with the metadata patterns intact, lives in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> repo: read <a href="https://github.com/DaveGold/mcp-metadata-demo/blob/main/src/tools/get-building-profile.ts"><code>get-building-profile.ts</code></a> to see the eight blocks and <code>.describe()</code> annotations on a real tool.</p>
<hr>
<p>Go deeper: read the full practitioner report, <a href="https://davidgolverdingen.nl/en/the-missing-layer"><em>The Missing Layer</em></a>, or explore the working code in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> server.</p>
]]></content:encoded>
    <category>MCP</category>
    <category>Metadata</category>
    <media:content url="https://davidgolverdingen.nl/images/og/97-percent-mcp-tool-descriptions-broken.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>The Six Levels of MCP Servers</title>
    <link>https://davidgolverdingen.nl/en/insights/six-levels-of-mcp-servers</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/six-levels-of-mcp-servers</guid>
    <pubDate>Fri, 03 Apr 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Not all MCP servers are the same. Here's how the types differ — a six-level maturity ladder from hollow API wrappers to apps that write back, drawn from eleven production servers and 90+ tools. Where does yours sit?]]></description>
    <content:encoded><![CDATA[<p>Most MCP servers do the same thing: wrap an API, expose tools with one-sentence descriptions, and hope the model figures out the rest. In <a href="https://davidgolverdingen.nl/en/insights/your-data-is-fine">a previous post</a> I described why this fails for enterprise data. Here&#39;s the maturity ladder that emerged after building eleven production servers with more than 90 tools across eleven APIs.</p>
<h2>The types of MCP servers and how they differ</h2>
<p>They differ along a single axis: how much of the domain the server carries, versus how much it leaves the model to guess. That is what separates one type from the next. Each level below hands the model something the previous one withheld — starting from bare endpoint names, through typed metadata and descriptions that teach the model how to query, up to interactive apps that write back to the system of record.</p>
<h2>Level 1: API Mapper (~70% of servers)</h2>
<p>One tool per endpoint. One-sentence descriptions. No domain context. The model figures everything out alone from the tool name. Ask <em>&quot;which projects are running over budget?&quot;</em> and it will happily invent a filter, hit an empty endpoint, and tell you everything is fine.</p>
<h2>Level 2: Functional (~20%)</h2>
<p>Tools are grouped sensibly. Descriptions are longer. Someone thought about how a human would use this. Still no domain knowledge, no cross-tool references, no query strategies. This is the ceiling most commercial MCP implementations aim for today.</p>
<h2>Level 3: Metadata-Rich (~8%)</h2>
<p>Knowledge graphs, glossaries, data catalogs. The metadata is real, but it lives next to the tool rather than inside it, and it was typically curated by hand over weeks or months. In practice, manual curation doesn&#39;t scale, and the agent only reads it if it happens to call the right meta-tool. I built two of these layers (MCP Resources and a parameterless &quot;guide&quot; tool) and removed both after testing. No Claude client ever requested them unprompted.</p>
<h2>Level 4: Self-Teaching (&lt;2%)</h2>
<p>The domain knowledge is <em>in the tool description and the input/output schemas</em>, the only channels the agent reads reliably on every call. And that knowledge wasn&#39;t written by humans scanning documentation; it was discovered by an AI examining real data, flagged with confidence levels, then validated by domain experts. I called the pattern <strong>Introspective Context Engineering for MCP</strong>.</p>
<p>The difference is not subtle. A Level 1 server says <em>&quot;query data from the ERP.&quot;</em> A Level 4 server says <em>&quot;always start with <code>summaryOnly=true</code>, active projects accumulate thousands of records. Type codes determine which fields are populated. Use <code>get_budget</code> for planned costs, this tool for actuals. Report friction via <code>report_problem</code>.&quot;</em> Not the same product. Not the same category.</p>
<h2>Level 5: Interactive App (emerging)</h2>
<p>The server doesn&#39;t just return data. It returns <strong>rendered UI</strong>. Interactive charts, sortable tables, clickable maps, typed forms, all drawn by the server and displayed inline in the conversation. The agent coordinates; the server controls presentation.</p>
<p>A table of 400 rows in a markdown code block is unreadable. A rendered, sortable, filterable table is a tool a business user can actually use. Level 5 is where the interface meets the user where they are.</p>
<p>Working examples from an open-source demo server: <a href="https://github.com/DaveGold/mcp-metadata-demo/blob/main/src/tools/render-chart.ts"><code>render_chart</code></a> and <a href="https://github.com/DaveGold/mcp-metadata-demo/blob/main/src/tools/render-table.ts"><code>render_table</code></a>, each a self-describing tool whose schema teaches the agent how to configure the view, no wrapper logic required.</p>
<h2>Level 6: Secure Write App (frontier)</h2>
<p>The server doesn&#39;t just read, it writes. Carefully. Two patterns: <strong>agent-initiated bounded writes</strong> for low-risk mutations (feedback, scores) through standard tools, and <strong>user-initiated secure writes</strong> for business-critical data through validated MCP App interactions. I call this the <strong>WriteIntent pattern</strong>: agent opens the door, user walks through it, server checks every step.</p>
<p>Almost nobody is here yet. Most builders are still nervous about giving MCP servers write access at all, and until Level 6 patterns exist, they should be.</p>
<h2>The progression</h2>
<p><strong>Expose data (1) → organize tools (2) → understand the domain (3) → learn from data and feedback (4) → present through interactive apps (5) → act through secure writes (6).</strong></p>
<p>Most of the public MCP ecosystem is stuck between 1 and 2. MCP isn&#39;t dead. Most MCP servers are empty. A different transport doesn&#39;t fix that; filling the tool interface with real domain knowledge does.</p>
<p>If you&#39;re building an MCP server today, the most useful question isn&#39;t <em>&quot;which framework should I pick?&quot;</em> It&#39;s <em>&quot;what level is mine, and what does Level N+1 look like?&quot;</em></p>
<hr>
<p>Go deeper: read the full practitioner report, <a href="https://davidgolverdingen.nl/en/the-missing-layer"><em>The Missing Layer</em></a>, or explore the working code in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> server.</p>
]]></content:encoded>
    <category>MCP</category>
    <category>Architecture</category>
    <category>AI</category>
    <media:content url="https://davidgolverdingen.nl/images/og/six-levels-of-mcp-servers.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>Your Data Is Fine. Your AI Doesn&apos;t Know What It Means.</title>
    <link>https://davidgolverdingen.nl/en/insights/your-data-is-fine</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/your-data-is-fine</guid>
    <pubDate>Fri, 27 Mar 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Enterprise AI pilots fail 80% of the time. Surveys blame data quality. After eleven production MCP servers, the real problem was almost never the data.]]></description>
    <content:encoded><![CDATA[<p>Enterprise AI investment tripled to $37 billion in 2025. The results don&#39;t match the spend. MIT found that only about 5% of enterprise AI pilots achieve rapid revenue acceleration. S&amp;P Global reports that 42% of companies abandoned most of their AI initiatives, up from 17% the year before. RAND puts the overall AI project failure rate at over 80%.</p>
<p>The industry blames data quality. Every survey names it the number one obstacle. But MIT identifies something deeper: not a data problem, but an adaptation failure. Generic AI tools work for individuals but stall in enterprise use because they don&#39;t learn from or adapt to specific workflows.</p>
<p><strong>Clean data is not the same as understood data.</strong></p>
<h2>The technician that wasn&#39;t there</h2>
<p>Consider a service management ERP. A maintenance record has an &quot;assigned technician&quot; field. The AI reads it, finds it empty, and tells the user no technician was assigned.</p>
<p>In reality, that field is never populated. The actual technician can only be found through time-booking records filtered by a specific work-order type. The data is perfectly clean. The AI simply doesn&#39;t have the interpretation layer to know that this field is a dead end.</p>
<p>This is a data interpretation problem, not a data quality problem.</p>
<h2>The market solves the wrong thing</h2>
<p>The response follows a consistent pattern: solve the transport problem. RAG embeds documents in vector stores, useful for policy questions but useless for live operational queries. Text-to-SQL translates natural language to syntax, but doesn&#39;t know that a &quot;type&quot; field needs a specific numeric code or that certain schema fields are dead ends. Vendor-built AI has platform knowledge but mostly serves standard workflows, not free natural language access across domain-specific data models.</p>
<p>In all cases, knowledge lives in infrastructure layers (vector stores, prompt templates, governance dashboards) rather than in the tool interface where the LLM actually reasons at call time.</p>
<h2>Skills: cookbooks at the door</h2>
<p>Anthropic&#39;s skills guide diagnoses the problem correctly: users connect an MCP server but don&#39;t know what to do next. It prescribes a client-side &quot;knowledge layer&quot; of markdown recipes. The diagnosis is right. The prescription treats the symptom.</p>
<p>Skills exist because MCP servers are Level 1. If the server already taught the agent what the data means, when to use which tool, and how to chain calls, there would be nothing left for the skill to teach. The kitchen has no recipes, so they hand out cookbooks at the door.</p>
<p>And client-side knowledge reintroduces the problems it claims to solve: it goes stale when the server updates, fragments across clients and versions, and only helps the one client that loaded it. A skill uploaded to Claude doesn&#39;t help the same server when connected to Cursor, Copilot, or a custom agent.</p>
<h2>Why nobody builds the missing layer</h2>
<p>Four reasons.</p>
<p><strong>Developer mindset.</strong> &quot;I make the API available, the LLM is smart enough to figure out the rest.&quot; For GitHub or Slack, this works. The model knows what a pull request is. For a mid-market ERP with industry-specific type codes? None of that is in any model&#39;s training data.</p>
<p><strong>No complex domain.</strong> Most MCP servers wrap well-understood APIs. A GitHub server doesn&#39;t need to explain what a commit is. An ERP server does need to explain what a service ticket (type 10) means versus a maintenance order (type 13) versus a service planning (type 48).</p>
<p><strong>No feedback loop.</strong> Most builders ship and move on. They don&#39;t have power users hitting limits daily, revealing which metadata is missing and which interpretations lead to wrong answers.</p>
<p><strong>New technology.</strong> MCP is still maturing. The idea that a tool description could contain multi-paragraph domain knowledge simply hasn&#39;t occurred to most builders yet.</p>
<p>The fix isn&#39;t a different transport or a client-side knowledge layer. It&#39;s filling existing servers with domain intelligence, directly in the tool descriptions and schemas where the agent actually reads it.</p>
<p>I mapped out <a href="https://davidgolverdingen.nl/en/insights/six-levels-of-mcp-servers">six maturity levels</a> to make sense of this gap.</p>
<hr>
<p>Go deeper: read the full practitioner report, <a href="https://davidgolverdingen.nl/en/the-missing-layer"><em>The Missing Layer</em></a>, or explore the working code in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> server.</p>
]]></content:encoded>
    <category>AI</category>
    <category>Enterprise</category>
    <media:content url="https://davidgolverdingen.nl/images/og/your-data-is-fine.en.jpg" medium="image" type="image/jpeg" />
  </item>
  <item>
    <title>Production MCP: A Practitioner&apos;s Guide</title>
    <link>https://davidgolverdingen.nl/en/insights/production-mcp-practitioners-guide</link>
    <guid isPermaLink="true">https://davidgolverdingen.nl/en/insights/production-mcp-practitioners-guide</guid>
    <pubDate>Thu, 26 Mar 2026 09:00:00 GMT</pubDate>
    <description><![CDATA[Eleven production MCP servers at one mid-market firm. The complete framework: from understanding data through identity-bound deployment, end to end.]]></description>
    <content:encoded><![CDATA[<p>Most MCP writing in 2026 falls into two buckets: marketing for a vendor&#39;s product, or tutorials for a hackathon demo. This is neither.</p>
<p>After shipping eleven production MCP servers at one mid-sized engineering firm over three months, a coherent framework emerged. The thesis: for mid-market companies (five to twenty key data sources, accessible domain experts, no Fortune-500-scale complexity), MCP plus your existing identity infrastructure is the entire AI platform. No RAG. No agent frameworks. No AI-platform vendors.</p>
<p>This guide walks the framework end to end. Each section links to a detailed post on the topic, and to the practitioner report for the full method.</p>
<h2>Why most enterprise AI fails</h2>
<p>MIT puts the enterprise AI failure rate above 80%. The industry blames data quality. That diagnosis is mostly wrong.</p>
<p>In the field, the data is usually fine. What&#39;s broken is <em>meaning</em>: the AI doesn&#39;t know what your data means, how the systems relate, which source answers which question. The technician field on the work order is empty, but the real technician name lives in the time-booking records of a parallel system, and nobody told the AI that.</p>
<p>Most &quot;AI data problems&quot; are documentation problems wearing a different name. Fix the documentation in the one place the model reliably reads on every call (the tool description) and the data starts working.</p>
<p>Full argument with concrete examples: <a href="https://davidgolverdingen.nl/en/insights/your-data-is-fine"><em>Your Data Is Fine. Your AI Doesn&#39;t Know What It Means.</em></a>.</p>
<h2>The six-level maturity ladder</h2>
<p>Once you accept that meaning lives in tool descriptions, you can grade MCP servers by how seriously they treat that layer. After more than 90 tools across eleven APIs, a ladder emerged:</p>
<ul>
<li><strong>Level 1: API Mapper (~70%).</strong> One tool per endpoint. One-sentence descriptions. The model invents and fails.</li>
<li><strong>Level 2: Functional (~20%).</strong> Tools grouped sensibly, longer descriptions, no domain knowledge. The ceiling most commercial implementations aim for.</li>
<li><strong>Level 3: Metadata-Rich (~8%).</strong> Knowledge graphs living <em>next to</em> the tool. The agent rarely reads side-channels; I built two of these layers and removed both.</li>
<li><strong>Level 4: Self-Teaching (&lt;2%).</strong> Domain knowledge lives <em>inside</em> the tool description, discovered by AI from real data, validated by experts. Production-ready.</li>
<li><strong>Level 5: Interactive App (emerging).</strong> The server returns rendered UI.</li>
<li><strong>Level 6: Secure Write App (frontier).</strong> The server writes back, gated by IdP, structured around explicit user intent.</li>
</ul>
<p>Most public servers sit between 1 and 2. MCP isn&#39;t dead; most servers are empty.</p>
<p>Full breakdown: <a href="https://davidgolverdingen.nl/en/insights/six-levels-of-mcp-servers"><em>The Six Levels of MCP Servers</em></a>.</p>
<h2>Tool descriptions are the work</h2>
<p>97.1% of MCP tool descriptions, across 856 tools in 103 servers (<a href="https://arxiv.org/abs/2602.14878"><em>MCP Tool Descriptions Are Smelly!</em>, arXiv:2602.14878</a>), contain at least one critical smell: unstated limitations, missing usage guidelines, opaque parameters. That&#39;s not fringe. That&#39;s the baseline.</p>
<p>A tool description is not a sentence. It&#39;s an operational manual, structured into blocks, each one added because the agent failed without it. After 52 production tools, the pattern converged to eight: RETURNS, WHEN TO USE, WHEN NOT TO USE, QUERY STRATEGY, INTERPRETATION, EXAMPLES, CROSS-REFERENCES, FAILURE MODES.</p>
<p>Same data behind a Level 1 and a Level 4 tool. Different products entirely in production behaviour.</p>
<p>The eight-block pattern with examples: <a href="https://davidgolverdingen.nl/en/insights/97-percent-mcp-tool-descriptions-broken"><em>How to Write MCP Tool Descriptions: The 8-Block Pattern from 11 Production Servers</em></a>.</p>
<h2>Introspective Context Engineering for MCP</h2>
<p>The hardest part of writing good tool descriptions is that the domain knowledge they require lives in your team&#39;s heads, not in any document. Asking a domain expert to dictate 500 words of operational guidance per tool produces dry, incomplete text. Asking them to <em>review</em> AI-discovered patterns produces precision in a fraction of the time.</p>
<p>That insight became a five-phase pattern I call <strong>Introspective Context Engineering for MCP</strong> (ICE):</p>
<ol>
<li><strong>Examine</strong>: point an AI at real data. Ask it to discover patterns, flag confusing fields, generate hypotheses.</li>
<li><strong>Flag</strong>: the AI marks each pattern with a confidence level (certain, probable, uncertain).</li>
<li><strong>Validate</strong>: the domain expert reviews. Confirms, corrects, or rejects.</li>
<li><strong>Encode</strong>: validated patterns are written into the tool description and schema.</li>
<li><strong>Iterate</strong>: production usage exposes new gaps; the cycle repeats.</li>
</ol>
<p>This inverts the traditional metadata-curation pipeline. Instead of asking humans to write down everything (which doesn&#39;t scale), the AI asks questions and the human approves answers (which does). The output is structured domain knowledge in the only place the agent reliably reads it.</p>
<p>ICE is what makes Level 4 reachable in days rather than quarters. It&#39;s also why the pattern works for mid-market companies and stalls at Fortune 500 scale: it needs a domain expert who can sit with you for an afternoon, not a 40-person governance committee.</p>
<p>The full five-phase method with the feedback architecture: <a href="https://davidgolverdingen.nl/en/the-missing-layer">practitioner report</a>.</p>
<h2>The feedback loop</h2>
<p>A good MCP server isn&#39;t built once. It evolves. Three patterns show up in production telemetry that no design session anticipates:</p>
<ul>
<li>The agent calls the same tool three times with tightening filters → the description didn&#39;t say which filter to try first.</li>
<li>The agent invents a parameter that doesn&#39;t exist → the schema left ambiguity about what&#39;s available.</li>
<li>The agent uses a tool for a query that belongs to a different tool → both WHEN TO USE blocks need sharpening.</li>
</ul>
<p>Each one is invisible without instrumentation. The pattern that works: every tool accepts a <code>queryIntent</code> string (one sentence from the agent describing what it&#39;s trying to find) and logs it alongside parameters. The logs reveal what the agent <em>thought</em> it was doing, which exposes the metadata gap exactly.</p>
<p>Fixes are small. Minutes per fix, not weeks per design cycle.</p>
<p>The full pattern with the queryIntent design: <a href="https://davidgolverdingen.nl/en/insights/mcp-server-smarter-every-week"><em>Your MCP Server Should Get Smarter Every Week</em></a>.</p>
<h2>What the MCP spec gets wrong</h2>
<p>After more than 90 tools in production, six gaps in the current MCP spec became hard to ignore. The headline ones:</p>
<ul>
<li><strong>Resources</strong> are the spec&#39;s answer to reference documentation. No tested client surfaces them reliably. I built two Resource layers and removed both. Everything has to live in the tool description.</li>
<li><strong>Enums</strong> are loose. Different clients render them differently; some don&#39;t show allowed values to the model at all. The agent invents values and the tool rejects them.</li>
<li><strong>No standard for tool-level RBAC</strong>. Every team rolls its own auth pattern; few survive an enterprise audit.</li>
</ul>
<p>Not reasons to abandon MCP. Reasons to write servers around the parts of the spec that work and push for changes where they don&#39;t.</p>
<p>Full list: <a href="https://davidgolverdingen.nl/en/insights/six-things-mcp-spec-should-fix"><em>Six Things the MCP Spec Should Fix</em></a>.</p>
<h2>MCP plus identity is the platform</h2>
<p>This is the reframe most &quot;do I need an AI platform?&quot; conversations miss. The platform isn&#39;t a product you buy. It&#39;s two pieces you already have, composed differently:</p>
<ol>
<li>A frontier model that speaks MCP (Claude, GPT, Gemini, protocol&#39;s the same).</li>
<li>The corporate identity layer the business already pays for (Entra, Okta, Google Cloud Identity).</li>
</ol>
<p>Wire MCP servers through enterprise identity and the AI platform is built. Tools authenticate against the existing IdP, scope access by role, log every call to the audit trail every other corporate system uses. A field engineer sees only their own work orders. A controller sees aggregated financials, not raw payroll. Every action is attributable to a named identity.</p>
<p>That&#39;s the entire enterprise AI security model. No prompt-firewall vendor. No AI governance platform. No model gateway. Just access control the company already runs, enforced at the MCP tool boundary. Anthropic&#39;s 2026 roadmap leads with enterprise authentication. The pattern works.</p>
<p>Complete argument with the use/skip table: <a href="https://davidgolverdingen.nl/en/insights/mcp-is-the-ai-platform"><em>MCP Is the AI Platform</em></a>.</p>
<h2>Without an enterprise budget</h2>
<p>Enterprise AI gets sold as something only large companies can afford. The path that justifies that price tag (custom chat UI, prompt platform, RAG pipeline, agent framework, model gateway, observability stack) commits a smaller company to a build budget, a platform team, an aging pinned model, and an unpredictable per-token bill, all at once.</p>
<p>The alternative is one structural decision: rent the surface, own the domain. Subscribe to the vendor&#39;s chat client at €19/seat/month. Connect your existing IdP. Put your engineering hours into MCP servers for the systems no vendor will ever connect for you. No frontend to maintain, no platform team to staff, no orchestration platform to buy. The model upgrades on the vendor&#39;s clock, for free.</p>
<p>The full architecture with the rent/own breakdown and the framework-absorption argument: <a href="https://davidgolverdingen.nl/en/insights/enterprise-ai-without-enterprise-budget"><em>Enterprise AI Without an Enterprise Budget</em></a>.</p>
<h2>Where to start</h2>
<p>The recipe that worked, applied nine times:</p>
<ol>
<li><strong>Pick one domain expert who has too much to do</strong>. The accountant who keeps getting margin questions. The fleet manager who keeps getting asked where the vans are. The energy specialist who keeps pulling weather data manually. The person whose week would get visibly better if a specific question got an instant answer.</li>
<li><strong>Pick one specific question that person gets asked every week</strong>. Not a category. One actual question, with a known correct answer, that takes them more than five minutes manually.</li>
<li><strong>Build one MCP tool that answers it</strong>. Wrap whatever access the data lives behind (REST, GraphQL, SQL, file). Write the description as if explaining the data to a sharp new hire who has never seen this domain.</li>
<li><strong>Wire it through your existing IdP</strong> so only that expert (and people they explicitly authorise) can call it.</li>
<li><strong>Hand it to the expert. Watch what they try</strong>. Log every call. They will try things you didn&#39;t anticipate.</li>
<li><strong>Update the tool description to fix what you saw</strong>. Add WHEN NOT TO USE blocks. Add examples. Tighten the QUERY STRATEGY. Ship the fix the same day if you can.</li>
<li><strong>Pick the next question</strong>. Build the next tool. Add it to the same server if it&#39;s the same domain, a new server if it&#39;s a new domain.</li>
</ol>
<p>Three months of that, applied across domains, produces a portfolio of working MCP servers and a team that uses them unprompted. Not a project plan. A practice.</p>
<p>The skip list, equally important:</p>
<ul>
<li>Don&#39;t build a framework first.</li>
<li>Don&#39;t build a registry, a router, or an &quot;MCP platform&quot; first.</li>
<li>Don&#39;t try to &quot;do AI strategy&quot; before you&#39;ve shipped one useful tool.</li>
<li>Don&#39;t ask permission. Domain experts will thank you; committees will slow you down.</li>
</ul>
<h2>The full method</h2>
<p>This guide is the practitioner&#39;s overview. The complete framework with the Introspective Context Engineering for MCP method, the six-level maturity model, the WriteIntent pattern for secure writes, and the analysis of where the MCP ecosystem is heading is the <a href="https://davidgolverdingen.nl/en/the-missing-layer">practitioner report</a>.</p>
<p>If you ship one tool that one colleague uses unprompted within a week, you&#39;ve already crossed the line that 80% of enterprise AI projects never cross. The rest is repeating that loop until your business has one MCP-shaped layer instead of many integration projects.</p>
<p>The model is the agent. The IdP is the security boundary. MCP is the platform. Build for the platform, not around it.</p>
<hr>
<p>Go deeper: read the full practitioner report, <a href="https://davidgolverdingen.nl/en/the-missing-layer"><em>The Missing Layer</em></a>, or explore the working code in the <a href="https://github.com/DaveGold/mcp-metadata-demo">mcp-metadata-demo</a> server.</p>
]]></content:encoded>
    <category>MCP</category>
    <category>AI</category>
    <category>Architecture</category>
    <category>Production</category>
    <media:content url="https://davidgolverdingen.nl/images/og/production-mcp-practitioners-guide.en.jpg" medium="image" type="image/jpeg" />
  </item>
  </channel>
</rss>
