<?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: Muhammet Ali</title>
    <description>The latest articles on DEV Community by Muhammet Ali (@muhammetali).</description>
    <link>https://dev.to/muhammetali</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%2F4108666%2F8d8526ef-a760-48c4-88c2-526c53f3cd13.jpg</url>
      <title>DEV Community: Muhammet Ali</title>
      <link>https://dev.to/muhammetali</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhammetali"/>
    <language>en</language>
    <item>
      <title>Why I Rewrote My Flutter Tray App as a C++ Daemon</title>
      <dc:creator>Muhammet Ali</dc:creator>
      <pubDate>Thu, 03 Sep 2026 20:53:21 +0000</pubDate>
      <link>https://dev.to/muhammetali/why-i-rewrote-my-flutter-tray-app-as-a-c-daemon-1oec</link>
      <guid>https://dev.to/muhammetali/why-i-rewrote-my-flutter-tray-app-as-a-c-daemon-1oec</guid>
      <description>&lt;p&gt;I built a screen-capture tool for Linux in Flutter. Tray icon, global hotkey, region select, done — that was the plan. It worked, right up until three separate parts of the Linux desktop stack quietly broke it in ways that never showed up as errors. I ended up splitting the app into a long-running C++ daemon plus an on-demand Flutter UI. Here's what actually forced that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: the tray menu ignores your second click
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;system_tray&lt;/code&gt; and &lt;code&gt;tray_manager&lt;/code&gt; (the two Flutter packages everyone points you to for Linux tray icons) both go through &lt;code&gt;libayatana-appindicator&lt;/code&gt;. First click on the tray icon: menu opens, works fine. Second click, same session: nothing. Menu doesn't open. Not a crash, not a log line — the click event just doesn't make it to the app.&lt;/p&gt;

&lt;p&gt;This turned out to be a dispatch issue specific to the &lt;code&gt;ubuntu-appindicators&lt;/code&gt; GNOME Shell extension on GNOME 46 — after the first menu activation, it stops forwarding the next click to the same indicator until something else resets its internal state. It's not a Flutter bug and not really an appindicator bug either; it's an interaction between GNOME 46's extension and any app using the older appindicator activation pattern. Nothing in either package's issue tracker fixes this at the plugin layer, because the plugin layer isn't where the problem is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 2: your hotkey stops firing, silently
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;hotkey_manager_linux&lt;/code&gt; 0.2.0 calls &lt;code&gt;keybinder_bind&lt;/code&gt; under the hood and does not check its return value. When &lt;code&gt;keybinder_bind&lt;/code&gt; fails — which it does, for reasons that have nothing to do with your code — the plugin reports success anyway. Your app thinks the hotkey is registered. It isn't. Press it, nothing happens. Restart the app, still nothing. There's no exception to catch, because as far as the plugin is concerned, nothing went wrong.&lt;/p&gt;

&lt;p&gt;I only found this by instrumenting the native call myself and comparing the actual &lt;code&gt;keybinder_bind&lt;/code&gt; return value against what the Dart side believed. They disagreed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 3: the editor window freezes with visual garbage
&lt;/h2&gt;

&lt;p&gt;The pattern I was using — spawn an off-screen window, hide it, show it on demand for the annotation editor — interacts badly with Mutter's compositor. Under specific show/hide timing, the window would come back on screen with stale, frozen frame content baked in: old cursor positions, half-rendered UI from before it was hidden. Not every time. Often enough to make the editor unusable in a live demo.&lt;/p&gt;

&lt;p&gt;None of these three are the kind of bug you fix with a patch release. They're structural — a consequence of driving a compositor-managed, extension-dependent desktop shell through a cross-platform abstraction that doesn't know GNOME 46 changed, doesn't check the return value it should, and manages window visibility in a way the compositor doesn't expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: stop asking Flutter to be the always-on part
&lt;/h2&gt;

