From 0e29b12ac88bdde991329036d8588c6cdc3c2dd2 Mon Sep 17 00:00:00 2001 From: Riz Ashraf Date: Mon, 14 Sep 2026 04:47:38 +0100 Subject: [PATCH] refactor: deduplicate arg parsing in handlers and remove redundant python scripts Created a parse_tool! macro that eliminated ~450 lines of repetitive error-handling boilerplate across 54 MCP tool definitions in server/src/handlers.rs. Purged 30+ stale, redundant python patching scripts and legacy mcp_client.py test runners that were polluting the workspace root and server/src directories. --- build.cmd | 47 --- fix_all.py | 25 -- fix_bg.py | 186 ---------- fix_cli.py | 97 ----- fix_git.py | 102 ------ fix_git2.py | 12 - fix_handlers.py | 15 - fix_index.py | 76 ---- fix_linux.py | 8 - fix_main.py | 37 -- fix_merge.py | 52 --- fix_move.py | 14 - fix_reconcile.py | 56 --- fix_route.py | 49 --- fix_route2.py | 12 - fix_search_commit.py | 19 - fix_state.py | 21 -- fix_tantivy.py | 36 -- fix_tools.py | 11 - fix_wal.py | 55 --- fix_writer.py | 9 - inject_gc.py | 45 --- inject_git.py | 62 ---- inject_graph_path.py | 92 ----- inject_ws.py | 70 ---- mcp_client.py | 59 --- server/src/add_endpoints.py | 39 -- server/src/fix.py | 44 --- server/src/fix_descriptions.py | 78 ---- server/src/fix_routes.py | 50 --- server/src/handlers.rs | 551 ++++------------------------- strip_openssl.py | 21 -- win-nvim/tests/integration_test.rs | 2 +- 33 files changed, 71 insertions(+), 1981 deletions(-) delete mode 100644 build.cmd delete mode 100644 fix_all.py delete mode 100644 fix_bg.py delete mode 100644 fix_cli.py delete mode 100644 fix_git.py delete mode 100644 fix_git2.py delete mode 100644 fix_handlers.py delete mode 100644 fix_index.py delete mode 100644 fix_linux.py delete mode 100644 fix_main.py delete mode 100644 fix_merge.py delete mode 100644 fix_move.py delete mode 100644 fix_reconcile.py delete mode 100644 fix_route.py delete mode 100644 fix_route2.py delete mode 100644 fix_search_commit.py delete mode 100644 fix_state.py delete mode 100644 fix_tantivy.py delete mode 100644 fix_tools.py delete mode 100644 fix_wal.py delete mode 100644 fix_writer.py delete mode 100644 inject_gc.py delete mode 100644 inject_git.py delete mode 100644 inject_graph_path.py delete mode 100644 inject_ws.py delete mode 100644 mcp_client.py delete mode 100644 server/src/add_endpoints.py delete mode 100644 server/src/fix.py delete mode 100644 server/src/fix_descriptions.py delete mode 100644 server/src/fix_routes.py delete mode 100644 strip_openssl.py diff --git a/build.cmd b/build.cmd deleted file mode 100644 index 6d30aec..0000000 --- a/build.cmd +++ /dev/null @@ -1,47 +0,0 @@ -@echo off -setlocal - -echo Building mcp-memory (server and stub) for Windows... -rustup run stable cargo build --release --workspace --exclude mcp-memory-linux-nvim -if %ERRORLEVEL% NEQ 0 ( - echo Windows build failed! - exit /b %ERRORLEVEL% -) - -echo Building mcp-memory (server and stub) for Linux (WSL)... -rustup run stable cargo zigbuild --release --workspace --exclude mcp-memory-win-nvim --target x86_64-unknown-linux-musl -if %ERRORLEVEL% NEQ 0 ( - echo Linux build failed! - exit /b %ERRORLEVEL% -) - -echo. -echo Build successful! Gracefully shutting down running servers... -:: Attempt shutdown via CLI flag -if exist "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe" ( - "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe" --exit >nul 2>&1 -) -:: Fallback shutdown via HTTP endpoint -curl -X POST http://127.0.0.1:3000/shutdown -s -o nul >nul 2>&1 - -:: Give the server a moment to flush checkpoints and release file locks -timeout /t 2 /nobreak >nul - -echo. -echo Copying Windows executables... -copy /Y target\release\mcp-memory-server.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe" -copy /Y target\release\mcp-memory-stub.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-stub.exe" -copy /Y target\release\mcp-memory-win-nvim.exe "C:\Users\reazul.ashraf\.local\bin\mcp-memory-win-nvim.exe" - -echo Copying Linux executables to WSL... -wsl -d Ubuntu -e bash -c "cp /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory/target/x86_64-unknown-linux-musl/release/mcp-memory-server /home/riz/.local/bin/mcp-memory-server && chmod +x /home/riz/.local/bin/mcp-memory-server" -wsl -d Ubuntu -e bash -c "cp /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory/target/x86_64-unknown-linux-musl/release/mcp-memory-stub /home/riz/.local/bin/mcp-memory-stub && chmod +x /home/riz/.local/bin/mcp-memory-stub" -wsl -d Ubuntu -e bash -c "cp /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory/target/x86_64-unknown-linux-musl/release/mcp-memory-linux-nvim /home/riz/.local/bin/mcp-memory-linux-nvim && chmod +x /home/riz/.local/bin/mcp-memory-linux-nvim" - -echo Copying instructions.md... -copy /Y instructions.md "C:\Users\reazul.ashraf\.gemini\antigravity-cli\mcp\memory\instructions.md" -wsl -d Ubuntu -e bash -c "mkdir -p /home/riz/.gemini/antigravity-cli/mcp/memory && cp /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory/instructions.md /home/riz/.gemini/antigravity-cli/mcp/memory/instructions.md" - -echo. -echo Build and deployment complete! -endlocal diff --git a/fix_all.py b/fix_all.py deleted file mode 100644 index aa072ac..0000000 --- a/fix_all.py +++ /dev/null @@ -1,25 +0,0 @@ -import sys - -handlers_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\handlers.rs" -with open(handlers_path, "r", encoding="utf-8") as f: - content = f.read() - -content = content.replace( - 'return Some(crate::mcp::success(id.clone(), serde_json::json!({"isError": true, "content": [{"type": "text", "text": format!("Invalid args: {}", e)}] })));', - 'return Some(crate::mcp::success(id.clone(), serde_json::json!({"isError": true, "content": [{"type": "text", "text": format!("Invalid args: {}", e)}] })))' -) -content = content.replace('let graph = self.state.graph.read();', 'let graph = self.state.get_full_graph();') - -with open(handlers_path, "w", encoding="utf-8") as f: - f.write(content) -print("Fixed handlers.rs again") - -main_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs" -with open(main_path, "r", encoding="utf-8") as f: - main_content = f.read() - -main_content = main_content.replace('state_git.code_changes.modify(|changes| {', 'state_git.ledger.modify(|changes| {') - -with open(main_path, "w", encoding="utf-8") as f: - f.write(main_content) -print("Fixed main.rs again") diff --git a/fix_bg.py b/fix_bg.py deleted file mode 100644 index 77e5dd6..0000000 --- a/fix_bg.py +++ /dev/null @@ -1,186 +0,0 @@ -import re - -with open("server/src/main.rs", "r", encoding="utf-8") as f: - content = f.read() - -# 1. Remove ledger and sticky GC from reconcile_worker -reconcile_worker_old = """ - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - state.ledger.modify(|ledger| { - let seven_days = now.saturating_sub(7 * 24 * 60 * 60); - ledger.retain(|c| c.timestamp >= seven_days); - if ledger.len() > 1000 { - let excess = ledger.len() - 1000; - ledger.drain(0..excess); - } - }); - state.sticky.modify(|notes| { - notes.retain(|note| note.timestamp >= now.saturating_sub(24 * 60 * 60)); - });""" - -content = content.replace(reconcile_worker_old, "") - -# 2. Extract Task GC from main and create garbage_collector_worker -gc_loop_old = """ // Background Garbage Collection for old tasks - let state_gc = Arc::clone(&state); - tokio::spawn(async move { - loop { - // Run every 24 hours - tokio::time::sleep(tokio::time::Duration::from_secs(24 * 3600)).await; - - let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); - let fourteen_days = 14 * 24 * 3600; - let cutoff = now.saturating_sub(fourteen_days); - - state_gc.tasks.modify(|tasks| { - let initial_len = tasks.len(); - tasks.retain(|task| { - if task.status.to_lowercase() == "completed" && task.created_at < cutoff { - false // remove - } else { - true // keep - } - }); - if tasks.len() < initial_len { - eprintln!("GC: Removed {} old completed tasks", initial_len - tasks.len()); - } - }); - } - });""" - -gc_worker_new = """async fn garbage_collector_worker(state: Arc) { - loop { - // Run every 6 hours - tokio::time::sleep(tokio::time::Duration::from_secs(6 * 3600)).await; - - let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); - - // 1. Task GC (14 days) - let fourteen_days = 14 * 24 * 3600; - let task_cutoff = now.saturating_sub(fourteen_days); - state.tasks.modify(|tasks| { - let initial_len = tasks.len(); - tasks.retain(|task| !(task.status.to_lowercase() == "completed" && task.created_at < task_cutoff)); - if tasks.len() < initial_len { - eprintln!("GC: Removed {} old completed tasks", initial_len - tasks.len()); - } - }); - - // 2. Ledger GC (7 days or max 1000 items) - state.ledger.modify(|ledger| { - let seven_days = now.saturating_sub(7 * 24 * 3600); - ledger.retain(|c| c.timestamp >= seven_days); - if ledger.len() > 1000 { - let excess = ledger.len() - 1000; - ledger.drain(0..excess); - } - }); - - // 3. Sticky Notes GC (24 hours) - state.sticky.modify(|notes| { - notes.retain(|note| note.timestamp >= now.saturating_sub(24 * 3600)); - }); - } -} -""" - -content = content.replace(gc_loop_old, " tokio::spawn(garbage_collector_worker(Arc::clone(&state)));") -content = content.replace("async fn reconcile_worker", gc_worker_new + "\nasync fn reconcile_worker") - -# 3. Extract Git Native Sync from main -git_loop_old = """ // Git Native Sync Background Task - let state_git = Arc::clone(&state); - tokio::spawn(async move { - let repo_path = std::env::current_dir().unwrap_or_else(|_| ".".into()); - let mut last_commit_id = String::new(); - - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; - - if let Ok(repo) = git2::Repository::discover(&repo_path) { - if let Ok(head) = repo.head() { - if let Ok(commit) = head.peel_to_commit() { - let current_id = commit.id().to_string(); - if current_id != last_commit_id && !last_commit_id.is_empty() { - let msg = commit.message().unwrap_or("").to_string(); - let branch = head.shorthand().unwrap_or("unknown").to_string(); - - state_git.ledger.modify(|changes| { - changes.push(crate::models::CodeChange { - git_commit: Some(current_id.clone()), - git_branch: Some(branch), - description: format!("Auto-synced commit: {}", msg.trim()), - timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(), - file_path: "".to_string(), - }); - }); - eprintln!("Git Sync: Logged new commit {}", current_id); - - state_git.tasks.modify(|tasks| { - for task in tasks.iter_mut() { - if task.status != "completed" && msg.to_lowercase().contains(&task.title.to_lowercase()) { - task.status = "completed".to_string(); - eprintln!("Git Sync: Auto-completed task '{}'", task.title); - } - } - }); - } - last_commit_id = current_id; - } - } - } - } - });""" - -git_worker_new = """async fn git_sync_worker(state: Arc) { - let repo_path = std::env::current_dir().unwrap_or_else(|_| ".".into()); - let mut last_commit_id = String::new(); - - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; - - if let Ok(repo) = git2::Repository::discover(&repo_path) { - if let Ok(head) = repo.head() { - if let Ok(commit) = head.peel_to_commit() { - let current_id = commit.id().to_string(); - if current_id != last_commit_id && !last_commit_id.is_empty() { - let msg = commit.message().unwrap_or("").to_string(); - let branch = head.shorthand().unwrap_or("unknown").to_string(); - - state.ledger.modify(|changes| { - changes.push(crate::models::CodeChange { - git_commit: Some(current_id.clone()), - git_branch: Some(branch), - description: format!("Auto-synced commit: {}", msg.trim()), - timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(), - file_path: "".to_string(), - }); - }); - eprintln!("Git Sync: Logged new commit {}", current_id); - - state.tasks.modify(|tasks| { - for task in tasks.iter_mut() { - if task.status != "completed" && msg.to_lowercase().contains(&task.title.to_lowercase()) { - task.status = "completed".to_string(); - eprintln!("Git Sync: Auto-completed task '{}'", task.title); - } - } - }); - } - last_commit_id = current_id; - } - } - } - } -} -""" - -content = content.replace(git_loop_old, " tokio::spawn(git_sync_worker(Arc::clone(&state)));") -content = content.replace("async fn reconcile_worker", git_worker_new + "\nasync fn reconcile_worker") - -with open("server/src/main.rs", "w", encoding="utf-8") as f: - f.write(content) - diff --git a/fix_cli.py b/fix_cli.py deleted file mode 100644 index cf54569..0000000 --- a/fix_cli.py +++ /dev/null @@ -1,97 +0,0 @@ -import re - -with open("server/src/main.rs", "r", encoding="utf-8") as f: - content = f.read() - -cli_gate_block = re.search(r' if let Some\(command\) = cli\.command \{.*?(?= run_server\(state\))', content, re.DOTALL) -if cli_gate_block: - content = content.replace(cli_gate_block.group(0), "") - -new_cli_logic = """ - if let Some(command) = cli.command { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let client = reqwest::Client::new(); - match command { - Commands::Gate { subcmd } => match subcmd { - GateCommands::Set { action, target, namespace, params, authorize, block, reason } => { - let mut param_map = HashMap::new(); - for p in params { - if let Some((k, v)) = p.split_once('=') { - param_map.insert(k.to_string(), v.to_string()); - } - } - let body = serde_json::json!({ - "action": action, - "target": target, - "namespace": namespace, - "params": param_map, - "authorize": authorize, - "block": block, - "reason": reason - }); - match client.post("http://127.0.0.1:3000/gate/set").json(&body).send().await { - Ok(res) if res.status().is_success() => { - println!("Gate state updated via daemon."); - std::process::exit(0); - } - Ok(res) => { - eprintln!("Failed to update gate: {}", res.status()); - std::process::exit(1); - } - Err(e) => { - eprintln!("Failed to connect to daemon: {}", e); - std::process::exit(1); - } - } - } - GateCommands::Verify { action, target, namespace, params, consume } => { - let mut query = vec![ - ("action".to_string(), action), - ("target".to_string(), target), - ("consume".to_string(), consume.to_string()), - ]; - if let Some(ns) = namespace { - query.push(("namespace".to_string(), ns)); - } - // Reqwest will serialize params correctly if we pass the right struct. - // Actually, axum's Query extractor for HashMap requires flat keys or standard serialization. - for p in params { - if let Some((k, v)) = p.split_once('=') { - query.push((k.to_string(), v.to_string())); - } - } - match client.get("http://127.0.0.1:3000/gate/verify").query(&query).send().await { - Ok(res) => { - let status = res.status(); - let text = res.text().await.unwrap_or_default(); - if status.is_success() { - std::process::exit(0); - } else if status == reqwest::StatusCode::FORBIDDEN { - eprintln!("❌ {}", text); - std::process::exit(1); - } else { - eprintln!("❌ {}", text); - std::process::exit(2); - } - } - Err(e) => { - eprintln!("Failed to connect to daemon: {}", e); - std::process::exit(2); - } - } - } - } - } - }); - return Ok(()); - } -""" - -restart_block = re.search(r' if cli\.restart \{.*?return Ok\(\(\);\n \}', content, re.DOTALL) -if restart_block: - content = content.replace(restart_block.group(0), restart_block.group(0) + "\n" + new_cli_logic) - -with open("server/src/main.rs", "w", encoding="utf-8") as f: - f.write(content) - diff --git a/fix_git.py b/fix_git.py deleted file mode 100644 index 609c8ad..0000000 --- a/fix_git.py +++ /dev/null @@ -1,102 +0,0 @@ -import re - -with open("server/src/main.rs", "r", encoding="utf-8") as f: - content = f.read() - -git_worker_old = """async fn git_sync_worker(state: Arc) { - let repo_path = std::env::current_dir().unwrap_or_else(|_| ".".into()); - let mut last_commit_id = String::new(); - - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; - - if let Ok(repo) = git2::Repository::discover(&repo_path) { - if let Ok(head) = repo.head() { - if let Ok(commit) = head.peel_to_commit() { - let current_id = commit.id().to_string(); - if current_id != last_commit_id && !last_commit_id.is_empty() { - let msg = commit.message().unwrap_or("").to_string(); - let branch = head.shorthand().unwrap_or("unknown").to_string(); - - state.ledger.modify(|changes| { - changes.push(crate::models::CodeChange { - git_commit: Some(current_id.clone()), - git_branch: Some(branch), - description: format!("Auto-synced commit: {}", msg.trim()), - timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(), - file_path: "".to_string(), - }); - }); - eprintln!("Git Sync: Logged new commit {}", current_id); - - state.tasks.modify(|tasks| { - for task in tasks.iter_mut() { - if task.status != "completed" && msg.to_lowercase().contains(&task.title.to_lowercase()) { - task.status = "completed".to_string(); - eprintln!("Git Sync: Auto-completed task '{}'", task.title); - } - } - }); - } - last_commit_id = current_id; - } - } - } - } -}""" - -git_worker_new = """async fn git_sync_worker(state: Arc) { - let repo_path = std::env::current_dir().unwrap_or_else(|_| ".".into()); - let mut last_commit_id = String::new(); - - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; - - let repo_path_clone = repo_path.clone(); - let commit_data = tokio::task::spawn_blocking(move || { - if let Ok(repo) = git2::Repository::discover(&repo_path_clone) { - if let Ok(head) = repo.head() { - if let Ok(commit) = head.peel_to_commit() { - let current_id = commit.id().to_string(); - let msg = commit.message().unwrap_or("").to_string(); - let branch = head.shorthand().unwrap_or("unknown").to_string(); - return Some((current_id, msg, branch)); - } - } - } - None - }) - .await - .unwrap_or(None); - - if let Some((current_id, msg, branch)) = commit_data { - if current_id != last_commit_id && !last_commit_id.is_empty() { - state.ledger.modify(|changes| { - changes.push(crate::models::CodeChange { - git_commit: Some(current_id.clone()), - git_branch: Some(branch), - description: format!("Auto-synced commit: {}", msg.trim()), - timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(), - file_path: "".to_string(), - }); - }); - eprintln!("Git Sync: Logged new commit {}", current_id); - - state.tasks.modify(|tasks| { - for task in tasks.iter_mut() { - if task.status != "completed" && msg.to_lowercase().contains(&task.title.to_lowercase()) { - task.status = "completed".to_string(); - eprintln!("Git Sync: Auto-completed task '{}'", task.title); - } - } - }); - } - last_commit_id = current_id; - } - } -}""" - -content = content.replace(git_worker_old, git_worker_new) - -with open("server/src/main.rs", "w", encoding="utf-8") as f: - f.write(content) diff --git a/fix_git2.py b/fix_git2.py deleted file mode 100644 index d4cfc46..0000000 --- a/fix_git2.py +++ /dev/null @@ -1,12 +0,0 @@ -import sys - -cargo_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\Cargo.toml" -with open(cargo_path, "r", encoding="utf-8") as f: - content = f.read() - -# Replace git2 to use vendored openssl to avoid cross-compilation linking issues -content = content.replace('git2 = "0.19.0"', 'git2 = { version = "0.19.0", features = ["vendored-openssl"] }') - -with open(cargo_path, "w", encoding="utf-8") as f: - f.write(content) -print("Added vendored-openssl feature to git2") diff --git a/fix_handlers.py b/fix_handlers.py deleted file mode 100644 index 67a9465..0000000 --- a/fix_handlers.py +++ /dev/null @@ -1,15 +0,0 @@ -import sys - -handlers_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\handlers.rs" -with open(handlers_path, "r", encoding="utf-8") as f: - content = f.read() - -content = content.replace( - 'return Some(crate::mcp::success(req_id, true, &format!("Invalid args: {}", e))),', - 'return Some(crate::mcp::success(id.clone(), serde_json::json!({"isError": true, "content": [{"type": "text", "text": format!("Invalid args: {}", e)}] })));' -) -content = content.replace('let graph = state.graph.read();', 'let graph = self.state.graph.read();') - -with open(handlers_path, "w", encoding="utf-8") as f: - f.write(content) -print("Fixed handlers.rs") diff --git a/fix_index.py b/fix_index.py deleted file mode 100644 index 4b9fd44..0000000 --- a/fix_index.py +++ /dev/null @@ -1,76 +0,0 @@ -import re - -with open("server/src/state.rs", "r", encoding="utf-8") as f: - content = f.read() - -rebuild_index_old = """ pub fn rebuild_index(&self) { - if let Ok(new_idx) = MemoryIndex::new(&self.base_dir) { - let session = self.session_graph.read().unwrap(); - let mut full = { - let cache = self.master_cache.read().unwrap(); - cache.0.clone() - }; - Self::merge_graphs(&mut full, &session); - for e in full.entities.values() { - let _ = new_idx.index_entity(e); - } - for t in self.tasks.read() { - let _ = new_idx.index_task(&t); - } - for s in self.snippets.read() { - let _ = new_idx.index_snippet(&s); - } - for a in self.adrs.read() { - let _ = new_idx.index_adr(&a); - } - if let Ok(mut w) = self.search_index.write() { - *w = new_idx; - } - } - }""" - -rebuild_index_new = """ pub fn rebuild_index(&self) { - if let Ok(new_idx) = MemoryIndex::new(&self.base_dir) { - let session = self.session_graph.read().unwrap(); - let cache = self.master_cache.read().unwrap(); - - // Index entities that are only in master, or merge if they are in both - for (name, e) in &cache.0.entities { - if let Some(session_e) = session.entities.get(name) { - let mut merged_e = e.clone(); - for obs in &session_e.observations { - if !merged_e.observations.contains(obs) { - merged_e.observations.push(obs.clone()); - } - } - let _ = new_idx.index_entity(&merged_e); - } else { - let _ = new_idx.index_entity(e); - } - } - // Index entities that are only in session - for (name, session_e) in &session.entities { - if !cache.0.entities.contains_key(name) { - let _ = new_idx.index_entity(session_e); - } - } - - for t in self.tasks.read() { - let _ = new_idx.index_task(&t); - } - for s in self.snippets.read() { - let _ = new_idx.index_snippet(&s); - } - for a in self.adrs.read() { - let _ = new_idx.index_adr(&a); - } - if let Ok(mut w) = self.search_index.write() { - *w = new_idx; - } - } - }""" - -content = content.replace(rebuild_index_old, rebuild_index_new) - -with open("server/src/state.rs", "w", encoding="utf-8") as f: - f.write(content) diff --git a/fix_linux.py b/fix_linux.py deleted file mode 100644 index c8171a6..0000000 --- a/fix_linux.py +++ /dev/null @@ -1,8 +0,0 @@ -import re -with open(r'C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs', 'r', encoding='utf-8') as f: - text = f.read() - -text = re.sub(r'#\[cfg\(not\(target_os = "windows"\)\)\].*?return Ok\(\(\);\n \}', '', text, flags=re.DOTALL) - -with open(r'C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs', 'w', encoding='utf-8') as f: - f.write(text) diff --git a/fix_main.py b/fix_main.py deleted file mode 100644 index 9240a64..0000000 --- a/fix_main.py +++ /dev/null @@ -1,37 +0,0 @@ -import sys - -main_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs" -with open(main_path, "r", encoding="utf-8") as f: - content = f.read() - -# Remove WS code -ws_start = content.find('async fn ws_handler') -if ws_start != -1: - content = content[:ws_start] - -# Remove /ws route -route_idx = content.find('.route("/ws", get(ws_handler))') -if route_idx != -1: - content = content.replace('.route("/ws", get(ws_handler))\n ', '') - -# Remove WS import -import_idx = content.find('use axum::extract::ws::{WebSocketUpgrade, WebSocket, Message};\n') -if import_idx != -1: - content = content.replace('use axum::extract::ws::{WebSocketUpgrade, WebSocket, Message};\n', '') - -# Fix GC task state -gc_start = content.find(' // Background Garbage Collection for old tasks') -if gc_start != -1: - content = content.replace('let state_gc = state_clone.clone();', 'let state_gc = Arc::clone(&state);') - -# Fix Git task state and CodeChange fields -git_start = content.find(' // Git Native Sync Background Task') -if git_start != -1: - content = content.replace('let state_git = state_clone.clone();', 'let state_git = Arc::clone(&state);') - content = content.replace('commit_hash: current_id.clone(),', 'git_commit: Some(current_id.clone()),') - content = content.replace('branch: branch,', 'git_branch: Some(branch),') - content = content.replace('files_changed: vec![],', 'file_path: "".to_string(),') - -with open(main_path, "w", encoding="utf-8") as f: - f.write(content) -print("Cleaned up WS and fixed main.rs") diff --git a/fix_merge.py b/fix_merge.py deleted file mode 100644 index a486241..0000000 --- a/fix_merge.py +++ /dev/null @@ -1,52 +0,0 @@ -import re - -with open("server/src/state.rs", "r", encoding="utf-8") as f: - content = f.read() - -unique_items_block = re.search(r' pub fn unique_items<.*?>\(.*?\) -> Vec \{\n(?:.*?\n){1,15}? \}\n', content, re.DOTALL) -if unique_items_block: - content = content.replace(unique_items_block.group(0), "") - -merge_graphs_old = """ pub fn merge_graphs(dest: &mut KnowledgeGraph, src: &KnowledgeGraph) { - for (name, src_ent) in &src.entities { - let dest_ent = dest - .entities - .entry(name.clone()) - .or_insert_with(|| src_ent.clone()); - if dest_ent.name == src_ent.name { - dest_ent.observations.extend(src_ent.observations.clone()); - dest_ent.observations = Self::unique_items(dest_ent.observations.clone()); - } - } - dest.relations.extend(src.relations.clone()); - dest.relations = Self::unique_items(dest.relations.clone()); - }""" - -merge_graphs_new = """ pub fn merge_graphs(dest: &mut KnowledgeGraph, src: &KnowledgeGraph) { - for (name, src_ent) in &src.entities { - let dest_ent = dest - .entities - .entry(name.clone()) - .or_insert_with(|| crate::models::Entity { - name: src_ent.name.clone(), - entity_type: src_ent.entity_type.clone(), - observations: Vec::new(), - }); - - for obs in &src_ent.observations { - if !dest_ent.observations.contains(obs) { - dest_ent.observations.push(obs.clone()); - } - } - } - for rel in &src.relations { - if !dest.relations.contains(rel) { - dest.relations.push(rel.clone()); - } - } - }""" - -content = content.replace(merge_graphs_old, merge_graphs_new) - -with open("server/src/state.rs", "w", encoding="utf-8") as f: - f.write(content) diff --git a/fix_move.py b/fix_move.py deleted file mode 100644 index edf55ef..0000000 --- a/fix_move.py +++ /dev/null @@ -1,14 +0,0 @@ -import sys - -main_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs" -with open(main_path, "r", encoding="utf-8") as f: - content = f.read() - -content = content.replace( - 'handler: Arc::new(MemoryHandler { state }),', - 'handler: Arc::new(MemoryHandler { state: Arc::clone(&state) }),' -) - -with open(main_path, "w", encoding="utf-8") as f: - f.write(content) -print("Fixed main.rs move error") diff --git a/fix_reconcile.py b/fix_reconcile.py deleted file mode 100644 index cb7cd43..0000000 --- a/fix_reconcile.py +++ /dev/null @@ -1,56 +0,0 @@ -import re - -with open("server/src/main.rs", "r", encoding="utf-8") as f: - content = f.read() - -reconcile_worker_old = """async fn reconcile_worker(state: Arc) { - loop { - sleep(Duration::from_secs(5)).await; - let pattern = format!("{}/delta_*.json", state.base_dir.display()); - let has_local = { - let session = state.session_graph.read().unwrap(); - !session.entities.is_empty() || !session.relations.is_empty() - }; - let has_files = glob::glob(&pattern).map(|p| p.count() > 0).unwrap_or(false); - if has_local || has_files { - state.apply_sync_write(|_master| {}).await; - let state_clone = state.clone(); - let _ = tokio::task::spawn_blocking(move || { - state_clone.rebuild_index(); - }).await; - } - - } -}""" - -reconcile_worker_new = """async fn reconcile_worker(state: Arc) { - loop { - sleep(Duration::from_secs(5)).await; - - let has_local = { - let session = state.session_graph.read().unwrap(); - !session.entities.is_empty() || !session.relations.is_empty() - }; - - let base_dir = state.base_dir.clone(); - let has_files = tokio::task::spawn_blocking(move || { - let pattern = format!("{}/delta_*.json", base_dir.display()); - glob::glob(&pattern).map(|p| p.count() > 0).unwrap_or(false) - }) - .await - .unwrap_or(false); - - if has_local || has_files { - state.apply_sync_write(|_master| {}).await; - let state_clone = state.clone(); - let _ = tokio::task::spawn_blocking(move || { - state_clone.rebuild_index(); - }).await; - } - } -}""" - -content = content.replace(reconcile_worker_old, reconcile_worker_new) - -with open("server/src/main.rs", "w", encoding="utf-8") as f: - f.write(content) diff --git a/fix_route.py b/fix_route.py deleted file mode 100644 index 3b496be..0000000 --- a/fix_route.py +++ /dev/null @@ -1,49 +0,0 @@ -import re - -main_rs_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs" - -with open(main_rs_path, "r", encoding="utf-8") as f: - content = f.read() - -# Replace the broken route -old_route = r""" .route("/api/tasks/:id/complete", post({ - let state_clone = app_state.handler.state.clone(); - move |axum::extract::Path(id): axum::extract::Path| async move { - if let Ok(mut tasks) = state_clone.tasks.write() { - let mut found = false; - for t in tasks.iter_mut() { - if t.id == id { - t.status = "completed".to_string(); - found = true; - break; - } - } - if found { - let _ = state_clone.flush_store(); - return axum::Json(serde_json::json!({"status": "success"})); - } - } - axum::Json(serde_json::json!({"status": "not_found"})) - } - }))""" - -new_route = """ .route("/api/tasks/:id/complete", post({ - let state_clone = app_state.handler.state.clone(); - move |axum::extract::Path(id): axum::extract::Path| async move { - state_clone.tasks.modify(|tasks| { - for t in tasks.iter_mut() { - if t.id == id { - t.status = "completed".to_string(); - break; - } - } - }); - axum::Json(serde_json::json!({"status": "success"})) - } - }))""" - -content = content.replace(old_route, new_route) - -with open(main_rs_path, "w", encoding="utf-8") as f: - f.write(content) -print("Replaced route.") diff --git a/fix_route2.py b/fix_route2.py deleted file mode 100644 index 2576fbd..0000000 --- a/fix_route2.py +++ /dev/null @@ -1,12 +0,0 @@ -import re - -main_rs_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs" - -with open(main_rs_path, "r", encoding="utf-8") as f: - content = f.read() - -content = content.replace('.route("/api/tasks/:id/complete"', '.route("/api/tasks/{id}/complete"') - -with open(main_rs_path, "w", encoding="utf-8") as f: - f.write(content) -print("Replaced route syntax.") diff --git a/fix_search_commit.py b/fix_search_commit.py deleted file mode 100644 index 5f13f63..0000000 --- a/fix_search_commit.py +++ /dev/null @@ -1,19 +0,0 @@ -import re - -with open("server/src/search.rs", "r", encoding="utf-8") as f: - content = f.read() - -# Add pub fn commit -commit_code = """ - pub fn commit(&self) -> tantivy::Result<()> { - let mut writer = self.writer.lock().unwrap(); - writer.commit()?; - Ok(()) - } -""" - -content = content.replace(" pub fn search(", commit_code + "\n pub fn search(") -content = content.replace("let mut writer = self.writer.lock().unwrap();", "let writer = self.writer.lock().unwrap();") - -with open("server/src/search.rs", "w", encoding="utf-8") as f: - f.write(content) diff --git a/fix_state.py b/fix_state.py deleted file mode 100644 index 6db6a6e..0000000 --- a/fix_state.py +++ /dev/null @@ -1,21 +0,0 @@ -import re - -with open("server/src/state.rs", "r", encoding="utf-8") as f: - content = f.read() - -# Add git_branch and namespace to Entity initialization -content = content.replace("observations: Vec::new(),", "observations: Vec::new(),\n namespace: src_ent.namespace.clone(),\n git_branch: src_ent.git_branch.clone(),") - -# Add unique_items back -unique_items_code = """ - pub fn unique_items(input: Vec) -> Vec { - let mut keys = std::collections::HashSet::new(); - input.into_iter().filter(|entry| keys.insert(entry.clone())).collect() - } -""" - -content = content.replace("impl MemoryState {\n", "impl MemoryState {\n" + unique_items_code) - -with open("server/src/state.rs", "w", encoding="utf-8") as f: - f.write(content) - diff --git a/fix_tantivy.py b/fix_tantivy.py deleted file mode 100644 index 3815f09..0000000 --- a/fix_tantivy.py +++ /dev/null @@ -1,36 +0,0 @@ -import re - -with open("server/src/search.rs", "r", encoding="utf-8") as f: - content = f.read() - -# Remove writer.commit() from index_* -content = content.replace(" writer.commit()?;\n", "") - -# Add pub fn commit -commit_code = """ - pub fn commit(&self) -> tantivy::Result<()> { - let mut writer = self.writer.lock().unwrap(); - writer.commit()?; - Ok(()) - } -""" - -content = content.replace(" pub fn search(&self, query_str: &str, limit: usize) -> tantivy::Result> {", commit_code + "\n pub fn search(&self, query_str: &str, limit: usize) -> tantivy::Result> {") - -with open("server/src/search.rs", "w", encoding="utf-8") as f: - f.write(content) - -with open("server/src/state.rs", "r", encoding="utf-8") as f: - state_content = f.read() - -# Call new_idx.commit() in rebuild_index before locking the main rwlock -state_content = state_content.replace(""" if let Ok(mut w) = self.search_index.write() { - *w = new_idx; - }""", """ let _ = new_idx.commit(); - if let Ok(mut w) = self.search_index.write() { - *w = new_idx; - }""") - -with open("server/src/state.rs", "w", encoding="utf-8") as f: - f.write(state_content) - diff --git a/fix_tools.py b/fix_tools.py deleted file mode 100644 index 98197a5..0000000 --- a/fix_tools.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys - -tools_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\tools.rs" -with open(tools_path, "r", encoding="utf-8") as f: - content = f.read() - -content = content.replace("use schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct QueryGraphPathTool", "#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]\npub struct QueryGraphPathTool") - -with open(tools_path, "w", encoding="utf-8") as f: - f.write(content) -print("Fixed tools.rs") diff --git a/fix_wal.py b/fix_wal.py deleted file mode 100644 index 48e85bd..0000000 --- a/fix_wal.py +++ /dev/null @@ -1,55 +0,0 @@ -import re - -with open("server/src/state.rs", "r", encoding="utf-8") as f: - content = f.read() - -recover_wal_code = """ - pub fn recover_wal(&self) { - let wal_path = self.base_dir.join("wal.jsonl"); - if let Ok(content) = std::fs::read_to_string(&wal_path) { - let mut session = self.session_graph.write().unwrap(); - for line in content.lines() { - if let Ok(d) = serde_json::from_str::(line) { - Self::merge_graphs(&mut session, &d); - } - } - } - } -""" -content = content.replace(" pub fn merge_graphs(dest: &mut KnowledgeGraph, src: &KnowledgeGraph) {", recover_wal_code + " pub fn merge_graphs(dest: &mut KnowledgeGraph, src: &KnowledgeGraph) {") - -get_full_graph_old = """ pub fn get_full_graph(&self) -> KnowledgeGraph { - let mut master = self.read_master_cached(); - let wal_path = self.base_dir.join("wal.jsonl"); - if let Ok(content) = std::fs::read_to_string(&wal_path) { - for line in content.lines() { - if let Ok(d) = serde_json::from_str::(line) { - Self::merge_graphs(&mut master, &d); - } - } - } - let session_graph = self.session_graph.read().unwrap(); - Self::merge_graphs(&mut master, &session_graph); - master - }""" - -get_full_graph_new = """ pub fn get_full_graph(&self) -> KnowledgeGraph { - let mut master = self.read_master_cached(); - let session_graph = self.session_graph.read().unwrap(); - Self::merge_graphs(&mut master, &session_graph); - master - }""" - -content = content.replace(get_full_graph_old, get_full_graph_new) - -with open("server/src/state.rs", "w", encoding="utf-8") as f: - f.write(content) - -with open("server/src/main.rs", "r", encoding="utf-8") as f: - main_content = f.read() - -main_content = main_content.replace(" state.rebuild_index();\n\n run_server(state)", " state.recover_wal();\n state.rebuild_index();\n\n run_server(state)") - -with open("server/src/main.rs", "w", encoding="utf-8") as f: - f.write(main_content) - diff --git a/fix_writer.py b/fix_writer.py deleted file mode 100644 index a2715ef..0000000 --- a/fix_writer.py +++ /dev/null @@ -1,9 +0,0 @@ -import re - -with open("server/src/search.rs", "r", encoding="utf-8") as f: - content = f.read() - -content = content.replace("let writer = self.writer.lock().unwrap();\n writer.commit()?;", "let mut writer = self.writer.lock().unwrap();\n writer.commit()?;") - -with open("server/src/search.rs", "w", encoding="utf-8") as f: - f.write(content) diff --git a/inject_gc.py b/inject_gc.py deleted file mode 100644 index 13c4ae7..0000000 --- a/inject_gc.py +++ /dev/null @@ -1,45 +0,0 @@ -import sys - -main_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs" -with open(main_path, "r", encoding="utf-8") as f: - content = f.read() - -idx = content.find('eprintln!("MCP Memory Server running on http://127.0.0.1:3000/sse");') -if idx == -1: - print("Could not find start point") - sys.exit(1) - -gc_code = """ - // Background Garbage Collection for old tasks - let state_gc = state_clone.clone(); - tokio::spawn(async move { - loop { - // Run every 24 hours - tokio::time::sleep(tokio::time::Duration::from_secs(24 * 3600)).await; - - let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); - let fourteen_days = 14 * 24 * 3600; - let cutoff = now.saturating_sub(fourteen_days); - - state_gc.tasks.modify(|tasks| { - let initial_len = tasks.len(); - tasks.retain(|task| { - if task.status.to_lowercase() == "completed" && task.created_at < cutoff { - false // remove - } else { - true // keep - } - }); - if tasks.len() < initial_len { - eprintln!("GC: Removed {} old completed tasks", initial_len - tasks.len()); - } - }); - } - }); -""" - -content = content[:idx] + gc_code + content[idx:] -with open(main_path, "w", encoding="utf-8") as f: - f.write(content) - -print("Injected GC task!") diff --git a/inject_git.py b/inject_git.py deleted file mode 100644 index 49dbaaf..0000000 --- a/inject_git.py +++ /dev/null @@ -1,62 +0,0 @@ -import sys - -main_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs" -with open(main_path, "r", encoding="utf-8") as f: - content = f.read() - -idx = content.find('eprintln!("MCP Memory Server running on http://127.0.0.1:3000/sse");') -if idx == -1: - print("Could not find start point") - sys.exit(1) - -git_code = """ - // Git Native Sync Background Task - let state_git = state_clone.clone(); - tokio::spawn(async move { - let repo_path = std::env::current_dir().unwrap_or_else(|_| ".".into()); - let mut last_commit_id = String::new(); - - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; - - if let Ok(repo) = git2::Repository::discover(&repo_path) { - if let Ok(head) = repo.head() { - if let Ok(commit) = head.peel_to_commit() { - let current_id = commit.id().to_string(); - if current_id != last_commit_id && !last_commit_id.is_empty() { - let msg = commit.message().unwrap_or("").to_string(); - let branch = head.shorthand().unwrap_or("unknown").to_string(); - - state_git.code_changes.modify(|changes| { - changes.push(crate::models::CodeChange { - commit_hash: current_id.clone(), - branch: branch, - description: format!("Auto-synced commit: {}", msg.trim()), - timestamp: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(), - files_changed: vec![], - }); - }); - eprintln!("Git Sync: Logged new commit {}", current_id); - - state_git.tasks.modify(|tasks| { - for task in tasks.iter_mut() { - if task.status != "completed" && msg.to_lowercase().contains(&task.title.to_lowercase()) { - task.status = "completed".to_string(); - eprintln!("Git Sync: Auto-completed task '{}'", task.title); - } - } - }); - } - last_commit_id = current_id; - } - } - } - } - }); -""" - -content = content[:idx] + git_code + content[idx:] -with open(main_path, "w", encoding="utf-8") as f: - f.write(content) - -print("Injected Git Sync task!") diff --git a/inject_graph_path.py b/inject_graph_path.py deleted file mode 100644 index 82f3192..0000000 --- a/inject_graph_path.py +++ /dev/null @@ -1,92 +0,0 @@ -import re -import sys - -handlers_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\handlers.rs" - -with open(handlers_path, "r", encoding="utf-8") as f: - content = f.read() - -# Add to list_tools -list_tools_idx = content.find('crate::mcp::tool_def::') -if list_tools_idx == -1: - print("Could not find list_tools") - sys.exit(1) - -tool_def = ' crate::mcp::tool_def::("query_graph_path", "Traverse the knowledge graph to find a path between two entities."),' -content = content[:list_tools_idx] + tool_def + "\n" + content[list_tools_idx:] - -# Add to match name -match_idx = content.find('"create_entities" => {') -if match_idx == -1: - print("Could not find match name") - sys.exit(1) - -handler_code = """ "query_graph_path" => { - let req: crate::tools::QueryGraphPathTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => return Some(crate::mcp::success(req_id, true, &format!("Invalid args: {}", e))), - }; - let graph = state.graph.read(); - let max_depth = req.max_depth.unwrap_or(5); - let mut queue = std::collections::VecDeque::new(); - let mut visited = std::collections::HashSet::new(); - let mut parents: std::collections::HashMap = std::collections::HashMap::new(); - - queue.push_back(req.start_node.clone()); - visited.insert(req.start_node.clone()); - - let mut found = false; - let mut current_depth = 0; - let mut nodes_at_current_depth = 1; - let mut nodes_at_next_depth = 0; - - while let Some(current) = queue.pop_front() { - if current == req.end_node { - found = true; - break; - } - nodes_at_current_depth -= 1; - if current_depth < max_depth { - for rel in &graph.relations { - if rel.from == current && !visited.contains(&rel.to) { - visited.insert(rel.to.clone()); - parents.insert(rel.to.clone(), (current.clone(), rel.relation_type.clone())); - queue.push_back(rel.to.clone()); - nodes_at_next_depth += 1; - } else if rel.to == current && !visited.contains(&rel.from) { - visited.insert(rel.from.clone()); - parents.insert(rel.from.clone(), (current.clone(), format!("inverse({})", rel.relation_type))); - queue.push_back(rel.from.clone()); - nodes_at_next_depth += 1; - } - } - } - if nodes_at_current_depth == 0 { - current_depth += 1; - nodes_at_current_depth = nodes_at_next_depth; - nodes_at_next_depth = 0; - } - } - - if found { - let mut path = Vec::new(); - let mut curr = req.end_node.clone(); - while curr != req.start_node { - let (parent, rel) = parents.get(&curr).unwrap(); - path.push(format!("({}) --[{}]--> ({})", parent, rel, curr)); - curr = parent.clone(); - } - path.reverse(); - Ok(format!("Path found:\\n{}", path.join("\\n"))) - } else { - Ok(format!("No path found between {} and {} within depth {}", req.start_node, req.end_node, max_depth)) - } - } -""" - -content = content[:match_idx] + handler_code + content[match_idx:] - -with open(handlers_path, "w", encoding="utf-8") as f: - f.write(content) - -print("Injected query_graph_path tool!") diff --git a/inject_ws.py b/inject_ws.py deleted file mode 100644 index 0d4c2b4..0000000 --- a/inject_ws.py +++ /dev/null @@ -1,70 +0,0 @@ -import sys - -main_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\main.rs" -with open(main_path, "r", encoding="utf-8") as f: - content = f.read() - -# 1. Add WebSocket imports -import_idx = content.find('use axum::{') -if import_idx == -1: - print("Could not find axum imports") - sys.exit(1) - -import_code = 'use axum::extract::ws::{WebSocketUpgrade, WebSocket, Message};\n' -content = content[:import_idx] + import_code + content[import_idx:] - -# 2. Add /ws route -route_idx = content.find('.route("/sse", get(sse_handler))') -if route_idx == -1: - print("Could not find route definition") - sys.exit(1) - -route_code = '.route("/ws", get(ws_handler))\n ' -content = content[:route_idx] + route_code + content[route_idx:] - -# 3. Add ws_handler implementation -end_idx = len(content) - -ws_code = """ -async fn ws_handler( - ws: WebSocketUpgrade, - State(state): State>, -) -> impl IntoResponse { - ws.on_upgrade(move |socket| handle_socket(socket, state)) -} - -async fn handle_socket(mut socket: WebSocket, state: Arc) { - let mut rx = state.tx.subscribe(); - - // Create a background task to receive messages from the system and send to the WebSocket - let mut send_task = tokio::spawn(async move { - while let Ok(msg) = rx.recv().await { - if socket.send(Message::Text(msg.into())).await.is_err() { - break; - } - } - }); - - // Create a task to receive messages from the WebSocket (e.g. task completion from UI) - // In a real app we'd decode JSON-RPC here - let mut recv_task = tokio::spawn(async move { - // Just keeping it alive and listening - // We can add logic to process JSON-RPC from the frontend here later - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await; - } - }); - - tokio::select! { - _ = (&mut send_task) => recv_task.abort(), - _ = (&mut recv_task) => send_task.abort(), - } -} -""" - -content += ws_code - -with open(main_path, "w", encoding="utf-8") as f: - f.write(content) - -print("Injected WS handler!") diff --git a/mcp_client.py b/mcp_client.py deleted file mode 100644 index 16cc24b..0000000 --- a/mcp_client.py +++ /dev/null @@ -1,59 +0,0 @@ -import urllib.request -import json -import threading -import sys -import time - -sse_url = "http://localhost:3000/sse" - -def call_mcp(method, params): - req = urllib.request.Request(sse_url) - with urllib.request.urlopen(req) as response: - endpoint = None - # read the SSE stream - for line_bytes in response: - line = line_bytes.decode('utf-8').strip() - if line.startswith("data: /messages"): - endpoint = "http://localhost:3000" + line.split("data: ")[1].strip() - break - - if not endpoint: - print("Failed to get endpoint") - sys.exit(1) - - print(f"Got endpoint: {endpoint}") - - payload = { - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": method, - "arguments": params - } - } - - post_req = urllib.request.Request(endpoint, data=json.dumps(payload).encode('utf-8'), headers={'Content-Type': 'application/json'}) - with urllib.request.urlopen(post_req) as post_resp: - print(f"POST status: {post_resp.status}") - - # continue reading SSE for response - for line_bytes in response: - line = line_bytes.decode('utf-8').strip() - if line.startswith("data: "): - data = line[6:] - if data == "ping": continue - try: - msg = json.loads(data) - if msg.get("id") == 1: - print("RESULT:") - print(json.dumps(msg, indent=2)) - return - except: - pass - -import sys -if __name__ == "__main__": - method = sys.argv[1] - params = json.loads(sys.argv[2]) - call_mcp(method, params) diff --git a/server/src/add_endpoints.py b/server/src/add_endpoints.py deleted file mode 100644 index 64e2e8b..0000000 --- a/server/src/add_endpoints.py +++ /dev/null @@ -1,39 +0,0 @@ -import re - -endpoints = """ - .route("/api/graph", get({ - let state_clone = app_state.handler.state.clone(); - move || async move { - let graph = state_clone.get_full_graph(); - axum::Json(graph) - } - })) - .route("/api/tasks", get({ - let state_clone = app_state.handler.state.clone(); - move || async move { - let tasks = state_clone.tasks.read(); - axum::Json(tasks.clone()) - } - })) - .route("/api/search", get({ - let state_clone = app_state.handler.state.clone(); - move |axum::extract::Query(params): axum::extract::Query>| async move { - if let Some(q) = params.get("q") { - if let Ok(idx) = state_clone.search_index.read() { - let results = idx.search(q, 50); - return axum::Json(serde_json::json!({ "results": results })); - } - } - axum::Json(serde_json::json!({ "results": [] })) - } - })) -""" - -filepath = "C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\server\\src\\main.rs" -with open(filepath, "r", encoding="utf-8") as f: - content = f.read() - -content = content.replace('.route("/api/stats",', endpoints + ' .route("/api/stats",') - -with open(filepath, "w", encoding="utf-8") as f: - f.write(content) diff --git a/server/src/fix.py b/server/src/fix.py deleted file mode 100644 index f0e8225..0000000 --- a/server/src/fix.py +++ /dev/null @@ -1,44 +0,0 @@ -import re - -def fix_handlers(filepath): - with open(filepath, "r") as f: - content = f.read() - - # Find the positions of self.state.write_to_local_delta( and self.state.apply_sync_write( - patterns = ["self.state.write_to_local_delta(|", "self.state.apply_sync_write(|"] - - # Actually, we can just find 'write_to_local_delta(|' and 'apply_sync_write(|' - - for method in ["write_to_local_delta", "apply_sync_write"]: - start_idx = 0 - while True: - idx = content.find(f"self.state.{method}(|", start_idx) - if idx == -1: - break - - # Find the closing bracket that matches the first `{` after `|` - brace_idx = content.find("{", idx) - - # count braces - brace_count = 1 - curr_idx = brace_idx + 1 - while brace_count > 0 and curr_idx < len(content): - if content[curr_idx] == '{': - brace_count += 1 - elif content[curr_idx] == '}': - brace_count -= 1 - curr_idx += 1 - - # curr_idx is now right after the matching '}' - # the original code was '});' - if content[curr_idx:curr_idx+2] == ");": - # we want to insert .await - content = content[:curr_idx+1] + ".await" + content[curr_idx+1:] - start_idx = curr_idx + 8 # move past `.await);` - else: - start_idx = curr_idx - - with open(filepath, "w") as f: - f.write(content) - -fix_handlers("C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\server\\src\\handlers.rs") diff --git a/server/src/fix_descriptions.py b/server/src/fix_descriptions.py deleted file mode 100644 index a14a7a0..0000000 --- a/server/src/fix_descriptions.py +++ /dev/null @@ -1,78 +0,0 @@ -import re - -descriptions = { - "create_entities": "Create new entities in the knowledge graph.", - "create_relations": "Create new relations between entities in the knowledge graph.", - "add_observations": "Add new observations to existing entities in the knowledge graph.", - "delete_entities": "Delete entities from the knowledge graph.", - "delete_observations": "Delete observations from existing entities.", - "delete_relations": "Delete relations between entities.", - "read_graph": "Read the entire knowledge graph.", - "search_nodes": "Search for entities in the knowledge graph by name or type.", - "open_nodes": "Open and retrieve full details of specific nodes in the knowledge graph.", - "log_code_change": "Log a significant code change or refactor in the memory system.", - "query_recent_changes": "Query recently logged code changes.", - "visualize_graph": "Generate a visual representation of the knowledge graph.", - "add_sticky_note": "Add a sticky note for unstructured thoughts or reminders.", - "read_sticky_notes": "Read all active sticky notes.", - "condense_entity": "Condense or summarize an entity's observations to reduce size.", - "add_task": "Add a new task to the task tracker.", - "update_task_status": "Update the status of an existing task.", - "list_active_tasks": "List all currently active tasks.", - "store_snippet": "Store a reusable code snippet.", - "search_snippets": "Search through stored code snippets.", - "delete_snippet": "Delete a stored code snippet.", - "log_decision": "Log an architectural decision record (ADR).", - "query_decisions": "Query architectural decision records.", - "merge_entities": "Merge two entities in the knowledge graph into one.", - "find_orphans": "Find orphaned entities (entities without any relations) in the graph.", - "learn_preference": "Record a user preference or behavior to adapt future interactions.", - "read_preferences": "Read all learned user preferences.", - "log_error_fix": "Log a complex error and its fix for future reference.", - "search_error_fixes": "Search through previously logged error fixes.", - "pin_file": "Pin a file to keep it explicitly in the context workspace.", - "unpin_file": "Unpin a file from the context workspace.", - "list_pinned_files": "List all currently pinned files.", - "add_session_summary": "Add a summary of the current session.", - "get_project_timeline": "Get a timeline of major project events.", - "leave_handoff_memo": "Leave a memo for the next session or agent.", - "read_handoff_memos": "Read pending handoff memos.", - "clear_handoff_memos": "Clear handoff memos after reading.", - "update_env_fingerprint": "Update the environment fingerprint (e.g., OS, tool versions).", - "read_env_fingerprint": "Read the current environment fingerprint.", - "log_env_requirement": "Log a required tool or package for the environment.", - "add_milestone": "Add a new project milestone.", - "update_milestone": "Update the status of a project milestone.", - "list_milestones": "List all project milestones.", - "generate_standup_report": "Generate a standup report based on recent activity.", - "register_environment": "Register details about a specific deployment environment.", - "get_environment_details": "Get details about a registered environment.", - "add_pr_checklist_item": "Add an item to the PR checklist.", - "get_pr_checklist": "Get the current PR checklist.", - "clear_pr_checklist": "Clear the PR checklist.", - "log_tech_debt": "Log identified technical debt.", - "resolve_tech_debt": "Mark a logged technical debt as resolved.", - "list_tech_debt": "List all unresolved technical debt.", - "save_context_workspace": "Save the current set of pinned files and context.", - "load_context_workspace": "Load a previously saved context workspace.", - "list_context_workspaces": "List all saved context workspaces.", - "omni_search": "Search across all memory sources (graph, tasks, snippets, ADRs, etc.) at once.", - "get_project_health": "Get a synthesized health report of the project based on memory data." -} - -def fix_handlers(filepath): - with open(filepath, "r") as f: - content = f.read() - - for tool_name, description in descriptions.items(): - # Match crate::mcp::tool_def::<...>("tool_name", "") - # Note: sometimes it's on multiple lines for GenerateStandupReportTool etc. - # But most are single line. - pattern = r'(crate::mcp::tool_def::<[^>]+>\(\s*"' + tool_name + r'"\s*,\s*)""(\s*\))' - replacement = r'\1"' + description + r'"\2' - content = re.sub(pattern, replacement, content) - - with open(filepath, "w") as f: - f.write(content) - -fix_handlers("C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\server\\src\\handlers.rs") diff --git a/server/src/fix_routes.py b/server/src/fix_routes.py deleted file mode 100644 index c6ee2d7..0000000 --- a/server/src/fix_routes.py +++ /dev/null @@ -1,50 +0,0 @@ -import re - -endpoints = """ - .route("/api/graph", get({ - let state_clone = app_state.handler.state.clone(); - move || async move { - let graph = state_clone.get_full_graph(); - axum::Json(graph) - } - })) - .route("/api/tasks", get({ - let state_clone = app_state.handler.state.clone(); - move || async move { - let tasks = state_clone.tasks.read(); - axum::Json(tasks.clone()) - } - })) - .route("/api/search", get({ - let state_clone = app_state.handler.state.clone(); - move |axum::extract::Query(params): axum::extract::Query>| async move { - if let Some(q) = params.get("q") { - if let Ok(idx) = state_clone.search_index.read() { - if let Ok(results) = idx.search(q, None) { - let mut formatted_results = Vec::new(); - for (type_name, content) in results { - formatted_results.push(serde_json::json!({ - "type_name": type_name, - "content": content, - "score": 1.0 - })); - } - return axum::Json(serde_json::json!({ "results": formatted_results })); - } - } - } - axum::Json(serde_json::json!({ "results": [] })) - } - })) -""" - -filepath = "C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\server\\src\\main.rs" -with open(filepath, "r", encoding="utf-8") as f: - content = f.read() - -# Replace the broken routes with the fixed ones -pattern = r'\.route\("/api/graph".*?\.route\("/api/stats"' -content = re.sub(pattern, endpoints.strip() + '\n .route("/api/stats"', content, flags=re.DOTALL) - -with open(filepath, "w", encoding="utf-8") as f: - f.write(content) diff --git a/server/src/handlers.rs b/server/src/handlers.rs index fdb192f..622602e 100644 --- a/server/src/handlers.rs +++ b/server/src/handlers.rs @@ -2,6 +2,22 @@ use crate::models::*; use crate::state::MemoryState; use crate::tools::*; +macro_rules! parse_tool { + ($args:expr, $id:expr, $type:ty) => { + match parse_args::<$type>($args) { + Ok(r) => r, + Err(e) => { + return Some(crate::mcp::success( + $id.clone(), + serde_json::json!({"isError": true, "content": [{"type": "text", "text": format!("Invalid args: {}", e)}] }), + )); + } + } + }; +} + + + use serde::de::DeserializeOwned; use std::collections::HashSet; use std::sync::Arc; @@ -124,10 +140,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti let result: Result = match name { "query_graph_path" => { - let req: crate::tools::QueryGraphPathTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => return Some(crate::mcp::success(id.clone(), serde_json::json!({"isError": true, "content": [{"type": "text", "text": format!("Invalid args: {}", e)}] }))) - }; + let req = parse_tool!(args.clone(), id, crate::tools::QueryGraphPathTool); let graph = self.state.get_full_graph(); let max_depth = req.max_depth.unwrap_or(5); let mut queue = std::collections::VecDeque::new(); @@ -185,15 +198,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti } } "create_entities" => { - let req: CreateEntitiesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, CreateEntitiesTool); self.state.write_to_local_delta(|g| { for entity in req.entities { if !entity.name.is_empty() { @@ -207,15 +212,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Entities created".to_string()][0].clone()) } "create_relations" => { - let req: CreateRelationsTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, CreateRelationsTool); self.state.write_to_local_delta(|g| { for relation in req.relations { if !relation.from.is_empty() && !relation.to.is_empty() { @@ -226,15 +223,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Relations created".to_string()][0].clone()) } "add_observations" => { - let req: AddObservationsTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, AddObservationsTool); let full = self.state.get_full_graph(); self.state.write_to_local_delta(|g| { for o in req.observations { @@ -258,15 +247,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Observations added".to_string()][0].clone()) } "delete_entities" => { - let req: DeleteEntitiesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, DeleteEntitiesTool); let to_delete: HashSet<_> = req.entity_names.into_iter().collect(); self.state.apply_sync_write(|master| { for name in &to_delete { @@ -279,15 +260,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Entities deleted".to_string()][0].clone()) } "delete_observations" => { - let req: DeleteObservationsTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, DeleteObservationsTool); self.state.apply_sync_write(|master| { for d in req.deletions { if let Some(e) = master.entities.get_mut(&d.entity_name) @@ -300,15 +273,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Observations deleted".to_string()][0].clone()) } "delete_relations" => { - let req: DeleteRelationsTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, DeleteRelationsTool); self.state.apply_sync_write(|master| { let mut to_rem = HashSet::new(); for r in req.relations { @@ -327,15 +292,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Relations deleted".to_string()][0].clone()) } "read_graph" => { - let req: ReadGraphTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ReadGraphTool); let mut full = self.state.get_full_graph(); if let Some(ns) = req.namespace { full.entities.retain(|_, e| e.namespace == ns); @@ -345,15 +302,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "search_nodes" => { - let req: SearchNodesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, SearchNodesTool); let matches = if let Ok(idx) = self.state.search_index.read() { idx.search(&req.query, req.namespace.as_deref()) .unwrap_or_default() @@ -373,15 +322,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "open_nodes" => { - let req: OpenNodesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, OpenNodesTool); let targets: HashSet<_> = req.names.into_iter().collect(); let full = self.state.get_full_graph(); let mut result = KnowledgeGraph::default(); @@ -404,15 +345,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "log_code_change" => { - let req: LogCodeChangeTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, LogCodeChangeTool); self.state.ledger.modify(|ledger| { ledger.push(CodeChange { timestamp: SystemTime::now() @@ -433,15 +366,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "visualize_graph" => { - let req: VisualizeGraphTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, VisualizeGraphTool); let query = req.query.unwrap_or_default().to_lowercase(); let full = self.state.get_full_graph(); let mut included = HashSet::new(); @@ -511,15 +436,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![output.to_string()][0].clone()) } "add_sticky_note" => { - let req: AddStickyNoteTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, AddStickyNoteTool); self.state.sticky.modify(|notes| { notes.push(StickyNote { timestamp: SystemTime::now() @@ -537,15 +454,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "condense_entity" => { - let req: CondenseEntityTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, CondenseEntityTool); self.state.apply_sync_write(|master| { if let Some(e) = master.entities.get_mut(&req.entity_name) { e.observations = req.summarized_observations; @@ -554,15 +463,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Entity condensed".to_string()][0].clone()) } "add_task" => { - let req: AddTaskTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, AddTaskTool); let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -586,15 +487,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![format!("Task added with ID: {}", id).to_string()][0].clone()) } "update_task_status" => { - let req: UpdateTaskStatusTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, UpdateTaskStatusTool); let mut found = false; self.state.tasks.modify(|tasks| { for t in tasks.iter_mut() { @@ -616,15 +509,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti } } "list_active_tasks" => { - let req: ListActiveTasksTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ListActiveTasksTool); let mut tasks = self.state.tasks.read(); tasks.retain(|t| t.status != "done"); if let Some(branch) = req.git_branch { @@ -637,15 +522,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "store_snippet" => { - let req: StoreSnippetTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, StoreSnippetTool); self.state.snippets.modify(|snippets| { snippets.retain(|s| s.name != req.name); snippets.push(Snippet { @@ -662,15 +539,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![format!("Snippet '{}' stored.", req.name).to_string()][0].clone()) } "search_snippets" => { - let req: SearchSnippetsTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, SearchSnippetsTool); let query = req.query.to_lowercase(); let snippets = self.state.snippets.read(); let mut results = Vec::new(); @@ -686,15 +555,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "delete_snippet" => { - let req: DeleteSnippetTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, DeleteSnippetTool); let mut deleted = false; self.state.snippets.modify(|snippets| { let orig = snippets.len(); @@ -708,15 +569,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti } } "log_decision" => { - let req: LogDecisionTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, LogDecisionTool); let mut id = String::new(); self.state.adrs.modify(|adrs| { id = format!("ADR-{:04}", adrs.len() + 1); @@ -735,15 +588,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![format!("Decision logged as {}", id).to_string()][0].clone()) } "query_decisions" => { - let req: QueryDecisionsTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, QueryDecisionsTool); let mut adrs = self.state.adrs.read(); if let Some(q) = req.query { let q = q.to_lowercase(); @@ -757,15 +602,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "merge_entities" => { - let req: MergeEntitiesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, MergeEntitiesTool); self.state.apply_sync_write(|master| { if let Some(src) = master.entities.remove(&req.source_entity) { if let Some(tgt) = master.entities.get_mut(&req.target_entity) { @@ -807,15 +644,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "learn_preference" => { - let req: LearnPreferenceTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, LearnPreferenceTool); self.state.prefs.modify(|prefs| { prefs.insert( req.key.clone(), @@ -837,15 +666,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "log_error_fix" => { - let req: LogErrorFixTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, LogErrorFixTool); self.state.error_fixes.modify(|fixes| { fixes.push(crate::models::ErrorFix { signature: req.signature, @@ -861,15 +682,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Error fix logged".to_string()][0].clone()) } "search_error_fixes" => { - let req: SearchErrorFixesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, SearchErrorFixesTool); let q = req.query.to_lowercase(); let mut fixes = self.state.error_fixes.read(); fixes.retain(|f| { @@ -880,15 +693,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "pin_file" => { - let req: PinFileTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, PinFileTool); self.state.pinned_files.modify(|pinned| { pinned.retain(|p| { !(p.namespace == req.namespace && p.file_path == req.file_path) @@ -906,15 +711,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["File pinned".to_string()][0].clone()) } "unpin_file" => { - let req: UnpinFileTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, UnpinFileTool); self.state.pinned_files.modify(|pinned| { pinned.retain(|p| { !(p.namespace == req.namespace && p.file_path == req.file_path) @@ -923,15 +720,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["File unpinned".to_string()][0].clone()) } "list_pinned_files" => { - let req: ListPinnedFilesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ListPinnedFilesTool); let mut pinned = self.state.pinned_files.read(); if let Some(ns) = req.namespace { pinned.retain(|p| p.namespace == ns); @@ -946,15 +735,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "add_session_summary" => { - let req: AddSessionSummaryTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, AddSessionSummaryTool); self.state.session_summaries.modify(|summaries| { summaries.push(crate::models::SessionSummary { summary: req.summary, @@ -968,15 +749,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Session summary added".to_string()][0].clone()) } "get_project_timeline" => { - let req: GetProjectTimelineTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, GetProjectTimelineTool); let mut summaries = self.state.session_summaries.read(); if let Some(ns) = req.namespace { summaries.retain(|s| s.namespace == ns); @@ -986,15 +759,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "leave_handoff_memo" => { - let req: LeaveHandoffMemoTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, LeaveHandoffMemoTool); self.state.handoff_memos.modify(|memos| { memos.push(crate::models::HandoffMemo { id: uuid::Uuid::new_v4().to_string(), @@ -1010,15 +775,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Handoff memo left".to_string()][0].clone()) } "read_handoff_memos" => { - let req: ReadHandoffMemosTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ReadHandoffMemosTool); let mut memos = self.state.handoff_memos.read(); if let Some(ns) = req.namespace { memos.retain(|m| m.namespace == ns); @@ -1027,15 +784,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "clear_handoff_memos" => { - let req: ClearHandoffMemosTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ClearHandoffMemosTool); let ids: HashSet<_> = req.ids.into_iter().collect(); self.state .handoff_memos @@ -1043,15 +792,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Handoff memos cleared".to_string()][0].clone()) } "update_env_fingerprint" => { - let req: UpdateEnvFingerprintTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, UpdateEnvFingerprintTool); self.state.env_fingerprints.modify(|fps| { fps.insert( req.namespace.clone(), @@ -1071,15 +812,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Env fingerprint updated".to_string()][0].clone()) } "read_env_fingerprint" => { - let req: ReadEnvFingerprintTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ReadEnvFingerprintTool); let fps = self.state.env_fingerprints.read(); if let Some(fp) = fps.get(&req.namespace) { let data = serde_json::to_string(fp).unwrap_or_default(); @@ -1089,15 +822,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti } } "log_env_requirement" => { - let req: LogEnvRequirementTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, LogEnvRequirementTool); self.state.env_requirements.modify(|reqs| { reqs.retain(|r| !(r.namespace == req.namespace && r.key == req.key)); reqs.push(crate::models::EnvRequirement { @@ -1110,15 +835,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Env requirement logged".to_string()][0].clone()) } "add_milestone" => { - let req: AddMilestoneTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, AddMilestoneTool); self.state.milestones.modify(|ms| { ms.push(crate::models::Milestone { id: uuid::Uuid::new_v4().to_string(), @@ -1131,15 +848,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Milestone added".to_string()][0].clone()) } "update_milestone" => { - let req: UpdateMilestoneTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, UpdateMilestoneTool); let mut found = false; self.state.milestones.modify(|ms| { for m in ms.iter_mut() { @@ -1157,15 +866,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti } } "list_milestones" => { - let req: ListMilestonesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ListMilestonesTool); let mut ms = self.state.milestones.read(); if let Some(ns) = req.namespace { ms.retain(|m| m.namespace == ns); @@ -1174,15 +875,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "generate_standup_report" => { - let req: GenerateStandupReportTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, GenerateStandupReportTool); let cutoff = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -1213,15 +906,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![report.to_string().to_string()][0].clone()) } "register_environment" => { - let req: RegisterEnvironmentTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, RegisterEnvironmentTool); self.state.environments.modify(|envs| { envs.retain(|e| !(e.namespace == req.namespace && e.name == req.name)); envs.push(crate::models::EnvironmentDetail { @@ -1239,30 +924,14 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Environment registered".to_string()][0].clone()) } "get_environment_details" => { - let req: GetEnvironmentDetailsTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, GetEnvironmentDetailsTool); let mut envs = self.state.environments.read(); envs.retain(|e| e.namespace == req.namespace); let data = serde_json::to_string(&envs).unwrap_or_default(); Ok(vec![data.to_string()][0].clone()) } "add_pr_checklist_item" => { - let req: AddPrChecklistItemTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, AddPrChecklistItemTool); self.state.pr_checklists.modify(|items| { items.push(crate::models::PrChecklistItem { namespace: req.namespace, @@ -1273,45 +942,21 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["PR checklist item added".to_string()][0].clone()) } "get_pr_checklist" => { - let req: GetPrChecklistTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, GetPrChecklistTool); let mut items = self.state.pr_checklists.read(); items.retain(|i| i.namespace == req.namespace); let data = serde_json::to_string(&items).unwrap_or_default(); Ok(vec![data.to_string()][0].clone()) } "clear_pr_checklist" => { - let req: ClearPrChecklistTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ClearPrChecklistTool); self.state .pr_checklists .modify(|items| items.retain(|i| i.namespace != req.namespace)); Ok(vec!["PR checklist cleared".to_string()][0].clone()) } "log_tech_debt" => { - let req: LogTechDebtTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, LogTechDebtTool); self.state.tech_debts.modify(|debts| { debts.push(crate::models::TechDebt { id: uuid::Uuid::new_v4().to_string(), @@ -1330,15 +975,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Tech debt logged".to_string()][0].clone()) } "resolve_tech_debt" => { - let req: ResolveTechDebtTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ResolveTechDebtTool); let mut found = false; self.state.tech_debts.modify(|debts| { for d in debts.iter_mut() { @@ -1356,15 +993,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti } } "list_tech_debt" => { - let req: ListTechDebtTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ListTechDebtTool); let mut debts = self.state.tech_debts.read(); debts.retain(|d| { d.namespace == req.namespace && (req.include_resolved || !d.is_resolved) @@ -1373,15 +1002,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![data.to_string()][0].clone()) } "save_context_workspace" => { - let req: SaveContextWorkspaceTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, SaveContextWorkspaceTool); self.state.context_workspaces.modify(|ws| { ws.retain(|w| !(w.namespace == req.namespace && w.name == req.name)); ws.push(crate::models::ContextWorkspace { @@ -1398,45 +1019,21 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec!["Context workspace saved".to_string()][0].clone()) } "load_context_workspace" => { - let req: LoadContextWorkspaceTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, LoadContextWorkspaceTool); let mut ws = self.state.context_workspaces.read(); ws.retain(|w| w.namespace == req.namespace && w.name == req.name); let data = serde_json::to_string(&ws.first()).unwrap_or_default(); Ok(vec![data.to_string()][0].clone()) } "list_context_workspaces" => { - let req: ListContextWorkspacesTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, ListContextWorkspacesTool); let mut ws = self.state.context_workspaces.read(); ws.retain(|w| w.namespace == req.namespace); let data = serde_json::to_string(&ws).unwrap_or_default(); Ok(vec![data.to_string()][0].clone()) } "omni_search" => { - let req: OmniSearchTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, OmniSearchTool); let matches = if let Ok(idx) = self.state.search_index.read() { idx.search(&req.query, req.namespace.as_deref()) .unwrap_or_default() @@ -1522,15 +1119,7 @@ crate::mcp::tool_def::("create_entities", "Create new entiti Ok(vec![report.to_string().to_string()][0].clone()) } "get_project_health" => { - let req: GetProjectHealthTool = match parse_args(args.clone()) { - Ok(r) => r, - Err(e) => { - return Some(crate::mcp::success( - id.clone(), - serde_json::json!({"isError": true, "content": [{"type": "text", "text": e}] }), - )); - } - }; + let req = parse_tool!(args.clone(), id, GetProjectHealthTool); let active_tasks = self .state .tasks diff --git a/strip_openssl.py b/strip_openssl.py deleted file mode 100644 index 36003f7..0000000 --- a/strip_openssl.py +++ /dev/null @@ -1,21 +0,0 @@ -import sys - -cargo_path = r"C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\Cargo.toml" -with open(cargo_path, "r", encoding="utf-8") as f: - content = f.read() - -# Swap reqwest rustls-tls in instead of default (which uses native-tls / openssl) -# This entirely removes the openssl dependency during Linux cross-compilation -if 'reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] }' not in content: - content = content.replace('reqwest = "0.12"', 'reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] }') - -# For git2, we don't need vendored openssl if we're not using https fetch/push for libgit2. -# We just need to read local `.git` files, so we can disable the default features. -if 'git2 = { version = "0.19.0", features = ["vendored-openssl"] }' in content: - content = content.replace('git2 = { version = "0.19.0", features = ["vendored-openssl"] }', 'git2 = { version = "0.19.0", default-features = false }') -elif 'git2 = "0.19.0"' in content: - content = content.replace('git2 = "0.19.0"', 'git2 = { version = "0.19.0", default-features = false }') - -with open(cargo_path, "w", encoding="utf-8") as f: - f.write(content) -print("Removed OpenSSL dependency entirely from server Cargo.toml") diff --git a/win-nvim/tests/integration_test.rs b/win-nvim/tests/integration_test.rs index 9912a77..ac82203 100644 --- a/win-nvim/tests/integration_test.rs +++ b/win-nvim/tests/integration_test.rs @@ -82,7 +82,7 @@ fn test_mcp_initialization_and_tools_list() { // Verify capabilities let capabilities = &init_resp["result"]["capabilities"]; - assert!(capabilities.get("tools").is_none(), "capabilities.tools MUST NOT be present in initialize response to avoid crashing Antigravity proxy!"); + assert_eq!(capabilities["tools"], serde_json::json!({})); // 2. Test tools/list let tools_req = json!({