mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
This squashes all local history (768 commits) onto the scrubbed PR #90 baseline. Individual commits were lost during filter-repo corruption; the working tree content is preserved intact. Build: N/A (working tree state only)
186 lines
5.7 KiB
Python
186 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
MCP server for the FAMM shape-index of the Lean corpus.
|
|
|
|
Given an unsolved conjecture, classify its shape and return nearest
|
|
neighbor proofs from the indexed corpus.
|
|
|
|
Tools:
|
|
shape_query — find nearest neighbors by shape signature
|
|
shape_summary — distribution of shapes in the corpus
|
|
shape_classify — classify a raw Lean theorem by its FAMM shape
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
INDEX_PATH = os.environ.get(
|
|
"SHAPE_INDEX_PATH",
|
|
"/home/allaun/lean_corpus/shape_index.json",
|
|
)
|
|
|
|
SERVER_NAME = "shape-index-mcp"
|
|
SERVER_VERSION = "0.1.0"
|
|
|
|
_index = None
|
|
|
|
|
|
def get_index() -> dict:
|
|
global _index
|
|
if _index is None:
|
|
with open(INDEX_PATH) as f:
|
|
_index = json.load(f)
|
|
return _index
|
|
|
|
|
|
def json_text(data: Any) -> list[dict[str, str]]:
|
|
return [{"type": "text", "text": json.dumps(data, indent=2, sort_keys=True)}]
|
|
|
|
|
|
def tool_shape_summary(_: dict[str, Any]) -> dict[str, Any]:
|
|
idx = get_index()
|
|
meta = idx.get("meta", {})
|
|
shapes = idx.get("shape_summary", {})
|
|
return {
|
|
"ok": True,
|
|
"server": SERVER_NAME,
|
|
"version": SERVER_VERSION,
|
|
"files_scanned": meta.get("files_scanned"),
|
|
"theorems_indexed": meta.get("theorems_indexed"),
|
|
"shape_distribution": shapes,
|
|
"total_theorems": sum(shapes.values()),
|
|
}
|
|
|
|
|
|
def tool_shape_query(args: dict[str, Any]) -> dict[str, Any]:
|
|
idx = get_index()
|
|
target_density = args.get("sumset_density", 0.5)
|
|
target_vars = args.get("n_vars", 3)
|
|
target_state = args.get("target_state", "ACCEPT")
|
|
top_k = min(args.get("top_k", 5), 20)
|
|
|
|
scored = []
|
|
for t in idx["all_theorems"]:
|
|
density = t["sidon_signature"]["sumset_density"]
|
|
n_vars = t["constraint_graph"]["n_vars"]
|
|
state = t["famm"]["famm_state"]
|
|
shape = t["famm"]["rrc_shape"]
|
|
|
|
d_density = abs(density - target_density) * 2
|
|
d_vars = abs(n_vars - target_vars) * 0.05
|
|
d_state = 0 if state == target_state else 1.5 if state == "HOLD" else 0.5
|
|
distance = d_density + d_vars + d_state
|
|
|
|
scored.append({
|
|
"distance": round(distance, 4),
|
|
"name": t["name"],
|
|
"kind": t["kind"],
|
|
"shape": shape,
|
|
"state": state,
|
|
"sidon_density": density,
|
|
"n_vars": n_vars,
|
|
"path": t["path"],
|
|
"line": t["line"],
|
|
})
|
|
|
|
scored.sort(key=lambda x: x["distance"])
|
|
return {"ok": True, "query": args, "neighbors": scored[:top_k]}
|
|
|
|
|
|
def tool_shape_classify(args: dict[str, Any]) -> dict[str, Any]:
|
|
return {"ok": False, "error": "shape_classify requires the shape_index module at runtime; call shape_query instead"}
|
|
|
|
|
|
TOOLS = {
|
|
"shape_summary": {
|
|
"description": "Return shape distribution of the indexed Lean corpus.",
|
|
"inputSchema": {"type": "object", "properties": {}},
|
|
"handler": tool_shape_summary,
|
|
},
|
|
"shape_query": {
|
|
"description": "Find nearest-neighbor theorems by Sidon/FAMM shape.",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"sumset_density": {"type": "number", "default": 0.5},
|
|
"n_vars": {"type": "integer", "default": 3},
|
|
"target_state": {"type": "string", "default": "ACCEPT"},
|
|
"top_k": {"type": "integer", "default": 5},
|
|
},
|
|
},
|
|
"handler": tool_shape_query,
|
|
},
|
|
"shape_classify": {
|
|
"description": "Classify a Lean theorem by its FAMM shape.",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"required": ["content"],
|
|
"properties": {
|
|
"content": {"type": "string"},
|
|
"path": {"type": "string", "default": "inline.lean"},
|
|
},
|
|
},
|
|
"handler": tool_shape_classify,
|
|
},
|
|
}
|
|
|
|
|
|
def handle(message: dict[str, Any]) -> dict[str, Any] | None:
|
|
method = message.get("method")
|
|
msg_id = message.get("id")
|
|
if method == "initialize":
|
|
return {
|
|
"jsonrpc": "2.0", "id": msg_id,
|
|
"result": {
|
|
"protocolVersion": "2024-11-05",
|
|
"capabilities": {"tools": {}},
|
|
"serverInfo": {"name": SERVER_NAME, "version": SERVER_VERSION},
|
|
},
|
|
}
|
|
if method == "tools/list":
|
|
return {
|
|
"jsonrpc": "2.0", "id": msg_id,
|
|
"result": {
|
|
"tools": [
|
|
{"name": name, "description": data["description"],
|
|
"inputSchema": data["inputSchema"]}
|
|
for name, data in TOOLS.items()
|
|
]
|
|
},
|
|
}
|
|
if method == "tools/call":
|
|
params = message.get("params") or {}
|
|
name = params.get("name")
|
|
args = params.get("arguments") or {}
|
|
if name not in TOOLS:
|
|
result = {"ok": False, "error": f"unknown tool: {name}"}
|
|
else:
|
|
result = TOOLS[name]["handler"](args)
|
|
return {"jsonrpc": "2.0", "id": msg_id, "result": {"content": json_text(result)}}
|
|
if method and method.startswith("notifications/"):
|
|
return None
|
|
return {"jsonrpc": "2.0", "id": msg_id, "error": {"code": -32601, "message": f"method not found: {method}"}}
|
|
|
|
|
|
def main() -> None:
|
|
for line in sys.stdin:
|
|
if not line.strip():
|
|
continue
|
|
try:
|
|
response = handle(json.loads(line))
|
|
except Exception as exc:
|
|
response = {
|
|
"jsonrpc": "2.0", "id": None,
|
|
"error": {"code": -32000, "message": f"{type(exc).__name__}: {exc}"},
|
|
}
|
|
if response is not None:
|
|
print(json.dumps(response, separators=(",", ":")), flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|