&lt;p&gt;The actual redesign is a two-process split:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Binary&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Lifetime&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yakala-daemon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;C++17&lt;/td&gt;
&lt;td&gt;long-running, autostarted&lt;/td&gt;
&lt;td&gt;tray icon, global hotkey, IPC server, native capture, clipboard, notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yakala-ui&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;on-demand, exits when done&lt;/td&gt;
&lt;td&gt;annotation editor, region overlay, settings window&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The daemon talks directly to GTK/&lt;code&gt;libayatana-appindicator3&lt;/code&gt;/GIO — no Flutter plugin abstraction between it and the desktop shell, so there's no cross-platform layer that can silently swallow a &lt;code&gt;keybinder_bind&lt;/code&gt;-style failure. It spawns &lt;code&gt;yakala-ui&lt;/code&gt; as a subprocess (&lt;code&gt;fork&lt;/code&gt;+&lt;code&gt;execvp&lt;/code&gt; via &lt;code&gt;GSubprocess&lt;/code&gt;) only when a window is actually needed, with stdin/stdout/stderr inherited so the UI's own debug output flows straight into the daemon's log. Communication happens over a Unix domain socket at &lt;code&gt;$XDG_RUNTIME_DIR/yakala-daemon.sock&lt;/code&gt;, line-delimited JSON. The UI sends &lt;code&gt;{"cmd": "ui_result", "ok": true, "output": "/tmp/yakala_edit_xxx.png"}&lt;/code&gt; when the user finishes, then calls &lt;code&gt;exit(0)&lt;/code&gt; — it never lingers.&lt;/p&gt;

&lt;p&gt;The hotkey problem got solved by leaving &lt;code&gt;hotkey_manager&lt;/code&gt; out of the picture entirely: &lt;code&gt;yakala-daemon&lt;/code&gt; also runs as a CLI client of itself. &lt;code&gt;yakala-daemon --capture-fullscreen&lt;/code&gt; opens the same Unix socket, sends &lt;code&gt;{"cmd": "capture_full"}&lt;/code&gt; to whatever instance is already running as the daemon, and exits. &lt;code&gt;linux/install-launcher.sh&lt;/code&gt; registers that exact command as a GNOME custom keybinding via &lt;code&gt;gsettings&lt;/code&gt; — the same mechanism GNOME uses for its own built-in shortcuts, which sidesteps &lt;code&gt;keybinder_bind&lt;/code&gt; and its silent-failure mode completely. It's the same pattern git, docker, and most daemon/CLI pairs use, just applied to a desktop tray app instead of a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this actually bought
&lt;/h2&gt;

&lt;p&gt;Capture latency dropped to ~190ms typical (the daemon shells straight out to &lt;code&gt;grim&lt;/code&gt;/&lt;code&gt;import&lt;/code&gt;/&lt;code&gt;scrot&lt;/code&gt;/&lt;code&gt;maim&lt;/code&gt;, no Flutter engine sitting on the hot path). The tray menu opens every time, because activation now goes through the same GTK/appindicator code path GNOME's own indicators use, not a wrapper that assumes GNOME 45 behavior. And the frozen-editor artifact went away on its own — a Flutter window that only exists for the seconds it's actually being used has no stale-frame state to accumulate.&lt;/p&gt;

&lt;p&gt;Linux is production-ready with this architecture now. macOS is still on the old single-binary model — the daemon there is scaffolded but not wired up to the OS's own tray/hotkey APIs (Carbon &lt;code&gt;RegisterEventHotKey&lt;/code&gt; planned, not implemented).&lt;/p&gt;

&lt;p&gt;Source and a prebuilt Linux release: &lt;a href="https://github.com/muhammetali/yakala" rel="noopener noreferrer"&gt;github.com/muhammetali/yakala&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>linux</category>
      <category>cpp</category>
      <category>gnome</category>
    </item>
    <item>
      <title>I Published 3 MCP Servers in One Day. Here's What Actually Broke.</title>
      <dc:creator>Muhammet Ali</dc:creator>
      <pubDate>Thu, 03 Sep 2026 20:08:42 +0000</pubDate>
      <link>https://dev.to/muhammetali/i-published-3-mcp-servers-in-one-day-heres-what-actually-broke-5apf</link>
      <guid>https://dev.to/muhammetali/i-published-3-mcp-servers-in-one-day-heres-what-actually-broke-5apf</guid>
      <description>&lt;p&gt;I had three MCP servers sitting in private repos — one for App Store Connect, one for Google Play Console, one wrapping Recraft's image API. All working, all tested, none of them usable by anyone but me. So I spent an afternoon getting them onto npm and the official MCP Registry.&lt;/p&gt;

