Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 73 additions & 5 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ tauri-plugin-process = "2.4.0"
# upgrade and a run of the ignored native_store_round_trip test on every OS.
keyring = { version = "=3.6.3", features = ["apple-native", "windows-native", "sync-secret-service"] }
pulldown-cmark = { version = "0.13.5", default-features = false }
# Exact pin: the MCP SDK releases often; upgrade deliberately (#101).
rmcp = { version = "=3.8.0", default-features = false, features = ["server", "macros", "transport-async-rw"] }

# Read the optimization guideline for more details: https://tauri.app/concept/size/#cargo-configuration
[profile.release]
Expand All @@ -68,3 +70,5 @@ tauri-plugin-single-instance = "2"
[target.'cfg(windows)'.dependencies]
# Already in the tree through hyper-util; writes the "Open with LowNotes" keys.
windows-registry = "0.6"
# Already in the tree through tokio; restricts the MCP pipe to the current user.
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", "Win32_System_Threading"] }
18 changes: 14 additions & 4 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use crate::{
vault_service, web_search,
};

/// Shared handles: a clone sees the same settings, documents and network.
#[derive(Clone)]
pub struct AppState {
pub settings: Arc<RwLock<AppSettings>>,
pub crdt: CrdtManager,
Expand Down Expand Up @@ -262,13 +264,17 @@ pub async fn forget_vault(

#[tauri::command]
pub async fn list_notes(app: AppHandle) -> Result<Vec<VaultItem>, String> {
on_worker(app, move |state, _app| vault_service::list_notes(state)).await
on_worker(app, move |state, _app| {
vault_service::list_notes(&vault_service::active_vault(state)?)
})
.await
}

#[tauri::command]
pub async fn read_note(path: String, app: AppHandle) -> Result<NoteReadResponse, String> {
on_worker(app, move |state, _app| {
let note = vault_service::read_note(state, &path)?;
let vault = vault_service::active_vault(state)?;
let note = vault_service::read_note(state, &vault, &path)?;
Ok(NoteReadResponse {
content: note.content,
crdt_update_base64: URL_SAFE_NO_PAD.encode(note.crdt_state),
Expand Down Expand Up @@ -851,7 +857,8 @@ pub async fn search_vault_rag(
app: AppHandle,
) -> Result<Vec<RagChunk>, String> {
on_worker(app, move |state, _app| {
vault_service::search(state, &query, limit.unwrap_or(5))
let vault = vault_service::active_vault(state)?;
vault_service::search(&vault, &query, limit.unwrap_or(5))
})
.await
}
Expand Down Expand Up @@ -1123,7 +1130,10 @@ pub fn mark_welcome_seen(state: State<'_, AppState>) -> Result<(), String> {

#[tauri::command]
pub async fn links_get(app: AppHandle) -> Result<Vec<links::LinkEdge>, String> {
on_worker(app, move |state, _app| vault_service::links(state)).await
on_worker(app, move |state, _app| {
vault_service::links(&vault_service::active_vault(state)?)
})
.await
}

#[tauri::command]
Expand Down
17 changes: 17 additions & 0 deletions src-tauri/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,19 @@ impl Default for ImageUploadSettings {
}
}

/// The local MCP server for external AI clients. Off by default: what it
/// serves leaves LowNotes for the client and the model behind it.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct McpSettings {
pub enabled: bool,
/// Vaults MCP clients may read; only the active one is served.
pub vault_ids: Vec<String>,
/// Authenticates the bridge that MCP clients launch. Kept in the OS
/// credential store and never sent to the webview.
pub token: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppSettings {
#[serde(default)]
Expand Down Expand Up @@ -542,6 +555,8 @@ pub struct AppSettings {
pub context_menu: bool,
#[serde(default)]
pub skipped_version: String,
#[serde(default)]
pub mcp: McpSettings,
}

impl Default for AppSettings {
Expand Down Expand Up @@ -573,6 +588,7 @@ impl Default for AppSettings {
close_to_tray: default_true(),
context_menu: false,
skipped_version: String::new(),
mcp: McpSettings::default(),
}
}
}
Expand Down Expand Up @@ -711,6 +727,7 @@ impl AppSettings {
.contains_key(&format!("web:{}", source.id));
source.api_key.clear();
}
public.mcp.token.clear();
public.credential_refs.clear();
public
}
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ fn visit(settings: &mut AppSettings, mut f: impl FnMut(String, &mut String)) {
for source in &mut settings.web_search.sources {
f(format!("web:{}", source.id), &mut source.api_key);
}
f(MCP_TOKEN.into(), &mut settings.mcp.token);
}

/// The settings slot of the MCP token, also read by the bridge.
pub const MCP_TOKEN: &str = "mcp:token";

fn hydrate(settings: &mut AppSettings, store: &mut impl Store) {
let references = settings.credential_refs.clone();
let mut unavailable = BTreeSet::new();
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub mod http;
pub mod image_upload;
pub mod local_images;
pub mod logging;
pub mod mcp;
pub mod note_history;
pub mod note_transaction;
pub mod retention;
Expand Down Expand Up @@ -203,6 +204,7 @@ pub fn run() {
.manage(app_state)
.manage(PendingOpen::default())
.manage(FirstShow::default())
.manage(mcp::McpService::default())
.setup(move |app| {
let first_show = app.handle().clone();
std::thread::spawn(move || {
Expand Down Expand Up @@ -265,6 +267,8 @@ pub fn run() {
settings.clone(),
app.state::<AppState>().crdt.clone(),
);
app.state::<mcp::McpService>()
.apply(&app.state::<AppState>());

let (open_label, quit_label) = tray_labels(&settings.read().language);
let open = MenuItem::with_id(app, "open", open_label, true, None::<&str>)?;
Expand Down
Loading
Loading