Binary Ninja MCP

Below is an example of a complete Python‐based implementation that sets up an MCP server exposing several Binary Ninja API endpoints and a corresponding client plugin that can be loaded into Binary Ninja (and, by extension, used with Claude Desktop if integrated there). In this example, the server uses Flask to expose REST endpoints (following the Model Context Protocol “MCP” style) that wrap common Binary Ninja functions, and the client uses Python’s requests library together with Binary Ninja’s plugin API to call those endpoints and display results.

Note:

-You must have Binary Ninja Pro installed and the Binary Ninja Python API available.
-The server requires installation of Flask (e.g. via pip install flask) and the client requires the requests library (pip install requests).
-In a real-world deployment you would expand the endpoints to cover the full MCP specification and additional Binary Ninja API functions; the following code is a representative “fully implemented” sample covering the key endpoints.


File: mcp_server.py

#!/usr/bin/env python

MCP Server for Binary Ninja Pro

This server implements a RESTful interface following the Model Context Protocol (MCP)
to expose several Binary Ninja API endpoints. Clients (such as Claude Desktop or a
Binary Ninja plugin) can query this server to retrieve core version information,
load binaries for analysis, set a license, or even trigger debugger connections.

Exposed Endpoints:
    GET /api/core_version_info
        Returns the Binary Ninja core version info (major, minor, build, channel).

    GET /api/core_version
        Returns the Binary Ninja core version string.

    GET /api/bundled_plugin_path
        Returns the current bundled plugin path.

    GET /api/core_serial
        Returns the current Binary Ninja serial number.

    POST /api/core_set_license
        Sets the Binary Ninja license. Expects JSON with key "licenseData".

    POST /api/load
        Loads a binary file. Expects JSON with key "filepath" and returns analysis info.

    POST /api/shutdown
        Cleanly shuts down the Binary Ninja core.

    GET /api/connect_pycharm_debugger
        Initiates a connection to the PyCharm debugger. (Port number may be specified as a query parameter.)

    GET /api/connect_vscode_debugger
        Initiates a connection to the VSCode debugger. (Port number may be specified as a query parameter.)
from flask import Flask, request, jsonify
import binaryninja as bn
import os
import threading

app = Flask(__name__)

@app.route('/api/core_version_info', methods=['GET'])
def get_core_version_info():
    """
    Retrieve Binary Ninja core version information.
    Returns:
        JSON object with keys: major, minor, build, channel.
    """
    version_info = bn.core_version_info()
    result = {
        "major": version_info.major,
        "minor": version_info.minor,
        "build": version_info.build,
        "channel": version_info.channel,
    }
    return jsonify(result)

@app.route('/api/core_version', methods=['GET'])
def get_core_version():
    """
    Retrieve the Binary Ninja core version string.
    Returns:
        JSON object with key "version".
    """
    version = bn.core_version()
    return jsonify({"version": version})

@app.route('/api/bundled_plugin_path', methods=['GET'])
def get_bundled_plugin_path():
    """
    Retrieve the current bundled plugin path.
    Returns:
        JSON object with key "bundled_plugin_path".
    """
    path = bn.bundled_plugin_path()
    return jsonify({"bundled_plugin_path": path})

@app.route('/api/core_serial', methods=['GET'])
def get_core_serial():
    """
    Retrieve the current Binary Ninja serial number.
    Returns:
        JSON object with key "core_serial".
    """
    serial = bn.core_serial()
    return jsonify({"core_serial": serial})

@app.route('/api/core_set_license', methods=['POST'])
def set_license():
    """
    Set the Binary Ninja license.
    Expects:
        JSON payload with key "licenseData" containing the full license text.
    Returns:
        JSON status message.
    """
    data = request.get_json()
    if not data or 'licenseData' not in data:
        return jsonify({"error": "licenseData not provided"}), 400
    license_data = data['licenseData']
    try:
        bn.core_set_license(license_data)
        return jsonify({"status": "License set successfully."})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/api/load', methods=['POST'])
def load_binary():
    """
    Load a binary file and perform analysis.
    Expects:
        JSON payload with key "filepath" (absolute or relative file path).
    Returns:
        JSON object containing the file path, function count, and list of function names.
    """
    data = request.get_json()
    if not data or 'filepath' not in data:
        return jsonify({"error": "filepath not provided"}), 400
    filepath = data['filepath']
    if not os.path.exists(filepath):
        return jsonify({"error": "File does not exist."}), 400
    try:
        bv = bn.load(filepath)
        # By default update_analysis is True so analysis will run.
        functions = list(bv.functions)
        result = {
            "filepath": filepath,
            "function_count": len(functions),
            "functions": [func.name for func in functions]
        }
        bv.shutdown()  # Clean up resources
        return jsonify(result)
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/api/shutdown', methods=['POST'])
def shutdown_core():
    """
    Shutdown the Binary Ninja core cleanly.
    Returns:
        JSON status message.
    """
    try:
        bn.shutdown()
        return jsonify({"status": "Binary Ninja core shutdown initiated."})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/api/connect_pycharm_debugger', methods=['GET'])
def connect_pycharm():
    """
    Connect to the PyCharm debugger.
    Query Parameters:
        port (optional, int): Port number to connect to (default is 5678).
    Returns:
        JSON status message.
    """
    port = request.args.get('port', default=5678, type=int)
    try:
        # Run the debugger connection in a separate thread so as not to block the server.
        threading.Thread(target=bn.connect_pycharm_debugger, args=(port,)).start()
        return jsonify({"status": f"Connecting to PyCharm debugger on port {port}."})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/api/connect_vscode_debugger', methods=['GET'])
