The Bristlenose extension
The whole connect UX, after the hand-paste path failed three times in one sitting. Drawn 31 July 2026 as a proposal — shipped in 0.23.0 on 1 August 2026, and trued against what got built on 2 August.
- The scope model is neither A nor B. §5 decided B (pin one project through
Claude Desktop's picker). §5a then reframed it as Option A restricted to an explicit
allowlist — and that is what shipped. No
user_config, no directory picker, no pin. §5 is superseded by the section directly after it. - Anonymise became one global switch, not the per-project control §1 promised to leave alone. Off by default, top of the pane, same strings as Export. The CLI keeps per-project semantics; the two channels genuinely differ.
- The Settings project list was built, then cut. §5a's "decided: both" shipped as one: the right-click menu is the act, the sidebar antenna is the audit.
- The sleeper win didn't land.
bristlenose mcp-proxywas never written, so §2's two address-free panes are still aspirational and Claude Code / Codex keep their URL and token. - The TCC answer in §4 and Q7 is wrong. The prompt does fire. It fires once, and the grant persists — but "no prompt" was measured from a shell, and the shell wasn't the thing doing the reading.
0 · What happened tonight THE MOTIVATION
Three attempts to connect Claude Desktop by hand. All three failed, all from one cause, and the second one cost unrelated settings.
- JSON has no append-safe form. Adding a key means a comma on the previous line and none after. No wording of an instruction removes that.
- The failure is destructive. Claude Desktop doesn't skip the bad key — it rewrites the whole settings file and drops what it can't parse.
- We can't help. The App Sandbox keeps Bristlenose out of another app's container: reading that file is as blocked as writing it.
- The restart costs more than we said. That config is the host app's own state — quitting to apply it can end the researcher's own working session.
1 · The new flow SHIPPED
-
Bristlenose ▸ Settings ▸ MCP Agents
Or Bristlenose ▸ Connect MCP agent… in the menu bar, which goes to the same place. Setup is a once-ever, app-level act — it does not depend on which project is open, or on any project being open at all.
-
The Claude Desktop tab is one button
Connect Agent — Claude DesktopAgents read whichever project is selected in Bristlenose
Now showing: IKEA discovery · 6 sessions · 214 quotes
Claude DesktopClaude CodeChatGPT & CodexGeneric MCPAdds Bristlenose to Claude Desktop as an extension. Claude Desktop will ask you to confirm.
BBristlenoseAn agent has asked about this project recentlyInstall Extension…Done- The footnote is gone entirely — a fourth tab replaced it.
“Works with any MCP-compatible agent” was doing the work of a missing tab, and beside an
Install button it was actively wrong (a
.mcpbis Claude Desktop-only). The picker is now the statement, and it's actionable. - No address, no token, no JSON — nothing to mistype, so nothing to get wrong.
- The Anonymise switch stays exactly where it is: it's the researcher's per-project compliance choice and has nothing to do with transport.
- Cancel became Done. Nothing here is cancellable any more — you either install or you close the sheet.
Drawn correctly — the install row really does live inside the Claude Desktop tab. It shipped above the picker first and had to be moved back down, because a.mcpb-only action was showing while three tabs it does nothing for were selected. The pane's fixed payload height is sized to the tallest tab (ChatGPT & Codex), so switching client never reflows it.- The Anonymise bullet is wrong. It did not stay where it was. On the Mac it became one global switch at the top of the pane, off by default — a posture toward agents rather than a property of a project. The CLI still reads the per-project database flag, so the two channels differ deliberately.
- The subtitle drawn here (“An agent has asked about this project recently”) shipped verbatim, and still reads our own server's activity — never Claude's install state.
- The footnote is gone entirely — a fourth tab replaced it.
“Works with any MCP-compatible agent” was doing the work of a missing tab, and beside an
Install button it was actively wrong (a
-
Claude Desktop runs its own install
Claude DesktopInstall “Bristlenose”?This extension will be able to run on your computer and access the Bristlenose project you have open.CancelInstallApproximation of Claude Desktop's own dialog — we don't draw this, it draws it. That's the point: the consent moment belongs to the app being modified, not to us. -
Done — it appears where Figma appears
Claude Desktop → Settings → DeveloperLocal MCP serversAdd and manage MCP servers that you're working on.Bristlenose running This server is managed by an extensionFigma running This server is managed by an extension
The two entry points — wording and placement
LLM tab is part of the proposal, and it earns its churn.
- It makes the two axes obvious. Bare “LLM” next to “Agents” reads as two flavours of the same thing. LLM Provider vs MCP Agents says it plainly: which model does the analysis (in), versus which agents can read the results (out).
- “LLM” names a domain, not a setting — it tells you the topic but not what you do there. The pane is a provider list with a detail pane (the Mail Accounts shape), so Provider is what it actually configures, and it's already Bristlenose's own word.
- The singular/plural asymmetry is deliberate. One Provider — only one is active at a time, by design. Several Agents — Claude Desktop and Codex can both be connected at once. The labels encode a real difference rather than just matching shape.
- Cost, stated: one string × 20 locales, right after the seeding pass. Small, and better now than once the cohort has muscle memory.
- Icon: the antenna — the same glyph as the sidebar badge. One concept, one symbol, two surfaces.
- Last in the list. Appearance is chrome, LLM and Transcription are the engines; who can read your work is a fourth concern, and last is least disruptive to muscle memory.
| Setup — once | Enablement — per project | |
|---|---|---|
| Where | Bristlenose menu | Project menu / right-click |
| Wording | Connect an Agent… | Turn On / Turn Off Agent Access |
| Ellipsis? | Yes — opens a window | No — it toggles immediately |
- “Connect an Agent…” over “Connect MCP Agent…”: the menu is where a researcher looks when they don't yet know the jargon, and the pane they land on says MCP everywhere. Put the plain word where discovery happens and the precise word where the work happens.
- It's honest that the item only opens Settings — that's the same shape as Add Account…, which also opens a pane rather than adding anything.
- The Project menu's old “Connect Agent…” retires. Its job is now enablement, and Turn On / Turn Off Agent Access names it truthfully — a verb swap, not a window. (“Share” was rejected: on macOS it means the share sheet, and its punctual tense misreads a standing permission. See the decided menu above.)
- Vendor-neutral on purpose: never “Connect Claude”. The whole premise is any MCP agent.
Before / after
| Hand-paste (today) | Extension (proposed) | |
|---|---|---|
| Steps for the researcher | Copy · find file · edit JSON · fix commas · save · quit · relaunch | Click Install · confirm |
| Ways to get it wrong | Comma, brace, wrong nesting, wrong file, stale port | None available |
| Cost of a mistake | Claude Desktop rewrites its settings file | — |
| Where the token lives | Plaintext in another vendor's config, on the pasteboard, in Time Machine | 0600 file in Bristlenose's own container, read at connect time |
| When the port changes | Config is dead; re-paste | Nothing to do |
| Switching project | Re-paste per project | Nothing to do |
| Revoking | Hunt through a foreign config | Bristlenose deletes one file |
2 · Every state the sheet can be in SHIPPED, TWO PANES CHANGED
Paste in any terminal:
Add to ~/.codex/config.toml:
bristlenose mcp-proxy, then every client's config becomes address-free and
token-free — a command, not a secret. Claude Code and Codex stop needing a re-paste when the port
rotates too, and the three tabs finally say the same thing in three dialects.
- Cost: one CLI subcommand wrapping the same proxy logic. Open question 3 in the plan.
- Without it, these two tabs keep tonight's URL + token payload — which works, but leaves the token in two more config files.
bristlenose mcp-proxy does not exist, so both tabs shipped carrying
“tonight's URL + token payload” — the second bullet's case, not the first.
- Claude Code, as shipped:
claude mcp add --transport http bristlenose <endpoint> --header "Authorization: Bearer <token>" - ChatGPT & Codex, as shipped:
[mcp_servers.bristlenose]withhttp_headerscarrying the same bearer. One entry still covers ChatGPT desktop, Codex CLI and the IDE extension. - The consequence the drawing hides: the re-copy-after-restart caveat isn't confined to tab four. Three of the four tabs carry it. Only the extension re-resolves — so what the commentary calls a “sleeper win” is really what would make the rest of this page's port-stops-mattering claim true for anyone not on Claude Desktop.
The fourth tab — Generic MCP
Give these two values to any MCP-compatible agent:
The port number changes each time Bristlenose starts — copy the address again after a restart. Your token stays the same.
- It's also the fallback that makes “replace hand-paste” safe. A researcher whose extension silently fails isn't stranded — and the escape hatch hands two values to a client that accepts them, rather than sending anyone back into another app's JSON. Honest limit: it's a fallback across clients, not within Claude Desktop.
- Scope is already correct under Option B: each project's serve has its own port and its own token, so a URL+token pair names one project.
- It keeps the rotating-port problem — no proxy in front to re-resolve — so this is the re-copy-after-restart tier.
- That caveat is
info.circle/ secondary, not a yellow caution triangle. The HIG reserves warnings for negative consequences and data loss (“don't warn when data loss is the expected result”), and our ownMessageKind.WARNINGmeans something soft-degraded in this run — which hasn't happened. It would also show on every single view of this tab, and permanent warnings stop being read. The words carry the load, not the colour: name the moment and the remedy.
Settings ▸ MCP Agents — with and without a project selected
- Installing is a higher-order concern than which project is open. A researcher who opens Settings from the Welcome screen is doing setup — the pane has no business changing shape, showing an empty state, or telling them to go and pick something first.
- “Agents read whichever project is selected in Bristlenose” stays as the header. That switching projects changes what Claude can see is not obvious and not intuitive — saying it plainly is the v1 answer.
- “Now showing: …” is a for-example line: concrete, and it makes the header's rule legible by demonstrating it. With nothing selected it simply disappears — no placeholder, no dash, no “no project selected”. Absence is the information.
- Cut with it: the old “Bristlenose isn't running a project” sheet state. That was a hangover from the per-project sheet, and under a global home it was answering a question nobody asked.
3 · What the agent sees SHIPPED — SIX MESSAGES, NOT FIVE
Bristlenose isn't serving anything yet VERIFIED IN SPIKE
Serving — and the project is named in the answer
The full message vocabulary — five states, found by running it
Not a dialog and not a Bristlenose window: our proxy returns these as ordinary tool results, Claude reads them, and the model relays them in the conversation. So they're written addressed to the model, and the researcher sees Claude say it.
| State | Signal | What the agent tells the researcher |
|---|---|---|
| Ready | 200 | The real answer, from real quotes. |
| Not shared / BN closed | no handshake | “Bristlenose isn't open, so there is no study data available… Do not answer from memory.” |
| BN starting | port silent | “Bristlenose is starting — ask again in a moment.” |
| No agent support in this build | 404 | “This copy of Bristlenose was built without agent support. No setting will enable it.” |
| Credential rejected | 401 | “Bristlenose rejected this connection. Tell the person to open Bristlenose ▸ Settings ▸ MCP Agents (⌘,) and check this project is still shared.” |
404 on /mcp/ and a corrupted token returned
401, and a proxy that lumps both into “upstream error” hides the difference at
exactly the moment it matters.
- 404 and 401 mean opposite things. One is “your installation can't do this”, the other is “your access was withdrawn”. Same HTTP-shaped failure, completely different next action.
- Name the app, not just “Settings”. This text is read inside Claude Desktop, where “Settings” means Claude's. It has to say Bristlenose ▸ Settings ▸ MCP Agents — app, then pane. (Which makes the pane's name a dependency of this copy, not just a label.)
- Correction from tracing it: unsharing deletes the handshake, so that path gives the “not shared” message, not a 401. A 401 means the credential is stale or was rotated — so the remedy is checking the share state, not reinstalling anything. Getting that wrong would send the researcher down a reinstall rabbit-hole.
- 404 has no remedy, and should say so. Pointing at a Settings pane that cannot fix it is worse than admitting the build lacks the feature.
- We don't control the final wording — the model paraphrases. So the facts have to survive rewording (app name, pane name), and we can't lean on exact phrasing, formatting or markdown. It also can't be a button: we can name where to go, never take them there. That's the price of the only channel we have into another app.
- Bristlenose's server does see the 401, so it could raise something too — but a failed-auth notice on a single-user machine is noise. Left to the agent's reply on purpose.
- Every message ends with the same anti-fabrication clause. A model handed “no data” will otherwise apologise and answer from memory — the worst possible failure for a tool whose output lands in a research deliverable.
- The sixth message names the macOS dialog in its own words and gives the remedy (“click Allow”, or System Settings ▸ Privacy & Security) — deliberately not “reinstall”, because the extension is fine. Same discipline as the 404/401 split: same HTTP-shaped nothing, completely different next action.
- A seventh,
upstream, covers any other status — the honest “I don't know what that was” tier, still carrying the anti-fabrication clause. - Confirmed as a rule rather than a preference: the messages stay English in all twenty locales. The model relays them in the conversation's language, so translating them would fork a string that gets rewritten anyway.
The researcher switches project mid-conversation
- “Every tool payload carries the project” is passive metadata. A model summarising for a human drops it nine times in ten. An in-band note it will relay.
- The anonymisation clause appears only when it differs from the previous project — otherwise the governance boundary moves without a gesture, and real names start appearing in a chat the researcher believes is anonymised.
- ~10 lines in the proxy. The sidebar antenna moving is the same message in Bristlenose; no toast, no banner.
- What that leaves, stated honestly: switching between two shared projects mid-conversation is still silent. The residual harm is a confident answer about the wrong study in a scrollback that reads as one study — mitigated only by the passive metadata this commentary already judged insufficient.
- The ~10 lines remain the fix, and need no design reopening if a cohort tester hits it.
4 · How it works SHIPPED
mcp-handshake.json (port · token · project) into its own container at 0600.
Deletes it on stop..mcpb.
Reads the handshake, speaks stdio to the client and HTTP to Bristlenose. If there's no
handshake, serves the four tools with "open Bristlenose" answers.- The token stops travelling. It never enters another vendor's config, never touches the pasteboard, never lands in a Time Machine backup of Claude's settings.
- The port stops mattering. Kernel-assigned per launch is fine when nobody has to write it down — which retires the “this address changes when Bristlenose restarts” caveat we shipped yesterday.
- Revocation becomes a file delete, not a hunt through a foreign config.
- Same shape as Figma's shipping extension, which proxies to
127.0.0.1:3845. They pin a port; we can't, so the handshake file is the one part we're inventing. - “Does the researcher need Node installed?” No —
checked on this machine. Claude Desktop ships its own Node (24.18.0) and uses it for
MCP servers:
isUsingBuiltInNodeForMcp is true and built-in node is compatiblein its log. This was the one risk that could have sunk the approach for a non-engineer audience, and it's answered.
- “~150 lines of Node” is now 308 — all hardening, no features: a fail-closed instance-id check, the permission path, the six-message vocabulary, terminal-write self-heal, and a tools block that CI diffs against the live server so drift fails the build instead of the researcher. Still zero dependencies.
- The proxy reads the handshake only inside tool calls. The drawing implies a
background reader; there was one — a 5-second self-heal watcher — and it had to be deleted
outright. On a consent-gated path a timer races its own pending dialog no matter how it backs
off. The
tools/list_changedrecovery now rides tool-call reads for free, because the tool list is static.
- It costs one dialog, not a permanent tax — the grant persists; a full multi-question conversation after the watcher's removal cost zero further prompts.
- The “storm” was self-inflicted. ~100 dialogs in two minutes on the QA machine came from the watcher's queue outrunning the researcher's clicking, which presented as “Allow doesn't stick” and was briefly misdiagnosed as per-access granting.
- The transferable lesson — worth more than the fix: a permission measured from a shell is not the same measurement as that read performed by another app's process. Verify from the process that will actually do it.
- Still owed: pre-announcing the prompt in the pane's Claude Desktop hint. A prompt you were told to expect reads as a boundary working; an unannounced one reads as a fault — and this one arrives at the exact moment we promised “done”.
5 · The decision to take SUPERSEDED BY §5a
One connector. It reads whatever project Bristlenose is showing.
Switch project in Bristlenose, and the agent's subject switches with it.
| Matches “ask about what I'm looking at” |
| Nothing to reconfigure, ever |
| Per-project Anonymise still applies correctly |
| Subject can change mid-conversation |
The extension is bound to one study at install time.
Claude Desktop collects the choice through its
own settings UI (user_config).
| Narrow, explicit grant |
| Subject can never change under you |
| Per-project setup returns — the thing we're removing |
| Two studies = two installs? |
- “The researcher can already see both projects on the same trackpad” is true and irrelevant. What changes isn't who can see the data — it's whether it crosses the network. That's the line they signed something about.
- v1 = one project, picked through Claude Desktop's own directory picker.
- Phase 2 = a folder grant, which is the better end state: grant a folder, then move projects in and out of it. Exposure becomes a drag — the most legible consent gesture there is — and it unlocks asking questions across a whole folder.
user_config, no Claude Desktop directory picker, no “two studies = two installs”.
- The argument in this box survives intact — it is still the reason the shipped model needs an allowlist at all. The Ollama-only study, the warning riding the same message as the breach: all of it holds. What changed is the mechanism chosen to satisfy it.
- The allowlist beats the pin on both axes, which is why it won: it keeps A's
zero-friction feel for projects you did share, and it makes the secret study safe by
construction rather than by pinning away from it. It also sidesteps two live
.mcpbbugs for free —user_configmangles values containing$, and a reinstall loses stored configuration. A manifest with no user config cannot be bitten by either. - Phase 2 (the folder grant) is untouched by this and still the better end state.
5a · Two surfaces, two jobs THIS IS WHAT SHIPPED
Setup once, then share per project. The unlock: enablement is Bristlenose writing the
handshake file, not Claude Desktop's config. That means BN owns it completely — no
user_config, no directory picker, no “one install = one project”.
- The secret Ollama-only study is safe by construction: never shared, no handshake, invisible.
- Residual risk shrinks to switching between projects you deliberately shared — which is exactly what the in-band announcement above is for.
- Naming, decided: Turn On / Turn Off Agent Access. Never a vendor — several agents can be connected at once, so “Share with Claude” would be factually wrong, not merely narrow. And not “Share”, which macOS has already claimed for the share sheet.
- Doesn't fix the TCC question — the proxy still reads a file BN wrote. That spike still goes first.
The badge — three states, and two that were cut
| State | Meaning | Treatment |
|---|---|---|
| Off | Cannot be reached, ever | No badge — absence is the information |
| On, project not open | Open it and it's reachable | Pale / outline |
| On, project open | Exposed now | Solid |
- Also cut: a flourish when a query lands. They asked the question; they know.
- Deliberately off the badge: the Anonymise state. “Exposed with names” vs “codes only” is a real governance difference, but it's a second axis, and two axes on one glyph is unreadable. Tooltip and the Settings list.
- Known hole: during a run the activity ring takes this slot and wins, so an exposed project shows no antenna. Exposure is still true, just invisible — acceptable, but as a decision rather than a discovery.
- A second, unplanned hole shipped and had to be fixed. The badge was invisible in precisely the ordinary case — shared, analysed, not currently running — because a clean idle row takes the sidebar's single-line collapse, and that layout had no right-hand slot at all. The run ring and the iCloud glyph never noticed, because both of those states always carry subtitle text; the antenna is the first thing that has to render on an otherwise-silent row. The rule to carry forward: check a new sidebar affordance against the collapsed row, not the populated one. The quiet row is the common row.
- The pale tier is only possible because the flag lives host-side. The obvious move
was to put it in the project database beside the Anonymise switch — but that database is
only readable while that project's serve is running, so the app could never render
“on, but not open”. It belongs in
projects.jsonwith the other host-side facts (path, icon, folder). Same reason the Settings list can show every project's state without booting a serve for each one.
The project context menu — decided MAC REVIEW
Drawn from
buildProjectMenu. Conditional items appear only in their state, each with its own
separator; the stable tail is below.
- Music.app ships both
Turn On Home SharingandTurn Off Home Sharing— a network-exposure boolean as a symmetric verb swap. That is exactly our class. Notes does the same for governance (Lock Note/Remove Lock). - Finder uses a checkmark for
Use Stacks— but that's a view mode, and the same file verb-swapsShow Sidebar/Hide Sidebar. Within one Apple app: checkmark for modes, verb swap for exposure. - The argument that decides it: an unchecked checkmark item looks identical to an ordinary action. In the OFF state “Agent Access” would sit among Rename and Choose Icon looking like one of them. The misread — clicking a checked item believing you're enabling, and silently revoking — is the one we can't afford.
- State display isn't lost, because it was never this menu's job: the antenna is permanent while exposed, and Settings ▸ MCP Agents is the audit list. The menu is the act.
- Apple lists Share beside Copy and Delete as an action with one meaning and
one glyph everywhere. Borrowing it obliges
square.and.arrow.up; using the antenna instead breaks that contract, and using the share glyph would lie about what happens. - Wrong tense. Share is punctual — I sent a thing, it went. This is a standing permission to read, pulled repeatedly by whoever is connected. Exactly the misreading a contractually-barred study cannot afford.
- Rejected alongside: Expose to Agents (security-incident register), Readable by Agents (engineer-speak, negates badly), Allow Agents (allow them to what?). Agent Access is already the app's own noun — Settings ▸ MCP Agents.
- Title case, no ellipsis (it acts immediately), no vendor — several agents can be connected at once.
Connect Agent… sat — adjacent to Show in Finder it inherited that item's
reading, two neighbours about this project's files, one revealing them in Finder and one letting
a cloud vendor read them. That adjacency asserts an equivalence that isn't true.
- Everyday items keep the top (HIG: most-frequent first). Flipping exposure is once per study.
- It lands at exactly three groups in the common case — HIG's guidance for context menus is “no more than about three”.
- Hide it, don't dim it, when the project can't be shared — the context-menu rule (“displays only the actions that are relevant”), which the file's own lifecycle block already follows and the trailing block breaks.
- Gate on the right predicate:
canShowInFinderis file-presence, so a never-analysed folder passes it with no quotes to read. NeedscanShareWithAgents— locatable and analysed.
“Turn on access when I have no agent?” — a permission, not a connection
| Condition | Can we know? | What the menu does |
|---|---|---|
| Project not analysed / not locatable | Yes | Hide the item. Nothing to read; a context menu shows only relevant actions. |
| This build has no MCP support | Yes — mcp.mounted | Hide the item. Genuinely impossible, permanently, for this build. |
| No agent installed | No | Show it. Turning it on is not an error. |
- We can't know. The extension lives in Claude Desktop's container, and reading a foreign app's install state is the fragility class this whole plan exists to avoid. Hiding on a fact we don't have would make the feature invisible to someone who does have an agent.
- And the gate would be backwards. “Has an agent ever connected?” is knowable — but the first-time order is turn access on, then ask a question. Gating on it blocks the exact flow it's meant to enable.
- It's a permission, not a connection. Turning it on with nothing installed is an unused permission, which is coherent. System Settings ▸ Sharing is the precedent: enable Screen Sharing with no client anywhere and macOS doesn't hide, grey or nag — it turns on and shows you the address.
- Where “you need an agent” lives instead: Settings ▸ MCP Agents, which is where install already lives. That's discovery's job, not the toggle's.
Where the control lives — decided: both SHIPPED AS ONE
A checkmark item, exactly where Show in Finder lives. One gesture, no window.
The whole posture on one screen — the answer to “what have I exposed?”
Right-click toggles one project. Settings ▸ MCP Agents lists every project with its state, alongside the install button and connection status.
- Precedent: macOS does exactly this for Sharing, Login Items and Screen Time — act in context, audit in Settings.
- The badge stops meaning “an agent asked just now” and starts meaning “this is exposed” — permanent while shared and open. That's the fact worth showing: the researcher already knows they just asked a question; what they can't otherwise see is that a project is reachable at all. It makes the sidebar an audit surface — what's exposed right now — pairing with Settings, which answers what have I shared.
- Supersedes what's built: the shipped badge reads
mcp.active, a 120-second activity window. Exposure is a different input.
- The argument above defeated itself. The bullet directly preceding this says the sidebar becomes an audit surface — so a Settings list rendering the same fact is a duplicate, not a pairing. “What have I exposed?” is answerable from the sidebar, which was the whole point of making the badge mean exposure rather than activity.
- Two consequences worth holding. The pale tier now carries the entire “what have I shared?” question alone, which raises its value and makes the invisible-badge bug above the more serious for having shipped at all. And with Anonymise global, the tooltip is the only place the names-vs-codes distinction can live — so reinstating a per-project Anonymise would reopen the badge's second-axis question with it.
- The rejected framing to keep rejected: this is one concept, one home. The list's real appeal was that it looked like a governance dashboard, and the sidebar already is one.
6 · For the morning ALL ANSWERED
- 1 · Scope model — neither. Decided B; shipped A-plus-allowlist (§5a).
- 2 · Replace or sit beside? — replace, as recommended. Hand-paste is gone from the product entirely.
- 3 ·
bristlenose mcp-proxy? — no. Not in that pass, not since. Still the cheapest available win: one subcommand wrapping a proxy that already exists and is already tested. - 4 · Directory too? — no, as recommended. Local install only.
- 5 · The parked project — resolved by the allowlist plus fronted-only naming: the handshake names the fronted, turned-on project, so a parked sidecar is never what the agent reaches. The correctness bug the review flagged does not arise.
- 6 · Where does this live? — option 2 and further. Not a split by lifetime but a replacement: the per-project sheet retired outright rather than keeping a status line and a “Set Up…” link.
- 7 · The spike — the one answer that was wrong. See the correction in §4.
bristlenose mcp-proxy in the same pass? It makes Claude
Code and Codex address-free and token-free too — the §2 sleeper win. Costs one CLI subcommand
wrapping the proxy that has to exist anyway.user_config is per-extension, identity keyed on a unique manifest name).
Of the six jobs the sheet does — install, point-at-a-project, the Claude Code / Codex command, the
raw primitives, Anonymise, and the badge — only install is machine-wide. That single
mismatch is the whole problem.
| Option | Verdict |
|---|---|
| 1 · App-level only Settings ▸ Connections, project picker inside |
Four of six jobs are per-project, so it re-asks “which project?” — a selection the sidebar already makes. |
| 2 · Split by lifetime Install → Settings; sheet keeps the rest |
Recommended. Two places, but two genuinely different lifetimes. The Claude Desktop tab becomes instructional — the folder path plus Reveal in Finder, which is exactly what's needed in Claude's picker. |
| 3 · Menu bar only One window, project selector on top |
Self-contained, but duplicates the sidebar's job of expressing which project you mean. |
| 4 · The antenna badge is the entry point | Flagged, not proposed — collides with the settled sidebar rule that status is attention, not affordance (the Mail model). |
| 5 · Leave as-is Keep the right-click |
Cheapest, and honest if the copy says what to do in Claude Desktop. The right answer if we want zero structural change before the TCC spike settles. |
SystemPolicyAppData
protects Apple's own app containers (Notes, Safari, Mail all denied) but not third-party ones
(Bristlenose, Adobe and others readable, from a shell with no Full Disk Access). Same-UID is the
actual gate, and the proxy runs as the researcher.
- No prompt, no sticky denial — false as measured. The prompt fires the first time Claude Desktop's Node reads the container. It fires once and the grant persists, so the design survives — but the shell measurement above didn't transfer, because responsible-process attribution is the whole variable. The rest of this bullet holds: the handshake stays in the container, so no token is ever written into a project folder and the Dropbox-sync problem never arises.
- The mechanism was proved end to end too: real data through a 90-line, zero-dependency proxy, three states, self-heal, and a wrong token correctly rejected. Shipped at 308 lines, still zero-dependency — the growth is hardening, and three of the additions exist because this spike's caveat came true.
- The one-way-boundary rule at the end of §5a held completely — we can offer, we cannot
observe — and shipped in a place nobody planned: the Claude-Desktop-absent check asks
LaunchServices whether anything opens a
.mcpb, never whether Claude Desktop is installed. The rule applied at exactly the point where breaking it would have been easiest.