Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
still fetch installed model IDs through `/models` instead of relying on a
stale static default (#2742). Thanks @reidliu41 for the focused report and
draft fix.
- MCP runtime API tool listings and approval summaries no longer split
underscored MCP server names at the first `_`. Tool-call routing already used
the longest registered server name; the list endpoint now reuses that parser,
and approval cards show the full MCP target route instead of a guessed server
segment (#2744). Thanks @lioryx, @cyq1017, and @puneetdixit200 for the report
and matching fixes.
- Documented the agent and sub-agent stewardship ethos so future automation
preserves human issue intake, careful PR review, and contributor credit.
- Moved the TUI Starlark execpolicy parser and PTY support behind non-OHOS
Expand Down
6 changes: 6 additions & 0 deletions crates/tui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
still fetch installed model IDs through `/models` instead of relying on a
stale static default (#2742). Thanks @reidliu41 for the focused report and
draft fix.
- MCP runtime API tool listings and approval summaries no longer split
underscored MCP server names at the first `_`. Tool-call routing already used
the longest registered server name; the list endpoint now reuses that parser,
and approval cards show the full MCP target route instead of a guessed server
segment (#2744). Thanks @lioryx, @cyq1017, and @puneetdixit200 for the report
and matching fixes.
- Documented the agent and sub-agent stewardship ethos so future automation
preserves human issue intake, careful PR review, and contributor credit.
- Moved the TUI Starlark execpolicy parser and PTY support behind non-OHOS
Expand Down
26 changes: 25 additions & 1 deletion crates/tui/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2261,7 +2261,10 @@ impl McpPool {
}

/// Parse a prefixed name into (server_name, tool_name)
fn parse_prefixed_name<'a>(&self, prefixed_name: &'a str) -> Result<(&'a str, &'a str)> {
pub(crate) fn parse_prefixed_name<'a>(
&self,
prefixed_name: &'a str,
) -> Result<(&'a str, &'a str)> {
let Some(rest) = prefixed_name.strip_prefix("mcp_") else {
anyhow::bail!("Invalid MCP tool name: {prefixed_name}");
};
Expand Down Expand Up @@ -3151,6 +3154,27 @@ mod tests {
assert_eq!(server.env.get("FOO"), Some(&"bar".to_string()));
}

#[test]
fn mcp_pool_parse_prefixed_name_preserves_registered_underscored_server() {
let config: McpConfig = serde_json::from_str(
r#"{
"servers": {
"my": {"command": "node"},
"my_db": {"command": "node"}
}
}"#,
)
.unwrap();
let pool = McpPool::new(config);

let (server, tool) = pool
.parse_prefixed_name("mcp_my_db_execute_sql")
.expect("registered underscored server should parse");

assert_eq!(server, "my_db");
assert_eq!(tool, "execute_sql");
}

#[test]
fn mcp_server_config_parses_custom_headers() {
let json = r#"{
Expand Down
5 changes: 1 addition & 4 deletions crates/tui/src/runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1427,10 +1427,7 @@ async fn list_mcp_tools(

let mut tools = Vec::new();
for (prefixed_name, tool) in pool.all_tools() {
let Some(rest) = prefixed_name.strip_prefix("mcp_") else {
continue;
};
let Some((server, name)) = rest.split_once('_') else {
let Ok((server, name)) = pool.parse_prefixed_name(&prefixed_name) else {
continue;
};

Expand Down
50 changes: 38 additions & 12 deletions crates/tui/src/tui/approval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,13 +344,12 @@ fn param_preview(params: &Value, keys: &[&str], max_len: usize) -> Option<String
None
}

fn mcp_server_hint(tool_name: &str) -> Option<String> {
fn mcp_target_hint(tool_name: &str) -> Option<String> {
let remainder = tool_name.strip_prefix("mcp_")?;
let (server, _) = remainder.split_once('_')?;
if server.is_empty() {
if remainder.is_empty() {
None
} else {
Some(server.to_string())
Some(remainder.to_string())
}
}
Comment on lines +347 to 354

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The mcp_target_hint function currently returns an owned String by calling to_string(). Since the returned hint is only used for formatting impact summary strings (which accept &str), we can avoid this unnecessary heap allocation by returning Option<&str> instead.

Suggested change
fn mcp_target_hint(tool_name: &str) -> Option<String> {
let remainder = tool_name.strip_prefix("mcp_")?;
let (server, _) = remainder.split_once('_')?;
if server.is_empty() {
if remainder.is_empty() {
None
} else {
Some(server.to_string())
Some(remainder.to_string())
}
}
fn mcp_target_hint(tool_name: &str) -> Option<&str> {
let remainder = tool_name.strip_prefix("mcp_")?;
if remainder.is_empty() {
None
} else {
Some(remainder)
}
}


Expand Down Expand Up @@ -393,16 +392,16 @@ fn build_impact_summary(tool_name: &str, category: ToolCategory, params: &Value)
ToolCategory::McpRead => {
let mut impacts =
vec!["Reads from an MCP server without an obvious local write.".to_string()];
if let Some(server) = mcp_server_hint(tool_name) {
impacts.push(format!("Server: {server}"));
if let Some(target) = mcp_target_hint(tool_name) {
impacts.push(format!("MCP target: {target}"));
}
impacts
}
ToolCategory::McpAction => {
let mut impacts =
vec!["Calls an MCP server action that may have side effects.".to_string()];
if let Some(server) = mcp_server_hint(tool_name) {
impacts.push(format!("Server: {server}"));
if let Some(target) = mcp_target_hint(tool_name) {
impacts.push(format!("MCP target: {target}"));
}
impacts
}
Expand Down Expand Up @@ -475,15 +474,15 @@ fn build_impact_summary_zh_hans(
}
ToolCategory::McpRead => {
let mut impacts = vec!["从 MCP 服务器读取信息,不应产生本地写入。".to_string()];
if let Some(server) = mcp_server_hint(tool_name) {
impacts.push(format!("服务器:{server}"));
if let Some(target) = mcp_target_hint(tool_name) {
impacts.push(format!("MCP 目标:{target}"));
}
impacts
}
ToolCategory::McpAction => {
let mut impacts = vec!["调用可能产生副作用的 MCP 服务器操作。".to_string()];
if let Some(server) = mcp_server_hint(tool_name) {
impacts.push(format!("服务器:{server}"));
if let Some(target) = mcp_target_hint(tool_name) {
impacts.push(format!("MCP 目标:{target}"));
}
impacts
}
Expand Down Expand Up @@ -1447,6 +1446,33 @@ mod tests {
);
}

#[test]
fn mcp_impact_summary_preserves_full_target_for_underscored_names() {
let request = ApprovalRequest::new(
"test-id",
"mcp_my_db_execute_sql",
"Call an MCP tool",
&json!({}),
"tool:mcp_my_db_execute_sql",
);

assert!(
request
.impacts
.iter()
.any(|line| line == "MCP target: my_db_execute_sql")
);
assert!(!request.impacts.iter().any(|line| line == "Server: my"));

let zh_impacts = request.impacts_for_locale(Locale::ZhHans);
assert!(
zh_impacts
.iter()
.any(|line| line == "MCP 目标:my_db_execute_sql")
);
assert!(!zh_impacts.iter().any(|line| line == "服务器:my"));
}

#[test]
fn test_prominent_details_shell_does_not_truncate_long_command() {
let command = format!("printf '{}\\n' > /tmp/x && cat /tmp/x", "x".repeat(300));
Expand Down
Loading