aboutsummaryrefslogtreecommitdiffstats
path: root/server/patch_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'server/patch_server.py')
-rw-r--r--server/patch_server.py73
1 files changed, 73 insertions, 0 deletions
diff --git a/server/patch_server.py b/server/patch_server.py
new file mode 100644
index 0000000..b989930
--- /dev/null
+++ b/server/patch_server.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+"""Inject idatui's extra ida-pro-mcp tools into the installed server package.
+
+ida-pro-mcp exposes no delete-type tool, which idatui's struct editor needs for
+the 'D' in CRUD. Rather than vendor/fork the server, we keep the tool source here
+and append it (idempotently) to the installed ``api_types.py``. That module is
+imported by every worker (``python -m ida_pro_mcp.idalib_server``), so the tool
+registers itself via ``@tool`` on the shared ``MCP_SERVER`` — no server code is
+forked, and re-running this (spawn.sh does, on every start) re-applies it after a
+reinstall/upgrade of ida-pro-mcp.
+
+Run with the *same* interpreter the server uses (the idalib-mcp entry point's
+``/usr/bin/python``), so it patches the file the workers actually import.
+Safe to run repeatedly; a no-op once applied.
+"""
+from __future__ import annotations
+
+import importlib.util
+import pathlib
+import sys
+
+BEGIN = "# >>> idatui-ext: begin (auto-injected by server/patch_server.py) >>>"
+END = "# <<< idatui-ext: end <<<"
+
+# Appended to ida_pro_mcp/ida_mcp/api_types.py, which already imports
+# ``Annotated``, ``tool``, ``idasync`` and ``ida_typeinf`` at module scope.
+SNIPPET = f'''
+
+{BEGIN}
+@tool
+@idasync
+def del_type(
+ name: Annotated[str, "Local type name to delete (struct/union/enum/typedef)"],
+) -> dict:
+ """Delete a named local type from the local type library."""
+ til = ida_typeinf.get_idati()
+ ok = ida_typeinf.del_named_type(til, name, ida_typeinf.NTF_TYPE)
+ if not ok:
+ return {{"name": name, "error": f"Type '{{name}}' not found or could not be deleted"}}
+ return {{"name": name, "deleted": True}}
+{END}
+'''
+
+
+def api_types_path() -> pathlib.Path | None:
+ """Locate ida_pro_mcp/ida_mcp/api_types.py without importing it (importing the
+ submodule would pull in IDA, which isn't available outside a worker)."""
+ spec = importlib.util.find_spec("ida_pro_mcp") # top-level pkg is IDA-free
+ if spec is None or not spec.submodule_search_locations:
+ return None
+ p = pathlib.Path(spec.submodule_search_locations[0]) / "ida_mcp" / "api_types.py"
+ return p if p.exists() else None
+
+
+def main() -> int:
+ path = api_types_path()
+ if path is None:
+ print("idatui: ida_pro_mcp not found; skipping del_type injection", file=sys.stderr)
+ return 0
+ text = path.read_text()
+ if BEGIN in text:
+ return 0 # already applied
+ try:
+ path.write_text(text.rstrip() + "\n" + SNIPPET)
+ except OSError as e:
+ print(f"idatui: could not patch {path}: {e}", file=sys.stderr)
+ return 1
+ print(f"idatui: injected del_type into {path}", file=sys.stderr)
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())