<?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: metataro</title>
    <description>The latest articles on DEV Community by metataro (@metataro).</description>
    <link>https://dev.to/metataro</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%2F4181017%2Fc5a2c2a5-dc6b-4fc5-a789-941233530f6f.jpg</url>
      <title>DEV Community: metataro</title>
      <link>https://dev.to/metataro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/metataro"/>
    <language>en</language>
    <item>
      <title>Let your AI agent look at your server — on the same screen, over the same SSH, without ever handing it a shell</title>
      <dc:creator>metataro</dc:creator>
      <pubDate>Wed, 04 Nov 2026 02:53:51 +0000</pubDate>
      <link>https://dev.to/metataro/let-your-ai-agent-look-at-your-server-on-the-same-screen-over-the-same-ssh-without-ever-handing-1e05</link>
      <guid>https://dev.to/metataro/let-your-ai-agent-look-at-your-server-on-the-same-screen-over-the-same-ssh-without-ever-handing-1e05</guid>
      <description>&lt;p&gt;Maintaining an old VPS with an AI agent goes like this, every time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; I can't see the server. Could you show me the service status?&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; &lt;em&gt;(opens a terminal, runs it, pastes)&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;AI:&lt;/strong&gt; Thanks. Now the config file, please.&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; &lt;em&gt;(opens an SFTP client, downloads it, pastes)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The obvious fix is to give the agent a shell. The obvious problem is that a shell does everything, out of sight, and you find out what it did afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sshboard&lt;/strong&gt; is an open-source desktop app built for the space in between. You and the agent look at the same server, on the same screen, over &lt;strong&gt;one&lt;/strong&gt; SSH connection — and everything the agent does scrolls past in your window as it happens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8entom2ncese6s74yus7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8entom2ncese6s74yus7.png" alt="The shared console: you and the agent on one shell, with every operation listed on the right" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The one idea: there is no &lt;code&gt;run_command&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;sshboard speaks MCP, so Claude Code (or any MCP client) can use it. What it does &lt;strong&gt;not&lt;/strong&gt; have is a tool that takes an arbitrary string and runs it in a shell. Instead the agent gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools for looking&lt;/strong&gt;: list a directory, read a file, &lt;code&gt;stat&lt;/code&gt;, search, disk usage, processes, listening ports, service status, log tails, runtime versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commands you allow-listed&lt;/strong&gt; — a file you write, by id. Empty by default, so nothing runs until you write it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations you listed&lt;/strong&gt; that change state (&lt;code&gt;systemctl reload nginx&lt;/code&gt;, &lt;code&gt;pm2 restart …&lt;/code&gt;) — by id only, with an hourly ceiling, and &lt;strong&gt;each run asks you first&lt;/strong&gt; on screen. If the command starts with &lt;code&gt;sudo&lt;/code&gt; and you set the connection to ask, a password box appears and the password is passed to that one run and never stored&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A shared console&lt;/strong&gt; — a real shell, but only one side types at a time. You can take it back at any moment, and &lt;strong&gt;Stop&lt;/strong&gt; always works&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those calls is shown in a band on your screen, before it runs. If the screen cannot confirm it showed the line, the call is refused.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent writes only where you said it could
&lt;/h2&gt;

&lt;p&gt;Per connection, you list the directories the agent may write to. &lt;strong&gt;The default is empty: not one byte.&lt;/strong&gt; Inside them it can upload files and create directories. Delete, rename, move, chmod, restart services, install packages — those simply do not exist for the agent. You, the person, are not restricted at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmyt72wgahvred90e7ng7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmyt72wgahvred90e7ng7.png" alt="The file view: two panes, with &amp;quot;AI may write in&amp;quot; shown at the top" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things it deliberately keeps from the agent
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hostnames, users, keys.&lt;/strong&gt; The agent sees connection ids like &lt;code&gt;web-01&lt;/code&gt;, never where they point. Credentials stay in your OS keychain and ssh-agent; sshboard has no key store of its own&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A second, invisible connection.&lt;/strong&gt; The app and the agent share the one SSH session you can see&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your local disk.&lt;/strong&gt; There is no tool to download from the server to your machine — the fence on the server would not protect it. The agent reads files; you download them&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Set it up in Claude Code
&lt;/h2&gt;

