binaryninja/mcp/
server.rs1use binaryninjacore_sys::*;
18use serde_json::Value;
19use std::borrow::Cow;
20use std::ffi::{c_char, c_void, CStr};
21use std::panic::{self, AssertUnwindSafe};
22use std::ptr;
23
24use super::tool::{create_with, CoreMcpTool, McpTool};
25use crate::binary_view::BinaryView;
26use crate::rc::*;
27use crate::string::{BnString, IntoCStr};
28
29pub fn create_mcp_tool<T: McpTool>(tool: T) -> Option<Ref<CoreMcpTool>> {
33 create_with(tool, BNCreateMcpTool)
34}
35
36#[cfg(feature = "schemars")]
39pub fn create_typed_mcp_tool<T: super::tool::TypedMcpTool>(tool: T) -> Option<Ref<CoreMcpTool>> {
40 create_mcp_tool(super::tool::typed::TypedAdapter::new(tool))
41}
42
43pub trait McpToolCallHost: Sync {
45 fn binary_view(&self) -> Option<Ref<BinaryView>>;
46
47 fn is_cancelled(&self) -> bool {
48 false
49 }
50
51 fn report_progress(&self, _progress: f64, _total: f64, _message: &str) {}
52}
53
54pub fn invoke_mcp_tool<H: McpToolCallHost>(
56 tool: &CoreMcpTool,
57 host: &H,
58 arguments: &Value,
59) -> Value {
60 extern "C" fn cb_get_binary_view<H: McpToolCallHost>(ctxt: *mut c_void) -> *mut BNBinaryView {
61 let host = unsafe { &*(ctxt as *const H) };
62 match panic::catch_unwind(AssertUnwindSafe(|| host.binary_view())) {
63 Ok(Some(view)) => unsafe { BNNewViewReference(view.handle) },
64 Ok(None) => ptr::null_mut(),
65 Err(_) => {
66 tracing::error!("MCP host panicked supplying a binary view");
67 ptr::null_mut()
68 }
69 }
70 }
71
72 extern "C" fn cb_is_cancelled<H: McpToolCallHost>(ctxt: *mut c_void) -> bool {
73 let host = unsafe { &*(ctxt as *const H) };
74 panic::catch_unwind(AssertUnwindSafe(|| host.is_cancelled())).unwrap_or_else(|_| {
75 tracing::error!("MCP host panicked reporting cancellation");
76 false
77 })
78 }
79
80 extern "C" fn cb_report_progress<H: McpToolCallHost>(
81 ctxt: *mut c_void,
82 progress: f64,
83 total: f64,
84 message: *const c_char,
85 ) {
86 let host = unsafe { &*(ctxt as *const H) };
87 let message = if message.is_null() {
88 Cow::Borrowed("")
89 } else {
90 unsafe { CStr::from_ptr(message) }.to_string_lossy()
91 };
92 if panic::catch_unwind(AssertUnwindSafe(|| {
93 host.report_progress(progress, total, &message)
94 }))
95 .is_err()
96 {
97 tracing::error!("MCP host panicked receiving progress");
98 }
99 }
100
101 let callbacks = BNMcpToolCallCallbacks {
102 context: host as *const H as *mut c_void,
103 getBinaryView: Some(cb_get_binary_view::<H>),
104 isCancelled: Some(cb_is_cancelled::<H>),
105 reportProgress: Some(cb_report_progress::<H>),
106 };
107 let arguments = arguments.to_string().to_cstr();
108 unsafe {
109 let call = BNCreateMcpToolCall(&callbacks);
110 let result = BNCreateMcpToolResult();
111 BNInvokeMcpTool(tool.handle, call, arguments.as_ptr(), result);
112 let json = BnString::into_string(BNGetMcpToolResultJson(result));
113 BNFreeMcpToolResult(result);
114 BNFreeMcpToolCall(call);
115 serde_json::from_str(&json).unwrap_or(Value::Null)
116 }
117}