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

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:
iven
2026-04-11 16:20:38 +08:00
parent 2843bd204f
commit 9e0aa496cd
12 changed files with 389 additions and 29 deletions

View File

@@ -231,15 +231,19 @@ impl AnthropicDriver {
input: input.clone(),
}],
}),
zclaw_types::Message::ToolResult { tool_call_id: _, tool: _, output, is_error } => {
let content = if *is_error {
zclaw_types::Message::ToolResult { tool_call_id, tool: _, output, is_error } => {
let content_text = if *is_error {
format!("Error: {}", output)
} else {
output.to_string()
};
Some(AnthropicMessage {
role: "user".to_string(),
content: vec![ContentBlock::Text { text: content }],
content: vec![ContentBlock::ToolResult {
tool_use_id: tool_call_id.clone(),
content: content_text,
is_error: *is_error,
}],
})
}
_ => None,

View File

@@ -116,6 +116,13 @@ pub enum ContentBlock {
Text { text: String },
Thinking { thinking: String },
ToolUse { id: String, name: String, input: serde_json::Value },
/// Anthropic API tool result — must be sent as `role: "user"` with this content block.
ToolResult {
tool_use_id: String,
content: String,
#[serde(skip_serializing_if = "std::ops::Not::not")]
is_error: bool,
},
}
/// Stop reason

View File

@@ -737,6 +737,9 @@ impl OpenAiDriver {
input: input.clone(),
});
}
ContentBlock::ToolResult { .. } => {
// ToolResult is only used in request messages, never in responses
}
}
}

View File

@@ -9,6 +9,7 @@ mod skill_load;
mod path_validator;
mod task;
mod ask_clarification;
pub mod mcp_tool;
pub use file_read::FileReadTool;
pub use file_write::FileWriteTool;
@@ -19,6 +20,7 @@ pub use skill_load::SkillLoadTool;
pub use path_validator::{PathValidator, PathValidatorConfig};
pub use task::TaskTool;
pub use ask_clarification::AskClarificationTool;
pub use mcp_tool::McpToolWrapper;
use crate::tool::ToolRegistry;

View File

@@ -0,0 +1,48 @@
//! MCP Tool Wrapper — bridges MCP server tools into the ToolRegistry
//!
//! Wraps `McpToolAdapter` (from zclaw-protocols) as a `Tool` trait object
//! so the LLM can discover and call MCP tools during conversations.
use async_trait::async_trait;
use serde_json::Value;
use std::sync::Arc;
use zclaw_types::Result;
use crate::tool::{Tool, ToolContext};
/// Wraps an MCP tool adapter into the `Tool` trait.
///
/// The wrapper holds an `Arc<McpToolAdapter>` and delegates execution
/// to the adapter, ignoring the `ToolContext` (MCP tools don't need
/// agent_id, workspace, etc.).
pub struct McpToolWrapper {
adapter: Arc<zclaw_protocols::McpToolAdapter>,
/// Cached qualified name (service.tool) for Tool::name()
qualified_name: String,
}
impl McpToolWrapper {
pub fn new(adapter: Arc<zclaw_protocols::McpToolAdapter>) -> Self {
let qualified_name = adapter.qualified_name();
Self { adapter, qualified_name }
}
}
#[async_trait]
impl Tool for McpToolWrapper {
fn name(&self) -> &str {
&self.qualified_name
}
fn description(&self) -> &str {
self.adapter.description()
}
fn input_schema(&self) -> Value {
self.adapter.input_schema().clone()
}
async fn execute(&self, input: Value, _context: &ToolContext) -> Result<Value> {
self.adapter.execute(input).await
}
}