&lt;p&gt;Install the app from the &lt;a href="https://github.com/meta-taro/sshboard/releases/latest" rel="noopener noreferrer"&gt;releases page&lt;/a&gt; (Windows x64 / ARM64, macOS Apple Silicon — not code-signed yet, so SmartScreen and Gatekeeper will ask once). Start it, then:&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;# macOS&lt;/span&gt;
claude mcp add &lt;span class="nt"&gt;--scope&lt;/span&gt; user sshboard &lt;span class="nt"&gt;--&lt;/span&gt; /Applications/sshboard.app/Contents/MacOS/sshboard &lt;span class="nt"&gt;--mcp-stdio-proxy&lt;/span&gt;

&lt;span class="c"&gt;# Windows&lt;/span&gt;
claude mcp add &lt;span class="nt"&gt;--scope&lt;/span&gt; user sshboard &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"%LOCALAPPDATA%&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;shboard&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;shboard.exe"&lt;/span&gt; &lt;span class="nt"&gt;--mcp-stdio-proxy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relay reads the app's token itself, so it never lands in your shell history or in Claude Code's config. Register connections in the app (or import them from &lt;code&gt;~/.ssh/config&lt;/code&gt;), connect, and ask the agent what is wrong with the mail server.&lt;/p&gt;

&lt;p&gt;No server to point it at yet? The repository has a throwaway Docker SSH server: &lt;code&gt;sh tools/test-server/up.sh&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Not an autonomous agent that fixes servers while you sleep. Not a replacement for your SSH client or your SFTP client — those stay yours. Not a way to move off the old VPS: the point is that the old setup keeps working, with a second pair of eyes beside you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/meta-taro/sshboard" rel="noopener noreferrer"&gt;https://github.com/meta-taro/sshboard&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;li&gt;Site: &lt;a href="https://meta-taro.github.io/sshboard/" rel="noopener noreferrer"&gt;https://meta-taro.github.io/sshboard/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Releases: &lt;a href="https://github.com/meta-taro/sshboard/releases/latest" rel="noopener noreferrer"&gt;https://github.com/meta-taro/sshboard/releases/latest&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>claudecode</category>
      <category>ssh</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Put a real map on your page with plain HTML — no API key, no per-view bill, no tile server</title>
      <dc:creator>metataro</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:36:51 +0000</pubDate>
      <link>https://dev.to/metataro/put-a-real-map-on-your-page-with-plain-html-no-api-key-no-per-view-bill-no-tile-server-53ah</link>
      <guid>https://dev.to/metataro/put-a-real-map-on-your-page-with-plain-html-no-api-key-no-per-view-bill-no-tile-server-53ah</guid>
      <description>&lt;p&gt;Most sites that need a map end up with one of three options. The first is a commercial map API that bills per view. The second is the OpenStreetMap tile server, but its &lt;a href="https://operations.osmfoundation.org/policies/tiles/" rel="noopener noreferrer"&gt;Tile Usage Policy&lt;/a&gt; rules out commercial and heavy-traffic use. The third is a tile server you run yourself, which you then have to keep running.&lt;/p&gt;

