diff options
| author | blasty <blasty@local> | 2026-07-10 11:55:33 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-10 11:55:33 +0200 |
| commit | 25590831d851e9e8ab8ff15b4122e01dfafd6d58 (patch) | |
| tree | 9f26bc179bd407e94217865c13219ba9d7a0c33e | |
| parent | struct editor: C-style CRUD for local types (Ctrl+T) (diff) | |
| download | ida-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.py | 73 | ||||
| -rwxr-xr-x | spawn.sh | 7 |
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()) @@ -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 |
