<?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: AssetHoard</title>
    <description>The latest articles on DEV Community by AssetHoard (@assethoard).</description>
    <link>https://dev.to/assethoard</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%2F4180408%2F4ac664e6-4c5e-4c53-95bf-481dd35542b2.png</url>
      <title>DEV Community: AssetHoard</title>
      <link>https://dev.to/assethoard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/assethoard"/>
    <language>en</language>
    <item>
      <title>Keeping tags attached when files move: watched folders in AssetHoard 0.2.4</title>
      <dc:creator>AssetHoard</dc:creator>
      <pubDate>Sat, 24 Oct 2026 04:05:05 +0000</pubDate>
      <link>https://dev.to/assethoard/keeping-tags-attached-when-files-move-watched-folders-in-assethoard-024-3ddp</link>
      <guid>https://dev.to/assethoard/keeping-tags-attached-when-files-move-watched-folders-in-assethoard-024-3ddp</guid>
      <description>&lt;p&gt;A file manager knows where a file is. An asset library also knows what you said about it: its tags, its category, which bundle it belongs to and which projects use it. The moment someone renames that file in Explorer or Finder, those two views of the world disagree.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://assethoard.com" rel="noopener noreferrer"&gt;AssetHoard&lt;/a&gt; is a local-first desktop asset manager for indie game devs, built in Rust and Tauri. Watched folders let you point it at directories you already organise by hand, and it keeps the library in step. Version 0.2.4 rewrites how watched folders handle files that change underneath them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cases that break naive watching
&lt;/h2&gt;

&lt;p&gt;A file watcher reports events. Turning those events into "this is the same asset as before" is where it gets interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Renames and moves.&lt;/strong&gt; The event stream for a move is not always a tidy rename. Moves across directories, or batches of them from a sync tool, can arrive as separate removals and creations. In 0.2.4 the asset is the thing being preserved, so tags, category, bundle and project links follow the file to its new path. Mirrored bundles move with their folders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case-only renames.&lt;/strong&gt; Renaming &lt;code&gt;Rock.png&lt;/code&gt; to &lt;code&gt;rock.png&lt;/code&gt; is a real rename, but a case-insensitive comparison sees no change at all. These now keep their metadata too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changes made while the app was closed.&lt;/strong&gt; There are no events to listen to when nothing is running. AssetHoard now recognises moves made while it was closed. Existing libraries are catalogued in the background after the update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saving over a file.&lt;/strong&gt; Plenty of tools save by writing a new file and replacing the old one. To a watcher, that looks like a deletion. AssetHoard now waits briefly before marking anything missing, so a save does not flag the asset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disconnected drives.&lt;/strong&gt; An unplugged external drive is not a deleted library. Assets on a disconnected drive or network share are paused rather than marked missing, and if the drive comes back under a different letter, AssetHoard picks it up again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nested watched folders.&lt;/strong&gt; Watch &lt;code&gt;Assets/&lt;/code&gt; and also &lt;code&gt;Assets/Characters/&lt;/code&gt;, and two watchers claim the same files. The rule now is that the innermost watched folder owns its files, and you get a preview with exact counts before anything moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limit: network drives
&lt;/h2&gt;

&lt;p&gt;Live rename and move tracking depends on the operating system sending file change events. Network drives over SMB or UNC paths do not provide them, so they do not get live tracking. They still pause cleanly when they disconnect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in 0.2.4
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;3D model thumbnails generate around 20x faster on import.&lt;/li&gt;
&lt;li&gt;Most image thumbnails are now generated after import, so imports finish much sooner.&lt;/li&gt;
&lt;li&gt;Re-importing a changed file updates the existing asset instead of creating a duplicate.&lt;/li&gt;
&lt;li&gt;The search index is reused on launch instead of rebuilt, and text search no longer waits for image search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else is in the &lt;a href="https://assethoard.com/releases/0.2.4" rel="noopener noreferrer"&gt;0.2.4 release notes&lt;/a&gt;, and setup is covered in the &lt;a href="https://assethoard.com/docs/watched-folders" rel="noopener noreferrer"&gt;watched folders docs&lt;/a&gt;. AssetHoard is free to download during the open beta.&lt;/p&gt;

&lt;p&gt;If you have built file watching into a desktop app, I would like to hear how you handled renames and saves that replace files.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>rust</category>
      <category>showdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a local MCP server inside a Rust desktop app</title>
      <dc:creator>AssetHoard</dc:creator>
      <pubDate>Tue, 13 Oct 2026 04:54:53 +0000</pubDate>
      <link>https://dev.to/assethoard/building-a-local-mcp-server-inside-a-rust-desktop-app-14gj</link>
      <guid>https://dev.to/assethoard/building-a-local-mcp-server-inside-a-rust-desktop-app-14gj</guid>
      <description>&lt;p&gt;Your AI assistant can write a state machine in eight seconds but has no idea what assets you own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://assethoard.com" rel="noopener noreferrer"&gt;AssetHoard&lt;/a&gt; is a local-first desktop asset manager for indie game devs, built in Rust and Tauri. It already knows every model, texture, sprite and sound in your library, what bundle it lives in, how it is tagged and what it depends on. Version 0.2.3 adds a built-in MCP server so your assistant can ask.&lt;/p&gt;

&lt;p&gt;This is the engineering side: where the server lives, why it talks HTTP, and what it takes to make a local server that can write to your library safe to leave running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inside the app, not beside it
&lt;/h2&gt;

