Both were caught by running the check against a state whose answer was already
known from the screen, which is the only reason they were caught at all. A
check that reports PENDING for a thing that has plainly happened is worse than
no check: it invites the reader to distrust the instrument or, worse, to
believe it.
1. It looked for a directory named after the plugin. Cowork installs into
rpm/plugin_<opaque id>/, so the name appears in no path -- only in
rpm/manifest.json, which is now what gets read. The old code also used an
unquoted $(find ...) in a for-loop over a path containing "Application
Support", so the space split every path in half. That is the exact
word-splitting failure this machine's shell rules warn about, and it made
the check report "not installed" while the server was running.
2. It expected ~/Library/Logs/Claude/mcp-server-<name>.log. That naming is for
Claude Desktop's own connectors; a plugin's MCP server is logged by
LocalMcpServerManager into main.log. An absent file at a guessed path is a
statement about the guess, not about the world.
What the corrected check now reads, and what it establishes:
rpm/manifest.json -> jobbsok-probe, plugin_01NxHfqM495jtJzjVjRvgWDm,
marketplace "My Uploads", installed 2026-09-04T16:48:20Z
installed copy -> 4 files, manifest version 0.0.1 (no stale cache)
main.log 18:48:28 -> Connecting to plugin:jobbsok-probe:probe-tools
negotiated protocol version: 2025-11-25
Connected ... (1 tools)
process -> pid 12997, /usr/local/bin/python3, a child of Claude.app
So a plugin-declared stdio MCP server does spawn and connect on this Intel Mac,
on the host interpreter rather than in the sandbox. The negotiated version also
vindicates one design choice: probe_tools.py echoes the client's protocolVersion
instead of asserting its own default, and the client asked for 2025-11-25, which
is newer than the default the server would otherwise have claimed.
Still open, and deliberately not inferred from the above: whether a tools/call
to probe_ping actually returns. main.log records the connection lifecycle, not
individual calls, so that one is confirmed in the chat or not at all -- the
script says so rather than treating a missing line as an answer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The probe's four questions are answered by the operator in Cowork, but three of
them leave traces on this Mac, and a trace is a measurement where "what did you
see in the UI" is a recollection. This reads the traces so each step can be
verified before the next one starts.
What it can actually see, discovered by measurement rather than assumed:
- Claude Desktop writes one log per MCP server, named mcp-server-<name>.log,
and it records the full JSON-RPC handshake (verified against the existing
mcp-server-filesystem.log: initialize, notifications/initialized, tools/list
all appear as "Message from client: method=..."). So if Cowork spawns
probe-tools, the entire exchange is readable from disk -- including the
server's own stderr line, which carries the interpreter version.
- The absence of that log file is itself informative: it means Cowork never
attempted to start the server, which is a different finding from starting
and failing.
- pgrep on probe_tools.py confirms a live process, but its absence is not a
nei: a server may be spawned on demand. The log is the record; the process
check is corroboration. The script says so rather than over-claiming.
The Cowork store search is bounded to the session directory by name plus the
small manifests. The first version ran a recursive content grep over the whole
Claude support directory and did not finish inside two minutes -- it walks
caches and VM images. A check that hangs is not a check. Bounded version runs
in 0.17s.
It reports, it does not gate: exit 0 when everything has landed, 1 while
anything is pending. Baseline now: vehicle and archive done, three pending.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>