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 {
79 Self::with_approval_gui(true)
80 }
81
82 pub fn with_approval_gui(launch_gui: bool) -> Self {
83 let mut server = Self::with_defaults();
84 match crate::approval::ipc::ApprovalIpc::start_with_gui(launch_gui) {
85 Ok(hub) => {
86 server.ctx.approval_ipc = Some(hub);
87 }
88 Err(e) => {
89 eprintln!(
90 "[sequel-mcp] approval IPC unavailable ({}); elicitation-only approvals",
91 e
92 );
93 }
94 }
95 if let Ok(cfg) = server.ctx.config.load()
98 && let Some(report) =
99 crate::audit::retention::maybe_auto_cleanup(&server.ctx.audit, &cfg.retention)
100 {
101 eprintln!(
102 "[sequel-mcp] auto-cleanup: pruned {} audit row(s), {} backup(s), reclaimed {} byte(s)",
103 report.audit_deleted, report.backup_deleted, report.bytes_reclaimed
104 );
105 }
106 server
107 }
108}
109
110pub fn json_tool_result(value: serde_json::Value) -> CallToolResult {
113 CallToolResult::structured(value)
114}
115
116pub fn error_tool_result(text: impl Into<String>) -> CallToolResult {
118 CallToolResult::error(vec![ContentBlock::text(text)])
119}
120
121pub fn text_tool_result(text: impl Into<String>) -> CallToolResult {
123 CallToolResult::success(vec![ContentBlock::text(text)])
124}