&lt;p&gt;The obvious shortcut is a standalone MCP binary that opens the app's SQLite file and reads it directly. We ruled that out for two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Two processes writing to one SQLite file is how you corrupt a library.&lt;/li&gt;
&lt;li&gt;The app would never learn about changes. Ask the assistant to tag forty rocks and the open window keeps showing them untagged until restart.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the server runs inside AssetHoard and only answers while the app is open. That turned out to be a feature: every change the assistant makes shows up in the UI immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  One rule: MCP never touches the database
&lt;/h2&gt;

&lt;p&gt;The MCP module is not allowed to call app commands or SQL. Everything goes through a small internal library API that sits underneath both the UI and the MCP server.&lt;/p&gt;

&lt;p&gt;When an assistant moves an asset into a bundle, it runs the same function as a drag and drop. Same validation, same audit log, same undo entry. There is no second code path drifting out of sync with the real one.&lt;/p&gt;

&lt;p&gt;It also makes new tools cheap. A tool is a typed argument struct, whose doc comments become the descriptions the model reads, plus a thin method that calls the library API. The interesting logic lives in one place and gets tested once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack: rmcp, streamable HTTP, axum
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/modelcontextprotocol/rust-sdk" rel="noopener noreferrer"&gt;rmcp&lt;/a&gt;, the official Rust MCP SDK, with its streamable HTTP transport mounted on axum&lt;/li&gt;
&lt;li&gt;Database work on blocking worker threads, so a large search never stalls the async runtime&lt;/li&gt;
&lt;li&gt;Each call recorded in a recent-activity list visible in Settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We chose HTTP over stdio because the server lives inside an app that is already running. Claude Code, Cursor, VS Code and Windsurf connect to a local endpoint with a header, which is a one-line setup. Claude Desktop only launches stdio servers, so for now it goes through the &lt;code&gt;mcp-remote&lt;/code&gt; bridge.&lt;/p&gt;

&lt;p&gt;The port is fixed at &lt;code&gt;47823&lt;/code&gt; by default. It is configurable, but the app never picks another one on its own if it is busy. Auto-switching sounds helpful until every client config you have written silently stops matching. A taken port is a readable error instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it safe to leave on
&lt;/h2&gt;

&lt;p&gt;A local server that reads your files and writes to your library deserves some paranoia.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Off by default.&lt;/strong&gt; One toggle in Settings, and a toolbar icon whenever it is running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loopback only.&lt;/strong&gt; Binds to &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bearer token.&lt;/strong&gt; 48 random hex characters, compared in constant time. Regenerating it disconnects every client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browsers refused.&lt;/strong&gt; Any request with an &lt;code&gt;Origin&lt;/code&gt; header is rejected, and &lt;code&gt;Host&lt;/code&gt; must be a loopback address. Browsers always send &lt;code&gt;Origin&lt;/code&gt;; CLI and IDE clients do not. This blocks DNS rebinding, and it is what the MCP spec asks local servers to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write tools are a second opt-in.&lt;/strong&gt; Checked on every call, so switching it off takes effect for already-connected clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No delete tools&lt;/strong&gt; for assets or bundles. Exports skip existing files rather than overwrite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Imports ask first.&lt;/strong&gt; A native prompt shows the path, file count, size and target bundle. No answer within 60 seconds is a no.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything is undoable.&lt;/strong&gt; Writes land on the normal undo stack with an &lt;code&gt;MCP:&lt;/code&gt; label, e.g. &lt;code&gt;MCP: Moved 1 asset into 'Rocks'&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Small decisions that paid off
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Text results, separate thumbnails.&lt;/strong&gt; Search returns ids, names, types, paths and tags. If the model wants to look, it calls &lt;code&gt;get_thumbnail&lt;/code&gt; and gets an image of at most 256px. It can check that the "mossy rock" really is mossy without flooding its context with every result's picture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependencies travel with exports.&lt;/strong&gt; A model brings its textures, a Godot &lt;code&gt;.tres&lt;/code&gt; brings what it references, Unreal assets bring theirs. Assets inside zips or &lt;code&gt;.unitypackage&lt;/code&gt; files are extracted first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deep links.&lt;/strong&gt; Every asset, bundle and project returned includes an &lt;code&gt;assethoard://&lt;/code&gt; link, so the assistant can hand you something clickable that jumps straight to the item in the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Errors the model can read.&lt;/strong&gt; Unknown ids or disabled write tools come back as plain error messages rather than protocol failures, so the assistant can explain or try something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like in use
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find three different barrel models and a wood texture that matches, and copy them into &lt;code&gt;Assets/Props&lt;/code&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The assistant searches, checks thumbnails, picks a set and exports it, textures included. You keep building the level.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Make a bundle called 'Rocks' under 'Environment', move every rock model into it and tag them 'nature'."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each step appears live in the app as its own undo entry. If it moves a boulder that was actually a potato, Ctrl+Z.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;The main gap is Claude Desktop needing &lt;code&gt;mcp-remote&lt;/code&gt;. A small native shim is on the list.&lt;/p&gt;

&lt;p&gt;The full write-up, including more workflows, is on the &lt;a href="https://assethoard.com/blog/how-we-built-the-assethoard-mcp-server" rel="noopener noreferrer"&gt;AssetHoard blog&lt;/a&gt;. Setup per client is in the &lt;a href="https://assethoard.com/docs/mcp" rel="noopener noreferrer"&gt;MCP docs&lt;/a&gt; and every tool is in the &lt;a href="https://assethoard.com/docs/mcp-tools" rel="noopener noreferrer"&gt;tool reference&lt;/a&gt;. AssetHoard is free to download during the open beta.&lt;/p&gt;

&lt;p&gt;If you have built a local MCP server into a desktop app, I would like to hear how you handled auth and write access.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>mcp</category>
      <category>gamedev</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
