yggdrasil/mcp/tools/
settings.rs1#![cfg(feature = "server")]
13
14use rmcp::handler::server::tool::Extension;
15use rmcp::handler::server::wrapper::Parameters;
16use rmcp::model::{CallToolResult, ContentBlock, TextContent};
17use rmcp::{schemars, tool, tool_router, ErrorData as McpError};
18
19use super::common::require_admin;
20use serde::Deserialize;
21
22use crate::api::error::AppError;
23use crate::db::pool::get_conn;
24use crate::models::settings::{TrashSettings, DEFAULT_AUTO_PURGE_ENABLED, DEFAULT_RETENTION_DAYS};
25
26#[derive(Debug, Deserialize, schemars::JsonSchema, Default)]
28pub struct GetSettingsParams {}
29
30#[derive(Debug, Deserialize, schemars::JsonSchema)]
32pub struct UpdateSettingsParams {
33 pub auto_purge_enabled: bool,
35 pub retention_days: i32,
37}
38
39#[tool_router(router = settings_router, vis = "pub")]
40impl crate::mcp::server::YggMcpServer {
41 #[tool(description = "读取站点回收站配置(自动清理开关 + 保留天数)。需要 admin 作用域。")]
43 async fn get_settings(
44 &self,
45 Parameters(_p): Parameters<GetSettingsParams>,
46 Extension(parts): Extension<http::request::Parts>,
47 ) -> Result<CallToolResult, McpError> {
48 require_admin(&parts, "get_settings")?;
49
50 let settings = load_trash_settings()
51 .await
52 .map_err(|e| McpError::internal_error(format!("settings read failed: {e:?}"), None))?;
53
54 let text = serde_json::to_string_pretty(&settings)
55 .map_err(|e| McpError::internal_error(format!("encode failed: {e}"), None))?;
56 Ok(CallToolResult::success(vec![ContentBlock::Text(
57 TextContent::new(text),
58 )]))
59 }
60
61 #[tool(
63 description = "更新站点回收站配置(自动清理开关 + 保留天数)。retention_days 会被钳制到 1..=365。需要 admin 作用域。"
64 )]
65 async fn update_settings(
66 &self,
67 Parameters(UpdateSettingsParams {
68 auto_purge_enabled,
69 retention_days,
70 }): Parameters<UpdateSettingsParams>,
71 Extension(parts): Extension<http::request::Parts>,
72 ) -> Result<CallToolResult, McpError> {
73 require_admin(&parts, "update_settings")?;
74
75 let retention_days = TrashSettings::clamp_retention(retention_days);
76
77 let updated = save_trash_settings(auto_purge_enabled, retention_days)
78 .await
79 .map_err(|e| McpError::internal_error(format!("settings write failed: {e:?}"), None))?;
80
81 let text = serde_json::to_string_pretty(&updated)
82 .map_err(|e| McpError::internal_error(format!("encode failed: {e}"), None))?;
83 Ok(CallToolResult::success(vec![ContentBlock::Text(
84 TextContent::new(text),
85 )]))
86 }
87}
88
89async fn load_trash_settings() -> Result<TrashSettings, AppError> {
93 let client = get_conn().await.map_err(AppError::db_conn)?;
94
95 let enabled: bool = client
96 .query_opt(
97 "SELECT value FROM settings WHERE key = 'trash_auto_purge_enabled'",
98 &[],
99 )
100 .await
101 .map_err(AppError::query)?
102 .and_then(|r| r.get::<_, String>("value").parse().ok())
103 .unwrap_or(DEFAULT_AUTO_PURGE_ENABLED);
104
105 let days: i32 = client
106 .query_opt(
107 "SELECT value FROM settings WHERE key = 'trash_retention_days'",
108 &[],
109 )
110 .await
111 .map_err(AppError::query)?
112 .and_then(|r| r.get::<_, String>("value").parse().ok())
113 .unwrap_or(DEFAULT_RETENTION_DAYS);
114
115 Ok(TrashSettings {
116 auto_purge_enabled: enabled,
117 retention_days: TrashSettings::clamp_retention(days),
118 })
119}
120
121async fn save_trash_settings(
123 auto_purge_enabled: bool,
124 retention_days: i32,
125) -> Result<TrashSettings, AppError> {
126 let client = get_conn().await.map_err(AppError::db_conn)?;
127
128 client
129 .execute(
130 "INSERT INTO settings (key, value, updated_at) VALUES ('trash_auto_purge_enabled', $1, NOW())
131 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
132 &[&auto_purge_enabled.to_string()],
133 )
134 .await
135 .map_err(AppError::query)?;
136
137 client
138 .execute(
139 "INSERT INTO settings (key, value, updated_at) VALUES ('trash_retention_days', $1, NOW())
140 ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
141 &[&retention_days.to_string()],
142 )
143 .await
144 .map_err(AppError::query)?;
145
146 tracing::info!(
147 "MCP: trash settings updated: auto_purge={}, retention_days={}",
148 auto_purge_enabled,
149 retention_days
150 );
151
152 Ok(TrashSettings {
153 auto_purge_enabled,
154 retention_days,
155 })
156}