def connect_vscode():
    """
    Connect to the Visual Studio Code debugger.
    Query Parameters:
        port (optional, int): Port number to connect to (default is 5678).
    Returns:
        JSON status message.
    """
    port = request.args.get('port', default=5678, type=int)
    try:
        threading.Thread(target=bn.connect_vscode_debugger, args=(port,)).start()
        return jsonify({"status": f"Connecting to VSCode debugger on port {port}."})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

def run_server():
    """
    Runs the MCP server on all network interfaces at port 5000.
    """
    app.run(host='0.0.0.0', port=5000)

if __name__ == '__main__':
    run_server()

File: mcp_client_plugin.py

MCP Client Plugin for Binary Ninja Pro (and Claude Desktop)

This plugin registers several Binary Ninja commands that communicate with the MCP server. Each command uses HTTP requests to invoke MCP endpoints and then displays the results in Binary Ninja.

Commands Registered: - MCP\Get Core Version Info: Retrieves and displays core version details. - MCP\Load Binary: Prompts for a file path, loads the binary via MCP server, and shows analysis info. - MCP\Set License: Prompts for license data and sends it to the MCP server. - MCP\Shutdown Core: Initiates a shutdown of the Binary Ninja core via MCP server.

from binaryninja import PluginCommand, show_message_box, MessageBoxButtonSet, MessageBoxIcon, get_form_input, TextLineField
import requests
import json

# URL for the locally running MCP server
MCP_SERVER_URL = "http://127.0.0.1:5000/api"

def get_core_version_info():
    """
    Command to get Binary Ninja core version info via the MCP server.
    """
    try:
        response = requests.get(f"{MCP_SERVER_URL}/core_version_info")
        if response.status_code == 200:
            info = response.json()
            show_message_box("Core Version Info", json.dumps(info, indent=4),
                             MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.InformationIcon)
        else:
            show_message_box("Error", f"Error fetching core version info: {response.text}",
                             MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
    except Exception as e:
        show_message_box("Exception", str(e),
                         MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)

def load_binary():
    """
    Command to load a binary file via the MCP server.
    Prompts the user for a file path and then displays analysis results.
    """
    fields = [
        TextLineField("File Path", "Enter the full path to the binary")
    ]
    result = get_form_input(fields, "Load Binary", "Enter file path for binary load:")
    if result is None:
        return
    filepath = result["File Path"]
    try:
        response = requests.post(f"{MCP_SERVER_URL}/load", json={"filepath": filepath})
        if response.status_code == 200:
            info = response.json()
            show_message_box("Binary Loaded", json.dumps(info, indent=4),
                             MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.InformationIcon)
        else:
            show_message_box("Error", f"Error loading binary: {response.text}",
                             MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
    except Exception as e:
        show_message_box("Exception", str(e),
                         MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)

def set_license():
    """
    Command to set the Binary Ninja license via the MCP server.
    Prompts the user for license data.
    """
    fields = [
        TextLineField("License Data", "Enter the full license text")
    ]
    result = get_form_input(fields, "Set License", "Enter license data:")
    if result is None:
        return
    license_data = result["License Data"]
    try:
        response = requests.post(f"{MCP_SERVER_URL}/core_set_license", json={"licenseData": license_data})
        if response.status_code == 200:
            info = response.json()
            show_message_box("License Set", json.dumps(info, indent=4),
                             MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.InformationIcon)
        else:
            show_message_box("Error", f"Error setting license: {response.text}",
                             MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
    except Exception as e:
        show_message_box("Exception", str(e),
                         MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)

def shutdown_core():
    """
    Command to shutdown the Binary Ninja core via the MCP server.
    """
    try:
        response = requests.post(f"{MCP_SERVER_URL}/shutdown")
        if response.status_code == 200:
            info = response.json()
            show_message_box("Shutdown", json.dumps(info, indent=4),
                             MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.InformationIcon)
        else:
            show_message_box("Error", f"Error shutting down core: {response.text}",
                             MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)
    except Exception as e:
        show_message_box("Exception", str(e),
                         MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)

# Register plugin commands in Binary Ninja
PluginCommand.register("MCP\\Get Core Version Info", "Retrieve core version info via MCP server", get_core_version_info)
PluginCommand.register("MCP\\Load Binary", "Load a binary via MCP server", load_binary)
PluginCommand.register("MCP\\Set License", "Set Binary Ninja license via MCP server", set_license)
PluginCommand.register("MCP\\Shutdown Core", "Shutdown Binary Ninja core via MCP server", shutdown_core)

Usage Instructions

  1. Server Setup:
    • Save the mcp_server.py file and run it in an environment where Binary Ninja’s Python API is available.
    • Start the server with:

    python mcp_server.py
    

    The server will listen on port 5000.

  2. Client Plugin Installation:
    • Save the mcp_client_plugin.py file into your Binary Ninja plugins folder (or load it manually via the Binary Ninja UI).
    • Once loaded, you will see new commands (prefixed with “MCP\”) under the Binary Ninja command palette.
    • These commands will communicate with the MCP server you just started.

  3. Integration with Claude Desktop:
    • If Claude Desktop supports loading Binary Ninja plugins, install the client plugin there as well.
    • Otherwise, you can use the MCP endpoints from any client that supports HTTP (e.g. via your own integration).

This complete solution demonstrates a fully implemented MCP server and client plugin for Binary Ninja Pro using Python. You can extend the endpoints and commands further to cover additional Binary Ninja API features as needed.



Tags: Python, Defensive Tooling

← Back home