writeonce/docs/examples/mcp-think/test_client.py
shoney.arickathil 49872a4b11 docs: six-bucket status board; recover lost vision doc; stop ignoring docs/
- 00-kanban.md rebuilt: ▶ NEXT PLAN pointer (iteration 4 — emitter, corpus,
  `woc build`) then six buckets — stories, in progress, done, pending,
  discarded, learnings. It tracked only the Rust runtime before, so the whole
  OOP track (wovm shipped, woc Tasks 1-8 shipped) was invisible.
- Board now records iteration 3's known gaps instead of silently owing them:
  `?T` plumbed but unenforced; E205/E201/E203/E204 dead, so structural
  interface satisfaction is unchecked.
- New discarded.md — settled rejections with reasons so they are not
  re-proposed: inheritance, `abstract` newtypes, Money/SKU/Float, Dynamic/cast/
  macro/extern, AOT-to-C, Menhir, shared engine state, external deployer.
- RECOVERED docs/plan/exploration/blue-green-vm/00-vision.md — gone from disk,
  never committed (gitignored path), cited by five docs incl. principle 12.
- Root cause was broader: all seven forward-roadmap plans in
  docs/superpowers/plans/ were untracked and ignored, on one disk only. Rules
  were half-fiction — 33 of 34 exploration files were already tracked, so they
  swallowed only *new* files.
- Dropped the docs ignore rules (exploration, oop-vm, superpowers/plans,
  examples/agent-loop, examples/mcp-think) with a do-not-re-add comment; added
  __pycache__/*.pyc. `tests/` stays ignored but warns that the next plan lands
  the corpus there.
2026-08-10 21:52:59 +02:00

85 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Smoke-test mcp-think over stdio, exactly the way Claude drives it.
Launches server.py as a subprocess (via `uv run --with mcp`), performs the
MCP lifecycle (initialize → initialized), lists the tools, and calls
`think` once. Requires Ollama running with the configured model pulled.
Usage: python3 test_client.py # standard library only
THINK_MODEL=qwen3:0.6b python3 test_client.py
"""
import json
import os
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
def main() -> int:
proc = subprocess.Popen(
["uv", "run", "--with", "mcp", "python3", os.path.join(HERE, "server.py")],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
env=os.environ.copy(), text=True,
)
stdin, stdout = proc.stdin, proc.stdout
assert stdin is not None and stdout is not None
msg_id = 0
def send(method: str, params: dict | None = None, notify: bool = False) -> dict:
nonlocal msg_id
msg: dict = {"jsonrpc": "2.0", "method": method}
if params is not None:
msg["params"] = params
if not notify:
msg_id += 1
msg["id"] = msg_id
stdin.write(json.dumps(msg) + "\n")
stdin.flush()
if notify:
return {}
# stdio transport: one JSON-RPC message per line
line = stdout.readline()
if not line:
raise RuntimeError("server closed the pipe")
return json.loads(line)
try:
r = send("initialize", {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "mcp-think-smoke", "version": "0"},
})
server = r["result"]["serverInfo"]
print(f"initialize ok — server: {server['name']}")
send("notifications/initialized", {}, notify=True)
r = send("tools/list", {})
tools = [t["name"] for t in r["result"]["tools"]]
print(f"tools/list ok — {tools}")
assert {"think", "critique", "brainstorm", "summarize"} <= set(tools)
print("tools/call think(...) — waiting on the local model…")
r = send("tools/call", {
"name": "think",
"arguments": {"task": "In one sentence: why do write-ahead logs "
"fsync before acknowledging a commit?"},
})
content = r["result"]["content"][0]["text"]
print(f"\n--- local model says ---\n{content}\n")
if content.startswith("[mcp-think]"):
print("NOT OK — the server answered, but the local model is unavailable.")
return 1
print("OK — MCP lifecycle, tool discovery, and local-model call all work.")
return 0
finally:
stdin.close()
proc.terminate()
proc.wait(timeout=5)
if __name__ == "__main__":
sys.exit(main())