docs: tambah doc comment, logging, dan inline comments di semua 255 file
Meliputi: - File-level //! doc comment: tujuan file, alur kerja, komponen utama - Function-level /// doc comment: apa, parameter, return, flow, edge cases - Struct/enum/trait /// doc comment: peran, field docs - Tracing logging (tracing::info!/debug!/trace!/warn!/error!) di setiap fungsi - Inline comments untuk variable dan branching logic penting - Seluruh 8 crates di workspace: zesdex-backend, zesdex-cms, zesdex-entities, zesdex-iam, zesdex-infra, zesdex-ipc, zesdex-middleware, zesdex-utils - Build: 0 errors, 242/242 tests passed
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
//! MCP transport layer: stdio child process management and HTTP client calls.
|
||||
//! This module handles the low-level protocol details of communicating with
|
||||
//! MCP servers (both spawned subprocesses and remote HTTP endpoints).
|
||||
//!
|
||||
//! Flow: `spawn_stdio_child` → `StdioChild::call` for JSON-RPC messages;
|
||||
//! `call_via_stdio` / `call_via_http` are convenience wrappers for
|
||||
//! `tools/call` that reuse a persistent child handle or spawn a fresh one.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
@@ -26,7 +31,7 @@ pub(super) fn mcp_static_str(s: &str) -> &'static str {
|
||||
let mut cache = match CACHE.get_or_init(|| Mutex::new(Vec::new())).lock() {
|
||||
Ok(c) => c,
|
||||
Err(poisoned) => {
|
||||
tracing::warn!("[mcp] static string cache mutex poisoned, recovering");
|
||||
warn!("[mcp] static string cache mutex poisoned, recovering");
|
||||
poisoned.into_inner()
|
||||
}
|
||||
};
|
||||
@@ -88,6 +93,7 @@ impl StdioChild {
|
||||
const MAX_LINE_LENGTH: usize = 1_048_576; // 1 MiB
|
||||
self.next_id += 1;
|
||||
let id = self.next_id;
|
||||
debug!(method = method, id = id, "MCP stdio call");
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": id,
|
||||
@@ -163,7 +169,7 @@ impl StdioChild {
|
||||
anyhow::bail!("MCP error: {err}");
|
||||
}
|
||||
return Ok(resp.get("result").cloned().unwrap_or_else(|| {
|
||||
tracing::warn!("[mcp] stdio response missing 'result' field: {}", trimmed);
|
||||
warn!("MCP stdio response missing 'result' field: {}", trimmed);
|
||||
Value::Null
|
||||
}));
|
||||
}
|
||||
@@ -179,6 +185,7 @@ pub(crate) fn spawn_stdio_child(
|
||||
command: &str,
|
||||
extra_args: &[String],
|
||||
) -> anyhow::Result<StdioChild> {
|
||||
info!(command = command, "MCP spawn stdio child");
|
||||
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||
let (prog, prog_args) = parts
|
||||
.split_first()
|
||||
@@ -249,6 +256,7 @@ pub(super) fn call_via_stdio(
|
||||
tool_name: &str,
|
||||
tool_args: &Value,
|
||||
) -> anyhow::Result<String> {
|
||||
debug!(tool = tool_name, has_handle = existing_handle.is_some(), "MCP 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 {
|
||||
@@ -257,6 +265,7 @@ pub(super) fn call_via_stdio(
|
||||
.map_err(|e| anyhow::anyhow!("MCP handle lock: {e}"))?;
|
||||
&mut guard
|
||||
} else {
|
||||
// No persistent handle — spawn a fresh child for this one call.
|
||||
let mut fresh = spawn_stdio_child(command, extra_args)?;
|
||||
let result = fresh.call(
|
||||
"tools/call",
|
||||
@@ -280,13 +289,14 @@ pub(super) fn call_via_stdio(
|
||||
}
|
||||
|
||||
pub(super) fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Result<String> {
|
||||
debug!(tool = tool_name, url = url, "MCP call_via_http");
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(std::time::Duration::from_millis(MCP_CALL_TIMEOUT_MS))
|
||||
.connect_timeout(std::time::Duration::from_millis(MCP_CONNECT_TIMEOUT_MS))
|
||||
.build()
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!(
|
||||
"[mcp] HTTP client builder failed with connect timeout: {}. \
|
||||
warn!(
|
||||
"MCP HTTP client builder failed with connect timeout: {}. \
|
||||
retrying without connect timeout",
|
||||
e,
|
||||
);
|
||||
@@ -294,8 +304,8 @@ pub(super) fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> an
|
||||
.timeout(std::time::Duration::from_millis(MCP_CALL_TIMEOUT_MS))
|
||||
.build()
|
||||
.unwrap_or_else(|e2| {
|
||||
tracing::warn!(
|
||||
"[mcp] also failed: {}. using default client (no configured timeouts)",
|
||||
warn!(
|
||||
"MCP also failed: {}. using default client (no configured timeouts)",
|
||||
e2,
|
||||
);
|
||||
reqwest::blocking::Client::new()
|
||||
@@ -323,7 +333,7 @@ pub(super) fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> an
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
let text = resp.text().unwrap_or_else(|e| {
|
||||
tracing::warn!("[mcp] failed to read HTTP response body: {}", e);
|
||||
warn!("MCP failed to read HTTP response body: {}", e);
|
||||
String::new()
|
||||
});
|
||||
anyhow::bail!("MCP HTTP server returned {status}: {text}");
|
||||
@@ -338,7 +348,7 @@ pub(super) fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> an
|
||||
}
|
||||
|
||||
let result = response.get("result").cloned().unwrap_or_else(|| {
|
||||
tracing::warn!("[mcp] HTTP response missing 'result' field");
|
||||
warn!("MCP HTTP response missing 'result' field");
|
||||
Value::Null
|
||||
});
|
||||
Ok(extract_text_content(&result))
|
||||
@@ -365,7 +375,7 @@ pub(super) fn extract_text_content(result: &Value) -> String {
|
||||
}
|
||||
}
|
||||
serde_json::to_string_pretty(result).unwrap_or_else(|e| {
|
||||
tracing::warn!("[mcp] failed to pretty-print result: {}", e);
|
||||
warn!("MCP failed to pretty-print result: {}", e);
|
||||
result.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user