docs(m1): record Cowork probe results and degradation branch

Plan Step 1. Builds the probe vehicle and the checklist; does NOT answer it.

The four questions all require the operator in a live Cowork session on this
Mac, and that is a fact about the measurement rather than a caveat: measured
2026-09-04, the session mode is not readable from disk (cowork_settings.json
carries only extraKnownMarketplaces; the desktop config carries
coworkNetworkMode and a trusted-folder list, neither states where a session
runs), and the other three each need a plugin installed in a live session.

The step's Verify is therefore RED on purpose and stays red until the operator
answers:
  test "$(grep -cE '...' docs/cowork-probe.md)" = 4   -> exit 1 (0 of 4 matched)
Validated in both directions: a fully answered copy exits 0, a copy with three
of four answered exits 1. The gate can pass, so its failure means something.

Probe vehicle: jobbsok-probe, exactly four files under
tests/fixtures/cowork-probe/ -- manifest, skills/probe-versjon/SKILL.md,
.mcp.json declaring the stdio server probe-tools, and probe_tools.py (standard
library only). Smoke-tested from Claude Code: initialize handshake completes,
tools/list returns probe_ping, tools/call returns an interpreter version, an
unknown tool name is rejected. What is NOT verified is whether Cowork ever
starts it -- that is the question.

Archive is built from an explicit four-file include list, never zip -r over the
repo root; verified to contain no .git, STATE.md, .venv or .claude/ entry.

The degradation branch for each possible nei is written down now, before any
answer is known, so it is not decided under pressure later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-04 17:22:59 +02:00
commit 0f92b2a1c3
5 changed files with 307 additions and 0 deletions

View file

@ -0,0 +1,5 @@
{
"name": "jobbsok-probe",
"version": "0.0.1",
"description": "Throwaway probe plugin: measures whether a plugin-declared stdio MCP server appears in a Cowork session, whether a skill can invoke python3, and whether ${CLAUDE_PLUGIN_ROOT} resolves. Not part of the jobbsok plugin."
}

8
tests/fixtures/cowork-probe/.mcp.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"mcpServers": {
"probe-tools": {
"command": "python3",
"args": ["${CLAUDE_PLUGIN_ROOT}/probe_tools.py"]
}
}
}

View file

@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Minimal stdio MCP server for the Cowork probe (plan Step 1).
Standard library only, and deliberately so: the point of the probe is to learn
whether a plugin-declared stdio server appears in a Cowork session at all. A
server that failed because a dependency was missing would answer a different
question than the one being asked.
Exposes one tool, ``probe_ping``, which reports the interpreter that is actually
running it. Transport is newline-delimited JSON-RPC 2.0 over stdin/stdout, which
is what MCP stdio means; nothing is written to stdout except protocol messages.
"""
import json
import sys
SERVER_NAME = "probe-tools"
SERVER_VERSION = "0.0.1"
DEFAULT_PROTOCOL_VERSION = "2024-11-05"
TOOLS = [
{
"name": "probe_ping",
"description": (
"Return the interpreter version and executable path of the process "
"serving this tool. Used to establish whether a plugin-declared "
"stdio MCP server runs at all in a Cowork session."
),
"inputSchema": {"type": "object", "properties": {}, "additionalProperties": False},
}
]
def log(message):
"""Diagnostics go to stderr; stdout carries protocol traffic only."""
print("[probe-tools] %s" % message, file=sys.stderr, flush=True)
def ping_payload():
return {
"server": SERVER_NAME,
"server_version": SERVER_VERSION,
"python_version": sys.version,
"python_executable": sys.executable,
}
def handle(request):
"""Return a response dict, or None for a notification (no reply expected)."""
method = request.get("method")
req_id = request.get("id")
if req_id is None:
# A notification. `notifications/initialized` is the one we expect.
return None
if method == "initialize":
params = request.get("params") or {}
# Echo the client's protocol version when it offers one; guessing a
# newer version than the client speaks is how handshakes fail silently.
protocol_version = params.get("protocolVersion") or DEFAULT_PROTOCOL_VERSION
result = {
"protocolVersion": protocol_version,
"capabilities": {"tools": {}},
"serverInfo": {"name": SERVER_NAME, "version": SERVER_VERSION},
}
elif method == "tools/list":
result = {"tools": TOOLS}
elif method == "tools/call":
params = request.get("params") or {}
if params.get("name") != "probe_ping":
return {
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": -32602, "message": "unknown tool: %r" % params.get("name")},
}
result = {
"content": [
{"type": "text", "text": json.dumps(ping_payload(), indent=2, sort_keys=True)}
],
"isError": False,
}
elif method == "ping":
result = {}
else:
return {
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": -32601, "message": "method not found: %r" % method},
}
return {"jsonrpc": "2.0", "id": req_id, "result": result}
def main():
log("started on %s" % sys.version.replace("\n", " "))
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
request = json.loads(line)
except ValueError as exc:
log("undecodable line: %s" % exc)
continue
response = handle(request)
if response is not None:
sys.stdout.write(json.dumps(response) + "\n")
sys.stdout.flush()
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,57 @@
---
name: probe-versjon
description: Report what this probe plugin can actually see from inside a Cowork session - its own manifest version, the resolved value of ${CLAUDE_PLUGIN_ROOT}, and whether python3 can be invoked at all. Use when running the jobbsok Cowork probe, or when the operator asks to check Cowork session mode, host MCP visibility, python3 availability or plugin-root resolution.
---
# probe-versjon
This skill exists to answer three of the four questions in
`docs/cowork-probe.md`. It measures; it decides nothing.
Run all three checks and report each result verbatim. Do not summarise, do not
infer a result you did not observe, and do not fill in a value from memory of
how plugins usually behave — an unmeasured answer is worse than no answer here,
because the whole point of the probe is that these behaviours are unknown on
this machine.
## 1. Manifest version
Read `${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json` and report the `version`
field. Expected: `0.0.1`. A different value means the session served a cached or
different build, and every other result below is about that other build.
## 2. Plugin-root resolution
Report the value `${CLAUDE_PLUGIN_ROOT}` resolved to.
- If it is an absolute filesystem path: the answer to `- CLAUDE_PLUGIN_ROOT:` is `ja`.
- If it is the literal, unexpanded string `${CLAUDE_PLUGIN_ROOT}`, or empty: the answer is `nei`.
## 3. python3 invocation
Run exactly:
```bash
python3 -c 'import sys; print(sys.version)'
```
Report whether the command ran and what it printed.
- If it ran: the answer to `- python3:` is `ja`, followed by the reported version.
- If it could not run (no such command, blocked, sandbox refusal): the answer is
`nei`, followed by the error text.
## Reporting
Print the three results as a block the operator can copy into
`docs/cowork-probe.md`:
```
- python3: <ja|nei> <version or error>
- CLAUDE_PLUGIN_ROOT: <ja|nei> (<resolved value>)
manifest version: <value>
```
The fourth question (`- Host-MCP:`) is not this skill's to answer: it is about
whether the `probe-tools` connector appears in the session, which the operator
reads off the Cowork UI and confirms by calling `probe_ping`.