Research-Stack/4-Infrastructure/shim/mcp_bus_live_safe_probe.py
2026-05-11 22:18:31 -05:00

227 lines
8.6 KiB
Python

#!/usr/bin/env python3
"""First live-safe MCP bus probe.
Runs bounded, read-only ScienceHub CLI operations through the same receipt
discipline expected of future MCP/OpenClaw bus calls. This deliberately avoids
server activation, paper downloads, notebook execution, arbitrary Python REPL,
filesystem writes outside the receipt artifacts, and held surfaces.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
REPO = Path(__file__).resolve().parents[2]
SHIM = REPO / "4-Infrastructure" / "shim"
WIKI = REPO / "6-Documentation" / "tiddlywiki-local" / "wiki" / "tiddlers"
SCIENCEHUB = REPO / "scripts" / "sciencehub_mcp.py"
def sha256_text(text: str) -> str:
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def file_hash(path: Path) -> str | None:
if not path.exists():
return None
return hashlib.sha256(path.read_bytes()).hexdigest()
def run_sciencehub(args: list[str], timeout: int) -> dict[str, Any]:
command = ["python", str(SCIENCEHUB), *args]
proc = subprocess.run(
command,
cwd=str(REPO),
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=timeout,
check=False,
)
parsed: Any = None
json_ok = False
if args and args[0] == "--search":
try:
parsed = json.loads(proc.stdout)
json_ok = True
except Exception:
parsed = None
return {
"surface_id": "sciencehub_mcp",
"tool_name": "report" if args == ["--report"] else "search_local_corpus",
"arguments": args,
"arguments_hash": sha256_text(json.dumps(args, ensure_ascii=False)),
"command_shape": ["python", "scripts/sciencehub_mcp.py", *args],
"returncode": proc.returncode,
"stdout_hash": sha256_text(proc.stdout),
"stderr_hash": sha256_text(proc.stderr),
"stdout_tail": proc.stdout[-3000:],
"stderr_tail": proc.stderr[-1000:],
"json_ok": json_ok,
"parsed_summary": summarize_search(parsed) if json_ok else summarize_report(proc.stdout),
"lawful": proc.returncode == 0 and not proc.stderr.strip(),
"claim_boundary": "Read-only local ScienceHub CLI call; no MCP server activation, paper download, notebook execution, or arbitrary code execution.",
}
def summarize_report(text: str) -> dict[str, Any]:
metrics: dict[str, Any] = {}
for line in text.splitlines():
if ":" not in line:
continue
key, value = line.split(":", 1)
key = key.strip().lower().replace(" ", "_")
value = value.strip()
if value.isdigit():
metrics[key] = int(value)
return metrics
def summarize_search(parsed: dict[str, Any] | None) -> dict[str, Any]:
parsed = parsed or {}
zotero = parsed.get("zotero", []) or []
pdfs = parsed.get("pdfs", []) or []
arxiv_meta = parsed.get("arxiv_meta", []) or []
return {
"zotero_hits": len(zotero),
"pdf_hits": len(pdfs),
"arxiv_meta_hits": len(arxiv_meta),
"top_titles": [item.get("title") or item.get("title_guess") for item in [*zotero[:3], *pdfs[:3]] if item.get("title") or item.get("title_guess")],
}
def build_receipt(queries: list[str], timeout: int) -> dict[str, Any]:
calls = [run_sciencehub(["--report"], timeout)]
for query in queries:
calls.append(run_sciencehub(["--search", query], timeout))
lawful_calls = sum(1 for call in calls if call["lawful"])
hashed_calls = sum(1 for call in calls if call["arguments_hash"] and call["stdout_hash"])
boundary_calls = sum(1 for call in calls if call["claim_boundary"])
return {
"schema": "mcp_bus_live_safe_probe_receipt_v1",
"timestamp": datetime.now(timezone.utc).isoformat(),
"surface_id": "sciencehub_mcp",
"source_path": str(SCIENCEHUB.relative_to(REPO)),
"source_hash": file_hash(SCIENCEHUB),
"claim_boundary": "First live-safe bus probe uses read-only ScienceHub CLI calls only; no live MCP server activation or restricted retrieval.",
"queries": queries,
"calls": calls,
"call_count": len(calls),
"lawful_calls": lawful_calls,
"hashed_calls": hashed_calls,
"boundary_calls": boundary_calls,
"receipt_rule": "Every future live MCP call must include surface_id, tool_name, arguments_hash, stdout/output hash, source_path, lawful flag, and claim boundary.",
"lawful": lawful_calls == len(calls) and hashed_calls == len(calls) and boundary_calls == len(calls),
}
def curriculum_records(receipt: dict[str, Any]) -> list[dict[str, Any]]:
system = "You are a live-safe MCP bus router. Return compact JSON and preserve read-only boundaries."
records = []
for call in receipt["calls"]:
prompt = {
"task": "classify_live_safe_mcp_call",
"surface_id": call["surface_id"],
"tool_name": call["tool_name"],
"arguments_hash": call["arguments_hash"],
"parsed_summary": call["parsed_summary"],
"claim_boundary": call["claim_boundary"],
}
answer = {
"selected": bool(call["lawful"]),
"use_as": "live_safe_mcp_bus_probe",
"surface_id": call["surface_id"],
"tool_name": call["tool_name"],
"source_path": receipt["source_path"],
"source_hash": receipt["source_hash"],
"output_hash": call["stdout_hash"],
"claim_boundary": call["claim_boundary"],
"receipt_rule": receipt["receipt_rule"],
}
records.append(
{
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": json.dumps(prompt, ensure_ascii=False)},
{"role": "assistant", "content": json.dumps(answer, ensure_ascii=False)},
]
}
)
return records
def write_wiki(receipt: dict[str, Any], path: Path) -> None:
lines = [
"created: 20260507000000000",
"modified: 20260507000000000",
"tags: ResearchStack MCP OpenClaw Metaprobe LiveSafe ScienceHub",
"title: MCP Bus Live Safe Probe",
"type: text/vnd.tiddlywiki",
"",
"! MCP Bus Live Safe Probe",
"",
"This tiddler records the first read-only live-safe bus calls through the ScienceHub surface.",
"",
"Durable source: `4-Infrastructure/shim/mcp_bus_live_safe_probe.py`",
"",
"Receipt: `4-Infrastructure/shim/mcp_bus_live_safe_probe_receipt.json`",
"",
"Curriculum: `4-Infrastructure/shim/mcp_bus_live_safe_probe_curriculum.jsonl`",
"",
"!! Result",
"",
f"* Calls: {receipt['lawful_calls']}/{receipt['call_count']} lawful",
f"* Overall lawful: `{str(receipt['lawful']).lower()}`",
"",
"!! Claim Boundary",
"",
receipt["claim_boundary"],
"",
"!! Calls",
"",
]
for call in receipt["calls"]:
status = "PASS" if call["lawful"] else "FAIL"
lines.append(f"* {status} `{call['tool_name']}` args `{call['arguments']}` -> {call['parsed_summary']}")
lines.extend(
[
"",
"!! Links",
"",
"* [[MCP Bus Dry Run]]",
"* [[MCP Surface Catalog]]",
"* [[OpenClaw Shared Bus Surface]]",
]
)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--query", action="append", default=["compression", "erdos", "topology"])
parser.add_argument("--timeout", type=int, default=30)
parser.add_argument("--receipt", type=Path, default=SHIM / "mcp_bus_live_safe_probe_receipt.json")
parser.add_argument("--curriculum", type=Path, default=SHIM / "mcp_bus_live_safe_probe_curriculum.jsonl")
parser.add_argument("--wiki", type=Path, default=WIKI / "MCP Bus Live Safe Probe.tid")
args = parser.parse_args()
receipt = build_receipt(args.query, args.timeout)
args.receipt.write_text(json.dumps(receipt, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
with args.curriculum.open("w", encoding="utf-8") as handle:
for record in curriculum_records(receipt):
handle.write(json.dumps(record, ensure_ascii=False) + "\n")
write_wiki(receipt, args.wiki)
print(json.dumps(receipt, indent=2, ensure_ascii=False))
return 0 if receipt["lawful"] else 1
if __name__ == "__main__":
raise SystemExit(main())