Shared memory and context tools for agentic work.
Code Rooms
#!/usr/bin/env python3
"""Agent-first smoke test for the m1nd MCP stdio and HTTP surfaces.
The smoke proves the minimum trust loop an agent needs:
initialize -> tools/list -> trust_selftest -> session_handshake -> recovery_playbook when needed -> ingest -> seek -> help -> doctor
It intentionally talks JSON-RPC over Content-Length framed stdio instead of
calling Rust internals, so it catches transport/session issues that unit tests
can miss. If the host surface is missing a required recovery tool, the failure
is structured as degraded_host_tool_surface with a ready doctor payload. New
binaries expose the handshake as an MCP tool; older binaries use the local
harness fallback.
"""
from __future__ import annotations
import argparse
import json
import os
import select
import shutil
import socket
import subprocess
import sys
import tempfile
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any
SCHEMA = "m1nd-mcp-agent-smoke-v0"
HANDSHAKE_SCHEMA = "m1nd-session-handshake-v0"
TRUST_SELFTEST_SCHEMA = "m1nd-trust-selftest-v0"
RECOVERY_PLAYBOOK_SCHEMA = "m1nd-recovery-playbook-v0"
DEFAULT_QUERY = "where MCP tool schemas and runtime tool registry are declared"
REQUIRED_TOOLS = ("trust_selftest", "ingest", "seek", "help", "recovery_playbook", "doctor")
HANDSHAKE_REQUIRED_TOOLS = (
"health",
"trust_selftest",
"recovery_playbook",
"doctor",
"ingest",
"seek",
"help",
)
class SmokeFailure(RuntimeError):
def __init__(self, message: str, details: dict[str, Any] | None = None) -> None:
super().__init__(message)
self.details = details or {}
def find_free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(("127.0.0.1", 0))
return int(sock.getsockname()[1])
class McpStdioClient:
def __init__(self, binary: Path, runtime_dir: Path, timeout: float, cwd: Path) -> None:
self.binary = binary
self.runtime_dir = runtime_dir
self.timeout = timeout
self.cwd = cwd
self.proc: subprocess.Popen[bytes] | None = None
self.next_id = 1
self.read_buffer = bytearray()
def __enter__(self) -> "McpStdioClient":
self.proc = subprocess.Popen(
[str(self.binary), "--no-gui", "--runtime-dir", str(self.runtime_dir)],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=0,
cwd=str(self.cwd),
return self
def __exit__(self, _exc_type: object, _exc: object, _tb: object) -> None:
if not self.proc:
return
try:
if self.proc.stdin:
self.proc.stdin.close()
except OSError:
pass
if self.proc.poll() is None:
self.proc.terminate()
self.proc.wait(timeout=2)
except subprocess.TimeoutExpired:
self.proc.kill()
if self.proc.stderr:
self.proc.stderr.read()
def call_rpc(self, method: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
req_id = self.next_id
self.next_id += 1
request = {
"jsonrpc": "2.0",
"id": req_id,
"method": method,
"params": params or {},
}
self._write_message(request)
response = self._read_message()
if response.get("error"):
raise SmokeFailure(f"{method} returned JSON-RPC error: {response['error']}")
return response
def initialize(self) -> dict[str, Any]:
response = self.call_rpc(
"initialize",
{
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "m1nd-agent-smoke", "version": "0"},
},
return (response.get("result") or {}).get("serverInfo") or {}
def list_tools(self) -> list[dict[str, Any]]:
response = self.call_rpc("tools/list")
return (response.get("result") or {}).get("tools") or []
def call_tool(self, name: str, arguments: dict[str, Any]) -> dict[str, Any]:
response = self.call_rpc("tools/call", {"name": name, "arguments": arguments})
result = response.get("result") or {}
if result.get("isError"):
raise SmokeFailure(f"{name} returned MCP tool error: {result}")
content = result.get("content") or []
text = content[0].get("text") if content else None
if not isinstance(text, str):
raise SmokeFailure(f"{name} returned no text content")
return json.loads(text)
except json.JSONDecodeError as exc:
raise SmokeFailure(f"{name} returned non-JSON content: {text[:200]}") from exc
def _write_message(self, payload: dict[str, Any]) -> None:
if not self.proc or not self.proc.stdin:
raise SmokeFailure("MCP process is not running")
raw = json.dumps(payload, separators=(",", ":")).encode("utf-8")
self.proc.stdin.write(b"Content-Length: " + str(len(raw)).encode("ascii") + b"\r\n\r\n")
self.proc.stdin.write(raw)
self.proc.stdin.flush()
def _read_message(self) -> dict[str, Any]:
if not self.proc or not self.proc.stdout:
header = self._read_until(b"\r\n\r\n")
length = None
for line in header.decode("utf-8", errors="replace").split("\r\n"):
if line.lower().startswith("content-length:"):
length = int(line.split(":", 1)[1].strip())
break
if length is None:
raise SmokeFailure(f"response missing Content-Length header: {header!r}")
body = self._read_exact(length)
return json.loads(body)
def _read_until(self, marker: bytes) -> bytes:
deadline = time.monotonic() + self.timeout
fd = self.proc.stdout.fileno()
while marker not in self.read_buffer:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise SmokeFailure(f"timed out waiting for response header after {self.timeout}s")
ready, _, _ = select.select([fd], [], [], remaining)
if not ready:
chunk = os.read(fd, 4096)
if not chunk:
raise SmokeFailure("MCP process closed stdout while reading response header")
self.read_buffer.extend(chunk)
index = self.read_buffer.index(marker)
header = bytes(self.read_buffer[:index])
del self.read_buffer[: index + len(marker)]
return header
def _read_exact(self, length: int) -> str:
while len(self.read_buffer) < length:
raise SmokeFailure(f"timed out waiting for response body after {self.timeout}s")
chunk = os.read(fd, max(4096, length - len(self.read_buffer)))
raise SmokeFailure("MCP process closed stdout while reading response body")
body = bytes(self.read_buffer[:length])
del self.read_buffer[:length]
return body.decode("utf-8")
class McpHttpClient:
def __init__(self, binary: Path, runtime_dir: Path, timeout: float, cwd: Path, port: int) -> None:
self.port = port
self.base_url = f"http://127.0.0.1:{port}"
def __enter__(self) -> "McpHttpClient":
[
str(self.binary),
"--serve",
"--bind",
"127.0.0.1",
"--port",
str(self.port),
"--runtime-dir",
str(self.runtime_dir),
],
stdin=subprocess.DEVNULL,
self._wait_ready()
def _wait_ready(self) -> None:
last_error = ""
while time.monotonic() < deadline:
if self.proc and self.proc.poll() is not None:
raise SmokeFailure(f"HTTP MCP process exited early with code {self.proc.returncode}")
self.list_tools()
except SmokeFailure as exc:
last_error = str(exc)
time.sleep(0.1)
raise SmokeFailure(f"HTTP MCP server did not become ready after {self.timeout}s: {last_error}")
health = self._request("GET", "/api/health")
return {
"name": "m1nd-mcp",
"status": health.get("status"),
"domain": health.get("domain"),
"node_count_before_ingest": health.get("node_count"),
"edge_count_before_ingest": health.get("edge_count"),
payload = self._request("GET", "/api/tools")
return payload.get("tools") or []
payload = self._request("POST", f"/api/tools/{name}", arguments)
result = payload.get("result")
if not isinstance(result, dict):
raise SmokeFailure(f"{name} returned no JSON object result over HTTP: {payload}")
return result
def _request(self, method: str, path: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
body = None
headers = {"Accept": "application/json"}
if payload is not None:
body = json.dumps(payload).encode("utf-8")
headers["Content-Type"] = "application/json"
request = urllib.request.Request(
self.base_url + path,
data=body,
headers=headers,
method=method,
with urllib.request.urlopen(request, timeout=self.timeout) as response:
raw = response.read().decode("utf-8")
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise SmokeFailure(f"HTTP {method} {path} failed: {exc.code} {detail}") from exc
except OSError as exc:
raise SmokeFailure(f"HTTP {method} {path} failed: {exc}") from exc
return json.loads(raw)
raise SmokeFailure(f"HTTP {method} {path} returned non-JSON: {raw[:200]}") from exc
def default_binary(repo: Path) -> Path:
return repo / "target" / "debug" / "m1nd-mcp"
def summarize_ingest(payload: dict[str, Any]) -> dict[str, Any]:
"adapter": payload.get("adapter"),
"mode": payload.get("mode"),
"files_scanned": payload.get("files_scanned"),
"files_parsed": payload.get("files_parsed"),
"node_count": payload.get("node_count"),
"edge_count": payload.get("edge_count"),
"elapsed_ms": payload.get("elapsed_ms"),
def summarize_seek(payload: dict[str, Any]) -> dict[str, Any]:
results = payload.get("results") or []
"proof_state": payload.get("proof_state"),
"total_candidates_scanned": payload.get("total_candidates_scanned"),
"results_count": len(results),
"top_results": [
result.get("file_path") or result.get("path") or result.get("label") or result.get("node_id")
for result in results[:5]
def build_tool_surface_report(
tool_names: list[str],
min_tool_count: int,
required_tools: tuple[str, ...] = REQUIRED_TOOLS,
) -> dict[str, Any]:
available = sorted({name for name in tool_names if isinstance(name, str) and name})
missing_required = [name for name in required_tools if name not in available]
below_minimum = len(available) < min_tool_count
status = "degraded_host_tool_surface" if missing_required or below_minimum else "ok"
report: dict[str, Any] = {
"status": status,
"tool_count": len(available),
"min_tool_count": min_tool_count,
"required_tools": list(required_tools),
"required_tools_present": {name: name in available for name in required_tools},
"missing_required_tools": missing_required,
"available_tools_sample": available[:24],
"degraded_host_tool_surface": status != "ok",
if report["degraded_host_tool_surface"]:
report["recovery"] = {
"suggested_tool": "recovery_playbook" if "recovery_playbook" in available else ("doctor" if "doctor" in available else None),
"arguments": {
"agent_id": "m1nd-agent-smoke",
"observed_tool": "tools/list",
"observed_proof_state": "blocked",
"observed_tool_count": len(available),
"available_tools": available,
"missing_tools": missing_required,
"fallback": "if doctor is unavailable, restart or rebind the MCP host surface and use direct repo reads for final truth",
return report
def summarize_health(payload: dict[str, Any] | None) -> dict[str, Any] | None:
if payload is None:
return None
contract = payload.get("tool_surface_contract") or {}
alignment = payload.get("host_binding_alignment") or {}
"status": payload.get("status"),
"queries_processed": payload.get("queries_processed"),
"active_session_count": len(payload.get("active_sessions") or []),
"binding_fingerprint_schema": (payload.get("binding_fingerprint") or {}).get("schema"),
"tool_surface_contract_schema": contract.get("schema"),
"registry_tool_count": contract.get("registry_tool_count"),
"host_binding_alignment_status": alignment.get("status"),
def validate_health_surface_contract(payload: dict[str, Any]) -> None:
if contract.get("schema") != "m1nd-tool-surface-contract-v0":
raise SmokeFailure(f"health did not expose tool surface contract: {payload}")
required_host_tools = set(contract.get("required_host_visible_tools") or [])
if not {"health", "trust_selftest", "session_handshake", "recovery_playbook"}.issubset(required_host_tools):
raise SmokeFailure(f"health contract does not name required host binding tools: {contract}")
if alignment.get("schema") != "m1nd-host-binding-alignment-v0":
raise SmokeFailure(f"health did not expose host binding alignment guidance: {payload}")
def build_doctor_args_from_surface(agent_id: str, tool_surface: dict[str, Any]) -> dict[str, Any]:
recovery = tool_surface.get("recovery") or {}
arguments = dict(recovery.get("arguments") or {})
arguments["agent_id"] = agent_id
return arguments
def call_recovery_playbook(client: Any, agent_id: str, arguments: dict[str, Any]) -> dict[str, Any]:
playbook_args = dict(arguments)
playbook_args["agent_id"] = agent_id
playbook = client.call_tool("recovery_playbook", playbook_args)
if playbook.get("schema") != RECOVERY_PLAYBOOK_SCHEMA:
raise SmokeFailure(f"recovery_playbook returned unexpected schema: {playbook.get('schema')}")
steps = playbook.get("steps") or []
if not isinstance(steps, list) or not steps:
raise SmokeFailure(f"recovery_playbook returned no ordered steps: {playbook}")
if not playbook.get("binding_fingerprint"):
raise SmokeFailure(f"recovery_playbook returned no binding_fingerprint: {playbook}")
return playbook
def call_trust_selftest(client: Any, agent_id: str, arguments: dict[str, Any]) -> dict[str, Any]:
selftest_args = dict(arguments)
selftest_args["agent_id"] = agent_id
selftest = client.call_tool("trust_selftest", selftest_args)
if selftest.get("schema") != TRUST_SELFTEST_SCHEMA:
raise SmokeFailure(f"trust_selftest returned unexpected schema: {selftest.get('schema')}")
if not selftest.get("binding_fingerprint"):
raise SmokeFailure(f"trust_selftest omitted binding_fingerprint: {selftest}")
checks = selftest.get("checks") or {}
if not isinstance(checks, dict) or checks.get("binding_fingerprint_present") is not True:
raise SmokeFailure(f"trust_selftest returned unusable checks: {selftest}")
if not selftest.get("session_handshake"):
raise SmokeFailure(f"trust_selftest omitted session_handshake: {selftest}")
return selftest
def run_session_handshake_from_observed(
client: Any,
args: argparse.Namespace,
initialize: dict[str, Any],
tools: list[dict[str, Any]],
*,
run_probe: bool,
tool_names = [tool.get("name") for tool in tools]
available_tool_names = [name for name in tool_names if isinstance(name, str)]
tool_surface = build_tool_surface_report(
available_tool_names,
args.min_tool_count,
HANDSHAKE_REQUIRED_TOOLS,
can_ingest = "ingest" in available_tool_names
can_retrieve = "seek" in available_tool_names
can_recover = "recovery_playbook" in available_tool_names
can_diagnose = "doctor" in available_tool_names
health_payload = None
doctor_payload = None
recovery_playbook_payload = None
probe_payload = None
trust_mode = "full_trust"
next_action = "continue with m1nd-first retrieval; use compiler/tests for runtime truth"
if tool_surface["degraded_host_tool_surface"]:
trust_mode = "degraded_host_tool_surface"
next_action = (
"treat m1nd as orientation only, refresh the MCP binding, "
"and verify final truth with local files"
if can_recover:
recovery_playbook_payload = call_recovery_playbook(
client,
args.agent_id,
build_doctor_args_from_surface(args.agent_id, tool_surface),
if can_diagnose:
doctor_payload = client.call_tool(
elif "health" in available_tool_names:
health_payload = client.call_tool("health", {"agent_id": args.agent_id})
node_count = int(health_payload.get("node_count") or 0)
edge_count = int(health_payload.get("edge_count") or 0)
if node_count <= 0 or edge_count <= 0:
trust_mode = "needs_ingest" if can_ingest else "orientation_only"
next_action = "run ingest for the intended repo before trusting graph retrieval"
"observed_tool": "health",
"observed_candidates": 0,
"agent_id": args.agent_id,
validate_health_surface_contract(health_payload)
if run_probe and can_retrieve:
probe_payload = client.call_tool(
"query": args.query,
"top_k": min(args.top_k, 3),
"graph_rerank": True,
candidates = int(probe_payload.get("total_candidates_scanned") or 0)
results = probe_payload.get("results") or []
proof_state = probe_payload.get("proof_state")
if proof_state == "blocked" or candidates <= 0 or not results:
trust_mode = "stale_binding_suspected"
next_action = "call recovery_playbook with the probe payload, then follow its ordered steps"
recovery = probe_payload.get("recovery") or {}
doctor_args = dict(recovery.get("arguments") or {})
if not doctor_args:
doctor_args = {
"observed_tool": "seek",
"observed_proof_state": proof_state,
"observed_candidates": candidates,
doctor_args["agent_id"] = args.agent_id
doctor_args,
doctor_payload = client.call_tool("doctor", doctor_args)
"schema": HANDSHAKE_SCHEMA,
"trust_mode": trust_mode,
"can_ingest": can_ingest,
"can_retrieve": can_retrieve,
"can_recover": can_recover,
"next_action": next_action,
"initialize": initialize,
"tool_surface": tool_surface,
"health": summarize_health(health_payload),
"doctor": doctor_payload,
"recovery_playbook": recovery_playbook_payload,
"probe": summarize_seek(probe_payload) if probe_payload else None,
"used_probe": bool(probe_payload),
def attach_handshake_probe(
handshake: dict[str, Any],
available_tool_names: list[str],
if not args.handshake_probe or not handshake.get("can_retrieve"):
return handshake
if handshake.get("trust_mode") != "full_trust":
handshake["probe"] = summarize_seek(probe_payload)
handshake["used_probe"] = True
handshake["trust_mode"] = "stale_binding_suspected"
handshake["next_action"] = "call recovery_playbook with the probe payload, then follow its ordered steps"
handshake["doctor_recovery"] = {
"suggested_tool": "recovery_playbook" if "recovery_playbook" in available_tool_names else ("doctor" if "doctor" in available_tool_names else None),
"arguments": doctor_args,
if "recovery_playbook" in available_tool_names:
handshake["recovery_playbook"] = call_recovery_playbook(
if "trust_selftest" in available_tool_names:
handshake["trust_selftest"] = call_trust_selftest(
if "doctor" in available_tool_names:
handshake["doctor"] = client.call_tool("doctor", doctor_args)
def run_session_handshake_from_live_surface(
if "session_handshake" in available_tool_names:
handshake = client.call_tool(
"session_handshake",
"observed_tool_count": len(available_tool_names),
"available_tools": available_tool_names,
handshake["initialize"] = initialize
handshake["tool_count"] = len(available_tool_names)
if not run_probe:
return attach_handshake_probe(client, args, handshake, available_tool_names)
return run_session_handshake_from_observed(
args,
initialize,
tools,
run_probe=run_probe,
def run_agent_loop(client: Any, args: argparse.Namespace, repo: Path) -> dict[str, Any]:
initialize = client.initialize()
tools = client.list_tools()
session_handshake = run_session_handshake_from_live_surface(
run_probe=False,
doctor = None
recovery_playbook = None
tool_surface["recovery"]["arguments"]["agent_id"] = args.agent_id
if tool_surface["recovery"]["suggested_tool"] == "recovery_playbook":
recovery_playbook = call_recovery_playbook(
dict(tool_surface["recovery"]["arguments"]),
doctor_args = dict(tool_surface["recovery"]["arguments"])
doctor = client.call_tool("doctor", doctor_args)
details = {
"surface_status": "degraded_host_tool_surface",
"session_handshake": session_handshake,
"recovery_playbook": session_handshake.get("recovery_playbook") or recovery_playbook,
"doctor": session_handshake.get("doctor") or doctor,
missing = tool_surface["missing_required_tools"]
raise SmokeFailure(
"degraded_host_tool_surface: "
+ (
f"missing required tools: {', '.join(missing)}"
if missing
else f"expected at least {args.min_tool_count} tools, got {len(tools)}"
),
details,
health_contract = client.call_tool("health", {"agent_id": args.agent_id})
validate_health_surface_contract(health_contract)
ingest = client.call_tool(
"path": str(repo),
"adapter": args.adapter,
"mode": "replace",
"include_dotfiles": args.include_dotfiles,
node_count = int(ingest.get("node_count") or 0)
edge_count = int(ingest.get("edge_count") or 0)
raise SmokeFailure(f"ingest produced an empty graph: nodes={node_count}, edges={edge_count}")
trust_selftest = call_trust_selftest(
if trust_selftest.get("verdict") != "full_trust":
raise SmokeFailure(f"trust_selftest did not reach full_trust after ingest: {trust_selftest}")
seek = client.call_tool(
"top_k": args.top_k,
candidates = int(seek.get("total_candidates_scanned") or 0)
results = seek.get("results") or []
proof_state = seek.get("proof_state")
"seek did not see the ingested graph: "
f"proof_state={proof_state}, candidates={candidates}, results={len(results)}"
help_payload = client.call_tool(
"tool_name": "seek",
"mode": "tool",
"render": "compact",
if not help_payload.get("found") or not (help_payload.get("guidance") or help_payload.get("formatted")):
raise SmokeFailure("help did not return usable guidance for seek")
doctor = client.call_tool(
if doctor.get("schema") != "m1nd-doctor-v0":
raise SmokeFailure(f"doctor returned unexpected schema: {doctor.get('schema')}")
diagnostics = doctor.get("diagnostics") or {}
if not diagnostics.get("graph_has_nodes"):
raise SmokeFailure(f"doctor does not see the ingested graph: {diagnostics}")
negative_seek = client.call_tool(
"query": "qzxqzxqzxqzx_jjvjjvjjvjjv",
recovery = negative_seek.get("recovery") or {}
graph_state = negative_seek.get("graph_state") or {}
if negative_seek.get("proof_state") != "blocked":
raise SmokeFailure(f"negative seek should be blocked, got {negative_seek.get('proof_state')}")
if negative_seek.get("next_suggested_tool") != "recovery_playbook":
raise SmokeFailure(f"negative seek did not suggest recovery_playbook: {negative_seek}")
if recovery.get("suggested_tool") != "recovery_playbook":
raise SmokeFailure(f"negative seek did not include recovery_playbook payload: {negative_seek}")
if int(graph_state.get("node_count") or 0) <= 0:
raise SmokeFailure(f"negative seek did not include populated graph_state: {negative_seek}")
dict(recovery.get("arguments") or {}),
negative_selftest = call_trust_selftest(
if recovery_playbook.get("trust_mode") != "stale_binding_suspected":
raise SmokeFailure(f"negative recovery_playbook did not flag stale binding: {recovery_playbook}")
if negative_selftest.get("verdict") != "stale_binding_suspected":
raise SmokeFailure(f"negative trust_selftest did not flag stale binding: {negative_selftest}")
if not recovery_playbook.get("binding_fingerprint"):
raise SmokeFailure(f"negative recovery_playbook omitted binding_fingerprint: {recovery_playbook}")
"tool_count": len(tools),
"health_contract": summarize_health(health_contract),
"required_tools_present": {name: name in tool_names for name in REQUIRED_TOOLS},
"trust_selftest": {
"schema": trust_selftest.get("schema"),
"status": trust_selftest.get("status"),
"verdict": trust_selftest.get("verdict"),
"next_action": trust_selftest.get("next_action"),
"ingest": summarize_ingest(ingest),
"seek": summarize_seek(seek),
"help": {
"tool": help_payload.get("tool"),
"found": help_payload.get("found"),
"proof_state": help_payload.get("proof_state"),
"next_suggested_tool": help_payload.get("next_suggested_tool"),
"has_guidance": bool(help_payload.get("guidance") or help_payload.get("formatted")),
"doctor": {
"schema": doctor.get("schema"),
"status": doctor.get("status"),
"graph_has_nodes": diagnostics.get("graph_has_nodes"),
"stale_binding_suspected": diagnostics.get("stale_binding_suspected"),
"warnings": doctor.get("warnings") or [],
"negative_recovery": {
"proof_state": negative_seek.get("proof_state"),
"next_suggested_tool": negative_seek.get("next_suggested_tool"),
"recovery_tool": recovery.get("suggested_tool"),
"playbook_schema": recovery_playbook.get("schema"),
"playbook_trust_mode": recovery_playbook.get("trust_mode"),
"playbook_next_action": recovery_playbook.get("next_action"),
"playbook_step_count": len(recovery_playbook.get("steps") or []),
"selftest_schema": negative_selftest.get("schema"),
"selftest_verdict": negative_selftest.get("verdict"),
"graph_node_count": graph_state.get("node_count"),
"observed_tool": ((recovery.get("arguments") or {}).get("observed_tool")),
"checks": {
"tools_list_ok": True,
"ingest_populated_graph": True,
"seek_scanned_ingested_graph": True,
"help_returned_guidance": True,
"doctor_confirmed_graph": True,
"health_surface_contract_exposed": True,
"trust_selftest_full_trust": True,
"negative_retrieval_suggested_recovery_playbook": True,
"negative_recovery_playbook_validated": True,
"negative_trust_selftest_validated": True,
def run_session_handshake(client: Any, args: argparse.Namespace) -> dict[str, Any]:
return run_session_handshake_from_live_surface(
run_probe=args.handshake_probe,
def run_smoke(args: argparse.Namespace) -> dict[str, Any]:
repo = Path(args.repo).expanduser().resolve()
binary = Path(args.binary).expanduser().resolve() if args.binary else default_binary(repo)
if not repo.exists():
raise SmokeFailure(f"repo path does not exist: {repo}")
if not binary.exists():
f"m1nd-mcp binary does not exist: {binary}. Build it first with `cargo build -p m1nd-mcp`."
runtime_dir = Path(args.runtime_dir).expanduser().resolve() if args.runtime_dir else Path(
tempfile.mkdtemp(prefix="m1nd-agent-smoke-")
runtime_created = not bool(args.runtime_dir)
runtime_dir.mkdir(parents=True, exist_ok=True)
started = time.monotonic()
if args.transport == "stdio":
with McpStdioClient(binary=binary, runtime_dir=runtime_dir, timeout=args.timeout, cwd=repo) as client:
result = run_session_handshake(client, args) if args.handshake_only else run_agent_loop(client, args, repo)
elif args.transport == "http":
port = args.port or find_free_port()
with McpHttpClient(
binary=binary,
runtime_dir=runtime_dir,
timeout=args.timeout,
cwd=repo,
port=port,
) as client:
result["port"] = port
result["base_url"] = client.base_url
else:
raise SmokeFailure(f"unsupported transport: {args.transport}")
finally:
if runtime_created and not args.keep_runtime_dir:
shutil.rmtree(runtime_dir, ignore_errors=True)
"schema": HANDSHAKE_SCHEMA if args.handshake_only else SCHEMA,
"ok": True,
"transport": args.transport,
"binary": str(binary),
"repo": str(repo),
"runtime_dir": str(runtime_dir) if args.keep_runtime_dir or args.runtime_dir else None,
"duration_ms": round((time.monotonic() - started) * 1000, 3),
**result,
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Run an agent-first smoke test against m1nd MCP transports.")
parser.add_argument("--repo", default=os.getcwd(), help="Repository path to ingest. Defaults to cwd.")
parser.add_argument("--binary", help="Path to m1nd-mcp binary. Defaults to <repo>/target/debug/m1nd-mcp.")
parser.add_argument("--transport", choices=("stdio", "http"), default="stdio", help="Transport to smoke.")
parser.add_argument("--port", type=int, help="HTTP port to use when --transport=http. Defaults to a free port.")
parser.add_argument("--runtime-dir", help="Runtime directory for isolated sidecar state.")
parser.add_argument("--keep-runtime-dir", action="store_true", help="Keep the temporary runtime dir for debugging.")
parser.add_argument("--agent-id", default="m1nd-agent-smoke", help="Agent id used for tool calls.")
parser.add_argument("--query", default=DEFAULT_QUERY, help="Seek query to run after ingest.")
parser.add_argument("--top-k", type=int, default=5, help="Seek result limit.")
parser.add_argument("--adapter", default="code", help="Ingest adapter.")
parser.add_argument("--include-dotfiles", action="store_true", help="Include allowed dotfiles during ingest.")
parser.add_argument("--min-tool-count", type=int, default=1, help="Minimum expected tools/list count.")
parser.add_argument("--timeout", type=float, default=20.0, help="Per-response timeout in seconds.")
parser.add_argument("--handshake-only", action="store_true", help="Run only the lightweight session trust handshake.")
parser.add_argument("--handshake-probe", action="store_true", help="Add one tiny seek probe to the session handshake.")
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
return parser
def main() -> int:
parser = build_parser()
args = parser.parse_args()
result = run_smoke(args)
failure = {
"ok": False,
"error": str(exc),
failure.update(exc.details)
print(json.dumps(failure, indent=2), file=sys.stderr if not args.json else sys.stdout)
return 1
if args.json:
print(json.dumps(result, indent=2))
if args.handshake_only:
print(f"m1nd MCP {result['transport']} session handshake passed")
print(f"- trust mode: {result['trust_mode']}")
print(f"- can ingest: {result['can_ingest']}")
print(f"- can retrieve: {result['can_retrieve']}")
print(f"- can recover: {result['can_recover']}")
print(f"- next action: {result['next_action']}")
print(f"m1nd MCP {result['transport']} smoke passed")
print(f"- tools: {result['tool_count']}")
print(f"- trust mode: {result['session_handshake']['trust_mode']}")
print(f"- graph nodes: {result['ingest']['node_count']}")
print(f"- graph edges: {result['ingest']['edge_count']}")
print(f"- seek candidates: {result['seek']['total_candidates_scanned']}")
print(f"- seek results: {result['seek']['results_count']}")
print(f"- doctor: {result['doctor']['status']}")
print(
"- negative recovery: "
f"{result['negative_recovery']['next_suggested_tool']} "
f"({result['negative_recovery']['playbook_trust_mode']})"
return 0
if __name__ == "__main__":
raise SystemExit(main())