Skip to main content

yggdrasil/mcp/tools/
settings.rs

1//! 站点设置 MCP 工具:读取/更新回收站自动清理配置。
2//!
3//! 与 Web server-fn 共用 `api::settings` 的配置读写函数。鉴权入口各自保留:Web 走 cookie
4//! `get_current_admin_user()`,MCP 走 bearer token → `McpPrincipal`,要求 admin 作用域。
5//!
6//! 缓存失效:与 web fn 保持一致——`update_trash_settings` **不做任何缓存失效**。
7//! 理由:回收站配置只影响管理后台(SSR 缓存在 `admin/`,`invalidate_ssr_all_public`
8//! 明确保留不动)和后台清理任务,没有公开页缓存表面,故无需失效。
9//! (约束 #5 要求「按 web admin server fn 的方式失效」——该 fn 的方式就是不失效。)
10
11#![cfg(feature = "server")]
12
13use rmcp::handler::server::tool::Extension;
14use rmcp::handler::server::wrapper::Parameters;
15use rmcp::model::{CallToolResult, ContentBlock, TextContent};
16use rmcp::{schemars, tool, tool_router, ErrorData as McpError};
17
18use super::common::require_admin;
19use serde::Deserialize;
20
21use crate::api::error::AppError;
22use crate::api::settings::{load_trash_settings, save_trash_settings};
23use crate::db::pool::get_conn;
24
25/// `get_settings` 入参(无字段——预留扩展点,未来可按子域过滤)。
26#[derive(Debug, Deserialize, schemars::JsonSchema, Default)]
27pub struct GetSettingsParams {}
28
29/// `update_settings` 入参:两项回收站配置。
30#[derive(Debug, Deserialize, schemars::JsonSchema)]
31pub struct UpdateSettingsParams {
32    /// 是否启用回收站自动清理。
33    pub auto_purge_enabled: bool,
34    /// 已删除文章保留天数(会被 clamp 到 [1, 365])。
35    pub retention_days: i32,
36}
37
38#[tool_router(router = settings_router, vis = "pub")]
39impl crate::mcp::server::YggMcpServer {
40    /// 读取站点回收站设置。要求 admin 作用域。
41    #[tool(description = "读取站点回收站配置(自动清理开关 + 保留天数)。需要 admin 作用域。")]
42    async fn get_settings(
43        &self,
44        Parameters(_p): Parameters<GetSettingsParams>,
45        Extension(parts): Extension<http::request::Parts>,
46    ) -> Result<CallToolResult, McpError> {
47        require_admin(&parts, "get_settings")?;
48
49        let settings = async {
50            let client = get_conn().await.map_err(AppError::db_conn)?;
51            load_trash_settings(&client).await
52        }
53        .await
54        .map_err(|e| McpError::internal_error(format!("settings read failed: {e:?}"), None))?;
55
56        let text = serde_json::to_string_pretty(&settings)
57            .map_err(|e| McpError::internal_error(format!("encode failed: {e}"), None))?;
58        Ok(CallToolResult::success(vec![ContentBlock::Text(
59            TextContent::new(text),
60        )]))
61    }
62
63    /// 更新站点回收站设置。要求 admin 作用域。retention_days 会 clamp 到 [1, 365]。
64    #[tool(
65        description = "更新站点回收站配置(自动清理开关 + 保留天数)。retention_days 会被钳制到 1..=365。需要 admin 作用域。"
66    )]
67    async fn update_settings(
68        &self,
69        Parameters(UpdateSettingsParams {
70            auto_purge_enabled,
71            retention_days,
72        }): Parameters<UpdateSettingsParams>,
73        Extension(parts): Extension<http::request::Parts>,
74    ) -> Result<CallToolResult, McpError> {
75        require_admin(&parts, "update_settings")?;
76
77        let updated = async {
78            let client = get_conn().await.map_err(AppError::db_conn)?;
79            save_trash_settings(&client, auto_purge_enabled, retention_days).await
80        }
81        .await
82        .map_err(|e| McpError::internal_error(format!("settings write failed: {e:?}"), None))?;
83
84        tracing::info!(
85            "MCP: trash settings updated: auto_purge={}, retention_days={}",
86            updated.auto_purge_enabled,
87            updated.retention_days
88        );
89
90        let text = serde_json::to_string_pretty(&updated)
91            .map_err(|e| McpError::internal_error(format!("encode failed: {e}"), None))?;
92        Ok(CallToolResult::success(vec![ContentBlock::Text(
93            TextContent::new(text),
94        )]))
95    }
96}