&lt;p&gt;I expected the hard part to be the code. It wasn't. The code was already done. The hard part was everything downstream of &lt;code&gt;npm publish&lt;/code&gt; — and none of it is documented anywhere in one place, so here's the writeup I wish I'd had.&lt;/p&gt;

&lt;h2&gt;
  
  
  npm's 2FA wall has a hole in it, and it's not the one you think
&lt;/h2&gt;

&lt;p&gt;First &lt;code&gt;npm publish&lt;/code&gt; attempt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm error code E403
npm error 403 Forbidden - Two-factor authentication or granular access token with bypass 2FA enabled is required to publish packages.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fine, I have 2FA. Except my account only has a security key registered — no TOTP app, because npm quietly dropped authenticator-app 2FA from the UI at some point and I hadn't noticed. &lt;code&gt;npm publish&lt;/code&gt; wants a typed six-digit code (&lt;code&gt;--otp=&amp;lt;code&amp;gt;&lt;/code&gt;). A WebAuthn security key doesn't produce one. There's no button for "just tap your key" in the CLI flow. So the account has 2FA, and there is &lt;em&gt;no way to satisfy it from a terminal&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The npm docs don't spell this out. What actually works: a &lt;strong&gt;Granular Access Token&lt;/strong&gt; with "Bypass two-factor authentication (2FA)" checked at creation. It's unchecked by default — I generated one, published, still got &lt;code&gt;EOTP&lt;/code&gt;, and only realized on the second token that I'd never ticked the box. Bypass tokens also need write access scoped to the specific package(s) you're publishing; scope one to package A and try to publish package B with it, and you get a plain 403 that looks identical to the 2FA error, which cost me another ten minutes of confused debugging.&lt;/p&gt;

&lt;p&gt;One more thing worth knowing if you're doing this after early 2027: npm's own dashboard now carries a banner warning that bypass-2FA tokens for direct publishing are being phased out. The documented replacement is trusted publishing (OIDC from CI), not a personal token. If you're setting this up for the first time today, you might as well build it against CI from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP Registry is not a GitHub pull request
&lt;/h2&gt;

&lt;p&gt;I assumed listing a server meant opening a PR against &lt;code&gt;modelcontextprotocol/servers&lt;/code&gt;, the way "awesome-list"-style ecosystems usually work. Wrong. That repo now hosts only the small set of reference servers the steering group maintains — the README says so directly, and the merged-PR history confirms it: nothing but doc fixes and CI tweaks get in from outsiders.&lt;/p&gt;

&lt;p&gt;The actual registry is a separate service at &lt;code&gt;registry.modelcontextprotocol.io&lt;/code&gt;, with its own CLI (&lt;code&gt;mcp-publisher&lt;/code&gt;). The flow, once you know it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add an &lt;code&gt;mcpName&lt;/code&gt; field to &lt;code&gt;package.json&lt;/code&gt; — &lt;code&gt;io.github.&amp;lt;user&amp;gt;/&amp;lt;name&amp;gt;&lt;/code&gt;. This is the ownership proof the registry checks against the published npm package. If the &lt;em&gt;currently live&lt;/em&gt; npm version doesn't have this field, validation fails, even if your local source does. You have to publish a version that includes it first.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcp-publisher init&lt;/code&gt; generates a &lt;code&gt;server.json&lt;/code&gt; — fill in the real env vars, and make sure &lt;code&gt;packages[0].identifier&lt;/code&gt; matches your actual npm package name (more on why that matters below).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcp-publisher login github&lt;/code&gt; — device code flow, same UX as &lt;code&gt;gh auth login&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcp-publisher publish&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The one operational quirk: the login session expires faster than I expected — I hit &lt;code&gt;Invalid or expired Registry JWT token&lt;/code&gt; twice in one afternoon, both times after getting pulled into an unrelated npm rabbit hole for 20+ minutes in between. Re-running &lt;code&gt;login&lt;/code&gt; fixes it instantly; nothing else needs to be redone.&lt;/p&gt;

&lt;h2&gt;
  
  
  I didn't own the npm name I'd been building against
&lt;/h2&gt;

&lt;p&gt;This one stung a little. My App Store Connect server's README had an npm install badge pointing at &lt;code&gt;asc-mcp-server&lt;/code&gt; — unscoped — for months. Turns out that name belongs to someone else's npm account entirely, unrelated to my project. I only found out because the registry publish step kept 403ing with "you do not have permission," which sent me to check &lt;code&gt;npm owner ls asc-mcp-server&lt;/code&gt;. One name, not mine.&lt;/p&gt;

