<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tomas Grasl</title>
    <description>The latest articles on DEV Community by Tomas Grasl (@freema).</description>
    <link>https://dev.to/freema</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3961521%2F1f52971d-ed17-4ad8-840e-bb48c43c9860.jpeg</url>
      <title>DEV Community: Tomas Grasl</title>
      <link>https://dev.to/freema</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/freema"/>
    <language>en</language>
    <item>
      <title>"Developers Will Lose Their Jobs": How You Were All Wrong</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Sun, 02 Aug 2026 17:56:50 +0000</pubDate>
      <link>https://dev.to/freema/developers-will-lose-their-jobs-how-you-were-all-wrong-1h5h</link>
      <guid>https://dev.to/freema/developers-will-lose-their-jobs-how-you-were-all-wrong-1h5h</guid>
      <description>&lt;p&gt;I don't write the code of our systems anymore. I write agents that write the code.&lt;/p&gt;

&lt;p&gt;And that is exactly why the job panic looks so silly from where I sit.&lt;/p&gt;

&lt;p&gt;I run a backend team at a Czech media house. Paywall, subscriptions, OAuth, CRM, mailing. Real systems with real money going through them. Most of my day now goes into rules, permissions, test gates and review steps for agents, not into functions. The work didn't disappear. It moved one layer up, and that layer is harder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every new name is a patch on the previous one
&lt;/h2&gt;

&lt;p&gt;Watch the vocabulary of the last four years. Prompt engineering. Context engineering. Harness engineering. Loop engineering. Now graph engineering.&lt;/p&gt;

&lt;p&gt;The community treats these like levels of enlightenment. They're not. They're workarounds. Each one exists because the previous one hit a wall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2022, Prompt.&lt;/strong&gt; You polish one sentence. "You are an experienced traffic lawyer, what do I do if they clocked me at 150 in a village, asking for a friend." It was a prestigious, well paid job title. Then agents started doing 50 steps in a row and one beautiful sentence stopped mattering.&lt;br&gt;
Weakness: it doesn't scale past a single turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2025, Context.&lt;/strong&gt; &lt;code&gt;agents.md&lt;/code&gt;, &lt;code&gt;.clinerules&lt;/code&gt;, repo conventions dumped into the window before the model touches anything. Stop guessing, here are the rules of this project.&lt;br&gt;
Weakness: the model knows what to do, but has nothing stopping it from doing something else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2026, Harness.&lt;/strong&gt; Sandbox, tests, permissions, logs. The model is just an engine, the harness is the rig around it. There's a number going around that the same model jumps from 52% to 66% on coding tasks purely from a better harness, without anyone touching the weights. I can't verify that number, but the direction matches what I see: most of my quality gains in the last year came from the rig, not the model.&lt;br&gt;
Weakness: you're still the one handing out every task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then, Loop.&lt;/strong&gt; "I don't write prompts anymore, I have loops that assign work to the model." You start it and go get coffee. It watches PRs, fixes CI, picks up feedback.&lt;br&gt;
Weakness: nondeterminism. Run it twice, get two different worlds. Fine for a side project, not fine for a paywall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now, Graph.&lt;/strong&gt; Instead of a loop that improvises, you draw the thing. Code, then ALWAYS review, then merge. No guessing about order. Google reportedly rebuilt their agent dev kit from an agent runner into a graph engine for this reason.&lt;br&gt;
Weakness: unknown so far. There will be one. Probably that the graph itself becomes a codebase nobody wants to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skill is spotting the ceiling, not learning the name
&lt;/h2&gt;

&lt;p&gt;Here's what I think people get wrong. They chase the vocabulary. New term drops, half the timeline rewrites their setup that weekend, usually because someone with a big following said so.&lt;/p&gt;

&lt;p&gt;The actual skill is different. It's looking at a layer and knowing quickly where its ceiling is, and which lever to pull so the output gets good. Not "which layer is currently correct", but "this one will break on step 30, so I need a gate there".&lt;/p&gt;

&lt;p&gt;You don't learn that from a thread. You learn it because you've watched it break a few dozen times. Which is a very old skill wearing a new hat.&lt;/p&gt;

&lt;h2&gt;
  
  
  What my day actually looks like
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I set the rules, the permissions, the test gates, the review steps.&lt;/li&gt;
&lt;li&gt;The agent works.&lt;/li&gt;
&lt;li&gt;I read diffs and say yes or no.&lt;/li&gt;
&lt;li&gt;When it gets stuck, I finish it by hand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Less code written. More decisions made. And the decisions are the part that was always hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern nobody says out loud
&lt;/h2&gt;

&lt;p&gt;Look at the direction of that chain again. Prompt is a request. Graph is architecture.&lt;/p&gt;

&lt;p&gt;Every single layer added since 2022 exists to take freedom away from the model and give control back to a human. Context takes away guessing. Harness takes away access. Loop takes away idle time, and then graph takes away the loop's improvisation.&lt;/p&gt;

&lt;p&gt;The better the models get, the more control we take back. Not less.&lt;/p&gt;

&lt;p&gt;That's the opposite of the story being sold. "AI writes everything, developers become obsolete" doesn't survive five minutes of contact with a production system. Somebody has to decide where the review step goes, where the rollback goes, which permissions the agent never gets, and whether a PR that looks fine is actually going to break checkout on Friday evening.&lt;/p&gt;

&lt;p&gt;The people most sure that developers are finished are usually the people who have never run an agent against a real repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what's next
&lt;/h2&gt;

&lt;p&gt;I don't know.&lt;/p&gt;

&lt;p&gt;Maybe the abstraction gets good enough that we end up back at prompting. You say what you want, and underneath it a graph of loops does something nobody fully follows anymore. The circle closes.&lt;/p&gt;

&lt;p&gt;Even then, someone designs that graph. Someone maintains it. Someone gets paged when it merges garbage at 3am.&lt;/p&gt;

&lt;p&gt;Guess who.&lt;/p&gt;




&lt;p&gt;Not interested in which layer you're on. Where did you hit its ceiling? That's the more useful conversation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>Can we play a game on pixelden? Made my pixel art game portal playable inside Claude via MCP</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Thu, 30 Jul 2026 20:01:58 +0000</pubDate>
      <link>https://dev.to/freema/can-we-play-a-game-on-pixelden-made-my-pixel-art-game-portal-playable-inside-claude-via-mcp-h4m</link>
      <guid>https://dev.to/freema/can-we-play-a-game-on-pixelden-made-my-pixel-art-game-portal-playable-inside-claude-via-mcp-h4m</guid>
      <description>&lt;p&gt;I hacked the new MCP UI extension 😎&lt;/p&gt;

