aboutsummaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--server/patch_server.py73
-rwxr-xr-xspawn.sh7
2 files changed, 80 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())
diff --git a/spawn.sh b/spawn.sh
index 4b0c63c..9db8310 100755
--- a/spawn.sh
+++ b/spawn.sh
@@ -1,5 +1,12 @@
#!/bin/sh
# Start the ida-pro-mcp supervisor. IDA_MCP_MAX_WORKERS raises the ceiling on
# concurrently-open binaries (default 4); idle workers self-exit and free slots.
+
+# Ensure idatui's extra server tools (del_type, needed by the struct editor) are
+# present in the installed ida-pro-mcp. Idempotent; patches the api_types.py that
+# the /usr/bin/python workers import. See server/patch_server.py.
+/usr/bin/python "$(dirname "$0")/server/patch_server.py" || \
+ echo "warn: server patch skipped (del_type may be unavailable)"
+
IDA_MCP_MAX_WORKERS="${IDA_MCP_MAX_WORKERS:-8}" \
uv run idalib-mcp --host 127.0.0.1 --port 8745 $(pwd)/bin/ls