&lt;p&gt;There is a fourth option, because &lt;strong&gt;almost nobody needs the whole planet&lt;/strong&gt;. You need the area your site covers, and you can host that part yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/meta-taro/mmj-map" rel="noopener noreferrer"&gt;MMJ&lt;/a&gt; is built on that idea. It is an open-source set of Web Components plus a tool that cuts out map tiles. &lt;strong&gt;We borrow the map data and own how it looks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrnvaltg0p731hq5xz04.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrnvaltg0p731hq5xz04.jpg" alt="A typhoon approaching Japan, drawn over a rain-radar animation" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole &lt;a href="https://meta-taro.github.io/mmj-map/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; is static files on GitHub Pages. Nothing in it needs a backend, including the animation above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cut your area into one file.&lt;/strong&gt; One command takes a bounding box from the &lt;a href="https://protomaps.com/" rel="noopener noreferrer"&gt;Protomaps&lt;/a&gt; daily build of the whole planet and writes it to a single &lt;a href="https://github.com/protomaps/PMTiles" rel="noopener noreferrer"&gt;PMTiles&lt;/a&gt; file. A city comes to 10–60 MB and takes under a minute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serve it as a file.&lt;/strong&gt; All you deploy is that file on static hosting. The browser asks for the parts it needs with HTTP Range requests, so traffic costs only what your host charges to serve a file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write HTML.&lt;/strong&gt; Eight custom elements render the map with &lt;a href="https://maplibre.org/" rel="noopener noreferrer"&gt;MapLibre&lt;/a&gt;: &lt;code&gt;mmj-map&lt;/code&gt;, &lt;code&gt;mmj-marker&lt;/code&gt;, &lt;code&gt;mmj-poi&lt;/code&gt;, &lt;code&gt;mmj-cluster&lt;/code&gt;, &lt;code&gt;mmj-route&lt;/code&gt;, &lt;code&gt;mmj-circle&lt;/code&gt;, &lt;code&gt;mmj-raster&lt;/code&gt; and &lt;code&gt;mmj-fill&lt;/code&gt;. They are plain ES modules, so there is no build step.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @mmj-map/tiles extract osaka &lt;span class="nt"&gt;--bbox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;135.4,34.6,135.6,34.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;mmj-map&lt;/span&gt;
  &lt;span class="na"&gt;tiles=&lt;/span&gt;&lt;span class="s"&gt;"./tiles/osaka.20260924.pmtiles"&lt;/span&gt;
  &lt;span class="na"&gt;style-url=&lt;/span&gt;&lt;span class="s"&gt;"./styles/modern-dark.json"&lt;/span&gt;
  &lt;span class="na"&gt;center=&lt;/span&gt;&lt;span class="s"&gt;"135.5023,34.6937"&lt;/span&gt;
  &lt;span class="na"&gt;zoom=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mmj-marker&lt;/span&gt; &lt;span class="na"&gt;lnglat=&lt;/span&gt;&lt;span class="s"&gt;"135.4959,34.7024"&lt;/span&gt; &lt;span class="na"&gt;popup=&lt;/span&gt;&lt;span class="s"&gt;"Umeda"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mmj-marker&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/mmj-map&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;center&lt;/code&gt; takes longitude first, then latitude, in the same order as GeoJSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we own the look but not the data
&lt;/h2&gt;

&lt;p&gt;OpenStreetMap data is free. Its default style, though, has to serve every use on earth with one design, so it fits no particular site well.&lt;/p&gt;

&lt;p&gt;The line we draw is this: &lt;strong&gt;fixes to the data go to OSM; how the map looks belongs to each site.&lt;/strong&gt; Your brand colour, labels in your readers' language, your own points, a photo at the corner where a route turns: none of that is map data, and none of it belongs upstream. MMJ comes with six hand-written styles. Give it your brand colour and it puts that colour on the motorways and station rings, not across the whole map.&lt;/p&gt;

&lt;p&gt;Your own points stay yours, too. The components read the GeoJSON from your own hosting, and MMJ has no server of its own to send it to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you skip
&lt;/h2&gt;

&lt;p&gt;Starting from zero, several problems stand between you and a working map. You usually find each one only after it has bitten you:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A host answers &lt;code&gt;Range&lt;/code&gt; requests with &lt;code&gt;200&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The map still draws, but every tile downloads the whole file. &lt;code&gt;npx @mmj-map/tiles check-range &amp;lt;url&amp;gt;&lt;/code&gt; tests your host for this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Old daily builds are removed upstream&lt;/td&gt;
&lt;td&gt;The build we pinned was gone nine days later. The extractor pins a build and falls back if it disappears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;name:zh&lt;/code&gt; does not exist&lt;/td&gt;
&lt;td&gt;The real keys are &lt;code&gt;zh-Hant&lt;/code&gt; / &lt;code&gt;zh-Hans&lt;/code&gt;. MMJ switches label language with one &lt;code&gt;lang&lt;/code&gt; attribute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outside your file's area, the map is empty&lt;/td&gt;
&lt;td&gt;That is correct, but it looks exactly like a broken map. MMJ shades the tiles that came back empty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because the map is just a file, it can keep working with the network off. One demo page needs 1.9 MB of tiles, 562 KB of glyphs and 1.15 MB of renderer. That is enough for a venue with no signal. GPS still works without a network; only the map was missing.&lt;/p&gt;

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