&lt;p&gt;In hindsight there was already a tell I'd walked past: the previously published version under that name had a completely different internal file layout than my actual source tree. I'd assumed it was just an old build artifact of my own. It wasn't — someone else's package, coincidentally similar in purpose, sitting on the exact name I was linking to in my own docs.&lt;/p&gt;

&lt;p&gt;There's no real recourse short of npm's name-dispute process, which is slow and not guaranteed. The fix that actually ships today: move to a scoped package, &lt;code&gt;@you/name&lt;/code&gt;. It's always available under your own username, &lt;code&gt;npm publish&lt;/code&gt; just needs &lt;code&gt;--access public&lt;/code&gt; since scoped packages default to private. Cost: about ten minutes to update &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;server.json&lt;/code&gt;, and the README badge. If you're about to publish something for the first time, checking &lt;code&gt;npm view &amp;lt;name&amp;gt;&lt;/code&gt; before you write a single line of your README costs nothing and saves this entire detour.&lt;/p&gt;

&lt;h2&gt;
  
  
  The security bug that only showed up because I tried to make the server &lt;em&gt;more&lt;/em&gt; discoverable
&lt;/h2&gt;

&lt;p&gt;This is the one I'm most glad happened before launch instead of after.&lt;/p&gt;

&lt;p&gt;My App Store Connect server supports two transports: stdio (the default — runs locally, one set of credentials per machine) and an HTTP mode with a full OAuth 2.1 layer, meant for connecting to it remotely. I wanted to list it on Smithery, which currently only accepts servers reachable over a public HTTPS URL — so the HTTP mode was the obvious path.&lt;/p&gt;

&lt;p&gt;Then I actually read the OAuth provider code before flipping it on publicly. The comment at the top of the file said exactly what it does, plainly: &lt;code&gt;/authorize&lt;/code&gt; auto-approves &lt;em&gt;any&lt;/em&gt; client that registers itself, no login screen, no consent step — because the whole thing was designed around "the operator already trusts the network, since it's their own VPS." Dynamic Client Registration means literally anyone who finds the URL can self-register, complete the OAuth flow with zero friction, and get a valid bearer token. From there, they'd have full API access — using &lt;em&gt;my&lt;/em&gt; App Store Connect credentials configured on that server, not their own. Delete a version, submit something for review, reply to a real customer's App Store review, as me.&lt;/p&gt;

&lt;p&gt;None of that is a bug in the traditional sense. It's a deliberate, reasonable trust model for "I'm the only one who will ever know this URL exists." It just becomes a real vulnerability the second you do the thing I was about to do: put the URL on a public directory with "10,000+ users" written on the landing page.&lt;/p&gt;

&lt;p&gt;I skipped the Smithery listing for that server. The lesson generalizes past MCP: any tool you build assuming private-URL-as-security stops being safe the moment "let's make this more discoverable" enters the conversation. Worth a second look at &lt;em&gt;why&lt;/em&gt; an auth flow is shaped the way it is before you widen its blast radius, not just whether it currently works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where things ended up
&lt;/h2&gt;

&lt;p&gt;All three are live now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/gpc-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;gpc-mcp-server&lt;/code&gt;&lt;/a&gt; — Google Play Console&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@muhammetali/asc-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;@muhammetali/asc-mcp-server&lt;/code&gt;&lt;/a&gt; — App Store Connect (scoped, for the reason above)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/recraft-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;recraft-mcp-server&lt;/code&gt;&lt;/a&gt; — Recraft AI image generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are also registered on the &lt;a href="https://registry.modelcontextprotocol.io" rel="noopener noreferrer"&gt;official MCP Registry&lt;/a&gt; and listed on &lt;a href="https://glama.ai/mcp/servers" rel="noopener noreferrer"&gt;Glama&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're about to publish an MCP server for the first time, the short version: check the npm name is actually yours before you build docs around it, get a 2FA-bypass token with the box checked before you're mid-&lt;code&gt;npm publish&lt;/code&gt;, use &lt;code&gt;mcp-publisher&lt;/code&gt; instead of looking for a PR template, and read your own auth code once more before you make anything easier to find.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>npm</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