&lt;p&gt;By which I mean I read the spec and used it exactly as documented, for a dumb little game. 😅&lt;/p&gt;

&lt;p&gt;MCP Apps is the official MCP UI extension. Short version: your server declares an HTML resource under ui://, links it to a tool via _meta.ui.resourceUri, and the host renders that HTML in a sandboxed iframe right in the conversation. The iframe talks back over JSON-RPC, so it can call tools. Not a rendered screenshot, a live UI.&lt;/p&gt;

&lt;p&gt;Everyone is shipping dashboards and forms with it. I put a game loop in there.&lt;/p&gt;

&lt;p&gt;The tool starts a run, the iframe is a real one button arcade game. When you crash, the widget sends the seed and the ticks you jumped at back to the server, the server replays them through the engine and computes the score. The model never touches the number.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.pixelden.io/play-in-claude" rel="noopener noreferrer"&gt;https://www.pixelden.io/play-in-claude&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>llm</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Claude Code plans, Cursor Composer executes, Claude reviews. I wired the three together.</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Thu, 30 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/freema/claude-code-plans-cursor-composer-executes-claude-reviews-i-wired-the-three-together-ffc</link>
      <guid>https://dev.to/freema/claude-code-plans-cursor-composer-executes-claude-reviews-i-wired-the-three-together-ffc</guid>
      <description>&lt;p&gt;I was burning expensive Claude tokens on tasks a cheaper model could handle just fine. Renames, placeholder swaps, grinding through a well-scoped task list. That's not where Claude's magic is, and it's not where I want to spend the budget. So I stopped picking one tool and wired two together.&lt;/p&gt;

&lt;p&gt;After a few weeks of juggling terminals, the setup settled into three moves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Claude Code makes the plan.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cursor Composer 2 executes it.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude reviews the diff.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tokens saved, quality kept. I packaged it as a Claude Code plugin so I don't reinvent the hand-off every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just stay in Cursor?
&lt;/h2&gt;

&lt;p&gt;Because Claude Code has a real edge in planning, and it's not only the model. It's the whole rig: long-context sessions, subagents, the TUI, the way tools compose. The plans it produces are consistently good, and I didn't want to give that up to save money on execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just stay in Claude?
&lt;/h2&gt;

&lt;p&gt;Because Composer 2 is genuinely good at day-to-day coding and one-shot work. File edits, applying diffs, churning through a well-scoped task list in force mode. Cursor and Composer are heavily tuned for exactly this CLI use case. Fast, and safe enough for mechanical changes.&lt;/p&gt;

&lt;p&gt;Picking one meant either overpaying for execution or under-planning. Wiring them together meant each tool does the part it's best at.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually use it for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bigger refactors&lt;/strong&gt;: think out loud with Claude, then let Composer grind through the mechanical edits in the background. Multilingual site integration, where Composer swaps every hard-coded string for a placeholder, is a perfect fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Rename X to Y across the codebase and update the tests"&lt;/strong&gt;: classic Composer territory. No reason to spend Claude cycles on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E2E testing via a browser MCP&lt;/strong&gt;: one slash command spins it up. Composer 2 is more than enough here; I don't need a frontier model to click through a page and assert on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;p&gt;Letting Cursor do the &lt;em&gt;planning&lt;/em&gt;. It's fine. But Claude's planning loop is better, and that's the one place I don't compromise. The lesson: don't split work by tool arbitrarily; split it by what each tool is actually best at. Planning to Claude, mechanical execution to Composer, review back to Claude.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the plugin
&lt;/h2&gt;

&lt;p&gt;Rather than re-do the hand-off by hand each time, I shipped it as a Claude Code plugin: eight &lt;code&gt;/cursor:*&lt;/code&gt; commands (delegate, status, result, cancel, resume, browser-verify, sessions, setup) plus a &lt;code&gt;cursor-runner&lt;/code&gt; subagent for automatic delegation. Zero runtime dependencies, MIT.&lt;/p&gt;

