Sidebar · exposure and activity, per project
The user-facing consequences of plan v2's decisions D1–D9
(docs/design-mcp-native-proxy.md §6.9), drawn at the shipped 660pt Settings width,
so they can be judged on UX as well as engineering grounds. 29 Sep 2026. Strings are the
shipped en keys unless a commentary block lists them as new.
Engineering read of D1–D9, before the pictures. Five decisions I'd take as written, two that need an amendment, and one new risk the plan doesn't name yet.
| # | Verdict | Why, and what it costs the user |
|---|---|---|
| D1 | Take | Fresh ids cost nothing, and the user never sees them. The number of signer classes is a P0.2 measurement, not a design choice: the local Apple Distribution build and the Apple-re-signed TestFlight/App Store build can share an id and still hang the helper (§6.6). In practice only the maintainer's Mac holds both, so the hazard is ours, not users'. |
| D2 | Take | A few seconds of swiftc per Release build buys "the helper is always as fresh as the host". A Debug build has no native path, so a Cmd+R build can't exercise it. That makes the manual P6 pass on a Release build a requirement, not a nicety. |
| D3 | Take | One gate over both copies is the right shape, with file-then-grep for the SIGPIPE trap. Invisible to users. |
| D4 | Take, with one question | Fail closed is right. But ask the server too: when Agent Access goes off, does serve refuse that project's MCP calls whatever the handshake says? If it does, a stale group copy only points a proxy at a door that's shut. If it doesn't, D4 is the only guard, and the unlink must be verified. Not checked yet. |
| D5 | Take | With no new diagnostics, the proxy's sentences have to carry the recovery on their own. Section 6 shows them. The native "can't reach" sentence is vaguer than today's Files & Folders one ("install again, or check for an update"), because it covers three different build defects, each of which D3 should stop from shipping. |
| D6 | Take | One commit, one release, one tester, and rollback is a revert. The cost is section 7: in that release, every existing Claude user needs one reinstall. |
| D7 | Take; the refusal needs copy | A marketplace inside the bundle is the only readable place. It is a .dmg-only case: the App Store never translocates. The refusal is drawn in state 2.6. |
| D8 | Take | Per-host state keyed by our own label keeps "know the protocol, never the client" intact. It inherits today's honest blind spot: until the first question arrives, the button still says Install, because we can’t see ChatGPT’s or Claude’s state. Section 2 shows it. |
| D9 | Take | External TestFlight before the store costs one review cycle, and it is the only way a human reviewer sees the helper before the public does. |
| new | Add to P0.4 | Where does the runtime .mcpb live when Claude opens it? If it's written to our temporary directory, which sits inside our sandbox container, then Claude reading it is exactly the cross-team container read macOS 27 denies. The Install dialog could then fail in Claude with no message from us. P0.4 must test the real path, not an unzip we control. Fallbacks, in order: a location Claude can read (the bundle can't hold it, because Apple re-signs the helper after the zip is made), or an explicit save. Inferred, not measured. |
The plan changes how an agent reaches Bristlenose. It does not change how Bristlenose tells the researcher what's exposed and what's being read. Everything in this section ships today (0.23.0–0.31.5), is pinned by tests, and appears unchanged in every frame below.
Sidebar · exposure and activity, per project
Project menu and sidebar context menu · the verb swaps
Agent Access is on for this project.
| Shipped mechanism | What it tells the researcher | What drives it | What the native plan must do |
|---|---|---|---|
| Antenna, solid | An agent could read this project right now | ServeFleet.handshakeProjectPaths: the handshake writer's own set | P1 writes the group copy from the same set, in the same call. D4's fail-closed rule stops the group copy outliving the solid antenna |
| Antenna, radiating, then two taps | An agent is reading this project now, and then that it has finished | serve's per-project tool-call counter (/api/agent-activity) | Nothing: the helper calls the same /mcp/ with the same bearer, so every call counts. P6 checks it: a question from ChatGPT and from Claude each radiates the right project |
| Projects register: Active / Available when opened, tick, Sessions, Last asked, roll-up, receipts, empty state, scope note | What's shared, what could be, what was asked and when; untick to revoke, re-tick to undo | AgentProjectRegister + projectsSection | Untouched by every phase. The P5 switch-over edits the top half of the pane only; AgentProjectRegisterTests and MCPAgentsSettingsViewTests must pass unedited |
| Turn On / Turn Off Agent Access, the badge tooltip, the consent sheet | Per-project permission, granted where the project lives | AgentAccessPolicy, the menu verbs, consent v2 (already client-neutral) | Untouched |
| Anonymise | Participant names removed from everything an agent reads | serve (mcp_server.py) | Untouched: the helper is a pipe |
| Install row: version compare and "asked recently" | Whether the agent's copy matches this build | X-Bristlenose-Proxy-Version on each call → agentProxyVersion | The helper sends the same header (the spike does, main.swift:256). D8 adds a host label beside it; it never replaces the per-project register |
Why this section exists. The first draft of this page drew the pane's top half in detail and collapsed the register into a one-line stub. That made it read as though the plan replaced the register. It doesn't, and it mustn't: the register and the antenna are the only places a researcher sees what agents can reach, and they took most of August to get right. Every Settings frame now carries the real register, in whichever state fits the story: typical, never asked, just asked, all closed, a receipt, empty.
Before · Claude Desktop tab (0.31.5, macOS 27)
Agents only see open projects
Adds Bristlenose to Claude Desktop as an extension. Claude Desktop will ask you to confirm.
The first time you ask a question, macOS blocks Claude from reading Bristlenose, without asking. To allow it, open System Settings ▸ Privacy & Security ▸ Files & Folders, expand Claude and turn on Bristlenose — then ask again.
Before · ChatGPT & Codex tab (0.31.5)
Agents only see open projects
In ChatGPT: Settings → MCP servers → Add server — or add to ~/.codex/config.toml (one entry covers ChatGPT desktop, Codex CLI, and the IDE extension):
This address changes when Bristlenose restarts — re-copy the config after relaunching.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
What's wrong with these today. Claude: on macOS 27 a fresh install fails its first question by design, and the user fixes it in System Settings. ChatGPT: the pasted config carries a port and a token that change on every relaunch, so the user re-copies after every restart. The one-click ChatGPT plugin is ready, but it needs Node.
2.1 · Ready — never asked (and just after installing)
Adds Bristlenose to ChatGPT as a plugin. ChatGPT will open and ask you to install it.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
2.2 · An agent has asked recently
Adds Bristlenose to ChatGPT as a plugin. ChatGPT will open and ask you to install it.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
2.3 · After a Bristlenose update, ChatGPT still runs the old plugin
Adds Bristlenose to ChatGPT as a plugin. ChatGPT will open and ask you to install it.
Installed copy is older: 0.32.0 (3950). In ChatGPT, choose ⋯ ▸ Uninstall, then Install plugin.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
2.4 · ChatGPT isn't installed
Adds Bristlenose to ChatGPT as a plugin. ChatGPT will open and ask you to install it.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
2.5 · ChatGPT too old to install by link
Adds Bristlenose to ChatGPT as a plugin. ChatGPT will open and ask you to install it.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
2.6 · .dmg app opened from Downloads (translocated), D7
Adds Bristlenose to ChatGPT as a plugin. ChatGPT will open and ask you to install it.
Move Bristlenose to your Applications folder and open it from there to install the plugin. ChatGPT can’t install it from where Bristlenose is running now.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
2.7 · "Or connect by hand" open
Adds Bristlenose to ChatGPT as a plugin. ChatGPT will open and ask you to install it.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
For Codex in a terminal or an IDE, add to ~/.codex/config.toml:
This address changes when Bristlenose restarts — re-copy the config after relaunching.
Consequences the pictures show.
.unknown and .matching map to Install) and D8 keeps it: we never say "Installed", because we can't see ChatGPT. The "it worked" signals are the shipped ones, and they arrive with the first question: the sidebar antenna radiates, the register's Last asked turns to Just now, and the row's subtitle says an agent asked recently (2.2). Benchmarked in section 8.codex:// handler is registered". A ChatGPT that has the handler but ignores marketplacePath isn't detectable (F23), so a minimum version check goes on top.New strings (would go into 21 locales): chatgptHint, installPlugin / reinstallPlugin / updatePlugin, pluginBuild, downloadChatGPT, updateChatGPT, moveToApplications, connectByHand, and a shorter chatgptCodexHow for the TOML route. The ellipsis stays on "Install Plugin…" because the command finishes in another app (F9).
After · any macOS, 15 to 27
Agents only see open projects
Adds Bristlenose to Claude Desktop as an extension. Claude Desktop will ask you to confirm.
After · the first launch after the switch-over, for an existing user
Agents only see open projects
Adds Bristlenose to Claude Desktop as an extension. Claude Desktop will ask you to confirm.
Installed copy is older: 0.31.5
fittingSize). The ChatGPT tab gets a little longer. Tab switches already refit, so neither is new behaviour.ChatGPT · today (Node installed; 0.31.5)
~/.codex/config.toml by hand, or find ChatGPT's MCP settings and pasteChatGPT · after (no Node, any macOS)
Claude Desktop · today (macOS 27, fresh install)
Claude Desktop · after (any macOS)
Step 3 · ChatGPT's plugin page (schematic)
Ask your Bristlenose study — quotes, themes, signals, frameworks — from your agent. Read-only.
MCP servers 1 · 0.32.0-3950 · Research
Step 4 · the toast, then a Work thread (schematic)
Measured vs inferred. The link, the plugin page, "Install plugin", the toast and "Try now" were all measured on 29 Sep with the spike, and so were Work mode and the per-tool approvals. What isn't measured: whether readOnlyHint removes the approval cards (work-list item 14, "not yet re-measured"), and the page's exact version label. Net: the ChatGPT path goes from 5+ steps with a hand-edited file and a re-copy after every relaunch, to 3 clicks. The Claude path on macOS 27 goes from 6 steps with a detour through System Settings to 3 clicks. Agent Access (step 1) is the same on both, and deliberately so.
Outcome A · the host updates in place (1 click there)
Outcome B · the host keeps the old copy
.mcpb today, Claude only previewed a same-named extension.)mcp.contract). The "outdated" sentence (section 6) only fires when it doesn't. So outcome B costs a detour only in a release that breaks the contract. The pane's "Update" is advice, not an alarm..olderRelease state), not in a new control.Bristlenose isn't running (both proxies, unchanged)
▸ bristlenose · search_quotes
Can't read the handshake · Node today, macOS 27
▸ bristlenose · search_quotes
Can't read the handshake · native, after
▸ bristlenose · search_quotes
Contract changed · outdated plugin (both)
▸ bristlenose · search_quotes
MSG_* table, which tells the model to "tell the person…". Claude and ChatGPT both relayed it faithfully in the 29 Sep runs.| Surface | 0.31.x (today) | The switch-over release | Who notices |
|---|---|---|---|
| Claude Desktop tab | Install Extension… + Files & Folders note and button on macOS 27; the "Allow" note on 26 | Install / Reinstall / Update Extension… only | Everyone on 27 (a shorter, calmer pane) |
| Existing Claude users | Node .mcpb; works on 26 with the grant, and on 27 once Files & Folders is on | Their old extension keeps working where it already works, until they click Update/Install. The pane says so on 26; on 27 it just says Install | All of them: one reinstall each |
| ChatGPT & Codex tab | TOML + Copy Config + Work note | Install Plugin… row + Work note; the TOML route folds under "Or connect by hand" | Every ChatGPT user; anyone on a pasted config still needs to re-copy after restarts until they switch |
| Register, antenna, Agent Access verbs, Anonymise | As shipped | Identical (section 0) | Nobody, by design |
| Files & Folders copy | On the pane (27), in the proxy, in the help page | Help page and the Node proxy only | Nobody new |
| Node dependency | ChatGPT plugin impossible without Node | None | ChatGPT users without Node: most of them |
| Rollback | — | Revert one commit, ship a patch; installed helpers keep answering, because the handshake format doesn't change | Maintainer |
The cost of no flag is that the first time anyone except the maintainer sees the native path is the release itself. D9's external TestFlight is the mitigation: it puts a human reviewer and any external tester on the build before the store does. The benefit is that no user ever sees a half-state (a native Claude tab next to a TOML ChatGPT tab, say), and there is no Diagnostics toggle to explain.
Five exemplars, read from their own pages on 29 Sep 2026: Apple Xcode ("Giving external agents access to Xcode"), DEVONthink 4.3, ACE Studio, Figma and Notion. None of the five documents a one-click route to both Claude Desktop and ChatGPT. Where a page is silent, it counts as silent, not as a failure.
| Measure | Best out there | Bristlenose, after the plan | Verdict |
|---|---|---|---|
| Steps to a first answer | Figma: a preferred route of about 4 actions per client (Codex: Plugins ▸ + ▸ Install ▸ Allow). Xcode: toggle + 1 Terminal command. ACE: 4–5 for ChatGPT, about 12 for Claude Desktop (hand-edited JSON + restart) | 3 clicks per client after Agent Access: Install… in Bristlenose, Install in the host, ask. No file, no terminal, no restart | Exceed |
| Runtime on the user's Mac | Xcode, DEVONthink and ACE ship a helper inside the app bundle. Notion falls back to npx mcp-remote | A signed, sandboxed helper inside the bundle. No Node | Match |
| Where you turn it on | DEVONthink: Settings ▸ AI ▸ MCP, a switch per provider. ACE: a per-client button that shows that client's steps. Xcode: one toggle | Settings ▸ MCP Agents, a tab per client, plus per-project Agent Access from the Project menu and the sidebar | Match |
| Preferred vs manual route | Figma labels one "preferred" route per client, with a manual fallback and deeplinks | Install… is the preferred route; "Or connect by hand" is the manual one. Both installs are links (codex://, .mcpb) | Match |
| Is it connected? Is it working? | Xcode: "alerts you when the external agent connects … and when it's active". Notion: every agent app listed as Connected / Not Connected, disconnect in-app. Figma: "enabled and running" confirmation | Active: the sidebar antenna (shipped). It is solid while a project is exposed, radiates while an agent is reading that project, and signs off with two taps when the agent has finished. It's per project, and it fires for every host. The register's Last asked column records it afterwards. Not covered: "connected" before the first question, which host asked, and the Settings install row, which still says Install after a successful install | Exceed on active |
| … and before the first question | Xcode's alert fires on connect, before any work | Nothing until the first question, and nothing that names the host | Short, narrowly |
| Scope control | DEVONthink: exclude one item or a whole database; encrypted databases excluded automatically; "Redact sensitive content". Notion: full user permissions | Per project, off by default; only open projects; Anonymise; read-only tools | Match DEVONthink per database; short of its per-item exclusion; exceed Notion |
| Errors and recovery | Notion and Figma: a troubleshooting page mapping each failure to one fix. None of the five speaks inside the chat | Every failure is one sentence naming one fix, spoken by the agent where the user is looking (section 6). The help page's troubleshooting table is still to be written | Exceed in context |
| ChatGPT Work vs Chat | ACE: "Standard Chat mode does not support local MCP", with screenshots of the mode selector and the Work toggle | One line in the pane. "Try now" lands in a Codex thread, which works. The help page has no screenshot yet | Match in the pane; short in the docs |
| After an app update | None of the five says anything (ACE: "may change between updates") | A version comparison says Update… and names the installed build. Outcome depends on P0.5 | Exceed |
| macOS permissions | None of the five mentions them. DEVONthink's one reported failure (config never written) looks like a sandbox problem and has no official answer | None needed on 15–27, which is measured on 27 and still P0.1 for 15/26 | Exceed, pending P0.1 |
Proposal · after the host has reached Bristlenose
Adds Bristlenose to ChatGPT as a plugin. ChatGPT will open and ask you to install it.
In ChatGPT, ask in Work mode and start with @Bristlenose — otherwise its tools aren’t used.
Already shipped · the sidebar antenna, per project
The antenna already answers "is it working?" (right-hand frame, drawn loosely: the real one is the SF Symbol antenna.radiowaves.left.and.right, animated through its three variable-value frames). It is solid for an exposed project, radiates while an agent reads it, and taps twice when the agent has finished. That beats Xcode's alert on two counts: it's per project, and it says when the agent has finished. Nothing in this plan should duplicate it, so the "is asking now" subtitle I first drew here is withdrawn.
What's left is narrow. The antenna can't show "connected" before the first question or which host asked, and the install row still says Install after a successful install. Two small changes close it, both facts about our own endpoint, not about the client:
initialize, the helper checks in once (/api/health, carrying its host label and build). The row can then say "ChatGPT connected today at 14:02" the moment "Try now" opens a thread. The helper writes nothing, so this isn't D5's rejected breadcrumb..matching becomes a bordered "Reinstall Plugin…", and at rest the pane has no filled button.Worth it? It's optional. For a researcher, the first question plus the antenna already proves it works. The check-in mostly fixes the Settings row, which would otherwise read as if the install hadn't taken.
The other shortfalls, and what I'd do. ChatGPT Work vs Chat in the docs: when the help page (work-list item 2) ships, give it ACE's treatment, with a screenshot of the Chat | Work toggle and one sentence on why Chat can't reach a local app. Per-item exclusion (DEVONthink): this is a product question, not part of this plan. A per-session "exclude from agents" would be the Bristlenose version. Park it.