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.
This commit is contained in:
Riz Ashraf committed 2026-09-14 04:57:31 +01:00
1 parent ee4157d07b
commit 0e29b12ac8
33 files changed
+71 -1981

No files matched your search

-47
View File
@@ -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
-25
View File
@@ -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")
-186
View File
@@ -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<MemoryState>) {
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<MemoryState>) {
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)
-97
View File
@@ -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)
-102
View File
@@ -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<MemoryState>) {
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<MemoryState>) {
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)
-12
View File
@@ -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")
-15
View File
@@ -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")
-76
View File
@@ -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)
-8
View File
@@ -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)
-37
View File
@@ -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")
-52
View File
@@ -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<T> \{\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)
-14
View File
@@ -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")
-56
View File
@@ -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<MemoryState>) {
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<MemoryState>) {
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)
-49
View File
@@ -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<String>| 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<String>| 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.")
-12
View File
@@ -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.")
-19
View File
@@ -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)
-21
View File
@@ -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<T: Eq + std::hash::Hash + Clone>(input: Vec<T>) -> Vec<T> {
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)
-36
View File
@@ -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<Vec<SearchResult>> {", commit_code + "\n pub fn search(&self, query_str: &str, limit: usize) -> tantivy::Result<Vec<SearchResult>> {")
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)
-11
View File
@@ -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")
-55
View File
@@ -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::<KnowledgeGraph>(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::<KnowledgeGraph>(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)
-9
View File
@@ -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)
-45
View File
@@ -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!")
-62
View File
@@ -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!")
-92
View File
@@ -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::<CreateEntitiesTool>')
if list_tools_idx == -1:
print("Could not find list_tools")
sys.exit(1)
tool_def = ' crate::mcp::tool_def::<crate::tools::QueryGraphPathTool>("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<String, (String, String)> = 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!")
-70
View File
@@ -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<Arc<AppState>>,
) -> impl IntoResponse {
ws.on_upgrade(move |socket| handle_socket(socket, state))
}
async fn handle_socket(mut socket: WebSocket, state: Arc<AppState>) {
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!")
-59
View File
@@ -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)
-39
View File
@@ -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<std::collections::HashMap<String, String>>| 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)
-44
View File
@@ -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")
-78
View File
@@ -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")
-50
View File
@@ -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<std::collections::HashMap<String, String>>| 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)
+70 -481
View File
@@ -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::<CreateEntitiesTool>("create_entities", "Create new entiti
let result: Result<String, String> = 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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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::<CreateEntitiesTool>("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
-21
View File
@@ -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")
+1 -1
View File
@@ -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!({