ci: add GitHub Actions workflows with semantic-release auto-versioning

chore: fix all 702 clippy warnings across codebase
- auto-fix 475 via cargo clippy --fix
- fix remaining 227 manually: uninlined_format_args, redundant_closure, match_same_arms,
  underscore_binding, format_push_string, items_after_statements, needless_pass_by_value,
  clone_on_copy, case_sensitive_extension, single_match/let-else, write_with_newline,
  and other clippy lints
This commit is contained in:
asepharyana
2026-07-13 08:12:12 +07:00
parent be921d6836
commit 29a9fae3f6
79 changed files with 826 additions and 904 deletions
+31 -32
View File
@@ -88,7 +88,8 @@ impl StdioChild {
///
/// Return: the `result` value of the matching response, or `Err` on
/// timeout, EOF, JSON-RPC error, or I/O failure.
pub fn call(&mut self, method: &str, params: Value) -> anyhow::Result<Value> {
pub fn call(&mut self, method: &str, params: &Value) -> anyhow::Result<Value> {
const MAX_LINE_LENGTH: usize = 1_048_576; // 1 MiB
self.next_id += 1;
let id = self.next_id;
let req = json!({
@@ -107,18 +108,17 @@ impl StdioChild {
+ std::time::Duration::from_millis(MCP_CALL_TIMEOUT_MS);
loop {
if std::time::Instant::now() > deadline {
anyhow::bail!("MCP call timed out after {}ms", MCP_CALL_TIMEOUT_MS);
anyhow::bail!("MCP call timed out after {MCP_CALL_TIMEOUT_MS}ms");
}
response_line.clear();
// Read one byte at a time up to MAX_LINE_LENGTH to prevent
// OOM from a malicious server (CWE-400). BufReader already
// buffers reads, so byte-by-byte over a buffered reader is
// cheap (hits the in-memory buffer).
const MAX_LINE_LENGTH: usize = 1_048_576; // 1 MiB
response_line.clear();
let mut line_truncated = false;
loop {
let byte = match self.stdout.fill_buf() {
Ok(buf) if buf.is_empty() => {
Ok([]) => {
// EOF without newline
anyhow::bail!("MCP stdio child process closed unexpectedly");
}
@@ -127,7 +127,7 @@ impl StdioChild {
self.stdout.consume(1);
b
}
Err(e) => anyhow::bail!("MCP stdio read error: {}", e),
Err(e) => anyhow::bail!("MCP stdio read error: {e}"),
};
if byte == b'\n' {
break;
@@ -137,7 +137,7 @@ impl StdioChild {
// Consume rest of line to keep stream in sync
loop {
let buf = self.stdout.fill_buf()
.map_err(|e| anyhow::anyhow!("MCP stdio read error: {}", e))?;
.map_err(|e| anyhow::anyhow!("MCP stdio read error: {e}"))?;
if buf.is_empty() {
anyhow::bail!("MCP stdio child closed mid-line");
}
@@ -153,8 +153,7 @@ impl StdioChild {
}
if line_truncated {
anyhow::bail!(
"MCP response line exceeded {} byte limit",
MAX_LINE_LENGTH,
"MCP response line exceeded {MAX_LINE_LENGTH} byte limit",
);
}
let trimmed = response_line.trim();
@@ -162,10 +161,10 @@ impl StdioChild {
continue;
}
let resp: Value = serde_json::from_str(trimmed)
.map_err(|e| anyhow::anyhow!("invalid JSON from MCP server: {}", e))?;
.map_err(|e| anyhow::anyhow!("invalid JSON from MCP server: {e}"))?;
if resp.get("id") == Some(&json!(id)) {
if let Some(err) = resp.get("error") {
anyhow::bail!("MCP error: {}", err);
anyhow::bail!("MCP error: {err}");
}
return Ok(resp.get("result").cloned().unwrap_or_else(|| {
tracing::warn!("[mcp] stdio response missing 'result' field: {}", trimmed);
@@ -191,7 +190,7 @@ pub(crate) fn spawn_stdio_child(command: &str, extra_args: &[String]) -> anyhow:
cmd.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn()
.map_err(|e| anyhow::anyhow!("failed to spawn MCP stdio server '{}': {}", command, e))?;
.map_err(|e| anyhow::anyhow!("failed to spawn MCP stdio server '{command}': {e}"))?;
let stdin = child.stdin.take()
.ok_or_else(|| anyhow::anyhow!("failed to get stdin for MCP server"))?;
@@ -207,7 +206,7 @@ pub(crate) fn spawn_stdio_child(command: &str, extra_args: &[String]) -> anyhow:
let deadline = std::time::Instant::now()
+ std::time::Duration::from_millis(MCP_CONNECT_TIMEOUT_MS);
let init_result = mcp.call("initialize", json!({
let init_result = mcp.call("initialize", &json!({
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
@@ -220,9 +219,9 @@ pub(crate) fn spawn_stdio_child(command: &str, extra_args: &[String]) -> anyhow:
anyhow::bail!("MCP initialize timed out");
}
init_result.map_err(|e| anyhow::anyhow!("MCP initialize failed: {}", e))?;
init_result.map_err(|e| anyhow::anyhow!("MCP initialize failed: {e}"))?;
let _ = mcp.call("notifications/initialized", json!({}));
let _ = mcp.call("notifications/initialized", &json!({}));
Ok(mcp)
}
@@ -237,23 +236,23 @@ fn call_via_stdio(
// Reuse the persistent child handle if available; otherwise spawn a new one.
let mut guard;
let child: &mut StdioChild = if let Some(mtx) = existing_handle {
guard = mtx.lock().map_err(|e| anyhow::anyhow!("MCP handle lock: {}", e))?;
guard = mtx.lock().map_err(|e| anyhow::anyhow!("MCP handle lock: {e}"))?;
&mut guard
} else {
let mut fresh = spawn_stdio_child(command, extra_args)?;
let result = fresh.call("tools/call", json!({
let result = fresh.call("tools/call", &json!({
"name": tool_name,
"arguments": tool_args
}))?;
return extract_text_content(&result);
return Ok(extract_text_content(&result));
};
let result = child.call("tools/call", json!({
let result = child.call("tools/call", &json!({
"name": tool_name,
"arguments": tool_args
}))?;
extract_text_content(&result)
Ok(extract_text_content(&result))
}
fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Result<String> {
@@ -294,7 +293,7 @@ fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Resul
.header("Content-Type", "application/json")
.json(&body)
.send()
.map_err(|e| anyhow::anyhow!("MCP HTTP request failed: {}", e))?;
.map_err(|e| anyhow::anyhow!("MCP HTTP request failed: {e}"))?;
if !resp.status().is_success() {
let status = resp.status();
@@ -302,42 +301,42 @@ fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Resul
tracing::warn!("[mcp] failed to read HTTP response body: {}", e);
String::new()
});
anyhow::bail!("MCP HTTP server returned {}: {}", status, text);
anyhow::bail!("MCP HTTP server returned {status}: {text}");
}
let response: Value = resp.json()
.map_err(|e| anyhow::anyhow!("invalid JSON from MCP HTTP server: {}", e))?;
.map_err(|e| anyhow::anyhow!("invalid JSON from MCP HTTP server: {e}"))?;
if let Some(err) = response.get("error") {
anyhow::bail!("MCP HTTP error: {}", err);
anyhow::bail!("MCP HTTP error: {err}");
}
let result = response.get("result").cloned().unwrap_or_else(|| {
tracing::warn!("[mcp] HTTP response missing 'result' field");
Value::Null
});
extract_text_content(&result)
Ok(extract_text_content(&result))
}
fn extract_text_content(result: &Value) -> anyhow::Result<String> {
fn extract_text_content(result: &Value) -> String {
if let Some(content) = result.get("content") {
if let Some(arr) = content.as_array() {
let text: Vec<String> = arr.iter().filter_map(|item| {
if item.get("type").and_then(|t| t.as_str()) == Some("text") {
item.get("text").and_then(|t| t.as_str()).map(|s| s.to_string())
item.get("text").and_then(|t| t.as_str()).map(std::string::ToString::to_string)
} else {
None
}
}).collect();
if !text.is_empty() {
return Ok(text.join("\n"));
return text.join("\n");
}
}
}
Ok(serde_json::to_string_pretty(result).unwrap_or_else(|e| {
serde_json::to_string_pretty(result).unwrap_or_else(|e| {
tracing::warn!("[mcp] failed to pretty-print result: {}", e);
result.to_string()
}))
})
}
/// Registry of connected MCP servers and their tools for the current session.
@@ -374,7 +373,7 @@ impl crate::tool::Tool for McpToolAdapter {
fn run(&self, _ctx: &crate::tool::ToolCtx, args: &Value) -> anyhow::Result<String> {
match &self.transport {
McpTransport::Stdio { command, args: extra_args } => {
call_via_stdio(self.child_handle.as_ref().map(|h| h.as_ref()), command, extra_args, &self.tool_name, args)
call_via_stdio(self.child_handle.as_ref().map(std::convert::AsRef::as_ref), command, extra_args, &self.tool_name, args)
}
McpTransport::StreamableHttp { url } => {
call_via_http(url, &self.tool_name, args)
@@ -428,7 +427,7 @@ impl McpManager {
};
let mut child = spawn_stdio_child(command, extra_args)?;
let result = child.call("tools/list", json!({}))?;
let result = child.call("tools/list", &json!({}))?;
let tools = if let Some(tool_list) = result.get("tools").and_then(|v| v.as_array()) {
tool_list.iter().filter_map(|t| {