aboutsummaryrefslogtreecommitdiffstats
path: root/server/patch_server.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-10 11:55:33 +0200
committerblasty <blasty@local>2026-07-10 11:55:33 +0200
commit25590831d851e9e8ab8ff15b4122e01dfafd6d58 (patch)
tree9f26bc179bd407e94217865c13219ba9d7a0c33e /server/patch_server.py
parentstruct editor: C-style CRUD for local types (Ctrl+T) (diff)
downloadida-tui-25590831d851e9e8ab8ff15b4122e01dfafd6d58.tar.gz
ida-tui-25590831d851e9e8ab8ff15b4122e01dfafd6d58.tar.xz
ida-tui-25590831d851e9e8ab8ff15b4122e01dfafd6d58.zip
server: inject a del_type tool into ida-pro-mcp (enables struct delete)
ida-pro-mcp has no delete-type tool, so struct-editor delete couldn't work. We don't vendor/fork the server; instead server/patch_server.py idempotently appends a del_type @tool to the installed ida_pro_mcp/ida_mcp/api_types.py, which every worker (python -m ida_pro_mcp.idalib_server) imports -> it self-registers on the shared MCP_SERVER. spawn.sh runs the patch before launching, so it's re-applied on each start (survives reinstalls) and tracked in-repo instead of hand-editing site-packages. del_type calls ida_typeinf.del_named_type(get_idati(), name, NTF_TYPE). After a supervisor restart, Program.delete_type() now succeeds; the struct editor's Del removes the type and the list refreshes. Verified end-to-end; pilot 84/84.
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())