MCP调用链路增加后端日志记录

This commit is contained in:
JSR
2026-03-21 11:05:01 +08:00
parent 1c44e19e15
commit d1aac07092
+184 -28
View File
@@ -140,6 +140,18 @@ fn check_admin_permission(state: &Arc<RwLock<AppState>>) -> Result<(), String> {
Ok(()) Ok(())
} }
fn mcp_log_info(app_state: &Arc<RwLock<AppState>>, message: &str, meta: Value) {
let state_guard = app_state.read();
let logger = state_guard.logger.read();
logger.info_with_meta(message, meta);
}
fn mcp_log_error(app_state: &Arc<RwLock<AppState>>, message: &str, meta: Value) {
let state_guard = app_state.read();
let logger = state_guard.logger.read();
logger.error_with_meta(message, meta);
}
fn jsonrpc_ok(id: Value, result: Value) -> Value { fn jsonrpc_ok(id: Value, result: Value) -> Value {
json!({ json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
@@ -211,11 +223,25 @@ async fn handle_mcp(
let req = match serde_json::from_value::<McpRequest>(payload) { let req = match serde_json::from_value::<McpRequest>(payload) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
mcp_log_error(
&app_state,
"mcp:request_parse_failed",
json!({ "error": e.to_string() }),
);
let body = jsonrpc_error(Value::Null, -32700, &format!("Parse error: {}", e)); let body = jsonrpc_error(Value::Null, -32700, &format!("Parse error: {}", e));
return (StatusCode::OK, Json(body)).into_response(); return (StatusCode::OK, Json(body)).into_response();
} }
}; };
mcp_log_info(
&app_state,
"mcp:request_received",
json!({
"method": req.method.clone(),
"has_id": req.id.is_some()
}),
);
let Some(id) = req.id.clone() else { let Some(id) = req.id.clone() else {
return StatusCode::NO_CONTENT.into_response(); return StatusCode::NO_CONTENT.into_response();
}; };
@@ -229,7 +255,14 @@ async fn handle_mcp(
.and_then(|p| serde_json::from_value::<ToolCallParams>(p).ok()); .and_then(|p| serde_json::from_value::<ToolCallParams>(p).ok());
match params { match params {
None => jsonrpc_error(id, -32602, "Invalid tools/call params"), None => {
mcp_log_error(
&app_state,
"mcp:tools_call_invalid_params",
json!({ "reason": "invalid_tools_call_params" }),
);
jsonrpc_error(id, -32602, "Invalid tools/call params")
}
Some(params) => match params.name.as_str() { Some(params) => match params.name.as_str() {
"add_score" => match serde_json::from_value::<AddScoreArgs>(params.arguments) { "add_score" => match serde_json::from_value::<AddScoreArgs>(params.arguments) {
Ok(args) => match mcp_add_score(&app_state, args).await { Ok(args) => match mcp_add_score(&app_state, args).await {
@@ -249,20 +282,40 @@ async fn handle_mcp(
"structuredContent": payload "structuredContent": payload
}), }),
), ),
Err(e) => jsonrpc_ok( Err(e) => {
id, mcp_log_error(
json!({ &app_state,
"content": [ "mcp:tool_call_failed",
{ json!({
"type": "text", "tool": "add_score",
"text": format!("加分失败: {}", e) "error": e
} }),
], );
"isError": true jsonrpc_ok(
}), id,
), json!({
"content": [
{
"type": "text",
"text": format!("加分失败: {}", e)
}
],
"isError": true
}),
)
}
}, },
Err(e) => jsonrpc_error(id, -32602, &format!("Invalid arguments: {}", e)), Err(e) => {
mcp_log_error(
&app_state,
"mcp:tool_call_invalid_arguments",
json!({
"tool": "add_score",
"error": e.to_string()
}),
);
jsonrpc_error(id, -32602, &format!("Invalid arguments: {}", e))
}
}, },
"list_students" => { "list_students" => {
match serde_json::from_value::<ListStudentsArgs>(params.arguments) { match serde_json::from_value::<ListStudentsArgs>(params.arguments) {
@@ -280,25 +333,52 @@ async fn handle_mcp(
"structuredContent": payload "structuredContent": payload
}), }),
), ),
Err(e) => jsonrpc_ok( Err(e) => {
id, mcp_log_error(
json!({ &app_state,
"content": [ "mcp:tool_call_failed",
{ json!({
"type": "text", "tool": "list_students",
"text": format!("获取学生列表失败: {}", e) "error": e
} }),
], );
"isError": true jsonrpc_ok(
}), id,
), json!({
"content": [
{
"type": "text",
"text": format!("获取学生列表失败: {}", e)
}
],
"isError": true
}),
)
}
}, },
Err(e) => { Err(e) => {
mcp_log_error(
&app_state,
"mcp:tool_call_invalid_arguments",
json!({
"tool": "list_students",
"error": e.to_string()
}),
);
jsonrpc_error(id, -32602, &format!("Invalid arguments: {}", e)) jsonrpc_error(id, -32602, &format!("Invalid arguments: {}", e))
} }
} }
} }
_ => jsonrpc_error(id, -32601, &format!("Unknown tool: {}", params.name)), _ => {
mcp_log_error(
&app_state,
"mcp:tool_call_unknown_tool",
json!({
"tool": params.name
}),
);
jsonrpc_error(id, -32601, &format!("Unknown tool: {}", params.name))
}
}, },
} }
} }
@@ -313,6 +393,16 @@ async fn mcp_add_score(
app_state: &Arc<RwLock<AppState>>, app_state: &Arc<RwLock<AppState>>,
args: AddScoreArgs, args: AddScoreArgs,
) -> Result<AddScoreResult, String> { ) -> Result<AddScoreResult, String> {
mcp_log_info(
app_state,
"mcp:tool_call_started",
json!({
"tool": "add_score",
"student_name": args.student_name.clone(),
"delta": args.delta
}),
);
let student_name = args.student_name.trim(); let student_name = args.student_name.trim();
if student_name.is_empty() { if student_name.is_empty() {
return Err("student_name 不能为空".to_string()); return Err("student_name 不能为空".to_string());
@@ -376,6 +466,18 @@ async fn mcp_add_score(
realtime_dual_write_sync(app_state).await?; realtime_dual_write_sync(app_state).await?;
mcp_log_info(
app_state,
"mcp:tool_call_succeeded",
json!({
"tool": "add_score",
"student_name": student_name,
"delta": args.delta,
"event_id": inserted.id,
"event_uuid": event_uuid
}),
);
Ok(AddScoreResult { Ok(AddScoreResult {
event_id: inserted.id, event_id: inserted.id,
event_uuid, event_uuid,
@@ -392,6 +494,15 @@ async fn mcp_list_students(
app_state: &Arc<RwLock<AppState>>, app_state: &Arc<RwLock<AppState>>,
args: ListStudentsArgs, args: ListStudentsArgs,
) -> Result<ListStudentsResult, String> { ) -> Result<ListStudentsResult, String> {
mcp_log_info(
app_state,
"mcp:tool_call_started",
json!({
"tool": "list_students",
"limit": args.limit
}),
);
let db_conn = { let db_conn = {
let state_guard = app_state.read(); let state_guard = app_state.read();
let db_guard = state_guard.db.read(); let db_guard = state_guard.db.read();
@@ -419,6 +530,15 @@ async fn mcp_list_students(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
mcp_log_info(
app_state,
"mcp:tool_call_succeeded",
json!({
"tool": "list_students",
"total": students.len()
}),
);
Ok(ListStudentsResult { Ok(ListStudentsResult {
total: students.len(), total: students.len(),
students, students,
@@ -431,9 +551,23 @@ pub async fn mcp_server_start(
state: State<'_, Arc<RwLock<AppState>>>, state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<IpcResponse<McpServerStartResult>, String> { ) -> Result<IpcResponse<McpServerStartResult>, String> {
check_admin_permission(&state)?; check_admin_permission(&state)?;
mcp_log_info(
state.inner(),
"mcp:server_start_requested",
json!({
"config": config.clone()
}),
);
let mut server_state = MCP_SERVER_STATE.lock().await; let mut server_state = MCP_SERVER_STATE.lock().await;
if server_state.is_running { if server_state.is_running {
mcp_log_info(
state.inner(),
"mcp:server_already_running",
json!({
"url": server_state.url.clone()
}),
);
return Ok(IpcResponse::failure_with_type( return Ok(IpcResponse::failure_with_type(
"MCP server is already running", "MCP server is already running",
)); ));
@@ -482,6 +616,16 @@ pub async fn mcp_server_start(
server_state.url = Some(url.clone()); server_state.url = Some(url.clone());
server_state.shutdown_tx = Some(shutdown_tx); server_state.shutdown_tx = Some(shutdown_tx);
mcp_log_info(
state.inner(),
"mcp:server_started",
json!({
"url": url.clone(),
"host": server_state.config.host.clone(),
"port": server_state.config.port
}),
);
Ok(IpcResponse::success(McpServerStartResult { Ok(IpcResponse::success(McpServerStartResult {
url, url,
config: server_state.config.clone(), config: server_state.config.clone(),
@@ -493,10 +637,12 @@ pub async fn mcp_server_stop(
state: State<'_, Arc<RwLock<AppState>>>, state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<IpcResponse<()>, String> { ) -> Result<IpcResponse<()>, String> {
check_admin_permission(&state)?; check_admin_permission(&state)?;
mcp_log_info(state.inner(), "mcp:server_stop_requested", json!({}));
let mut server_state = MCP_SERVER_STATE.lock().await; let mut server_state = MCP_SERVER_STATE.lock().await;
if !server_state.is_running { if !server_state.is_running {
mcp_log_info(state.inner(), "mcp:server_already_stopped", json!({}));
return Ok(IpcResponse::success_empty()); return Ok(IpcResponse::success_empty());
} }
@@ -507,14 +653,24 @@ pub async fn mcp_server_stop(
server_state.is_running = false; server_state.is_running = false;
server_state.url = None; server_state.url = None;
mcp_log_info(state.inner(), "mcp:server_stopped", json!({}));
Ok(IpcResponse::success_empty()) Ok(IpcResponse::success_empty())
} }
#[tauri::command] #[tauri::command]
pub async fn mcp_server_status( pub async fn mcp_server_status(
_state: State<'_, Arc<RwLock<AppState>>>, state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<IpcResponse<McpServerStatus>, String> { ) -> Result<IpcResponse<McpServerStatus>, String> {
let server_state = MCP_SERVER_STATE.lock().await; let server_state = MCP_SERVER_STATE.lock().await;
mcp_log_info(
state.inner(),
"mcp:server_status_requested",
json!({
"is_running": server_state.is_running,
"url": server_state.url.clone()
}),
);
Ok(IpcResponse::success(McpServerStatus { Ok(IpcResponse::success(McpServerStatus {
is_running: server_state.is_running, is_running: server_state.is_running,