1pub mod confirm;
7pub mod limits;
8pub mod mrtr;
9pub mod tools;
10
11use rmcp::model::{CallToolResult, ContentBlock, Implementation, ServerCapabilities, ServerInfo};
12use std::sync::Arc;
13
14use crate::approval::ApprovalEngine;
15use crate::audit::AuditDb;
16use crate::config::ConfigStore;
17use crate::vault::keychain::SecretStore;
18use crate::vault::touchid::SessionAuthenticator;
19
20#[derive(Clone)]
22pub struct AppCtx {
23 pub config: Arc<ConfigStore>,
24 pub audit: Arc<AuditDb>,
25 pub approvals: Arc<ApprovalEngine>,
26 pub auth: Arc<SessionAuthenticator>,
27 pub secrets: Arc<dyn SecretStore>,
28 pub approval_ipc: Option<Arc<crate::approval::ipc::ApprovalIpc>>,
32}
33
34pub fn build_server_info() -> ServerInfo {
35 let mut info = ServerInfo::default();
36 info.capabilities = ServerCapabilities::builder()
37 .enable_tools()
38 .enable_prompts()
39 .enable_resources()
40 .build();
41 info.server_info = Implementation::new(crate::PACKAGE_NAME, crate::PACKAGE_VERSION);
42 info.instructions = Some(
43 "Policy-gated MySQL/MariaDB and SQLite access. Reads are allowed by default; \
44 writes require policy + user confirmation; ambiguous statements fail closed. \
45 Prefer `query` for reads; `execute` for everything else."
46 .into(),
47 );
48 info
49}
50
51#[derive(Clone)]
52pub struct SequelServer {
53 ctx: AppCtx,
54}
55
56impl SequelServer {
57 pub fn new(ctx: AppCtx) -> Self {
58 Self { ctx }
59 }
60
61 pub fn with_defaults() -> Self {
62 Self {
63 ctx: AppCtx {
64 config: Arc::new(ConfigStore::new()),
65 audit: AuditDb::shared(),
66 approvals: Arc::new(ApprovalEngine::new()),
67 auth: Arc::new(SessionAuthenticator::new(
68 crate::vault::touchid::system_touch_id(),
69 )),
70 secrets: crate::vault::keychain::default_store(),
71 approval_ipc: None,
72 },
73 }
74 }
75
76 pub fn with_approval_ipc() -> Self {
80 let mut server = Self::with_defaults();
81 match crate::approval::ipc::ApprovalIpc::start() {
82 Ok(hub) => {
83 server.ctx.approval_ipc = Some(hub);
84 }
85 Err(e) => {
86 eprintln!(
87 "[sequel-mcp] approval IPC unavailable ({}); elicitation-only approvals",
88 e
89 );
90 }
91 }
92 if let Ok(cfg) = server.ctx.config.load()
95 && let Some(report) =
96 crate::audit::retention::maybe_auto_cleanup(&server.ctx.audit, &cfg.retention)
97 {
98 eprintln!(
99 "[sequel-mcp] auto-cleanup: pruned {} audit row(s), {} backup(s), reclaimed {} byte(s)",
100 report.audit_deleted, report.backup_deleted, report.bytes_reclaimed
101 );
102 }
103 server
104 }
105}
106
107pub fn json_tool_result(value: serde_json::Value) -> CallToolResult {
110 CallToolResult::structured(value)
111}
112
113pub fn error_tool_result(text: impl Into<String>) -> CallToolResult {
115 CallToolResult::error(vec![ContentBlock::text(text)])
116}
117
118pub fn text_tool_result(text: impl Into<String>) -> CallToolResult {
120 CallToolResult::success(vec![ContentBlock::text(text)])
121}