fix(runtime): 修复 Skill/MCP 调用链路3个断点
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
1. Anthropic Driver ToolResult 格式修复 — ContentBlock 添加 ToolResult 变体, tool_call_id 不再被丢弃, 按 Anthropic API 规范发送 tool_result 格式 2. 前端 callMcpTool 参数名对齐 — serviceName/toolName/args 改为 service_name/tool_name/arguments, 后端支持 service_name 精确路由 3. MCP 工具桥接到 ToolRegistry — McpToolAdapter 添加 service_name/clone, 新建 McpToolWrapper 实现 Tool trait, Kernel 添加 mcp_adapters 共享状态, McpManagerState 与 Kernel 共享同一 Arc<RwLock<Vec>>, MCP 服务启停时 自动同步工具列表到 LLM 可见的 ToolRegistry
This commit is contained in:
@@ -63,6 +63,7 @@ pub async fn kernel_init(
|
||||
state: State<'_, KernelState>,
|
||||
scheduler_state: State<'_, SchedulerState>,
|
||||
heartbeat_state: State<'_, HeartbeatEngineState>,
|
||||
mcp_state: State<'_, crate::kernel_commands::mcp::McpManagerState>,
|
||||
config_request: Option<KernelConfigRequest>,
|
||||
) -> Result<KernelStatusResponse, String> {
|
||||
let mut kernel_lock = state.lock().await;
|
||||
@@ -168,6 +169,20 @@ pub async fn kernel_init(
|
||||
kernel.set_extraction_driver(std::sync::Arc::new(extraction_driver));
|
||||
}
|
||||
|
||||
// Bridge MCP adapters — kernel reads from this shared list during
|
||||
// create_tool_registry() so the LLM can discover MCP tools.
|
||||
// Share the McpManagerState's Arc with the Kernel so both point to the same list.
|
||||
{
|
||||
let shared_arc = mcp_state.kernel_adapters.clone();
|
||||
// Copy any adapters already in the kernel's default list into the shared one
|
||||
let kernel_default = kernel.mcp_adapters();
|
||||
if let (Ok(src), Ok(mut dst)) = (kernel_default.read(), shared_arc.write()) {
|
||||
*dst = src.clone();
|
||||
}
|
||||
kernel.set_mcp_adapters(shared_arc);
|
||||
tracing::info!("[kernel_init] Bridged MCP adapters to Kernel for LLM tool discovery");
|
||||
}
|
||||
|
||||
// Configure summary driver so the Growth system can generate L0/L1 summaries
|
||||
if let Some(api_key) = config_request.as_ref().and_then(|r| r.api_key.clone()) {
|
||||
crate::summarizer_adapter::configure_summary_driver(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! MCP (Model Context Protocol) Tauri commands
|
||||
//!
|
||||
//! Manages MCP server lifecycle: start/stop servers, discover tools.
|
||||
//! When services change, the kernel's shared adapter list is synced
|
||||
//! so the LLM can discover and call MCP tools in conversations.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -9,11 +11,43 @@ use tauri::State;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::info;
|
||||
|
||||
use zclaw_protocols::{BasicMcpClient, McpServerConfig, McpServiceManager};
|
||||
use zclaw_protocols::{BasicMcpClient, McpServerConfig, McpServiceManager, McpToolAdapter};
|
||||
|
||||
/// Shared MCP service manager state (newtype for Tauri state management)
|
||||
#[derive(Clone, Default)]
|
||||
pub struct McpManagerState(pub Arc<Mutex<McpServiceManager>>);
|
||||
#[derive(Clone)]
|
||||
pub struct McpManagerState {
|
||||
pub manager: Arc<Mutex<McpServiceManager>>,
|
||||
/// Shared with Kernel — updated on every start/stop so
|
||||
/// `create_tool_registry()` picks up the latest MCP tools.
|
||||
pub kernel_adapters: Arc<std::sync::RwLock<Vec<McpToolAdapter>>>,
|
||||
}
|
||||
|
||||
impl Default for McpManagerState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
manager: Arc::new(Mutex::new(McpServiceManager::new())),
|
||||
kernel_adapters: Arc::new(std::sync::RwLock::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl McpManagerState {
|
||||
/// Create with a pre-allocated kernel_adapters Arc for sharing with Kernel.
|
||||
pub fn with_shared_adapters(kernel_adapters: Arc<std::sync::RwLock<Vec<McpToolAdapter>>>) -> Self {
|
||||
Self {
|
||||
manager: Arc::new(Mutex::new(McpServiceManager::new())),
|
||||
kernel_adapters,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync all adapters from the service manager into the kernel's shared list.
|
||||
fn sync_to_kernel(&self, mgr: &McpServiceManager) {
|
||||
let all: Vec<McpToolAdapter> = mgr.all_adapters().into_iter().cloned().collect();
|
||||
if let Ok(mut guard) = self.kernel_adapters.write() {
|
||||
*guard = all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MCP service configuration (from frontend)
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -53,7 +87,7 @@ pub async fn mcp_start_service(
|
||||
manager: State<'_, McpManagerState>,
|
||||
config: McpServiceConfig,
|
||||
) -> Result<Vec<McpToolInfo>, String> {
|
||||
let mut guard = manager.0.lock().await;
|
||||
let mut guard = manager.manager.lock().await;
|
||||
|
||||
let server_config = McpServerConfig {
|
||||
command: config.command,
|
||||
@@ -78,13 +112,16 @@ pub async fn mcp_start_service(
|
||||
let tools: Vec<McpToolInfo> = adapters
|
||||
.into_iter()
|
||||
.map(|a| McpToolInfo {
|
||||
service_name: config.name.clone(),
|
||||
tool_name: a.name().to_string(),
|
||||
service_name: a.service_name().to_string(),
|
||||
tool_name: a.tool_name().to_string(),
|
||||
description: a.description().to_string(),
|
||||
input_schema: a.input_schema().clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sync adapters to kernel so LLM can see the new tools
|
||||
manager.sync_to_kernel(&guard);
|
||||
|
||||
info!(service = %config.name, tool_count = tools.len(), "MCP tools registered");
|
||||
Ok(tools)
|
||||
}
|
||||
@@ -96,8 +133,12 @@ pub async fn mcp_stop_service(
|
||||
manager: State<'_, McpManagerState>,
|
||||
name: String,
|
||||
) -> Result<(), String> {
|
||||
let mut guard = manager.0.lock().await;
|
||||
let mut guard = manager.manager.lock().await;
|
||||
guard.remove_service(&name);
|
||||
|
||||
// Sync adapters to kernel so LLM no longer sees removed tools
|
||||
manager.sync_to_kernel(&guard);
|
||||
|
||||
info!(service = %name, "MCP service stopped");
|
||||
Ok(())
|
||||
}
|
||||
@@ -108,7 +149,7 @@ pub async fn mcp_stop_service(
|
||||
pub async fn mcp_list_services(
|
||||
manager: State<'_, McpManagerState>,
|
||||
) -> Result<Vec<McpServiceStatus>, String> {
|
||||
let guard = manager.0.lock().await;
|
||||
let guard = manager.manager.lock().await;
|
||||
let names = guard.service_names();
|
||||
let all = guard.all_adapters();
|
||||
let statuses: Vec<McpServiceStatus> = names
|
||||
@@ -116,9 +157,10 @@ pub async fn mcp_list_services(
|
||||
.map(|name| {
|
||||
let tools: Vec<McpToolInfo> = all
|
||||
.iter()
|
||||
.filter(|a| a.service_name() == name)
|
||||
.map(|a| McpToolInfo {
|
||||
service_name: name.to_string(),
|
||||
tool_name: a.name().to_string(),
|
||||
service_name: a.service_name().to_string(),
|
||||
tool_name: a.tool_name().to_string(),
|
||||
description: a.description().to_string(),
|
||||
input_schema: a.input_schema().clone(),
|
||||
})
|
||||
@@ -138,15 +180,24 @@ pub async fn mcp_list_services(
|
||||
#[tauri::command]
|
||||
pub async fn mcp_call_tool(
|
||||
manager: State<'_, McpManagerState>,
|
||||
service_name: Option<String>,
|
||||
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))?;
|
||||
let guard = manager.manager.lock().await;
|
||||
let adapter = if let Some(ref svc) = service_name {
|
||||
// Route by service name + tool name for precision
|
||||
guard.all_adapters()
|
||||
.into_iter()
|
||||
.find(|a| a.service_name() == svc && a.tool_name() == tool_name)
|
||||
.ok_or_else(|| format!("MCP tool '{}' in service '{}' not found", tool_name, svc))?
|
||||
} else {
|
||||
// Fallback: match by tool name only (first match)
|
||||
guard.all_adapters()
|
||||
.into_iter()
|
||||
.find(|a| a.tool_name() == tool_name)
|
||||
.ok_or_else(|| format!("MCP tool '{}' not found", tool_name))?
|
||||
};
|
||||
|
||||
adapter
|
||||
.execute(arguments)
|
||||
|
||||
@@ -73,5 +73,9 @@ export async function callMcpTool(
|
||||
args: Record<string, unknown>
|
||||
): Promise<unknown> {
|
||||
log.info('callMcpTool', { serviceName, toolName });
|
||||
return invoke<unknown>('mcp_call_tool', { serviceName, toolName, args });
|
||||
return invoke<unknown>('mcp_call_tool', {
|
||||
service_name: serviceName,
|
||||
tool_name: toolName,
|
||||
arguments: args,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user