修复 Claude Direct MCP 回合授权死循环
Project CI / AI game creator shell Rust crates (push) Successful in 2m47s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 4m49s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 6m16s
Project CI / Backend tests (push) Failing after 4m53s
Project CI / Frontend tests (push) Successful in 3m36s
Project CI / Repository checks (push) Failing after 3m57s
Project CI / AI game creator shell web tests (push) Successful in 2m33s
Project CI / Native shell tests (push) Successful in 6m55s

内部 Claude MCP 使用 Direct 工具桥并绑定当前回合授权

保留外部 MCP 隔离模式并补充回归测试与排障记录
This commit is contained in:
kdletters
2026-10-03 19:04:14 +08:00
parent 2233f1b7c4
commit e5fa123483
4 changed files with 46 additions and 6 deletions
@@ -960,7 +960,8 @@ pub(crate) async fn direct_game_creator_claude_code_chat_at(
observer: Option<&mut (dyn FnMut(TurnObservation) + Send)>,
) -> Result<String, String> {
direct_turn_trace("claude-executor-enter");
let (mcp_url, mcp_token) = start_external_mcp_loopback(root, llm.web_search_enabled).await?;
let (mcp_url, mcp_token) =
start_external_mcp_loopback_for_direct_turn(root, llm.web_search_enabled).await?;
direct_turn_trace("claude-mcp-loopback-ready");
let _mcp_guard = ExternalMcpStopGuard;
let home = crate::project::resolve_local_project_path(root, ".agent/runtime/claude-code/home")?;
@@ -4236,8 +4236,9 @@ pub(crate) async fn start_direct_tool_bridge(
pub(crate) async fn start_external_client_tool_bridge(
root: &Path,
controlled_web_search: bool,
direct_turn_execution: bool,
) -> Result<DirectToolBridge, String> {
start_tool_bridge_for_source(root, controlled_web_search, false).await
start_tool_bridge_for_source(root, controlled_web_search, direct_turn_execution).await
}
async fn start_tool_bridge_for_source(
@@ -4518,6 +4519,18 @@ mod tests {
}
}
#[tokio::test]
async fn external_client_bridge_can_be_enabled_for_a_direct_turn() {
let temporary = tempfile::tempdir().unwrap();
init_local_game_project_at(temporary.path(), "external-direct-turn", "外部直连回合")
.unwrap();
let bridge = start_external_client_tool_bridge(temporary.path(), false, true)
.await
.expect("direct Claude bridge");
assert!(bridge.state.direct_turn_execution);
drop(bridge);
}
#[tokio::test]
async fn host_cancellation_releases_queued_image_without_a_new_post() {
use std::sync::atomic::{AtomicBool, Ordering};
@@ -41,6 +41,7 @@ tokio::task_local! {
}
pub(crate) struct ExternalMcpServer {
_direct_turn_guard: Option<super::direct_tool_bridge::DirectToolBridgeTurnGuard>,
_bridge: super::direct_tool_bridge::DirectToolBridge,
task: tokio::task::JoinHandle<()>,
}
@@ -1759,6 +1760,21 @@ async fn handle_external_mcp_http_request(
pub(crate) async fn start_external_mcp_loopback(
root: &Path,
controlled_web_search: bool,
) -> Result<(String, String), String> {
start_external_mcp_loopback_with_mode(root, controlled_web_search, false).await
}
pub(crate) async fn start_external_mcp_loopback_for_direct_turn(
root: &Path,
controlled_web_search: bool,
) -> Result<(String, String), String> {
start_external_mcp_loopback_with_mode(root, controlled_web_search, true).await
}
async fn start_external_mcp_loopback_with_mode(
root: &Path,
controlled_web_search: bool,
direct_turn_execution: bool,
) -> Result<(String, String), String> {
let root = validate_direct_tools_project_root(root)?;
let session = current_platform_session()
@@ -1771,9 +1787,17 @@ pub(crate) async fn start_external_mcp_loopback(
let address = listener
.local_addr()
.map_err(|error| format!("读取客户端 MCP 地址失败:{error}"))?;
let bridge =
super::direct_tool_bridge::start_external_client_tool_bridge(&root, controlled_web_search)
.await?;
let bridge = super::direct_tool_bridge::start_external_client_tool_bridge(
&root,
controlled_web_search,
direct_turn_execution,
)
.await?;
let direct_turn_guard = if direct_turn_execution {
Some(bridge.begin_user_turn()?)
} else {
None
};
let state = ExternalMcpHttpState {
bridge_url: bridge.url().to_string(),
root,
@@ -1798,6 +1822,7 @@ pub(crate) async fn start_external_mcp_loopback(
drop(previous);
}
*guard = Some(ExternalMcpServer {
_direct_turn_guard: direct_turn_guard,
_bridge: bridge,
task,
});