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
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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue