2026-08-09 Obsidian MCP Bridge Outage and the Intel Wheel Cliff
What I set out to do
Answer a one-line question: is the remote Obsidian MCP on athena working? iOS Claude had failed to authenticate against obsidian-mcp.achhina.com, which read like the monthly Cloudflare Access rollover documented in 2026-07-25 Remote Obsidian MCP Access Session Rollover.
What I actually did
It was not the session. The bridge had been dead since 2026-08-01 17:17, eight days, and the tunnel had been serving a corpse.
Root cause was the same regression seen on 2026-08-03. ~/.local/bin/obsidian-remote-mcp-bridge runs uvx mcp-proxy with no version pin, resolving fresh at every launchd start. mcp-proxy 0.12.0 declares mcp>=1.17.0 with no ceiling; mcp 2.0.0 removed request_ctx, which mcp_proxy.proxy_server imports at module scope. The process died before binding :8788 and KeepAlive=true crash-looped it into a 36 MB log of identical tracebacks.
Why the earlier fix missed it. That fix went into the Home Manager module, now pkgs.obsidian-mcp-proxy, lockfile-pinned to mcp-proxy==0.12.0 plus mcp<2. But athena deliberately does not import that module (home-server.nix:74-82, ratified in the 2026-07-25 decision on Remote Obsidian MCP Cloud Host). The launch command exists in two copies and only one was fixed. Patched athena’s script by hand and verified 18 vault tools end to end.
Then chased whether athena could just run the nix-built proxy and retire the second copy. It evaluates fine for x86_64-darwin but fails to build, and the blocker is upstream of nix entirely: cryptography dropped x86_64 macOS wheels at 49.0.0 (48.0.1 is the last with macosx_10_9_universal2). The dependency chain is mcp-proxy to mcp to pyjwt[crypto] to cryptography, and the mcp<2 pin does not avoid it.
Tried three ways through it. Adding maturin, cargo and rustc got past the missing backend and straight into a refused network fetch from crates.io. Adding rustPlatform.fetchCargoVendor with a vendor hash, cargoSetupHook, cffi and setuptools built successfully and served 18 tools. Then found the cheaper answer: two dependency lines with disjoint PEP 508 markers fork the uv lock so Intel darwin resolves 48.0.1 (prebuilt wheel) while every other host stays on 50.0.0. That builds on athena with stock lib/uv.nix, no override, no hash, no Rust.
What was striking
A public 401 does not clear the origin. Cloudflare Access answers before cloudflared, so an unauthenticated probe returns the same well-formed www-authenticate: Bearer challenge whether the origin is healthy or dead. The rule from July, that a well-formed 401 means only the token expired, holds only once the bridge is confirmed alive. The authoritative signal sat in tunnel.stderr.log the whole time: authenticated requests passing Access and failing on dial tcp 127.0.0.1:8788: connect: connection refused. The token was fine. Eight days lost to a red herring that one log line would have killed on day one.
How the uvx path survives at all. It is not that uv picks an Intel-compatible cryptography; athena has 50.0.0, the exact version with no Intel wheel. uv builds it from sdist, and because uv isolates builds by default it installs maturin and bootstraps a whole Rust toolchain via puccinialin into ~/Library/Caches/puccinialin (691 MB, rustc 1.97.1). uv2nix runs with isolation off, which is the entire difference between the two paths. Same sdist, same machine, same architecture.
That reframes the risk. Today a cryptography bump means athena downloads a Rust toolchain and compiles a crypto library inside the launchd startup path, before anything binds :8788. The cached wheel timestamps tell the story: 49.0.0 built Jul 3 at 23:03, minutes before the agents were created, and 50.0.0 built Aug 4 at 07:54, in the middle of the outage. A uv cache prune re-arms the whole thing. Same fragility class as the unpinned resolve, with a compiler attached.
Nix’s hash is a toll, not a requirement. The vendor hash is not about the toolchain. Ordinary derivations get no network (SSL_CERT_FILE=/no-cert-file.crt, which is why athena’s sandbox = false changed nothing), a fixed-output derivation is the sole exception, and declaring the output hash up front is the price of that exception. Skip the source build and the hash disappears. I spent two builds solving the expensive version of a problem that a two-line marker constraint dissolves.
The 2026-07-25 decision was right for a sharper reason than it states. It justifies Homebrew by “nixpkgs 26.05 is the last release supporting x86_64-darwin”, a future horizon. The wall is already here and it comes from PyPI, not nixpkgs. Echoes 2026-07-25 athena 125-Commit Catch-Up and the Intel node-gyp Wall: Intel macOS support is being withdrawn a dependency at a time, not on nixpkgs’ schedule.
What shipped after the above
Items 1 and 3 landed the same day, and not the way the section below predicted. Four commits, merged to main and pushed (1e96fbb, 973ea95, 8cd9c5a, a6ed6cb).
Chose the vendor hash over the forked lock, on your reasoning not mine. The fork was cheaper but held cryptography back to 48.0.1 on the one host facing the public hostname, and reopened a lock that exists to stay frozen (a test relock also dragged starlette, uvicorn and sse-starlette along). The scoped fixed-output derivation keeps every host on 50.0.0. Two objections I had raised turned out to be wrong: the hash is not recurring maintenance, because just uv-relock is deliberately excluded from the update wrapper, and the Rust compile is once per version rather than once per switch.
athena now serves both halves from the flake. home-server.nix installs pkgs.mcpvault and pkgs.obsidian-mcp-proxy as packages only; the hand-written script execs ~/.nix-profile/bin/{mcp-proxy,mcpvault}. I had framed this as import-the-module-or-nothing, which was too binary: nothing forces launchd’s Program to become a store path, and that is the only part TCC blocks. The profile is the stable pointer AND the GC root in one, so no hand-managed nix-store --add-root to forget. Verified by process tree, supervisor → store python mcp-proxy → store nodejs mcpvault-0.15.0, and a real get_vault_stats returning isError: false, which is the actual TCC proof since a denial hangs rather than errors.
An audit found four more launch-time resolves, of which three are now flake inputs tracking default branches (mcpvault, mcp-image, mcp-neovim-server), with versions read from package.json rather than hand-maintained. mcp-neovim-server had been frozen at 0.5.2 while upstream reached 0.5.5, because it lived in a JSON asset that nix flake update cannot read. playwright and ccstatusline deliberately stay on @latest: interactive tools fail visibly in a session someone is watching, which is a different risk from an unattended service.
The audit’s own method was the lesson. Both greps were shaped by the last thing found: the first keyed on @latest and missed mcp-image (no version at all), the second on /bin/npx and missed the plugin.json (bare npx, in JSON). The complete set only appeared when the question changed from “what matches this string” to “what does update fail to reach”. Coverage questions find things that pattern questions cannot.
uv cache prune was blocked by ghosts, not config. Six uv run --script ~/bin/{arxiv,acl}-mcp-server processes, ten days old, held ~/.cache/uv/.lock. Those scripts no longer exist on disk; the processes outlived the config that spawned them, parented by two forgotten Claude Code sessions (one in popup:0.0, one nested inside nvim in personal-blog:0.0). A clean config and a clean running system are different claims, and lsof is what distinguishes them.
Top 3 tomorrow
- The absent-data alert on the bridge, which the 2026-07-25 decision already called for. Now the only item left, and the only one that shortens the next outage rather than making it less likely: every failure today was silent, and the eight days ended because I happened to try my phone.
- Pull
a6ed6cbon athena; it is one commit behind, harmless (that commit does not touch its profile) but drift. - Consider whether
tests/obsidian-mcp-bridge/default.nixshould also assert against athena’s hand-written script. It already fails the build on@latestoruvx, but reads the script from the Home Manager module: a guard aimed at the copy that never broke.
Related
Remote Obsidian MCP Cloud Host · 2026-07-03 Obsidian MCP Cutover to Athena · 2026-07-25 Obsidian MCP TCC Break from a Homebrew node Upgrade · 2026-07-25 Remote Obsidian MCP Access Session Rollover · 2026-07-25 athena 125-Commit Catch-Up and the Intel node-gyp Wall · Old MacBook Home Server Setup · Homelab Services Architecture