- fail closed on MCP input and compile database policy - add shared scan catalogs, finding queries, and tiered gates
132 lines
4.2 KiB
Python
132 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate docs/mcp-tools.md from the built Fold MCP schemas."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
ALIASES = {
|
|
"fold.get_job_findings": "fold.scan_job_findings",
|
|
"fold.activate_scan_job": "fold.scan_activate",
|
|
"fold.scan_project": "fold.scan",
|
|
"fold.scan_rust": "fold.scan",
|
|
"fold.scan_swift": "fold.scan",
|
|
"fold.scan_binary": "fold.scan",
|
|
"fold.find_duplicates": "fold.scan",
|
|
"fold.find_dead_code": "fold.scan",
|
|
"fold.find_duplicate_classes": "fold.scan",
|
|
"fold.find_security_issues": "fold.scan",
|
|
"fold.find_binary_security_issues": "fold.scan",
|
|
}
|
|
|
|
|
|
def mcp_tools(fold_binary: Path) -> list[dict[str, Any]]:
|
|
requests = [
|
|
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}},
|
|
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}},
|
|
]
|
|
result = subprocess.run(
|
|
[str(fold_binary), "mcp"],
|
|
input="\n".join(json.dumps(request) for request in requests) + "\n",
|
|
text=True,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
responses = [
|
|
json.loads(line) for line in result.stdout.splitlines() if line.strip()
|
|
]
|
|
listed = next(response for response in responses if response.get("id") == 2)
|
|
return listed["result"]["tools"]
|
|
|
|
|
|
def cell(value: Any) -> str:
|
|
if value is None:
|
|
return ""
|
|
if isinstance(value, (dict, list)):
|
|
rendered = json.dumps(value, separators=(",", ":"))
|
|
else:
|
|
rendered = str(value)
|
|
return rendered.replace("|", "\\|").replace("\n", " ")
|
|
|
|
|
|
def render(tools: list[dict[str, Any]]) -> str:
|
|
lines = [
|
|
"<!-- Generated by scripts/generate_mcp_docs.py; do not edit manually. -->",
|
|
"# Fold MCP tools",
|
|
"",
|
|
"Fold advertises only canonical operations. Deprecated aliases remain callable",
|
|
"through the next major-version compatibility checkpoint and return structured",
|
|
"deprecation metadata.",
|
|
"",
|
|
"## Canonical operations",
|
|
"",
|
|
"| Tool | Description |",
|
|
"|---|---|",
|
|
]
|
|
for tool in tools:
|
|
lines.append(f"| `{tool['name']}` | {cell(tool['description'])} |")
|
|
|
|
scan = next(tool for tool in tools if tool["name"] == "fold.scan")
|
|
properties = scan["inputSchema"].get("properties", {})
|
|
lines.extend(
|
|
[
|
|
"",
|
|
"## Generated scan option schema",
|
|
"",
|
|
"`fold.scan` and `fold.scan_start` advertise this same analysis schema.",
|
|
"Server configuration owns queue depth. `background` is accepted only as a",
|
|
"deprecated, hidden `fold.scan` call alias for `fold.scan_start`.",
|
|
"",
|
|
"| Property | Type | Default | Constraints | Description |",
|
|
"|---|---|---|---|---|",
|
|
]
|
|
)
|
|
for name, schema in sorted(properties.items()):
|
|
constraints = []
|
|
if "enum" in schema:
|
|
constraints.append("enum=" + cell(schema["enum"]))
|
|
for key in (
|
|
"minimum",
|
|
"maximum",
|
|
"minItems",
|
|
"maxItems",
|
|
"minLength",
|
|
"maxLength",
|
|
):
|
|
if key in schema:
|
|
constraints.append(f"{key}={schema[key]}")
|
|
lines.append(
|
|
f"| `{name}` | `{cell(schema.get('type'))}` | {cell(schema.get('default'))} | "
|
|
f"{cell(', '.join(constraints))} | {cell(schema.get('description'))} |"
|
|
)
|
|
|
|
lines.extend(
|
|
[
|
|
"",
|
|
"## Hidden compatibility aliases",
|
|
"",
|
|
"| Deprecated alias | Canonical replacement |",
|
|
"|---|---|",
|
|
]
|
|
)
|
|
for alias, replacement in ALIASES.items():
|
|
lines.append(f"| `{alias}` | `{replacement}` |")
|
|
lines.append("")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--fold", type=Path, default=Path("build/release/src/cli/fold"))
|
|
parser.add_argument("--output", type=Path, default=Path("docs/mcp-tools.md"))
|
|
args = parser.parse_args()
|
|
args.output.write_text(render(mcp_tools(args.fold)), encoding="utf-8")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|