&lt;p&gt;Heavily inspired by &lt;code&gt;openai/codex-plugin-cc&lt;/code&gt;, which pulls the same trick with GPT/Codex, credit where it's due.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The interesting shift here isn't "use a cheaper model." It's treating your coding agents like a small team with different strengths instead of looking for one model to rule them all. A planner, an executor, a reviewer. They just happen to be three different CLIs, and the plugin is the glue that makes the hand-off free.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/freema/cursor-plugin-cc" rel="noopener noreferrer"&gt;github.com/freema/cursor-plugin-cc&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Are you running a multi-tool coding setup, or still trying to make one model do everything? Curious how others split the work.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>cursor</category>
    </item>
    <item>
      <title>I've built a handful of MCP servers. Here's what separates a good one from a demo.</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Tue, 28 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/freema/ive-built-a-handful-of-mcp-servers-heres-what-separates-a-good-one-from-a-demo-4i4f</link>
      <guid>https://dev.to/freema/ive-built-a-handful-of-mcp-servers-heres-what-separates-a-good-one-from-a-demo-4i4f</guid>
      <description>&lt;p&gt;This year I've built more MCP servers than I meant to, across three pretty different shapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/freema/vellum" rel="noopener noreferrer"&gt;vellum&lt;/a&gt;&lt;/strong&gt;: a self-hosted server over a folder of markdown, so my agent has a memory that's just files I own. (Open source, MIT.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrifyr&lt;/strong&gt;: a marketing-data server that puts GA4, Search Console, AdSense and Tag Manager behind one connection.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;read-only gateway&lt;/strong&gt; over a company's internal user-data stack, federating half a dozen internal services behind one audited MCP surface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Personal, product, enterprise. Totally different data, totally different stakes. And yet the things that made each one &lt;em&gt;good&lt;/em&gt;, versus a flashy demo that falls over the moment a real agent uses it in anger, were the same every time. Here are the seven that keep recurring.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Every tool you expose is a tax on the agent's context
&lt;/h2&gt;

&lt;p&gt;This is the one nobody warns you about. Every tool definition (name, description, JSON schema) gets loaded into the model's context on &lt;em&gt;every single request&lt;/em&gt;, whether or not it's ever called. Twenty tools of boilerplate and the agent is reading pages of plumbing before it touches your data.&lt;/p&gt;

&lt;p&gt;So the design pressure runs the opposite way from a normal API: fewer, broader tools beat many narrow ones. vellum has 15 core tools and I fought to keep it there. If a tool doesn't earn its slot in the context window on most requests, it shouldn't be a tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tools are for acting. Resources are for reading.
&lt;/h2&gt;

&lt;p&gt;The corollary to #1. Most servers expose everything (including "read this thing") as a tool. But MCP has a better primitive for reading: &lt;strong&gt;resources&lt;/strong&gt;. In vellum, every note is a resource at a stable URI (&lt;code&gt;vellum://note/projects/x.md&lt;/code&gt;). The agent attaches a document by reference instead of spending a tool round-trip to fetch it, and it can &lt;em&gt;subscribe&lt;/em&gt; to that URI and get told the moment the note changes.&lt;/p&gt;

&lt;p&gt;Reading through a resource costs no tool definition. Reading through a tool costs one on every request. Use the right primitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fail closed on auth, fail open on limits
&lt;/h2&gt;

&lt;p&gt;A token with no grants should see &lt;strong&gt;zero tools&lt;/strong&gt;: not an error when it tries to call one, but genuinely nothing in &lt;code&gt;tools/list&lt;/code&gt;. Authorization fails &lt;em&gt;closed&lt;/em&gt;. Rate limiters, on the other hand, should fail &lt;em&gt;open&lt;/em&gt;: if the limiter itself breaks, you degrade to serving the request, not to locking everyone out. Getting these two backwards is how you either leak capability or take yourself down.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Annotate what's safe and what isn't
&lt;/h2&gt;

&lt;p&gt;MCP lets you tag tools with hints: &lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;destructiveHint&lt;/code&gt;, &lt;code&gt;idempotentHint&lt;/code&gt;. Use them. They're how a well-behaved client knows it can call your search tool freely but should think twice before your delete tool. On the marketing server this is the line between "the agent can answer any question about your analytics" and "the agent can reconfigure your analytics", and the annotations are what keep those two worlds visibly separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Writes must be conflict-safe
&lt;/h2&gt;

&lt;p&gt;The moment more than one thing can write (an agent &lt;em&gt;and&lt;/em&gt; a human, or two agents) last-write-wins silently eats data. vellum returns a content hash on every read, and a write fails on hash mismatch instead of clobbering. It's optimistic concurrency, the same trick databases have used forever, and it turns "the agent overwrote my edit" from a support ticket into a retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. For anything touching real data: read-only, and audit everything
&lt;/h2&gt;

&lt;p&gt;The enterprise gateway taught me this one hard. When an MCP server sits in front of a company's actual user data, the default posture is read-only, every tool call is logged with who called it and what came back, and any PII in a response comes back as a hash, not the raw value. An agent is a new kind of caller; treat it like one you'll have to explain to a security review later, because you will.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Tell the agent how to use you
&lt;/h2&gt;

&lt;p&gt;MCP has a spot in the handshake for &lt;strong&gt;server instructions&lt;/strong&gt;: a short document the agent reads before it does anything. This is where you say "notes are vault-relative paths," "prefer patch over rewrite," "empty query with tags is a pure tag filter." It's the difference between an agent that fumbles your API and one that uses it the way you intended on the first try. Most servers leave it blank. Don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  One gotcha that cost me an afternoon
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;structuredContent&lt;/code&gt; roughly &lt;em&gt;doubles&lt;/em&gt; your response size: the payload is serialized into the response twice. If you've got size-based metrics or limits, they'll suddenly read high for no obvious reason. Recalibrate them, or you'll spend an afternoon chasing a leak that isn't there.&lt;/p&gt;




&lt;p&gt;None of this is about clever features. Across a note vault, a marketing stack and an enterprise gateway, the servers that behaved well were the ones that respected the agent's context, used the right primitive for each job, and were honest about what was safe to call. The demo is easy. The seven things above are what make it survive contact with a real agent.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're building on MCP: which of these bit you first? For me it was #1. I over-exposed tools on my first server and watched the agent drown in its own toolbox.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Mozilla adopted my Firefox DevTools MCP, but I didn't build it to browse the web</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Thu, 23 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/freema/mozilla-adopted-my-firefox-devtools-mcp-but-i-didnt-build-it-to-browse-the-web-5142</link>
      <guid>https://dev.to/freema/mozilla-adopted-my-firefox-devtools-mcp-but-i-didnt-build-it-to-browse-the-web-5142</guid>
      <description>&lt;p&gt;A little while ago I wrote an MCP server that lets an AI agent inspect and control Firefox through the Remote Debugging Protocol. It got adopted into the Mozilla org. That's a nice flex, and I'll take it, but it buries the more useful story, which is &lt;em&gt;why&lt;/em&gt; I built it. It wasn't to give my agent a browser. It was to make my agent write its own tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem: e2e tests, not browsing
&lt;/h2&gt;

&lt;p&gt;Multi-agent orchestration I've mostly got a handle on. My actual pain, the thing that eats my evenings, is end-to-end testing. Nobody wants to write it, it rots the moment the UI shifts, and it's exactly the kind of mechanical-but-fiddly work you'd love to hand off.&lt;/p&gt;

&lt;p&gt;So the flow I actually care about looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Claude has a "test skill" that tells it to &lt;strong&gt;click through the app&lt;/strong&gt; using the Firefox MCP: open the page, fill the form, follow the flow, read the DOM back.&lt;/li&gt;
&lt;li&gt;Once it's walked the flow and knows what the app does, it &lt;strong&gt;writes a Cypress or Playwright test&lt;/strong&gt; for it.&lt;/li&gt;
&lt;li&gt;From then on it just &lt;strong&gt;runs the test&lt;/strong&gt;. The MCP was scaffolding to author the test, not a permanent dependency.&lt;/li&gt;
&lt;li&gt;If it gets stuck, it pings me and I finish that one by hand.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a reasonably tight dev loop: the AI builds a thing, tests the thing, and knows when it broke the thing. The browser control is a means to that end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Firefox, when Chrome DevTools MCP exists
&lt;/h2&gt;

&lt;p&gt;Fair question. Chrome's DevTools MCP does most of the same moves. Three reasons I reached for Firefox anyway, and none of them is "Firefox is better at everything."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's stricter about standards.&lt;/strong&gt; Firefox holds the line on W3C (CSS Grid inspection, layout edge cases) where Chrome quietly papers over your mistakes. That sounds like a downside until you realize it's exactly what you want from a &lt;em&gt;test&lt;/em&gt; browser. If a layout is going to break, I'd rather it break in the strict engine during testing than in front of a user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leaner to run on a server.&lt;/strong&gt; I don't run this on my laptop. It runs on a server, next to a Claude CLI or Codex session and whatever else is going. When you're stacking agents on one box, a lighter headless browser is the difference between fitting and swapping. And for my setup, headless Firefox is the lighter one. (Measure it on your own box; it depends on what you load.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's open source.&lt;/strong&gt; For something I want to run on my own infrastructure and tinker with, that speaks for itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this says about MCP servers in general
&lt;/h2&gt;

&lt;p&gt;The thing I keep taking away from this one: &lt;strong&gt;the best MCP servers are scaffolding for a workflow, not a permanent appendage.&lt;/strong&gt; The Firefox MCP is at its most valuable in the minutes it takes the agent to learn a flow and codify it into a real test. After that, a plain &lt;code&gt;playwright test&lt;/code&gt; command does the job: no agent, no MCP, no tokens.&lt;/p&gt;

&lt;p&gt;Designing for that ("help the agent build something that outlives the session" instead of "keep the agent calling me forever") is a different mindset than most tool servers take. And it happens to produce the tool I actually reach for every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If your pain is the same as mine, writing and maintaining e2e tests, this flow is worth stealing even if you never touch Firefox: let the agent explore the app through a browser MCP, then have it emit a real Playwright/Cypress test you can run headless forever after.&lt;/p&gt;

&lt;p&gt;The repo lives in the Mozilla org now: &lt;strong&gt;&lt;a href="https://github.com/mozilla/firefox-devtools-mcp" rel="noopener noreferrer"&gt;mozilla/firefox-devtools-mcp&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's your current e2e testing loop: hand-written, recorded, or generated? I'm always looking to tighten mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>firefox</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I gave my AI a memory that's just a folder of markdown I own</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Tue, 21 Jul 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/freema/i-gave-my-ai-a-memory-thats-just-a-folder-of-markdown-i-own-dgn</link>
      <guid>https://dev.to/freema/i-gave-my-ai-a-memory-thats-just-a-folder-of-markdown-i-own-dgn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🚀 &lt;strong&gt;It's live on Product Hunt today.&lt;/strong&gt; If this resonates, an upvote genuinely helps: &lt;a href="https://www.producthunt.com/products/vellum-9?launch=vellum-10" rel="noopener noreferrer"&gt;Vellum on Product Hunt&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every time I gave my AI assistant "memory," I ended up in the same place: the memory lived in &lt;strong&gt;someone else's database&lt;/strong&gt;, as &lt;strong&gt;embeddings I couldn't read&lt;/strong&gt;, in a &lt;strong&gt;cloud I didn't control&lt;/strong&gt;. Cancel the subscription and the memory goes with it. That felt backwards.&lt;/p&gt;

&lt;p&gt;So I built the opposite. It's called &lt;strong&gt;Vellum&lt;/strong&gt;, it's open source (MIT), and the whole idea fits in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Point your agent at a folder of markdown. It reads and writes notes over MCP. The folder stays flat files &lt;em&gt;you&lt;/em&gt; own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What it actually is
&lt;/h2&gt;

&lt;p&gt;Vellum is a small, self-hosted &lt;strong&gt;MCP server&lt;/strong&gt; over a plain directory of &lt;code&gt;.md&lt;/code&gt; files. Any MCP client (Claude, Cursor, ChatGPT desktop, the MCP Inspector) connects to it and can list, read, write, patch, search, tag, and backlink your notes. Every note is also exposed as a first-class &lt;strong&gt;MCP resource&lt;/strong&gt; (&lt;code&gt;vellum://note/{path}&lt;/code&gt;), so your agent can attach a note as context straight from its resource picker, no tool call, no copy-paste.&lt;/p&gt;

&lt;p&gt;And because the "database" is just a folder, you keep editing the same vault in Obsidian, vim, or &lt;code&gt;git&lt;/code&gt;. Nothing is trapped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# try it in one command&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/vault:/vault"&lt;/span&gt; ghcr.io/freema/vellum

&lt;span class="c"&gt;# point Claude at it&lt;/span&gt;
claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http vellum https://your-host/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The deliberate choices
&lt;/h2&gt;

&lt;p&gt;The whole project is an exercise in picking the smallest thing that just works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No database. Ever (v1).&lt;/strong&gt; Tags, backlinks, and task states live in an in-RAM index rebuilt in ~50 ms at startup. There's nothing to migrate, corrupt, or back up separately. Your data &lt;em&gt;is&lt;/em&gt; the files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No embeddings.&lt;/strong&gt; Search is a ranked in-memory scan: the metadata index narrows by tag/dir, content is matched from a cache, results are ranked (title &amp;gt; tag &amp;gt; path &amp;gt; body). It's case-, diacritics-, and typo-insensitive ("preklapy" finds "překlepy") and runs in ~0.4 ms warm over a 2,000-note vault. No vector store, no warm-up, no index to rebuild. The &lt;code&gt;Searcher&lt;/code&gt; interface exists so I can slot in bleve &lt;em&gt;if it ever hurts&lt;/em&gt;. It hasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It never calls an LLM itself.&lt;/strong&gt; Vellum prepares context; your agent decides. The optional "curator" tools (folder suggestions by tag overlap, orphan/untagged/stale-inbox lists) are all deterministic, no API keys, no calls out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's its own OAuth 2.1 issuer.&lt;/strong&gt; One client secret, PKCE, opaque in-memory tokens that die with the process. No external identity provider, nothing phones home. Writes are conflict-safe: every read returns a content hash, and write tools reject a stale hash instead of clobbering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small on purpose.&lt;/strong&gt; One static Go binary with an embedded React UI, a 24 MB distroless image, ~5 MB idle RAM, a read-only container that only ever writes into the mounted vault. &lt;code&gt;docker compose up -d&lt;/code&gt; and you're done, or one-click on Railway / Render / Fly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just…?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Obsidian + plugins&lt;/strong&gt;: that's your editor, not a server. Vellum serves the &lt;em&gt;same folder&lt;/em&gt; to your agent while you keep editing anywhere. No sync, no plugin sandbox, no Electron on a server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion / Anytype&lt;/strong&gt;: those own your data in a proprietary store. Vellum's store is &lt;code&gt;ls&lt;/code&gt; and &lt;code&gt;grep&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavier MCP note servers&lt;/strong&gt;: a vector DB, API keys, and a big tool surface that eats agent context. Vellum is one 24 MB container and 15 focused tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The web workspace
&lt;/h2&gt;

&lt;p&gt;It also ships an embedded three-pane workspace (tree / list / editor) in the same binary. It stays in sync with the vault on its own: MCP writes appear without a reload, a split view puts raw markdown beside the rendered note, and tasks (&lt;code&gt;type: task&lt;/code&gt; + status) filter by state. It's the "human" window into the same folder your agent is working in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's going
&lt;/h2&gt;

&lt;p&gt;v1 is intentionally boring and solid. I'd love feedback on two things especially: the &lt;strong&gt;MCP tool surface&lt;/strong&gt; (15 tools today: what's missing, what's noise?) and the &lt;strong&gt;no-embeddings&lt;/strong&gt; bet (where does a pure scan stop being enough for you?).&lt;/p&gt;

&lt;p&gt;If "your agent's memory should be files you own" resonates, the repo is here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/freema/vellum" rel="noopener noreferrer"&gt;github.com/freema/vellum&lt;/a&gt;&lt;/strong&gt; (MIT)&lt;br&gt;
🚀 And it's live on &lt;strong&gt;Product Hunt&lt;/strong&gt; today: &lt;strong&gt;&lt;a href="https://www.producthunt.com/products/vellum-9?launch=vellum-10" rel="noopener noreferrer"&gt;producthunt.com/products/vellum-9&lt;/a&gt;&lt;/strong&gt; — an upvote is genuinely appreciated.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where does a pure scan stop being enough for you: at what vault size would you reach for embeddings? That's the bet I'm least sure about.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>go</category>
      <category>programming</category>
    </item>
    <item>
      <title>I finally figured out what Claude Artifacts are actually for</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Fri, 17 Jul 2026 15:27:28 +0000</pubDate>
      <link>https://dev.to/freema/i-finally-figured-out-what-claude-artifacts-are-actually-for-581o</link>
      <guid>https://dev.to/freema/i-finally-figured-out-what-claude-artifacts-are-actually-for-581o</guid>
      <description>&lt;p&gt;I've been using Claude for a long time and mostly ignored Artifacts. Fine for a quick React demo. Not something I reached for.&lt;/p&gt;

&lt;p&gt;Then I needed to send an analysis to a few people at work, and it clicked. Or I'm just using it in a way nobody intended. Hard to say.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual case
&lt;/h2&gt;

&lt;p&gt;I own the paywall backend at a Czech media house. The subscription offer on our news site is embedded as an iframe, and iframes are a bad neighbourhood: context isolation means the iframe has no access to the parent page's session, so user identity kept breaking and we kept patching it over postMessage. Every iframe is its own page view, so GA4 data was skewed and we had to build server-side tracking and session stitching to make the numbers mean anything. And ad blockers, CSP, and timeouts mean sometimes the thing just doesn't render, so we maintain a fallback UI in parallel.&lt;/p&gt;

&lt;p&gt;I wanted to propose we drop the iframe and ship a JS embed library instead, distributed through our internal npm registry. That's an architecture change, so it needs a document: what we fixed, why the iframe is still structurally wrong, what the alternative costs, what the numbers say.&lt;/p&gt;

&lt;p&gt;The numbers part came out of the same agent session, by the way. GA4 said roughly 0.14% of paywalled page views hit an error, about half of them iframe-blocked-by-browser. That's every 700th reader. Small number, real money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring problem
&lt;/h2&gt;

&lt;p&gt;You get a good answer out of the model. Now what?&lt;/p&gt;

&lt;p&gt;You paste it into a doc. Reformat it, because chat markdown does not survive the trip. Fix the tables. Decide whether it goes in Confluence or an email. Send it. Then someone asks a follow-up, you go back to the model, get a better answer, and now there are two versions of the truth and one of them is in someone's inbox.&lt;/p&gt;

&lt;p&gt;I've done the email version of exactly this document before. Outlook ate the markdown. I ended up hand-rolling plain text with unicode bullets and uppercase section headers like it was 1998.&lt;/p&gt;

&lt;p&gt;Half of that work is transport, not thinking. And the transport step is where the document goes stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did instead
&lt;/h2&gt;

&lt;p&gt;I had the same agent that produced the analysis publish it as an Artifact, and sent people the link. We have Claude on a team plan, so they just open it.&lt;/p&gt;

&lt;p&gt;No docx export. No Confluence page. No slide deck. One URL.&lt;/p&gt;

&lt;p&gt;The thing worth noticing: there is no handoff. The agent that has the analysis in context is the agent that renders it. No step where I copy something out of one tool into another, which means no step where I introduce a formatting mistake or quietly drop a caveat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually surprised me
&lt;/h2&gt;

&lt;p&gt;The Artifact is not a snapshot. As the thing moved (proposal → decision → plan with tickets), I had the agent rewrite it. Same URL the whole time.&lt;/p&gt;

&lt;p&gt;So it stopped being "the thing I sent on Tuesday" and became the current state of the project. Nobody hunts for the newest version, because there is only one version. Nobody asks "is this still accurate?", because the answer is yes or the doc gets rewritten.&lt;/p&gt;

&lt;p&gt;That's a surprising amount of what a wiki does, without the part where you have to remember the wiki exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this does not work
&lt;/h2&gt;

&lt;p&gt;It is not documentation. No history, no diff, no "who changed this and why". If you need to know what the architecture decision was six months ago and what the reasoning was, you need a real doc in a real system.&lt;/p&gt;

&lt;p&gt;This is a tool for one phase: the window between "I have a finding" and "we've decided what to do". A few days, sometimes a couple of weeks. During that window the document changes constantly and permanence is a liability. After the decision lands, the durable version goes in the wiki. The Artifact was scaffolding.&lt;/p&gt;

&lt;p&gt;The other limit is obvious but worth saying: everyone who needs to read it has to be on the same team plan. Not a public publishing tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule I'd give anyone trying this
&lt;/h2&gt;

&lt;p&gt;Be deliberate about what data goes in.&lt;/p&gt;

&lt;p&gt;The agent will happily render whatever it has in context. When the analysis touched production analytics, "whatever it has in context" included things that should not exist in a link pasted into a chat channel. In that same GA4 dig I found we were sending subscriber UUIDs to Google as an event category, which is its own separate problem, but it's a good illustration: the raw data had identifiers in it, and the shareable document must not.&lt;/p&gt;

&lt;p&gt;Aggregates only. No personal data. Decide that before you ask for the artifact, not after you've shared it.&lt;/p&gt;

&lt;p&gt;I ended up writing my conventions down as a skill file so the agent applies them the same way every time instead of me remembering to say it. A rule you have to remember is a rule you will forget. My skills live here if you want the format: &lt;a href="https://github.com/freema/ai-skills" rel="noopener noreferrer"&gt;https://github.com/freema/ai-skills&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So is this what Artifacts are for?
&lt;/h2&gt;

&lt;p&gt;Genuinely not sure. The feature is marketed as a place to preview code and content, and I'm using it as an internal document surface with a stable URL. Might just be a shape that happens to fit.&lt;/p&gt;

&lt;p&gt;But "one link that stays current" is quietly the best part of it, and I haven't seen anyone talk about it that way.&lt;/p&gt;

&lt;p&gt;Are you doing something similar on your team?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>analytics</category>
    </item>
    <item>
      <title>My note server has no search engine. Search still answers in 0.4 ms.</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Thu, 16 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/freema/my-note-server-has-no-search-engine-search-still-answers-in-04-ms-3abd</link>
      <guid>https://dev.to/freema/my-note-server-has-no-search-engine-search-still-answers-in-04-ms-3abd</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/freema/vellum" rel="noopener noreferrer"&gt;vellum MCP&lt;/a&gt; is a self-hosted server over a folder of markdown, your agent's memory as plain &lt;code&gt;.md&lt;/code&gt; files you own. Search is the feature everyone expects to be "solved" by a library: bolt on &lt;a href="https://github.com/blevesearch/bleve" rel="noopener noreferrer"&gt;bleve&lt;/a&gt; or a vector index and move on.&lt;/p&gt;

&lt;p&gt;I never did. Search is a ranked scan over the notes, held in RAM. No engine, no index to build. Bragging about &lt;em&gt;not&lt;/em&gt; adding a dependency is a strange flex, so let me show you what that plain scan actually costs, and how one optimization pass later it runs about &lt;strong&gt;5× faster&lt;/strong&gt; than my first honest cut of it. This is the case for the boring option.&lt;/p&gt;

&lt;h2&gt;
  
  
  The heavy default
&lt;/h2&gt;

&lt;p&gt;A bundled search engine sounds like the responsible choice, and for a large enough corpus it is. But look at what it actually drags in for a personal note vault:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;strong&gt;index to build and keep in sync&lt;/strong&gt; with the files, a second source of truth that can drift, or corrupt&lt;/li&gt;
&lt;li&gt;a warm-up cost, and memory for the index structures&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;dependency&lt;/strong&gt; with its own release cadence, CVEs and quirks&lt;/li&gt;
&lt;li&gt;for vector search specifically: an embedding step, which means calling out to a model, which means API keys and network and cost, for &lt;em&gt;search&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a vault of a few thousand notes, that's a lot of moving parts to answer "which notes mention OAuth."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did instead
&lt;/h2&gt;

&lt;p&gt;The whole content is already in memory. Vellum holds the vault in an in-RAM index it rebuilds from disk in about &lt;strong&gt;50 ms at startup&lt;/strong&gt;. So search is just a &lt;strong&gt;ranked scan&lt;/strong&gt; over that: walk the notes, score each against the query, sort, return the top slice. No separate index, nothing to warm up, nothing to corrupt.&lt;/p&gt;

&lt;p&gt;Here's what that costs on a 2,000-note vault (Apple M4 Pro, warm):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query type&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;exact term&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.38 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;accent-folded, multi-term&lt;/td&gt;
&lt;td&gt;0.74 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worst-case typo match&lt;/td&gt;
&lt;td&gt;0.39 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sub-millisecond, every time. Nothing to invalidate except when a file actually changes. The cache keys off each file's &lt;strong&gt;mod-time and size&lt;/strong&gt;, so an edit through the MCP layer, the web editor, or your text editor all invalidate the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The matcher earns its keep
&lt;/h2&gt;

&lt;p&gt;The reason I don't miss the library is that writing the matcher myself made it &lt;em&gt;more&lt;/em&gt; forgiving, not less. Three things it does that matter for real notes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Folds diacritics both ways.&lt;/strong&gt; &lt;code&gt;ukol&lt;/code&gt; finds &lt;code&gt;úkol&lt;/code&gt;; &lt;code&gt;poznamka&lt;/code&gt; finds &lt;code&gt;poznámka&lt;/code&gt;; and the reverse. When half your notes are in a language with accents and you're typing on muscle memory, this is the difference between "found it" and "search is broken."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tolerates typos, scaled to word length.&lt;/strong&gt; For a 4 to 7 character word it allows one edit; for 8+ it allows two. So &lt;code&gt;preklapy&lt;/code&gt; finds &lt;code&gt;překlepy&lt;/code&gt;. Short words stay strict (you don't want &lt;code&gt;cat&lt;/code&gt; matching &lt;code&gt;car&lt;/code&gt;), long words get slack where a fat-fingered character is likely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ranks by where the match lands.&lt;/strong&gt; A hit in the &lt;strong&gt;title&lt;/strong&gt; beats a hit in a &lt;strong&gt;tag&lt;/strong&gt;, beats one in the &lt;strong&gt;path&lt;/strong&gt;, beats one buried in the &lt;strong&gt;body&lt;/strong&gt;. And a match at the &lt;em&gt;start of a word&lt;/em&gt; beats one mid-word. This is the part a generic engine gets generically right and a hand-written one gets &lt;em&gt;right for your data&lt;/em&gt;. The ranking encodes how you actually think about your own notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The escape hatch
&lt;/h2&gt;

&lt;p&gt;I'm not claiming a linear scan is the answer for everyone. It's the answer for &lt;em&gt;this size of problem&lt;/em&gt;. So the search sits behind a &lt;code&gt;Searcher&lt;/code&gt; interface. The day a vault gets big enough that a scan hurts, I can drop a real engine in behind the same seam without touching the callers.&lt;/p&gt;

&lt;p&gt;That day hasn't come. Two thousand notes in, a full ranked scan is still sub-millisecond, and honestly it would take an order of magnitude more before the constant factors start to matter. When you do cross that line (tens or hundreds of thousands of documents, or you genuinely need semantic similarity rather than lexical matching), reach for the index. Below it, the index is the heavy option pretending to be the safe one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;"Use a library for search" is good advice that stops being good at small scale, and small scale is where a lot of self-hosted tools live. Before you add the dependency, the index, the warm-up and the embedding pipeline, check whether a scan over what's already in RAM answers in under a millisecond. For a personal knowledge base, it very likely does.&lt;/p&gt;

&lt;p&gt;Sometimes the library is the heavy option.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/freema/vellum" rel="noopener noreferrer"&gt;github.com/freema/vellum&lt;/a&gt;. MIT, one 24 MB container.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where's your line for "just scan it" vs. "build an index"? I'd put it somewhere north of 50k documents for lexical search. Curious where others draw it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>mcp</category>
      <category>opensource</category>
      <category>performance</category>
    </item>
    <item>
      <title>Resources for reading, tools for acting: the MCP design choice most servers get wrong</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Wed, 15 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/freema/resources-for-reading-tools-for-acting-the-mcp-design-choice-most-servers-get-wrong-5d1f</link>
      <guid>https://dev.to/freema/resources-for-reading-tools-for-acting-the-mcp-design-choice-most-servers-get-wrong-5d1f</guid>
      <description>&lt;p&gt;Every tool you expose over MCP ships its full definition (name, description, JSON schema) into the model's context on &lt;strong&gt;every single request&lt;/strong&gt;, whether that tool ever gets called or not. Expose twenty tools and the agent wades through pages of boilerplate before it has read one of your notes.&lt;/p&gt;

&lt;p&gt;That cost shaped the best decision I made building an MCP server in the open this week, and it isn't a feature. It's a split: &lt;strong&gt;tools are for acting, resources are for reading.&lt;/strong&gt; Most MCP servers I've looked at ignore that line and expose everything as a tool. I think that's a mistake. Here's why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden cost of a tool
&lt;/h2&gt;

&lt;p&gt;Sit with what that per-request tax actually buys you: nothing, most of the time. A note server that exposes "read this note," "list those notes," "get this document," "fetch that attachment" as four separate tools pays for all four on every turn, including the turns that only write. It's quietly burning your context window on plumbing.&lt;/p&gt;

&lt;p&gt;MCP already gives you a better primitive for exactly this. It's called a &lt;strong&gt;resource&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What vellum MCP does instead
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/freema/vellum" rel="noopener noreferrer"&gt;vellum MCP&lt;/a&gt; is a self-hosted server over a plain folder of markdown files. Your notes stay as &lt;code&gt;.md&lt;/code&gt; on disk, no database, no embeddings. When I designed the surface, I split it clean down the middle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools are for acting.&lt;/strong&gt; Writing a note, moving it, patching a section, flipping a task from backlog to done. Fifteen of them, and I keep the number small on purpose. Every one costs context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources are for reading.&lt;/strong&gt; Every note is exposed as an MCP resource at a stable URI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vellum://note/projects/launch.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changes two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent (or you, in a client's resource picker) can attach a document as context &lt;strong&gt;by reference&lt;/strong&gt;. No tool call to fetch it. You point at the note by name and it's there.&lt;/li&gt;
&lt;li&gt;A client can &lt;strong&gt;subscribe&lt;/strong&gt; to that URI and get a notification the moment the note changes, whether the edit came through an MCP tool or from someone typing in the web editor. The list only re-announces when a note is created, deleted or renamed, not on every keystroke.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is an agent that references your documents by name instead of spending a tool round-trip to go get them. Less context on &lt;em&gt;how&lt;/em&gt;, more room for &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is the whole philosophy, in miniature
&lt;/h2&gt;

&lt;p&gt;I've been calling vellum MCP "deliberately light," and this is what I mean by it. The wins don't come from piling on features. They come from using the right primitive for each job and refusing to add weight that doesn't earn its place.&lt;/p&gt;

&lt;p&gt;The same instinct runs through the rest of it. Search is a ranked scan in RAM, not a bundled search engine you have to warm up and can corrupt. Auth is the server being its own OAuth issuer, no external identity provider. The whole thing is one 24 MB container that idles at about 5 MB of RAM. &lt;code&gt;docker compose up -d&lt;/code&gt; and it's running.&lt;/p&gt;

&lt;p&gt;None of that is clever. It's mostly saying no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;vellum MCP is MIT-licensed and self-hostable in one command. If you're building with MCP, the tools-vs-resources split is the one idea I'd take even if you never touch my code.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/freema/vellum" rel="noopener noreferrer"&gt;github.com/freema/vellum&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's your rule for when something becomes a tool vs. a resource on your own server? I'm curious how others draw that line.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Most code review catches bugs. I shipped one that argues with the design.</title>
      <dc:creator>Tomas Grasl</dc:creator>
      <pubDate>Tue, 14 Jul 2026 18:00:00 +0000</pubDate>
      <link>https://dev.to/freema/most-code-review-catches-bugs-i-shipped-one-that-argues-with-the-design-1h0h</link>
      <guid>https://dev.to/freema/most-code-review-catches-bugs-i-shipped-one-that-argues-with-the-design-1h0h</guid>
      <description>&lt;p&gt;The cheapest bug to catch is the one a reviewer circles on line 40. The expensive one never shows up as a bad line it ships as the &lt;em&gt;wrong approach&lt;/em&gt;, clean and green. Your retry loop is correct and also retrying in the wrong layer. Your cache has no bug and invalidates on the wrong key. A normal review human or AI slides right past those, because it's anchored on &lt;em&gt;"is this line right,"&lt;/em&gt; not &lt;em&gt;"is this the right thing to build."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This week I shipped &lt;strong&gt;v0.4.0&lt;/strong&gt; of &lt;a href="https://github.com/freema/cursor-plugin-cc" rel="noopener noreferrer"&gt;cursor-plugin-cc&lt;/a&gt;, and the headline is a review command whose entire job is that second question.&lt;/p&gt;

&lt;p&gt;Quick context if you haven't seen the plugin: it's a Claude Code plugin that keeps one loop running &lt;strong&gt;Claude plans, Cursor's Composer writes the code, Claude reviews the diff&lt;/strong&gt; without leaving the Claude Code TUI. Two tools, each doing the half it's best at. v0.4.0 sharpens the &lt;em&gt;review&lt;/em&gt; half.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;/cursor:adversarial-review&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A read-only review that questions the chosen implementation and design instead of only hunting implementation defects. You point it at a diff (working tree by default, &lt;code&gt;--base &amp;lt;ref&amp;gt;&lt;/code&gt; for a branch, &lt;code&gt;--scope&lt;/code&gt;, &lt;code&gt;--model&lt;/code&gt; same target selection as the normal &lt;code&gt;/cursor:review&lt;/code&gt;) and it pressure-tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;assumptions&lt;/strong&gt; the change depends on,&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;tradeoffs&lt;/strong&gt; you made, spoken or not,&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;failure modes&lt;/strong&gt; under real load, concurrency, and edge cases,&lt;/li&gt;
&lt;li&gt;and the one that matters most — &lt;strong&gt;whether a simpler or safer approach would have done the job.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's &lt;em&gt;steerable&lt;/em&gt;: any trailing text is a focus. &lt;code&gt;"is the retry/backoff design sound under load?"&lt;/code&gt; aims the whole challenge at exactly the thing you're nervous about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/cursor:adversarial-review
/cursor:adversarial-review &lt;span class="nt"&gt;--base&lt;/span&gt; main
/cursor:adversarial-review &lt;span class="s2"&gt;"question the retry/backoff design under load"&lt;/span&gt;
/cursor:adversarial-review &lt;span class="nt"&gt;--background&lt;/span&gt; &lt;span class="nt"&gt;--model&lt;/span&gt; opus look &lt;span class="k"&gt;for &lt;/span&gt;race conditions and question the approach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood it's &lt;code&gt;/cursor:review --adversarial&lt;/code&gt;, so it shows up as a normal job in &lt;code&gt;/cursor:status&lt;/code&gt;, &lt;code&gt;/cursor:result&lt;/code&gt; and &lt;code&gt;/cursor:cancel&lt;/code&gt;. What used to be a &lt;code&gt;--adversarial&lt;/code&gt; flag most people never found is now a first-class, discoverable command — and it's &lt;strong&gt;user-invoked only&lt;/strong&gt; (&lt;code&gt;disable-model-invocation: true&lt;/code&gt;), because a design challenge is something you reach for on purpose, not something an agent should fire off on its own.&lt;/p&gt;

&lt;p&gt;Why split it out instead of just telling &lt;code&gt;/cursor:review&lt;/code&gt; to "try harder"? Because a stricter bug pass and a design challenge are different jobs, and conflating them gets you neither. Naming the second one makes you actually run it — right before you ship a change you're &lt;em&gt;not fully sure about&lt;/em&gt;, which is exactly when nobody bothers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I'm quietly proud of: it can't touch your code
&lt;/h2&gt;

&lt;p&gt;Both review commands are read-only &lt;strong&gt;by construction&lt;/strong&gt;, not by good intentions. The run happens, then a post-flight check fails the job if the working tree changed. So a review can never quietly turn into an edit — even though the same plugin runs Composer elsewhere with auto-approve on. You can hand a fast model your diff and know the worst it can do is &lt;em&gt;talk&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That guarantee is the whole reason I trust an adversarial pass. A reviewer that might also "helpfully" refactor while it argues is a reviewer you have to babysit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estimate first, then ask
&lt;/h2&gt;

&lt;p&gt;Small ergonomics fix that removes a daily papercut. If you don't pass &lt;code&gt;--wait&lt;/code&gt; or &lt;code&gt;--background&lt;/code&gt;, the command now sizes the diff first — &lt;code&gt;git status&lt;/code&gt;, &lt;code&gt;git diff --shortstat&lt;/code&gt;, counting untracked files — and asks you &lt;em&gt;once&lt;/em&gt; whether to wait or run it in the background, recommending background for anything beyond a tiny 1–2 file change. A multi-file review takes a while; you shouldn't have to guess up front whether it's going to block your terminal for the next two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real lesson: I moved the know-how into a skill
&lt;/h2&gt;

&lt;p&gt;The most &lt;em&gt;interesting&lt;/em&gt; change isn't a command. For a while, the guidance for how to turn a task into a tight Cursor prompt ground it in the target repo's conventions, the five required sections (Goal / Repo context / Acceptance criteria / Files to touch / How to verify) plus a guardrails block, chunk anything over ~5 steps or ~10 files or 2 architectural layers, pick the smallest model that fits, resume vs. fresh — lived &lt;em&gt;inline&lt;/em&gt; inside the &lt;code&gt;cursor-runner&lt;/code&gt; subagent's prompt.&lt;/p&gt;

&lt;p&gt;In v0.4.0 that moved into a &lt;code&gt;composer-prompting&lt;/code&gt; &lt;strong&gt;skill&lt;/strong&gt;. The subagent now references it with a single &lt;code&gt;skills:&lt;/code&gt; line and shrank down to its operational spine: &lt;em&gt;ground → invoke &lt;code&gt;/cursor:delegate&lt;/code&gt; → return Cursor's output verbatim.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The lesson generalises past this plugin, and it's the same thread running through everything I've been writing about MCP and agents: &lt;strong&gt;an agent's prompt is context you pay for on every single run.&lt;/strong&gt; Reusable know-how doesn't belong restated inside each agent that needs it — it belongs in one referenced skill that loads when it's actually relevant. The agent gets leaner, the knowledge gets a single home instead of three drifting copies, and the thing you maintain shrinks. Same instinct as splitting tools from resources on an MCP server: put weight where it earns its place, and nowhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring guarantees
&lt;/h2&gt;

&lt;p&gt;Still &lt;strong&gt;zero runtime dependencies&lt;/strong&gt; — plain ESM, Node stdlib, no build step, no &lt;code&gt;dist/&lt;/code&gt;. Still &lt;strong&gt;MIT&lt;/strong&gt;. CI runs the tests and lint across Node 18.18 / 20 / 22 on Linux and macOS on every PR. And it's openly adapted from &lt;a href="https://github.com/openai/codex-plugin-cc" rel="noopener noreferrer"&gt;&lt;code&gt;openai/codex-plugin-cc&lt;/code&gt;&lt;/a&gt; — the Codex/GPT original that pulls the same trick on OpenAI's side — ported to the Cursor CLI. Credit where it's due.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add freema/cursor-plugin-cc
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;cursor@tomas-cursor
/reload-plugins
/cursor:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/freema/cursor-plugin-cc" rel="noopener noreferrer"&gt;github.com/freema/cursor-plugin-cc&lt;/a&gt;&lt;/strong&gt; — v0.4.0, MIT.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When did a code review last change your **approach&lt;/em&gt;* instead of your lines? That's the review I actually want — and the one almost nobody runs. Curious whether you've found a way to make it a habit.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>programming</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