&lt;p&gt;MMJ is not a tile server, a rendering engine, a geocoder or a router. It is also not a way around a provider's terms. If your coordinates come from a provider that requires you to show them on its own map, check those terms before you swap the base map.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Cut your town: &lt;code&gt;npx @mmj-map/tiles extract &amp;lt;name&amp;gt; --bbox=&amp;lt;w,s,e,n&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Follow the &lt;a href="https://github.com/meta-taro/mmj-map/blob/develop/docs/install/en.md" rel="noopener noreferrer"&gt;install guide&lt;/a&gt;. It is also in Japanese, Traditional Chinese, Simplified Chinese and Vietnamese&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;© OpenStreetMap contributors&lt;/code&gt; visible on the map. The map data is under the ODbL; the code, styles and components are MIT&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is early but usable. &lt;a href="https://www.npmjs.com/package/@mmj-map/elements" rel="noopener noreferrer"&gt;&lt;code&gt;@mmj-map/elements&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@mmj-map/tiles" rel="noopener noreferrer"&gt;&lt;code&gt;@mmj-map/tiles&lt;/code&gt;&lt;/a&gt; are on npm. There are no hosted tiles: you cut your own, which is the point. The styles are most complete for Japan so far. Bug reports and requests are welcome on &lt;a href="https://github.com/meta-taro/mmj-map/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt;, in English or Japanese.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>webcomponents</category>
      <category>maps</category>
    </item>
    <item>
      <title>Let Claude Code read your production database — even a write disguised as SELECT gets refused</title>
      <dc:creator>metataro</dc:creator>
      <pubDate>Mon, 02 Nov 2026 05:03:14 +0000</pubDate>
      <link>https://dev.to/metataro/let-claude-code-read-your-production-database-and-make-update-fail-at-the-server-g75</link>
      <guid>https://dev.to/metataro/let-claude-code-read-your-production-database-and-make-update-fail-at-the-server-g75</guid>
      <description>&lt;p&gt;Pointing an AI agent at a database is easy. Pointing it at &lt;strong&gt;production&lt;/strong&gt; is where people stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dbboard is an open-source desktop database client with an MCP server that gives Claude Code read-only access to Postgres, MySQL, Turso / libSQL, Cloudflare D1, MongoDB and Firestore — enforced by the database, not by a keyword filter.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The usual guard is a keyword filter: reject anything that starts with &lt;code&gt;UPDATE&lt;/code&gt; or &lt;code&gt;DROP&lt;/code&gt;. It holds until the agent writes a CTE that deletes, a &lt;code&gt;SELECT … FOR UPDATE&lt;/code&gt;, or two statements in one string. And the agent still needs the password, which now sits in its config and its transcript.&lt;/p&gt;

&lt;p&gt;dbboard is built so that this question has a boring answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1b5xcjnwr4a0wectbi4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1b5xcjnwr4a0wectbi4.png" alt="dbboard with a Postgres server open: databases, schemas and tables in the sidebar, and a JOIN with 180 rows in the grid" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The one idea: the database says no, not only a filter
&lt;/h2&gt;

&lt;p&gt;dbboard ships a separate MCP server, &lt;code&gt;dbboard-mcp&lt;/code&gt;, that hands the connections you already have to Claude Code or Claude Desktop. Every read passes two checks.&lt;/p&gt;

&lt;p&gt;First, the statement is &lt;strong&gt;parsed&lt;/strong&gt; — a real SQL parser, not string matching — and anything that is not a single read-only statement is refused. Then it &lt;strong&gt;runs inside the database's own read-only mode&lt;/strong&gt;, or through an adapter that cannot write at all:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;How a read is run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Postgres, Neon, Supabase, CockroachDB, Aurora DSQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BEGIN TRANSACTION READ ONLY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MySQL / MariaDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SET TRANSACTION READ ONLY&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turso / libSQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PRAGMA query_only&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare D1&lt;/td&gt;
&lt;td&gt;no read-only mode exists, so the parser is the enforcement and fails closed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Firestore&lt;/td&gt;
&lt;td&gt;the adapter has no write path to reach: it implements the read endpoints and nothing else&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;td&gt;only six commands (&lt;code&gt;find&lt;/code&gt;, &lt;code&gt;aggregate&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;distinct&lt;/code&gt;, &lt;code&gt;listCollections&lt;/code&gt;, &lt;code&gt;listIndexes&lt;/code&gt;) and only their known options; the pipeline is walked for a &lt;code&gt;$out&lt;/code&gt; or &lt;code&gt;$merge&lt;/code&gt; hidden inside a &lt;code&gt;$facet&lt;/code&gt;, &lt;code&gt;$lookup&lt;/code&gt; or &lt;code&gt;$unionWith&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second check is the one that matters. Here is &lt;code&gt;dbboard-mcp&lt;/code&gt; against a local Postgres with fictional data, called exactly as an agent calls it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run_read_query  DELETE FROM orders WHERE quantity = 1
→ query failed: not a single read-only statement: only read-only SELECT / WITH / EXPLAIN statements are allowed

