feat(protocols): MCP tool adapter + Tauri commands + initialize bug fix
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
S6 MCP Protocol: - Fix McpTransport::initialize() — store actual server capabilities instead of discarding them and storing empty ServerCapabilities::default() - Add send_notification() method to McpTransport for JSON-RPC notifications - Send notifications/initialized after MCP handshake (spec requirement) - Add McpToolAdapter: bridges MCP server tools into the tool execution path - Add McpServiceManager: lifecycle management for MCP server connections - Add 4 Tauri commands: mcp_start_service, mcp_stop_service, mcp_list_services, mcp_call_tool - Register zclaw-protocols dependency in desktop Cargo.toml New files: - crates/zclaw-protocols/src/mcp_tool_adapter.rs (153 lines) - desktop/src-tauri/src/kernel_commands/mcp.rs (145 lines) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
155
desktop/src-tauri/src/kernel_commands/mcp.rs
Normal file
155
desktop/src-tauri/src/kernel_commands/mcp.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
//! MCP (Model Context Protocol) Tauri commands
|
||||
//!
|
||||
//! Manages MCP server lifecycle: start/stop servers, discover tools.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::State;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::info;
|
||||
|
||||
use zclaw_protocols::{BasicMcpClient, McpServerConfig, McpServiceManager};
|
||||
|
||||
/// Shared MCP service manager state (newtype for Tauri state management)
|
||||
#[derive(Clone, Default)]
|
||||
pub struct McpManagerState(pub Arc<Mutex<McpServiceManager>>);
|
||||
|
||||
/// MCP service configuration (from frontend)
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct McpServiceConfig {
|
||||
pub name: String,
|
||||
pub command: String,
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub env: HashMap<String, String>,
|
||||
pub cwd: Option<String>,
|
||||
}
|
||||
|
||||
/// MCP tool info (returned to frontend)
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct McpToolInfo {
|
||||
pub service_name: String,
|
||||
pub tool_name: String,
|
||||
pub description: String,
|
||||
pub input_schema: serde_json::Value,
|
||||
}
|
||||
|
||||
/// MCP service status
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct McpServiceStatus {
|
||||
pub name: String,
|
||||
pub tool_count: usize,
|
||||
pub tools: Vec<McpToolInfo>,
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Start an MCP server and discover its tools
|
||||
/// @connected — frontend: MCPServices.tsx via mcp-client.ts
|
||||
#[tauri::command]
|
||||
pub async fn mcp_start_service(
|
||||
manager: State<'_, McpManagerState>,
|
||||
config: McpServiceConfig,
|
||||
) -> Result<Vec<McpToolInfo>, String> {
|
||||
let mut guard = manager.0.lock().await;
|
||||
|
||||
let server_config = McpServerConfig {
|
||||
command: config.command,
|
||||
args: config.args,
|
||||
env: config.env,
|
||||
cwd: config.cwd,
|
||||
};
|
||||
|
||||
let client = Arc::new(BasicMcpClient::new(server_config));
|
||||
client
|
||||
.initialize()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to initialize MCP service '{}': {}", config.name, e))?;
|
||||
|
||||
info!(service = %config.name, "MCP service initialized");
|
||||
|
||||
let adapters = guard
|
||||
.register_service(config.name.clone(), client)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to register MCP service '{}': {}", config.name, e))?;
|
||||
|
||||
let tools: Vec<McpToolInfo> = adapters
|
||||
.into_iter()
|
||||
.map(|a| McpToolInfo {
|
||||
service_name: config.name.clone(),
|
||||
tool_name: a.name().to_string(),
|
||||
description: a.description().to_string(),
|
||||
input_schema: a.input_schema().clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
info!(service = %config.name, tool_count = tools.len(), "MCP tools registered");
|
||||
Ok(tools)
|
||||
}
|
||||
|
||||
/// Stop an MCP server and remove its tools
|
||||
/// @connected — frontend: MCPServices.tsx via mcp-client.ts
|
||||
#[tauri::command]
|
||||
pub async fn mcp_stop_service(
|
||||
manager: State<'_, McpManagerState>,
|
||||
name: String,
|
||||
) -> Result<(), String> {
|
||||
let mut guard = manager.0.lock().await;
|
||||
guard.remove_service(&name);
|
||||
info!(service = %name, "MCP service stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List all active MCP services and their tools
|
||||
/// @connected — frontend: MCPServices.tsx via mcp-client.ts
|
||||
#[tauri::command]
|
||||
pub async fn mcp_list_services(
|
||||
manager: State<'_, McpManagerState>,
|
||||
) -> Result<Vec<McpServiceStatus>, String> {
|
||||
let guard = manager.0.lock().await;
|
||||
let names = guard.service_names();
|
||||
let all = guard.all_adapters();
|
||||
let statuses: Vec<McpServiceStatus> = names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let tools: Vec<McpToolInfo> = all
|
||||
.iter()
|
||||
.map(|a| McpToolInfo {
|
||||
service_name: name.to_string(),
|
||||
tool_name: a.name().to_string(),
|
||||
description: a.description().to_string(),
|
||||
input_schema: a.input_schema().clone(),
|
||||
})
|
||||
.collect();
|
||||
McpServiceStatus {
|
||||
name: name.to_string(),
|
||||
tool_count: tools.len(),
|
||||
tools,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(statuses)
|
||||
}
|
||||
|
||||
/// Call an MCP tool directly
|
||||
/// @connected — frontend: agent loop via mcp-client.ts
|
||||
#[tauri::command]
|
||||
pub async fn mcp_call_tool(
|
||||
manager: State<'_, McpManagerState>,
|
||||
tool_name: String,
|
||||
arguments: serde_json::Value,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let guard = manager.0.lock().await;
|
||||
let adapter = guard
|
||||
.all_adapters()
|
||||
.into_iter()
|
||||
.find(|a| a.name() == tool_name)
|
||||
.ok_or_else(|| format!("MCP tool '{}' not found", tool_name))?;
|
||||
|
||||
adapter
|
||||
.execute(arguments)
|
||||
.await
|
||||
.map_err(|e| format!("MCP tool '{}' execution failed: {}", tool_name, e))
|
||||
}
|
||||
@@ -12,6 +12,7 @@ pub mod approval;
|
||||
pub mod chat;
|
||||
pub mod hand;
|
||||
pub mod lifecycle;
|
||||
pub mod mcp;
|
||||
pub mod scheduled_task;
|
||||
pub mod skill;
|
||||
pub mod trigger;
|
||||
|
||||
@@ -122,6 +122,7 @@ pub fn run() {
|
||||
.manage(classroom_state)
|
||||
.manage(classroom_chat_state)
|
||||
.manage(classroom_gen_tasks)
|
||||
.manage(kernel_commands::mcp::McpManagerState::default())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// Internal ZCLAW Kernel commands (preferred)
|
||||
kernel_commands::lifecycle::kernel_init,
|
||||
@@ -323,7 +324,12 @@ pub fn run() {
|
||||
classroom_commands::generate::classroom_list,
|
||||
classroom_commands::chat::classroom_chat,
|
||||
classroom_commands::chat::classroom_chat_history,
|
||||
classroom_commands::export::classroom_export
|
||||
classroom_commands::export::classroom_export,
|
||||
// MCP (Model Context Protocol) lifecycle commands
|
||||
kernel_commands::mcp::mcp_start_service,
|
||||
kernel_commands::mcp::mcp_stop_service,
|
||||
kernel_commands::mcp::mcp_list_services,
|
||||
kernel_commands::mcp::mcp_call_tool,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
Reference in New Issue
Block a user