I originally set out to build ez-edge-cms—my own lightweight, edge-native CMS running on Cloudflare Workers and D1, designed around one specific goal: publishing articles straight from my terminal or coding agent over MCP without touching a browser dashboard or waiting for static site builds.

Then I took a closer look at EmDash and realized I was about to spend weeks reinventing something that already existed.

Everything on my architecture checklist—Cloudflare Workers at the edge, D1 SQLite storage, R2 media handling, structured Portable Text blocks, taxonomies, and a first-class Model Context Protocol (MCP) server—was already built and running in EmDash. So I scrapped ez-edge-cms and migrated ezinner.com to EmDash instead.

Here is how the migration went, why moving off static builds changed how I write, and the wild story of what happened when I hit a Markdown parsing bug and watched EmDash's autonomous agent turn my architectural suggestion into a merged fix in under an hour.

If I am being completely honest, I am not a fan of heavy React admin applications. Left to myself, I would never choose to bundle a sprawling React dashboard just to manage a technical blog.

But my primary goal from day one was headless publishing over MCP. Because EmDash exposes its entire content lifecycle through /_emdash/api/mcp—covering content_create, content_update, revision locking, taxonomies, media uploads, and schema introspection—I barely have to touch the React admin UI at all. My editor and my agent talk straight to the edge API.

Because my previous website was already built in Astro, migrating the actual site was painless. I kept my Astro templates, styling, and strict security setup, and only swapped the data layer. What changed completely is how an article goes from a draft to live in production:

  • Before (Static Astro): Write Markdown locally, commit to Git, manage local node_modules and dependency updates, trigger a CI/CD pipeline, wait for Astro and Shiki to rebuild every page, and deploy static assets. Fixing a single typo meant running a full repository build cycle.
  • Now (EmDash + MCP): Send the Markdown payload directly to the MCP server. EmDash converts it into Portable Text, writes it to D1, and serves it immediately at the edge. No builds, no Git commit noise for content edits, and no dependency churn just to publish a post.

Almost everything worked out of the box—except for one painful wall: converting complex technical Markdown into Portable Text.

My articles rely heavily on nested inline styles—things like inline code inside bold text (**Inline \<script>` handling**), code spans wrapped inside links (`astro-strict-csp``), and multi-column comparison tables.

When I first pushed my existing articles through the MCP endpoint, nested inline marks were mangled and Markdown tables weren't working out of the box. Digging into how Markdown was being converted to Portable Text, the root cause was obvious: the parser was built around regular expressions.

Anyone who has ever tried to parse nested Markdown formatting with regexes knows how that story ends. You write a regex to match **bold**, then another for `code`, then another for [links](url). The moment those tokens nest inside each other—or sit inside a pipe-delimited Markdown table row—regexes fall apart.

To get my own migration unblocked, I first tested a local workaround that threw out the regex passes and used a proper Abstract Syntax Tree (AST) lexer instead—walking block tokens and recursively resolving inline child tokens into Portable Text spans and markDefs.

Once I verified locally that an AST approach cleanly solved both nested inline styles and tables, I decided to contribute it upstream:

  1. I opened Issue #4605 on the EmDash repository detailing the regex failure modes on nested styles and tables, and proposing an AST-based parser instead.
  2. Right after filing the issue, I forked the repo and started wiring up a pull request in PR #4606.

While I was still working on my PR, something surreal happened.

EmDash's autonomous coding agent picked up the architectural proposal from Issue #4605, implemented a complete AST-based solution in PR #4607, handled all the tricky edge cases (nested mark stacks, link definitions, table header rows, and column alignments), and replied asking me to check if it worked!

I looked at my screen and thought: "Wait, wtf just happened?"

I closed my own pull request (PR #4606), deployed their agent's solution from PR #4607, and tested it against my most formatting-heavy posts—including the comparison table in Why Astro's Native security.csp Breaks on Shiki. It worked cleanly on the first try.

What impressed me most about that sequence wasn't just the raw speed—it was the contrast with how agents behave when they don't have architectural direction.

Agents had tried to fix Markdown-to-PortableText bugs before, but it was always an endless fight with regexes. Because the existing code used regular expressions, the agent stayed anchored to the existing implementation (a textbook example of the prompt and context anchoring trap in coding agents)—patching regex edge cases with even more complicated regexes.

Left inside the wrong abstraction, an LLM will burn hours fighting symptoms. But the moment a human engineer steps in, diagnoses the structural limitation, and points to the right engineering primitive—"replace the regexes with an AST"—the agent built, tested, and shipped a complete solution in less than an hour.

Dropping your own side project (ez-edge-cms) always stings for about five minutes, until you realize how much maintenance burden you just deleted from your life.

EmDash already gives me the exact workflow I wanted: an Astro frontend backed by Cloudflare D1 and R2, zero-build publishing straight from MCP, and a codebase that moves fast. Seeing how effectively their team—and their agents—turn a well-described issue and a clear architectural direction into working software sealed the deal. I am sticking with EmDash and will keep contributing back as I push it further in production.