run_read_query  WITH gone AS (DELETE FROM orders RETURNING id) SELECT count(*) FROM gone
→ query failed: not a single read-only statement: a data-modifying statement (in a CTE or set expression) is not read-only

run_read_query  SELECT nextval('orders_id_seq')
→ query failed: cannot execute nextval() in a read-only transaction

run_read_query  SELECT count(*) FROM orders
→ 180
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first two are caught by the parser. The third is a plain &lt;code&gt;SELECT&lt;/code&gt; that still writes — it advances a sequence — so no parser flags it. &lt;strong&gt;Postgres refuses it&lt;/strong&gt;, because the transaction itself is read-only. The table still has all 180 rows, and the sequence did not move.&lt;/p&gt;

&lt;p&gt;Three more rules sit around that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Passwords never reach the agent.&lt;/strong&gt; They stay in the OS keychain. The agent sees &lt;code&gt;{ id, name, kind }&lt;/code&gt; for each connection, and no error message embeds a URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing is off until a person turns it on, per connection.&lt;/strong&gt; Even then only an allowlist runs (&lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;CREATE TABLE&lt;/code&gt;, …). &lt;code&gt;DROP&lt;/code&gt; of anything, &lt;code&gt;TRUNCATE&lt;/code&gt;, &lt;code&gt;GRANT&lt;/code&gt; / &lt;code&gt;REVOKE&lt;/code&gt; and user or role changes are refused whatever the setting says.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results are bounded,&lt;/strong&gt; so one careless query cannot pour a whole table into the agent's context.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to give Claude Code read-only access to your database
&lt;/h2&gt;

&lt;p&gt;Download &lt;code&gt;dbboard-mcp&lt;/code&gt; for your platform from the &lt;a href="https://github.com/meta-taro/dbboard/releases/latest" rel="noopener noreferrer"&gt;latest release&lt;/a&gt; (one self-contained executable), then:&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;# macOS&lt;/span&gt;
claude mcp add dbboard &lt;span class="nt"&gt;--scope&lt;/span&gt; user &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.local/bin/dbboard-mcp"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows (PowerShell)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;claude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dbboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class="s2"&gt;/dbboard/dbboard-mcp.exe"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent can then list connections and tables, describe a table, search a connection's schema by column name, follow foreign keys, and run read queries. Ask it "which orders in the last week have no shipment row?" and it finds the tables itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  And for the person: one window
&lt;/h2&gt;

