Skip to main content

binaryninja/mcp/
server.rs

1// Copyright 2022-2026 Vector 35 Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Offering MCP tools from an MCP server.
16
17use 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
29/// Creates a tool without registering it. This can be used to expose a tool that is specific to one
30/// MCP server, such as one that manages that server's active binary view. Otherwise the same as
31/// [`register_mcp_tool`](super::tool::register_mcp_tool).
32pub fn create_mcp_tool<T: McpTool>(tool: T) -> Option<Ref<CoreMcpTool>> {
33    create_with(tool, BNCreateMcpTool)
34}
35
36/// Creates a [`TypedMcpTool`](super::tool::TypedMcpTool) without registering it. See
37/// [`create_mcp_tool`].
38#[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
43/// The request state an MCP server supplies for one invocation of a tool.
44pub 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
54/// Runs the tool for an MCP server and returns the MCP `CallToolResult`.
55pub 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}