Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ The following table lists the available MCP functions for use:
| `delete_function_comment` | Delete the comment for a function. |
| `declare_c_type(c_declaration)` | Create/update a local type from a single C declaration. |
| `format_value(address, text, size)` | Convert a value and annotate it at an address in BN (adds a comment). |
| `function_at` | Retrieve the name of the function the address belongs to. |
| `function_at` | Retrieve the name of the function the address belongs to. Non-blocking during analysis; auto-creates the function if it doesn't exist yet. |
| `get_assembly_function` | Get the assembly representation of a function by name or address. |
| `get_entry_points()` | List entry point(s) of the loaded binary. |
| `get_binary_status` | Get the current status of the loaded binary. |
| `get_binary_status` | Get the current status of the loaded binary. Returns `analysis_state`, `analysis_complete`, and `function_count`. |
| `get_comment` | Get the comment at a specific address. |
| `get_function_comment` | Get the comment for a function. |
| `get_user_defined_type` | Retrieve definition of a user-defined type (struct, enumeration, typedef, union). |
Expand All @@ -153,7 +153,7 @@ The following table lists the available MCP functions for use:
| `get_xrefs_to_union(union_name)` | Get xrefs/usages related to a union (members, globals, code refs). |
| `get_stack_frame_vars(function_identifier)` | Get stack frame variable information for a function (names, offsets, sizes, types). |
| `get_type_info(type_name)` | Resolve a type and return declaration, kind, and members. |
| `make_function_at(address, platform)` | Create a function at an address. `platform` optional; use `default` to pick the BinaryView/platform default. |
| `make_function_at(address, platform)` | Create a function at an address (no-op if it already exists). Non-blocking, safe to call during analysis. `platform` optional; use `default` to pick the BinaryView/platform default. |
| `list_platforms()` | List all available platform names. |
| `list_binaries()` | List managed/open binaries with ids and active flag. |
| `select_binary(view)` | Select active binary by id or filename. |
Expand Down
84 changes: 56 additions & 28 deletions bridge/binja_mcp_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def safe_post(endpoint: str, data: dict | str) -> str:


@mcp.tool()
def list_methods(offset: int = 0, limit: int = 100) -> list:
def list_methods(offset: int = 0, limit: int = 100) -> list[str]:
"""
List all function names in the program with pagination.
"""
Expand All @@ -145,7 +145,7 @@ def list_methods(offset: int = 0, limit: int = 100) -> list:


