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:
parent
2f99ac5951
commit
0f92b2a1c3
5 changed files with 307 additions and 0 deletions
5
tests/fixtures/cowork-probe/.claude-plugin/plugin.json
vendored
Normal file
5
tests/fixtures/cowork-probe/.claude-plugin/plugin.json
vendored
Normal 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
8
tests/fixtures/cowork-probe/.mcp.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"probe-tools": {
|
||||
"command": "python3",
|
||||
"args": ["${CLAUDE_PLUGIN_ROOT}/probe_tools.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
114
tests/fixtures/cowork-probe/probe_tools.py
vendored
Normal file
114
tests/fixtures/cowork-probe/probe_tools.py
vendored
Normal 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())
|
||||
57
tests/fixtures/cowork-probe/skills/probe-versjon/SKILL.md
vendored
Normal file
57
tests/fixtures/cowork-probe/skills/probe-versjon/SKILL.md
vendored
Normal 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`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue