Skip to main content

rustyclaw_core/gateway/
mcp_handler.rs

1//! MCP tool execution handler for the gateway.
2
3#[cfg(feature = "mcp")]
4use tracing::{debug, instrument, warn};
5
6#[cfg(feature = "mcp")]
7use crate::mcp::McpManager;
8#[cfg(feature = "mcp")]
9use std::sync::Arc;
10#[cfg(feature = "mcp")]
11use tokio::sync::Mutex;
12
13#[cfg(feature = "mcp")]
14pub type SharedMcpManager = Arc<Mutex<McpManager>>;
15
16/// Check if a tool name is an MCP tool.
17pub fn is_mcp_tool(name: &str) -> bool {
18    name.starts_with("mcp_")
19}
20
21/// Execute an MCP tool call.
22///
23/// Returns Ok(output) on success, Err(error_message) on failure.
24#[cfg(feature = "mcp")]
25#[instrument(skip(args, mcp_mgr), fields(%name))]
26pub async fn execute_mcp_tool(
27    name: &str,
28    args: &serde_json::Value,
29    mcp_mgr: &SharedMcpManager,
30) -> Result<String, String> {
31    debug!("Executing MCP tool");
32
33    let mgr = mcp_mgr.lock().await;
34
35    match mgr.call_tool_by_name(name, args.clone()).await {
36        Ok(result) => {
37            if result.success {
38                Ok(result.to_llm_string())
39            } else {
40                Err(result
41                    .error
42                    .unwrap_or_else(|| "Unknown MCP error".to_string()))
43            }
44        }
45        Err(e) => {
46            warn!(tool = name, error = %e, "MCP tool call failed");
47            Err(e.to_string())
48        }
49    }
50}
51
52/// Stub for when MCP feature is disabled.
53#[cfg(not(feature = "mcp"))]
54pub async fn execute_mcp_tool(
55    name: &str,
56    _args: &serde_json::Value,
57    _mcp_mgr: &(),
58) -> Result<String, String> {
59    Err(format!(
60        "MCP tool '{}' called but MCP support is not enabled. Rebuild with --features mcp",
61        name
62    ))
63}
64
65/// Get MCP tool schemas for the system prompt.
66#[cfg(feature = "mcp")]
67pub async fn get_mcp_tool_schemas(mcp_mgr: &SharedMcpManager) -> Vec<serde_json::Value> {
68    let mgr = mcp_mgr.lock().await;
69    mgr.get_tool_schemas().await
70}
71
72/// Stub for when MCP feature is disabled.
73#[cfg(not(feature = "mcp"))]
74pub async fn get_mcp_tool_schemas(_mcp_mgr: &()) -> Vec<serde_json::Value> {
75    Vec::new()
76}
77
78/// Generate MCP tools section for the system prompt.
79#[cfg(feature = "mcp")]
80pub async fn generate_mcp_prompt_section(mcp_mgr: &SharedMcpManager) -> String {
81    let mgr = mcp_mgr.lock().await;
82    let tools = mgr.list_all_tools().await;
83
84    if tools.is_empty() {
85        return String::new();
86    }
87
88    let mut section = String::from("\n## MCP Tools\n\n");
89    section.push_str("The following tools are provided by connected MCP servers:\n\n");
90
91    // Group by server
92    let mut by_server: std::collections::HashMap<String, Vec<_>> = std::collections::HashMap::new();
93    for tool in tools {
94        by_server
95            .entry(tool.server_name.clone())
96            .or_default()
97            .push(tool);
98    }
99
100    for (server, tools) in by_server {
101        section.push_str(&format!("### {} (MCP server)\n\n", server));
102        for tool in tools {
103            section.push_str(&format!(
104                "- **{}**: {}\n",
105                tool.prefixed_name(),
106                tool.description.as_deref().unwrap_or("(no description)")
107            ));
108        }
109        section.push('\n');
110    }
111
112    section
113}
114
115/// Stub for when MCP feature is disabled.
116#[cfg(not(feature = "mcp"))]
117pub async fn generate_mcp_prompt_section(_mcp_mgr: &()) -> String {
118    String::new()
119}