@mcp.tool()
def get_entry_points() -> list:
def get_entry_points() -> list[str]:
"""
List entry point(s) of the loaded binary.
"""
Expand Down Expand Up @@ -176,7 +176,7 @@ def retype_variable(function_name: str, variable_name: str, type_str: str) -> st
if not data:
return "Error: no response"
if isinstance(data, dict) and "status" in data:
return data["status"]
return str(data["status"])
if isinstance(data, dict) and "error" in data:
return f"Error: {data['error']}"
return str(data)
Expand All @@ -198,7 +198,7 @@ def rename_single_variable(function_name: str, variable_name: str, new_name: str
if not data:
return "Error: no response"
if isinstance(data, dict) and "status" in data:
return data["status"]
return str(data["status"])
if isinstance(data, dict) and "error" in data:
return f"Error: {data['error']}"
return str(data)
Expand Down Expand Up @@ -274,7 +274,7 @@ def define_types(c_code: str) -> str:


@mcp.tool()
def list_classes(offset: int = 0, limit: int = 100) -> list:
def list_classes(offset: int = 0, limit: int = 100) -> list[str]:
"""
List all namespace/class names in the program with pagination.
"""
Expand Down Expand Up @@ -329,6 +329,12 @@ def get_data_decl(name_or_address: str, length: int = -1) -> str:
def decompile_function(name: str) -> str:
"""
Decompile a specific function by name and return the decompiled C code.

WARNING: This blocks until the function's analysis is complete. For large
functions or if analysis is still running, this can hang for a long time.
Before calling this on an address/function that may not be analyzed yet,
call make_function_at to ensure the function is defined, and check
get_binary_status to see if analysis is complete.
"""
file_line = f"File: {_active_filename()}\n\n"
data = get_json("decompile", {"name": name}, timeout=None)
Expand All @@ -347,6 +353,11 @@ def get_il(name_or_address: str, view: str = "hlil", ssa: bool = False) -> str:
Get IL for a function in the selected view.
- view: one of hlil, mlil, llil
- ssa: set True to request SSA form (MLIL/LLIL only)

WARNING: This blocks until the function's analysis is complete. If the
function at the given address has not been defined or analysis is still
running, this can hang. Call make_function_at first to ensure the function
exists, and check get_binary_status for analysis_complete status.
"""
file_line = f"File: {_active_filename()}\n\n"
ident = (name_or_address or "").strip()
Expand Down Expand Up @@ -433,15 +444,15 @@ def get_function_comment(function_name: str) -> str:


@mcp.tool()
def list_segments(offset: int = 0, limit: int = 100) -> list:
def list_segments(offset: int = 0, limit: int = 100) -> list[str]:
"""
List all memory segments in the program with pagination.
"""
return safe_get("segments", {"offset": offset, "limit": limit})


@mcp.tool()
def list_sections(offset: int = 0, limit: int = 100) -> list:
def list_sections(offset: int = 0, limit: int = 100) -> list[str]:
"""
List sections in the program with pagination.

Expand Down Expand Up @@ -469,23 +480,23 @@ def list_sections(offset: int = 0, limit: int = 100) -> list:


@mcp.tool()
def list_imports(offset: int = 0, limit: int = 100) -> list:
def list_imports(offset: int = 0, limit: int = 100) -> list[str]:
"""
List imported symbols in the program with pagination.
"""
return safe_get("imports", {"offset": offset, "limit": limit})


@mcp.tool()
def list_strings(offset: int = 0, count: int = 100) -> list:
def list_strings(offset: int = 0, count: int = 100) -> list[str]:
"""
List all strings in the database (paginated).
"""
return safe_get("strings", {"offset": offset, "limit": count}, timeout=None)


@mcp.tool()
def list_strings_filter(offset: int = 0, count: int = 100, filter: str = "") -> list:
def list_strings_filter(offset: int = 0, count: int = 100, filter: str = "") -> list[str]:
"""
List matching strings in the database (paginated, filtered).
"""
Expand All @@ -497,7 +508,7 @@ def list_strings_filter(offset: int = 0, count: int = 100, filter: str = "") ->


@mcp.tool()
def list_local_types(offset: int = 0, count: int = 200, include_libraries: bool = False) -> list:
def list_local_types(offset: int = 0, count: int = 200, include_libraries: bool = False) -> list[str]:
"""
List all local types in the database (paginated).
"""
Expand All @@ -515,7 +526,7 @@ def list_local_types(offset: int = 0, count: int = 200, include_libraries: bool
@mcp.tool()
def search_types(
query: str, offset: int = 0, count: int = 200, include_libraries: bool = False
) -> list:
) -> list[str]:
"""
Search local types whose name or declaration contains the substring.
"""
Expand All @@ -532,7 +543,7 @@ def search_types(


@mcp.tool()
def list_all_strings(batch_size: int = 500) -> list:
def list_all_strings(batch_size: int = 500) -> list[str]:
"""
List all strings in the database (aggregated across pages).
"""
Expand All @@ -558,31 +569,31 @@ def list_all_strings(batch_size: int = 500) -> list:


@mcp.tool()
def list_exports(offset: int = 0, limit: int = 100) -> list:
def list_exports(offset: int = 0, limit: int = 100) -> list[str]:
"""
List exported functions/symbols with pagination.
"""
return safe_get("exports", {"offset": offset, "limit": limit})


@mcp.tool()
def list_namespaces(offset: int = 0, limit: int = 100) -> list:
def list_namespaces(offset: int = 0, limit: int = 100) -> list[str]:
"""
List all non-global namespaces in the program with pagination.
"""
return safe_get("namespaces", {"offset": offset, "limit": limit})


@mcp.tool()
def list_data_items(offset: int = 0, limit: int = 100) -> list:
def list_data_items(offset: int = 0, limit: int = 100) -> list[str]:
"""
List defined data labels and their values with pagination.
"""
return safe_get("data", {"offset": offset, "limit": limit})


@mcp.tool()
def search_functions_by_name(query: str, offset: int = 0, limit: int = 100) -> list:
def search_functions_by_name(query: str, offset: int = 0, limit: int = 100) -> list[str]:
"""
Search for functions whose name contains the given substring.
"""
Expand All @@ -595,20 +606,25 @@ def search_functions_by_name(query: str, offset: int = 0, limit: int = 100) -> l
def get_binary_status() -> str:
"""
Get the current status of the loaded binary.

Returns analysis_state and analysis_complete fields. Check analysis_complete
before calling blocking tools (decompile_function, get_il, function_at) on
addresses that may not have been analyzed yet. If analysis is still running,
use make_function_at first to define functions before decompiling them.
"""
return safe_get("status")[0]


@mcp.tool()
def list_binaries() -> list:
def list_binaries() -> list[str]:
"""
List managed/open binaries known to the server with ids and active flag.
"""
data = get_json("binaries")
if not data:
return ["Error: no response"]
if isinstance(data, dict) and data.get("error"):
return [data.get("error")]
return [str(data["error"])]
items = data.get("binaries", [])
out = []
for it in items:
Expand Down Expand Up @@ -676,23 +692,27 @@ def delete_function_comment(function_name: str) -> str:


@mcp.tool()
def function_at(address: str) -> str:
def function_at(address: str) -> list[str]:
"""
Retrive the name of the function the address belongs to. Address must be in hexadecimal format 0x00001

WARNING: This can hang if analysis is still running and the address is in an
unanalyzed region. If analysis is not complete (check get_binary_status),
call make_function_at first to define the function, which is non-blocking.
"""
return safe_get("functionAt", {"address": address})


@mcp.tool()
def get_user_defined_type(type_name: str) -> str:
def get_user_defined_type(type_name: str) -> list[str]:
"""
Retrive definition of a user defined type (struct, enumeration, typedef, union)
"""
return safe_get("getUserDefinedType", {"name": type_name})


@mcp.tool()
def get_xrefs_to(address: str) -> list:
def get_xrefs_to(address: str) -> list[str]:
"""
Get all cross references (code and data) to the given address.
Address can be hex (e.g., 0x401000) or decimal.
Expand All @@ -701,23 +721,23 @@ def get_xrefs_to(address: str) -> list:


@mcp.tool()
def get_xrefs_to_field(struct_name: str, field_name: str) -> list:
def get_xrefs_to_field(struct_name: str, field_name: str) -> list[str]:
"""
Get all cross references to a named struct field (member).
"""
return safe_get("getXrefsToField", {"struct": struct_name, "field": field_name})


@mcp.tool()
def get_xrefs_to_struct(struct_name: str) -> list:
def get_xrefs_to_struct(struct_name: str) -> list[str]:
"""
Get cross references/usages related to a struct name.
"""
return safe_get("getXrefsToStruct", {"name": struct_name})


@mcp.tool()
def get_xrefs_to_type(type_name: str) -> list:
def get_xrefs_to_type(type_name: str) -> list[str]:
"""
Get xrefs/usages related to a struct or type name.
Includes global instances, code refs to those, HLIL matches, and functions whose signature mentions the type.
Expand All @@ -726,15 +746,15 @@ def get_xrefs_to_type(type_name: str) -> list:


@mcp.tool()
def get_xrefs_to_enum(enum_name: str) -> list:
def get_xrefs_to_enum(enum_name: str) -> list[str]:
"""
Get usages/xrefs of an enum by scanning for member values and matches.
"""
return safe_get("getXrefsToEnum", {"name": enum_name})


@mcp.tool()
def get_xrefs_to_union(union_name: str) -> list:
def get_xrefs_to_union(union_name: str) -> list[str]:
"""
Get cross references/usages related to a union type by name.
"""
Expand Down Expand Up @@ -765,7 +785,7 @@ def get_stack_frame_vars(function_identifier: str) -> list:


@mcp.tool()
def format_value(address: str, text: str, size: int = 0) -> list:
def format_value(address: str, text: str, size: int = 0) -> list[str]:
"""
Convert and annotate a value at an address in Binary Ninja.
Adds a comment with hex/dec and C literal/string so you can see the change.
Expand Down Expand Up @@ -834,6 +854,14 @@ def make_function_at(address: str, platform: str = "") -> str:
Create a function at the given address. Platform is optional (e.g., "linux-x86_64").
Use "default" to explicitly select the BinaryView/platform default.
Returns status and function info; no-op if the function already exists.

IMPORTANT: This is non-blocking and safe to call during analysis. Always call
this BEFORE decompile_function, get_il, or function_at on addresses that may
not have a function defined yet. This prevents those tools from hanging while
waiting for analysis to discover the function. Recommended workflow:
1. get_binary_status() — check if analysis_complete
2. make_function_at(addr) — ensure function is defined (safe, non-blocking)
3. decompile_function / get_il — now safe to call
"""
params = {"address": address}
if platform:
Expand Down
Loading
Loading