&lt;p&gt;The desktop app is the same connections, for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every database you look after in one sidebar&lt;/strong&gt; — Postgres (Neon, Supabase, CockroachDB, Aurora DSQL), MySQL / MariaDB, Turso / libSQL, Cloudflare D1, Firestore and MongoDB. An SSH tunnel reaches the one behind a bastion; a colour and a tag on each connection show which one you are about to run against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave the database name blank&lt;/strong&gt; and one login lists every database it can open, with schemas and tables beneath.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix one value without writing an &lt;code&gt;UPDATE&lt;/code&gt;:&lt;/strong&gt; double-click the cell, type, save. Edits are keyed on the primary key, so a table without one stays read-only instead of guessing.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Not a hosted service: nothing leaves your machine except your own queries to your own databases. Not code-signed either — a certificate is a recurring purchase this project does not make, so every release ships &lt;code&gt;SHA256SUMS.txt&lt;/code&gt; to check instead.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Download from the &lt;a href="https://meta-taro.github.io/dbboard/" rel="noopener noreferrer"&gt;download page&lt;/a&gt; (Windows x64 / ARM64 &lt;code&gt;.exe&lt;/code&gt;, macOS &lt;code&gt;.dmg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage → Add connection&lt;/strong&gt;, pick the database type, fill in the server&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;dbboard-mcp&lt;/code&gt; to your agent as above&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MIT-licensed. Bug reports and requests: &lt;a href="https://github.com/meta-taro/dbboard/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>database</category>
      <category>postgres</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Human-in-the-loop QA: let an AI agent run your test sheet on Android, iPhone, web and desktop — and sign off each case yourself</title>
      <dc:creator>metataro</dc:creator>
      <pubDate>Tue, 13 Oct 2026 11:51:34 +0000</pubDate>
      <link>https://dev.to/metataro/human-in-the-loop-qa-let-an-ai-agent-run-your-test-sheet-on-android-iphone-web-and-desktop-and-2dp0</link>
      <guid>https://dev.to/metataro/human-in-the-loop-qa-let-an-ai-agent-run-your-test-sheet-on-android-iphone-web-and-desktop-and-2dp0</guid>
      <description>&lt;p&gt;Most "AI testing" tools aim for the same promise: the AI runs everything and tells you it passed.&lt;br&gt;
When something ships broken anyway, "the AI said it passed" is not an answer anyone accepts. A person is accountable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/meta-taro/git-qa" rel="noopener noreferrer"&gt;git-qa&lt;/a&gt; is an open-source &lt;strong&gt;human-in-the-loop QA runner&lt;/strong&gt; built around that fact.&lt;br&gt;
An AI agent works through your test sheet; you watch the live screen and sign off each case with one key.&lt;br&gt;
The evidence records who verified what.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reads a test sheet&lt;/strong&gt; — a TSV you can write in any spreadsheet: step, expected result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drives the target&lt;/strong&gt; — Android devices and emulators (adb), web pages (Chrome, Edge, Firefox, Safari),
desktop apps on macOS and Windows, and iPhone / iPad (USB mirroring; tapping through WebDriverAgent)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shows you the live screen&lt;/strong&gt; while it works, with a marker where it found the expected text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lets you place the verdict with one key&lt;/strong&gt; — &lt;code&gt;D&lt;/code&gt; pass, &lt;code&gt;F&lt;/code&gt; fail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps the evidence&lt;/strong&gt; — &lt;code&gt;run.json&lt;/code&gt; plus the screen at the moment of the verdict, per case&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The vocabulary is the point
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;VERIFIED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A person put their name on it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AUTO_PASS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The AI passed it. Nobody looked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FAIL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;It failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLOCKED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Could not run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SKIP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A plain &lt;code&gt;PASS&lt;/code&gt; would merge the first two. That merge is exactly what git-qa exists to prevent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Letting Claude Code test on a real device
&lt;/h2&gt;

&lt;p&gt;git-qa ships an MCP server. An agent such as Claude Code can take screenshots, read the screen,&lt;br&gt;
list what can be pressed and tap it — on Android, web, desktop or iPhone.&lt;br&gt;
You can run a sheet unattended overnight (&lt;code&gt;--no-ui&lt;/code&gt;) and review only the failures in the morning.&lt;br&gt;
&lt;strong&gt;The pass is still yours to give.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It also refuses sheets that verify nothing: if the expected text was already on screen before the step,&lt;br&gt;
the case is stopped as &lt;code&gt;BLOCKED&lt;/code&gt; with a note on how to fix the sheet.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Download from the &lt;a href="https://meta-taro.github.io/git-qa/en/" rel="noopener noreferrer"&gt;distribution page&lt;/a&gt; (macOS signed and notarized; Windows x64 / ARM64, unsigned) and install Node 22+&lt;/li&gt;
&lt;li&gt;Enter a handle, pick a device / URL / desktop app, pick a test sheet&lt;/li&gt;
&lt;li&gt;Start, watch, press &lt;code&gt;D&lt;/code&gt; / &lt;code&gt;F&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is beta and MIT-licensed. Bug reports and requests: &lt;a href="https://github.com/meta-taro/git-qa/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
    <item>
      <title>Let Claude Code draw your architecture diagram — and keep the box you moved</title>
      <dc:creator>metataro</dc:creator>
      <pubDate>Tue, 13 Oct 2026 05:14:27 +0000</pubDate>
      <link>https://dev.to/metataro/let-claude-code-draw-your-architecture-diagram-and-keep-the-box-you-moved-1n9b</link>
      <guid>https://dev.to/metataro/let-claude-code-draw-your-architecture-diagram-and-keep-the-box-you-moved-1n9b</guid>
      <description>&lt;p&gt;Asking an AI to draw an architecture diagram works fine — the first time.&lt;/p&gt;

&lt;p&gt;The trouble starts with the second one. The architecture changes, you ask the AI to update the diagram, and the layout you spent ten minutes tidying is gone. So you stop asking. A month later the diagram is fiction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;zumen&lt;/strong&gt; is an open-source diagram editor built around that second drawing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo5h0j65athkzqikiuhg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo5h0j65athkzqikiuhg.gif" alt="The AI draws; a person moves one box; the AI is asked again and adds a node; the moved box stays put" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The one idea: the AI may not write where you wrote
&lt;/h2&gt;

&lt;p&gt;A zumen diagram is one YAML file. The AI writes &lt;code&gt;nodes:&lt;/code&gt; and &lt;code&gt;edges:&lt;/code&gt;. When &lt;em&gt;you&lt;/em&gt; drag a box, the position goes into a separate &lt;code&gt;pins:&lt;/code&gt; block — and &lt;strong&gt;the AI is not allowed to write &lt;code&gt;pins:&lt;/code&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Order API&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Order DB&lt;/span&gt;
&lt;span class="na"&gt;edges&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
&lt;span class="na"&gt;pins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;40&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;170&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# you put it here; regeneration cannot move it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regenerate as often as you like: the AI rewrites the nodes and edges, your pins stay. Across 10 real AI round trips, every human edit survived; a pin survives regeneration in all 389 bundled example drawings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set it up in Claude Code
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;Other MCP clients: run &lt;code&gt;npx -y @metataro/zumen-mcp&lt;/code&gt; as a stdio server.&lt;/p&gt;

&lt;p&gt;The agent gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;zumen_spec&lt;/code&gt; — the format, read first&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zumen_examples&lt;/code&gt; — 389 worked examples to learn the notation from&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zumen_propose&lt;/code&gt; — a &lt;strong&gt;proposal&lt;/strong&gt;, shown to you as a diff. There is no tool that writes the file directly; a person presses apply&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zumen_inspect&lt;/code&gt; — checks for crossings, overlapping boxes, labels that did not fit, lines of different colours running on top of each other&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zumen_export&lt;/code&gt; — SVG, PNG, Mermaid, draw.io&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj1lno06z9guvt9u53h9c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj1lno06z9guvt9u53h9c.png" alt="The desktop app showing an AI proposal as a diff, with Apply and Cancel" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Show what changed, to people who will never read YAML
&lt;/h2&gt;

&lt;p&gt;From a clone of the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm compare before.zumen.yaml after.zumen.yaml out.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before and after, side by side: new nodes marked, removed ones dotted, and the nodes a person placed marked on both sides — so a reviewer can see at a glance that the hand edits survived.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not only boxes and arrows
&lt;/h2&gt;

&lt;p&gt;The same small set of primitives draws things most diagram tools never attempt: stage lighting plots in USITT RP-2 notation, pallet loading patterns in isometric (checked against the Japanese Industrial Standards), floor plans with door swings, periodontal charts, transit maps with parallel lines that don't hide each other. All 389 examples are in the &lt;a href="https://meta-taro.github.io/zumen/" rel="noopener noreferrer"&gt;gallery&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Not a Figma replacement, not a general drawing program, not a web app or real-time collaboration tool. Editing features exist only as far as a person needs them to approve what the AI changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/meta-taro/zumen" rel="noopener noreferrer"&gt;https://github.com/meta-taro/zumen&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;li&gt;Gallery and docs: &lt;a href="https://meta-taro.github.io/zumen/" rel="noopener noreferrer"&gt;https://meta-taro.github.io/zumen/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;code&gt;@metataro/zumen&lt;/code&gt; (library: YAML → SVG), &lt;code&gt;@metataro/zumen-mcp&lt;/code&gt; (MCP server)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>claudecode</category>
      <category>diagrams</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
