chore(server): remove dead scratch scripts, temporary files, and unused legacy modules

This commit is contained in:
Riz Ashraf committed 2026-09-22 03:34:13 +01:00
1 parent 1069096a29
commit ce88bef45e
32 files changed
-1983

No files matched your search

BIN
View File
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.64s
-312
View File
@@ -1,312 +0,0 @@
Blocking waiting for file lock on package cache
Blocking waiting for file lock on package cache
Compiling proc-macro2 v1.0.107
Compiling quote v1.0.47
Compiling unicode-ident v1.0.24
Compiling getrandom v0.4.3
Compiling serde_core v1.0.229
Checking windows-link v0.2.1
Compiling cfg-if v1.0.4
Compiling shlex v2.0.1
Compiling find-msvc-tools v0.1.12
Checking smallvec v1.16.0
Checking stable_deref_trait v1.2.1
Checking windows-sys v0.61.2
Checking pin-project-lite v0.2.17
Checking futures-core v0.3.34
Checking memchr v2.8.3
Checking itoa v1.0.18
Compiling serde v1.0.229
Checking bytes v1.12.1
Checking once_cell v1.21.4
Checking futures-sink v0.3.34
Compiling parking_lot_core v0.9.12
Checking scopeguard v1.2.0
Checking slab v0.4.12
Checking mio v1.2.3
Checking socket2 v0.6.5
Checking log v0.4.34
Checking futures-channel v0.3.34
Compiling version_check v0.9.5
Compiling jobserver v0.1.35
Checking lock_api v0.4.14
Compiling zmij v1.0.23
Checking futures-task v0.3.34
Checking futures-io v0.3.34
Compiling num-conv v0.2.2
Compiling time-core v0.1.9
Compiling cc v1.4.5
Compiling autocfg v1.5.1
Checking deranged v0.5.8
Checking http v1.5.0
Checking tracing-core v0.1.36
Compiling syn v3.0.5
Compiling syn v2.0.119
Checking percent-encoding v2.3.2
Checking litemap v0.8.3
Compiling time-macros v0.2.32
Checking writeable v0.6.4
Checking powerfmt v0.2.0
Checking equivalent v1.0.2
Checking parking_lot v0.12.5
Checking utf8_iter v1.0.4
Compiling icu_normalizer_data v2.3.0
Compiling icu_properties_data v2.3.0
Checking fnv v1.0.7
Compiling serde_json v1.0.151
Checking http-body v1.1.0
Checking hashbrown v0.17.1
Compiling httparse v1.10.1
Checking base64 v0.22.1
Compiling synstructure v0.13.2
Checking tower-service v0.3.3
Compiling ring v0.17.14
Compiling num-traits v0.2.19
Checking indexmap v2.14.2
Checking time v0.3.55
Checking try-lock v0.2.5
Compiling serde_derive v1.0.229
Compiling zerovec-derive v0.11.6
Compiling tokio-macros v2.7.2
Compiling zerofrom-derive v0.1.7
Compiling yoke-derive v0.8.2
Compiling displaydoc v0.2.7
Compiling futures-macro v0.3.34
Compiling tracing-attributes v0.1.31
Compiling zeroize_derive v1.5.0
Checking tokio v1.53.1
Compiling libc v0.2.189
Checking atomic-waker v1.1.2
Checking futures-util v0.3.34
Checking subtle v2.6.1
Checking zerofrom v0.1.8
Checking want v0.3.1
Checking zeroize v1.9.0
Checking tracing v0.1.44
Checking form_urlencoded v1.2.2
Checking yoke v0.8.3
Checking getrandom v0.2.17
Checking httpdate v1.0.3
Checking untrusted v0.9.0
Checking rustls-pki-types v1.15.1
Compiling generic-array v0.14.7
Checking sync_wrapper v1.0.2
Checking tower-layer v0.3.3
Checking tokio-util v0.7.19
Checking zerovec v0.11.8
Checking zerotrie v0.2.5
Compiling rustls v0.23.44
Compiling pkg-config v0.3.34
Checking ipnet v2.12.2
Compiling async-trait v0.1.92
Compiling cmake v0.1.58
Compiling cookie v0.18.2
Checking h2 v0.4.19
Checking tinystr v0.8.4
Checking potential_utf v0.1.6
Compiling dunce v1.0.5
Checking mime v0.3.17
Compiling zstd-sys v2.1.0+zstd.1.5.7
Compiling unicase v2.9.0
Checking icu_locale_core v2.3.0
Compiling thiserror v2.0.20
Checking icu_collections v2.3.0
Compiling fs_extra v1.3.0
Compiling crossbeam-utils v0.8.23
Checking typenum v1.20.1
Compiling mime_guess v2.0.5
Checking hyper v1.11.1
Checking tower v0.5.3
Checking rustls-webpki v0.103.15
Checking icu_provider v2.3.1
Compiling thiserror-impl v2.0.20
Compiling aws-lc-sys v0.45.0
Checking http-body-util v0.1.5
Checking icu_normalizer v2.3.0
Checking icu_properties v2.3.0
Checking hyper-util v0.1.20
Compiling litrs v1.0.0
Checking ryu v1.0.23
Checking psl-types v2.0.11
Checking either v1.18.0
Compiling crunchy v0.2.4
Checking idna_adapter v1.2.2
Checking serde_urlencoded v0.7.1
Compiling document-features v0.2.12
Checking idna v1.1.0
Checking webpki-roots v1.0.9
Compiling ident_case v1.0.1
Checking tokio-rustls v0.26.5
Checking byteorder v1.5.0
Checking url v2.5.8
Checking publicsuffix v2.3.0
Checking bitflags v2.13.1
Compiling crossbeam-epoch v0.9.21
Compiling windows_x86_64_msvc v0.52.6
Compiling zstd-safe v7.3.0
Checking regex-syntax v0.8.11
Compiling strsim v0.11.1
Checking hyper-rustls v0.27.9
Checking cookie_store v0.22.1
Checking tower-http v0.6.11
Compiling darling_core v0.24.1
Checking block-buffer v0.10.4
Checking crypto-common v0.1.7
Checking num-integer v0.1.47
Checking ownedbytes v0.9.0
Compiling crossbeam-deque v0.8.8
Compiling typeid v1.0.3
Checking once_cell_polyfill v1.70.2
Compiling aws-lc-rs v1.18.1
Checking utf8parse v0.2.2
Compiling prettyplease v0.3.0
Checking anstyle v1.0.14
Checking tantivy-common v0.11.0
Checking anstyle-parse v1.0.0
Compiling darling_macro v0.24.1
Checking anstyle-wincon v3.0.11
Checking num-bigint v0.4.8
Checking digest v0.10.7
Checking reqwest v0.12.28
Checking bitpacking v0.9.3
Checking futures-executor v0.3.34
Compiling fs-err v3.3.1
Checking anstyle-query v1.1.5
Compiling rayon-core v1.13.0
Checking colorchoice v1.0.5
Checking is_terminal_polyfill v1.70.2
Checking utf8-ranges v1.0.5
Compiling rustversion v1.0.23
Checking untrusted v0.7.1
Compiling erased-serde v0.4.10
Checking futures v0.3.34
Checking tantivy-bitpacker v0.10.0
Checking anstream v1.0.0
Checking zstd v0.13.3
Checking tantivy-fst v0.5.0
Checking simple_asn1 v0.6.4
Checking windows-targets v0.52.6
Compiling darling v0.24.1
Checking uuid v1.26.0
Checking itertools v0.14.0
Checking tokio-stream v0.1.19
Checking rust-mcp-schema v2.0.0
Checking pem v3.0.6
Compiling crc32fast v1.5.1
Checking minimal-lexical v0.2.1
Checking signature v2.2.0
Checking murmurhash32 v0.3.1
Checking clap_lex v1.1.0
Compiling heck v0.5.0
Checking cpufeatures v0.2.17
Checking allocator-api2 v0.2.21
Checking foldhash v0.2.0
Compiling typetag v0.2.23
Compiling winapi v0.3.9
Checking nom v7.1.3
Checking tantivy-stacker v0.7.0
Checking sha2 v0.10.9
Compiling clap_derive v4.6.4
Checking tantivy-sstable v0.7.0
Checking hashbrown v0.16.1
Checking clap_builder v4.6.6
Compiling rust-mcp-macros v2.0.0
Compiling bon-macros v3.10.1
Checking rust-mcp-transport v2.0.0
Checking windows-sys v0.59.0
Checking hmac v0.12.1
Checking regex-automata v0.4.18
Checking axum-core v0.5.6
Checking ordered-float v5.5.0
Compiling typetag-impl v0.2.23
Checking serde_path_to_error v0.1.20
Checking fastrand v2.5.0
Checking inventory v0.3.24
Checking downcast-rs v2.0.2
Checking matchit v0.8.4
Checking fastdivide v0.4.2
Checking option-ext v0.2.0
Checking base64 v0.23.1
Checking fs4 v0.13.1
Checking arc-swap v1.9.2
Checking tantivy-columnar v0.7.0
Checking dirs-sys v0.5.0
Checking tempfile v3.27.0
Checking clap v4.6.6
Checking regex v1.13.1
Checking tantivy-query-grammar v0.26.0
Checking axum v0.8.9
Checking rayon v1.12.0
Checking axum-server v0.8.0
Checking lru v0.16.4
Checking crossbeam-channel v0.5.17
Checking rust-stemmers v1.2.0
Checking sketches-ddsketch v0.4.1
Checking tantivy-tokenizer-api v0.7.0
Checking measure_time v0.9.0
Checking aho-corasick v1.1.5
Checking htmlescape v0.3.1
Checking lz4_flex v0.13.1
Checking bon v3.10.1
Checking memmap2 v0.9.11
Checking datasketches v0.2.0
Checking levenshtein_automata v0.2.1
Checking rustc-hash v2.1.3
Checking oneshot v0.1.13
Checking census v0.4.2
Checking dirs v6.0.0
Checking glob v0.3.4
Checking mcp-memory-stub v0.1.0 (C:\Users\reazul.ashraf\workspace\rust\mcp-memory\stub)
Checking tantivy v0.26.1
warning: this can be `std::io::Error::other(_)`
--> stub\src\main.rs:76:72
|
76 | let stream = resp.bytes_stream().map(|res| res.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#io_other_error
= note: `#[warn(clippy::io_other_error)]` on by default
help: use `std::io::Error::other`
|
76 - let stream = resp.bytes_stream().map(|res| res.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)));
76 + let stream = resp.bytes_stream().map(|res| res.map_err(|e| std::io::Error::other(e)));
|
warning: stripping a prefix manually
--> stub\src\main.rs:93:40
|
93 | println!("{}", &trimmed[6..]);
| ^^^^^^^^^^^^^
|
note: the prefix was tested here
--> stub\src\main.rs:91:24
|
91 | } else if trimmed.starts_with("data: ") {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#manual_strip
= note: `#[warn(clippy::manual_strip)]` on by default
help: try using the `strip_prefix` method
|
91 ~ } else if let Some(<stripped>) = trimmed.strip_prefix("data: ") {
92 | if is_message {
93 ~ println!("{}", <stripped>);
94 | is_message = false;
95 | } else if is_endpoint {
96 ~ let ep = <stripped>;
|
warning: redundant pattern matching
--> stub\src\main.rs:117:16
|
117 | if let Err(_) = std::net::TcpStream::connect(cli.target.replace("http://", "").replace("https://", "")) {
| ^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#redundant_pattern_matching
= note: `#[warn(clippy::redundant_pattern_matching)]` on by default
help: consider using `is_err()`
|
117 - if let Err(_) = std::net::TcpStream::connect(cli.target.replace("http://", "").replace("https://", "")) {
117 + if std::net::TcpStream::connect(cli.target.replace("http://", "").replace("https://", "")).is_err() {
|
warning: `mcp-memory-stub` (bin "mcp-memory-stub") generated 3 warnings (run `cargo clippy --fix --bin "mcp-memory-stub" -p mcp-memory-stub -- ` to apply 2 suggestions)
-14
View File
@@ -1,14 +0,0 @@
import re
with open("server/src/handlers.rs", "r", encoding="utf-8") as f:
text = f.read()
pattern = r'"(list_milestones|list_pinned_files|read_handoff_memos)" => \{\s*let req = parse_tool!\(args, id, ([a-zA-Z]+)\);\s*let mut [a-zA-Z_]+ = self\.state\.([a-zA-Z_]+)\.read\(\);\s*if let Some\(ns\) = req\.namespace \{\s*[a-zA-Z_]+\.retain\(\|.\| \w+\.namespace == ns\);\s*\}\s*let data = serde_json::to_string\(&[a-zA-Z_]+\)\.unwrap_or_default\(\);\s*Ok\(data\.to_string\(\)\)\s*\}'
def repl(m):
return f'"{m.group(1)}" => handle_list_with_namespace!(self, {m.group(3)}, {m.group(2)}, args, id),'
new_text = re.sub(pattern, repl, text)
with open("server/src/handlers.rs", "w", encoding="utf-8") as f:
f.write(new_text)
-14
View File
@@ -1,14 +0,0 @@
import re
with open("server/src/handlers.rs", "r", encoding="utf-8") as f:
text = f.read()
pattern = r'"([a-zA-Z_]+)" => \{\s*(?:let req = parse_tool!\(args, id, ([a-zA-Z]+)\);\s*)?let data = serde_json::to_string\(&self\.state\.([a-zA-Z_]+)\.read\(\)\)\s*\.unwrap_or_else\(\|_\| "\[\]"\.to_string\(\)\);\s*Ok\(data\.to_string\(\)\)\s*\}'
def repl(m):
return f'"{m.group(1)}" => {{\n let data = serde_json::to_string(&self.state.{m.group(3)}.read()).unwrap_or_else(|_| "[]".to_string());\n Ok(data)\n}},'
new_text = re.sub(pattern, repl, text)
with open("server/src/handlers.rs", "w", encoding="utf-8") as f:
f.write(new_text)
-40
View File
@@ -1,40 +0,0 @@
import re
# 1. Fix stub/src/main.rs
stub_path = r'C:\Users\reazul.ashraf\workspace\rust\mcp-memory\stub\src\main.rs'
with open(stub_path, 'r', encoding='utf-8') as f:
content = f.read()
content = content.replace('use tokio::io::AsyncBufReadExt;\n', '')
with open(stub_path, 'w', encoding='utf-8') as f:
f.write(content)
# 2. Fix nvim-core/src/lib.rs
nvim_path = r'C:\Users\reazul.ashraf\workspace\rust\mcp-memory\nvim-core\src\lib.rs'
with open(nvim_path, 'r', encoding='utf-8') as f:
content = f.read()
content = content.replace(
'use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};',
'use tokio::io::{AsyncReadExt, AsyncWriteExt};'
)
with open(nvim_path, 'w', encoding='utf-8') as f:
f.write(content)
# 3. Fix server/src/store.rs
store_path = r'C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\store.rs'
with open(store_path, 'r', encoding='utf-8') as f:
content = f.read()
inspect_method = """ pub fn inspect<F, R>(&self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
let lock = self.cache.read().unwrap();
f(&lock)
}
"""
content = content.replace(inspect_method, "")
with open(store_path, 'w', encoding='utf-8') as f:
f.write(content)
print("Warnings fixed!")
-168
View File
@@ -1,168 +0,0 @@
import re
import sys
with open('C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\nvim-core\\src\\lib.rs', 'r', encoding='utf-8') as f:
content = f.read()
start_pattern = r'#\[cfg\(windows\)\]\nasync fn call_nvim'
end_pattern = r'async fn send_nvim_command'
start_idx = re.search(start_pattern, content).start()
end_idx = re.search(end_pattern, content).start()
new_code = """use std::sync::LazyLock;
use std::sync::Arc;
use tokio::sync::{Mutex, mpsc, oneshot};
use std::collections::HashMap;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
pub struct NvimRequest {
pub msgid_str: String,
pub req: rmpv::Value,
pub reply: oneshot::Sender<Result<rmpv::Value, String>>,
}
static NVIM_CONN: LazyLock<Arc<Mutex<Option<mpsc::Sender<NvimRequest>>>>> = LazyLock::new(|| Arc::new(Mutex::new(None)));
async fn get_nvim_connection() -> Result<mpsc::Sender<NvimRequest>, String> {
let mut conn_lock = NVIM_CONN.lock().await;
if let Some(sender) = conn_lock.as_ref() {
if !sender.is_closed() {
return Ok(sender.clone());
}
}
tracing::info!("Establishing new persistent connection to Neovim");
let socket_path = get_socket_path().await?;
#[cfg(windows)]
let stream = {
use tokio::net::windows::named_pipe::ClientOptions;
ClientOptions::new().open(&socket_path).map_err(|e| e.to_string())?
};
#[cfg(unix)]
let stream = {
use tokio::net::UnixStream;
UnixStream::connect(socket_path).await.map_err(|e| e.to_string())?
};
let (mut read_half, mut write_half) = tokio::io::split(stream);
let (tx, mut rx) = mpsc::channel::<NvimRequest>(32);
let pending_requests: Arc<Mutex<HashMap<String, oneshot::Sender<Result<rmpv::Value, String>>>>> = Arc::new(Mutex::new(HashMap::new()));
// Write task
let pending_clone = Arc::clone(&pending_requests);
tokio::spawn(async move {
while let Some(req) = rx.recv().await {
let mut buf = Vec::new();
if let Err(e) = rmpv::encode::write_value(&mut buf, &req.req) {
let _ = req.reply.send(Err(e.to_string()));
continue;
}
pending_clone.lock().await.insert(req.msgid_str.clone(), req.reply);
if write_half.write_all(&buf).await.is_err() {
tracing::error!("Failed to write to Neovim socket");
break;
}
}
});
// Read task
let pending_clone2 = Arc::clone(&pending_requests);
tokio::spawn(async move {
let mut resp_buf = Vec::new();
let mut chunk = vec![0u8; 8192];
let mut offset = 0;
loop {
let mut cursor = std::io::Cursor::new(&resp_buf[offset..]);
match rmpv::decode::read_value(&mut cursor) {
Ok(val) => {
offset += cursor.position() as usize;
if let rmpv::Value::Array(ref arr) = val {
if arr.len() >= 4 && arr[0] == rmpv::Value::Integer(1.into()) {
let msgid = &arr[1];
let msgid_str = format!("{:?}", msgid);
if let Some(reply_sender) = pending_clone2.lock().await.remove(&msgid_str) {
let _ = reply_sender.send(Ok(val));
}
}
}
// Trim buffer if it gets too large
if offset > 1024 * 1024 {
resp_buf.drain(..offset);
offset = 0;
}
continue;
}
Err(_) => {
if offset > 0 {
resp_buf.drain(..offset);
offset = 0;
}
let read_future = read_half.read(&mut chunk);
match tokio::time::timeout(tokio::time::Duration::from_secs(60), read_future).await {
Ok(Ok(n)) if n > 0 => {
resp_buf.extend_from_slice(&chunk[..n]);
}
_ => {
tracing::error!("Neovim socket read loop closed or timeout");
break;
}
}
}
}
}
// Cleanup pending requests on disconnect
let mut pending = pending_clone2.lock().await;
for (_, sender) in pending.drain() {
let _ = sender.send(Err("Connection closed".to_string()));
}
});
*conn_lock = Some(tx.clone());
Ok(tx)
}
async fn call_nvim(req: rmpv::Value) -> Result<rmpv::Value, String> {
let msgid = if let rmpv::Value::Array(ref arr) = req {
if arr.len() > 1 {
arr[1].clone()
} else {
rmpv::Value::Nil
}
} else {
rmpv::Value::Nil
};
let msgid_str = format!("{:?}", msgid);
let tx = get_nvim_connection().await?;
let (reply_tx, reply_rx) = oneshot::channel();
tx.send(NvimRequest {
msgid_str,
req,
reply: reply_tx,
}).await.map_err(|_| "Failed to send request to Neovim connection manager")?;
match tokio::time::timeout(tokio::time::Duration::from_secs(10), reply_rx).await {
Ok(Ok(res)) => res,
Ok(Err(_)) => Err("Response channel dropped".to_string()),
Err(_) => Err("Timeout waiting for Neovim response".to_string()),
}
}
"""
new_content = content[:start_idx] + new_code + content[end_idx:]
with open('C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\nvim-core\\src\\lib.rs', 'w', encoding='utf-8') as f:
f.write(new_content)
print("Patched!")
-40
View File
@@ -1,40 +0,0 @@
import re
import sys
with open('C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\nvim-core\\src\\lib.rs', 'r', encoding='utf-8') as f:
content = f.read()
macro_def = """
macro_rules! send_text_result {
($id:expr, $text:expr) => {
send_response(JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id: $id,
result: Some(json!({
"content": [{"type": "text", "text": $text}]
})),
error: None,
}).await;
};
}
"""
if "macro_rules! send_text_result" not in content:
idx = content.find("pub async fn run_mcp_loop")
content = content[:idx] + macro_def + "\n" + content[idx:]
# Find tools/call block
tools_call_idx = content.find('"tools/call" => {')
if tools_call_idx != -1:
block = content[tools_call_idx:]
# Replace the verbose blocks
pattern = r'send_response\(JsonRpcResponse \{\s*jsonrpc: "2\.0"\.to_string\(\),\s*id,\s*result: Some\(json!\(\{\s*"content": \[\{"type": "text", "text": (.*?)\}\]\s*\}\)\),\s*error: None,\s*\}\)\s*\.await;'
new_block = re.sub(pattern, r'send_text_result!(id.clone(), \1);', block)
content = content[:tools_call_idx] + new_block
with open('C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\nvim-core\\src\\lib.rs', 'w', encoding='utf-8') as f:
f.write(content)
print("Patched nvim macro")
-15
View File
@@ -1,15 +0,0 @@
import re
import sys
with open('C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\stub\\src\\main.rs', 'r', encoding='utf-8') as f:
content = f.read()
start1 = r'let mut log_prefix = String::from\("Message"\);\n\s*if let Ok\(v\) = serde_json::from_str::<serde_json::Value>\(&msg\) \{[\s\S]*?log_prefix = format!\("Response id=\{\}", res_id\);\n\s*\}\n\s*\}'
content = re.sub(start1, r'let log_prefix = logger::extract_log_prefix(&msg, false);', content)
start2 = r'let mut log_prefix = String::from\("Message"\);\n\s*if let Ok\(v\) = serde_json::from_str::<serde_json::Value>\(&text\) \{[\s\S]*?log_prefix = format!\("Response id=\{\} \[Error: \{\}\]", id, is_error\);\n\s*\}'
content = re.sub(start2, r'let log_prefix = logger::extract_log_prefix(&text, true);', content)
with open('C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\stub\\src\\main.rs', 'w', encoding='utf-8') as f:
f.write(content)
print("Patched stub")
-12
View File
@@ -1,12 +0,0 @@
import re
with open('C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\nvim-core\\src\\lib.rs', 'r', encoding='utf-8') as f:
content = f.read()
content = content.replace('read_message(&mut reader)', 'mcp_stdio::read_mcp_message(&mut reader)')
content = content.replace('let req = mcp_stdio::read_mcp_message(&mut reader).await.unwrap();', 'let raw = mcp_stdio::read_mcp_message(&mut reader).await.unwrap();\n let req: JsonRpcRequest = serde_json::from_str(&raw).unwrap();')
content = content.replace('let req = mcp_stdio::read_mcp_message(&mut reader).await;', 'let req = mcp_stdio::read_mcp_message(&mut reader).await;')
with open('C:\\Users\\reazul.ashraf\\workspace\\rust\\mcp-memory\\nvim-core\\src\\lib.rs', 'w', encoding='utf-8') as f:
f.write(content)
print("Patched tests")
-24
View File
@@ -1,24 +0,0 @@
import re
with open(r'C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\handlers.rs', 'r', encoding='utf-8') as f:
content = f.read()
content = content.replace(
'crate::mcp::tool_def::<GenerateStandupReportTool>(\n "generate_standup_report",\n "",\n )',
'crate::mcp::tool_def::<GenerateStandupReportTool>(\n "generate_standup_report",\n "Generate a standup report summarizing recent work, blockers, and next steps.",\n )'
)
content = content.replace(
'crate::mcp::tool_def::<GetEnvironmentDetailsTool>(\n "get_environment_details",\n "",\n )',
'crate::mcp::tool_def::<GetEnvironmentDetailsTool>(\n "get_environment_details",\n "Get detailed information about a specific deployment environment.",\n )'
)
content = content.replace(
'crate::mcp::tool_def::<ListContextWorkspacesTool>(\n "list_context_workspaces",\n "",\n )',
'crate::mcp::tool_def::<ListContextWorkspacesTool>(\n "list_context_workspaces",\n "List all saved context workspaces.",\n )'
)
with open(r'C:\Users\reazul.ashraf\workspace\rust\mcp-memory\server\src\handlers.rs', 'w', encoding='utf-8') as f:
f.write(content)
print("Descriptions fixed.")
-62
View File
@@ -1,62 +0,0 @@
import os
import re
def bake_nvim_safeguards():
filepath = 'nvim-core/src/lib.rs'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# 1. We're going to inject an AST-level safeguard inside "nvim_execute_lua"
# Find the execute_lua block
target = """ "nvim_execute_lua" => {
if let Some(code) = args.get("code").and_then(|v| v.as_str()) {
match execute_nvim_lua(code).await {"""
safeguarded = """ "nvim_execute_lua" => {
if let Some(code) = args.get("code").and_then(|v| v.as_str()) {
// BAKE IN: Block interactive prompts that cause server deadlocks
let lower_code = code.to_lowercase();
if lower_code.contains("vim.fn.input") || lower_code.contains("vim.ui.select") || lower_code.contains("vim.fn.confirm") || lower_code.contains("vim.ui.input") {
send_error(id, -32600, "CRITICAL ERROR: Interactive Lua functions (input, select, confirm) are strictly forbidden via MCP as they will hang the headless server.").await;
return;
}
// Block the 'c' confirm flag in vim.cmd substitutions
if (code.contains("vim.cmd") || code.contains("vim.api.nvim_command")) && code.contains("%s") && code.contains("c") {
// A rough heuristic to block `%s/foo/bar/gc`
if regex::Regex::new(r"%s.*/.*c").unwrap().is_match(code) {
send_error(id, -32600, "CRITICAL ERROR: The 'c' (confirm) flag in Neovim substitutions is strictly forbidden via MCP as it triggers an interactive prompt that hangs the headless server. Use '/ge' instead.").await;
return;
}
}
match execute_nvim_lua(code).await {"""
if target in content:
# Also need to add regex as a dependency or just use basic string matching.
# Let's just use basic string matching for the %s block to avoid adding the regex crate to nvim-core if not present
safeguarded_no_regex = """ "nvim_execute_lua" => {
if let Some(code) = args.get("code").and_then(|v| v.as_str()) {
// BAKE IN: Block interactive prompts that cause server deadlocks
let lower_code = code.to_lowercase();
if lower_code.contains("vim.fn.input") || lower_code.contains("vim.ui.select") || lower_code.contains("vim.fn.confirm") || lower_code.contains("vim.ui.input") {
send_error(id, -32600, "CRITICAL ERROR: Interactive Lua functions (input, select, confirm) are strictly forbidden via MCP as they will hang the headless server.").await;
return;
}
// Block the 'c' confirm flag in vim.cmd substitutions
if (code.contains("vim.cmd") || code.contains("vim.api.nvim_command")) && code.contains("%s") && (code.contains("gc'") || code.contains('gc"') || code.contains("gc\\n") || code.contains("c'") || code.contains('c"')) {
send_error(id, -32600, "CRITICAL ERROR: The 'c' (confirm) flag in Neovim substitutions is strictly forbidden via MCP as it triggers an interactive prompt that hangs the headless server. Use '/g' or '/ge' instead.").await;
return;
}
match execute_nvim_lua(code).await {"""
content = content.replace(target, safeguarded_no_regex)
print("Baked safeguards into nvim_execute_lua")
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
else:
print("Could not find nvim_execute_lua block")
bake_nvim_safeguards()
-150
View File
@@ -1,150 +0,0 @@
import os
def rewrite_graph():
with open('server/src/handlers_v2/graph.rs', 'r', encoding='utf-8') as f:
content = f.read()
old_block = """ let mut included: HashSet<&str> = HashSet::new();
let mut to_draw = Vec::new();
state.read_graph(|full| {
for (name, e) in &full.entities {
if let Some(ns) = &req.namespace
&& e.namespace != *ns
{
continue;
}
if query.is_empty()
|| contains_ignore_ascii_case(name, &query)
|| contains_ignore_ascii_case(&e.entity_type, &query)
{
included.insert(name.as_str());
}
}
for r in &full.relations {
if let Some(ns) = &req.namespace
&& r.namespace != *ns
{
continue;
}
if query.is_empty() || included.contains(r.from.as_str()) || included.contains(r.to.as_str()) {
included.insert(r.from.as_str());
included.insert(r.to.as_str());
to_draw.push(r.clone());
}
}
});
use std::fmt::Write;
let mut output = String::with_capacity(included.len() * 40 + to_draw.len() * 60);
output.push_str("graph TD;\\n");
let sanitize = |s: &str, id_mode: bool| -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
if c != '"' && c != '(' && c != ')' {
if id_mode && (c == ' ' || c == '-' || c == '.') {
out.push('_');
} else {
out.push(c);
}
}
}
out
};
for name in &included {
let _ = writeln!(
output,
" id_{}[\\"{}\\"];",
sanitize(name, true),
sanitize(name, false)
);
}
for r in to_draw {
let _ = writeln!(
output,
" id_{}-->|\\"{}\\"|id_{};",
sanitize(&r.from, true),
r.relation_type.replace("\\"", ""),
sanitize(&r.to, true)
);
}"""
new_block = """ use std::fmt::Write;
let mut output = state.read_graph(|full| {
let mut included: HashSet<&str> = HashSet::new();
let mut to_draw = Vec::new();
for (name, e) in &full.entities {
if let Some(ns) = &req.namespace
&& e.namespace != *ns
{
continue;
}
if query.is_empty()
|| contains_ignore_ascii_case(name, &query)
|| contains_ignore_ascii_case(&e.entity_type, &query)
{
included.insert(name.as_str());
}
}
for r in &full.relations {
if let Some(ns) = &req.namespace
&& r.namespace != *ns
{
continue;
}
if query.is_empty() || included.contains(r.from.as_str()) || included.contains(r.to.as_str()) {
included.insert(r.from.as_str());
included.insert(r.to.as_str());
to_draw.push(r.clone());
}
}
let mut out = String::with_capacity(included.len() * 40 + to_draw.len() * 60);
out.push_str("graph TD;\\n");
let sanitize = |s: &str, id_mode: bool| -> String {
let mut o = String::with_capacity(s.len());
for c in s.chars() {
if c != '"' && c != '(' && c != ')' {
if id_mode && (c == ' ' || c == '-' || c == '.') {
o.push('_');
} else {
o.push(c);
}
}
}
o
};
for name in &included {
let _ = writeln!(
out,
" id_{}[\\"{}\\"];",
sanitize(name, true),
sanitize(name, false)
);
}
for r in to_draw {
let _ = writeln!(
out,
" id_{}-->|\\"{}\\"|id_{};",
sanitize(&r.from, true),
r.relation_type.replace("\\"", ""),
sanitize(&r.to, true)
);
}
out
});"""
if old_block in content:
with open('server/src/handlers_v2/graph.rs', 'w', encoding='utf-8') as f:
f.write(content.replace(old_block, new_block))
print("Replaced visualize_graph")
else:
print("Could not find old block")
rewrite_graph()
-10
View File
@@ -1,10 +0,0 @@
use rmcp::model::{InitializeResult, ServerCapabilities};
fn main() {
let init = InitializeResult::new(ServerCapabilities::builder().enable_tools().build())
.with_server_info(rmcp::model::Implementation::new(
"gemini-mcp-memory",
"3.0.0",
));
println!("{}", serde_json::to_string_pretty(&init).unwrap());
}
-61
View File
@@ -1,61 +0,0 @@
import os
def fix_env_and_workspaces():
filepath = 'server/src/handlers_v2/env.rs'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
old_env = """ let fps = state.env_fingerprints.read();
if let Some(fp) = fps.get(&req.namespace) {"""
new_env = """ let data = state.env_fingerprints.read_with(|fps| {
fps.get(&req.namespace).cloned()
});
if let Some(fp) = data {"""
if old_env in content:
content = content.replace(old_env, new_env)
print('Replaced env read')
else:
print('Could not find old_env')
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
filepath = 'server/src/handlers_v2/workspaces.rs'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
old_ws = """ let query = req.query.to_lowercase();
let snippets = state.snippets.read();
let mut results = Vec::new();
for s in snippets {
if contains_ignore_ascii_case(&s.name, &query)
|| contains_ignore_ascii_case(&s.description, &query)
|| contains_ignore_ascii_case(&s.language, &query)
{
results.push(s);
}
}
let data = serde_json::to_string(&results).unwrap_or_default();"""
new_ws = """ let query = req.query.to_lowercase();
let data = state.snippets.read_with(|snippets| {
let results: Vec<_> = snippets.iter().filter(|s| {
contains_ignore_ascii_case(&s.name, &query)
|| contains_ignore_ascii_case(&s.description, &query)
|| contains_ignore_ascii_case(&s.language, &query)
}).cloned().collect();
serde_json::to_string(&results).unwrap_or_default()
});"""
if old_ws in content:
content = content.replace(old_ws, new_ws)
print('Replaced workspaces read')
else:
print('Could not find old_ws')
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
fix_env_and_workspaces()
-60
View File
@@ -1,60 +0,0 @@
import os
def fix_main_stats():
filepath = 'server/src/main.rs'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
old_stats = """ let (entities, relations) = {
let graph = state_clone.get_full_graph();
(graph.entities.len(), graph.relations.len())
};
let tasks = state_clone.tasks.read().len();
let snippets = state_clone.snippets.read().len();
let tech_debts = state_clone.tech_debts.read().len();
let adrs = state_clone.adrs.read().len();
let ledger = state_clone.ledger.read().len();
let sticky = state_clone.sticky.read().len();
let error_fixes = state_clone.error_fixes.read().len();
let pinned_files = state_clone.pinned_files.read().len();
let session_summaries = state_clone.session_summaries.read().len();
let handoff_memos = state_clone.handoff_memos.read().len();
let env_fingerprints = state_clone.env_fingerprints.read().len();
let env_requirements = state_clone.env_requirements.read().len();
let milestones = state_clone.milestones.read().len();
let environments = state_clone.environments.read().len();
let pr_checklists = state_clone.pr_checklists.read().len();
let gates = state_clone.gates.read().len();
let context_workspaces = state_clone.context_workspaces.read().len();"""
new_stats = """ let (entities, relations) = state_clone.read_graph(|g| (g.entities.len(), g.relations.len()));
let tasks = state_clone.tasks.read_with(|items| items.len());
let snippets = state_clone.snippets.read_with(|items| items.len());
let tech_debts = state_clone.tech_debts.read_with(|items| items.len());
let adrs = state_clone.adrs.read_with(|items| items.len());
let ledger = state_clone.ledger.read_with(|items| items.len());
let sticky = state_clone.sticky.read_with(|items| items.len());
let error_fixes = state_clone.error_fixes.read_with(|items| items.len());
let pinned_files = state_clone.pinned_files.read_with(|items| items.len());
let session_summaries = state_clone.session_summaries.read_with(|items| items.len());
let handoff_memos = state_clone.handoff_memos.read_with(|items| items.len());
let env_fingerprints = state_clone.env_fingerprints.read_with(|items| items.len());
let env_requirements = state_clone.env_requirements.read_with(|items| items.len());
let milestones = state_clone.milestones.read_with(|items| items.len());
let environments = state_clone.environments.read_with(|items| items.len());
let pr_checklists = state_clone.pr_checklists.read_with(|items| items.len());
let gates = state_clone.gates.read_with(|items| items.len());
let context_workspaces = state_clone.context_workspaces.read_with(|items| items.len());"""
if old_stats in content:
content = content.replace(old_stats, new_stats)
print('Replaced stats reads')
else:
print('Could not find old_stats')
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
fix_main_stats()
-111
View File
@@ -1,111 +0,0 @@
import os
def fix_meta():
filepath = 'server/src/handlers_v2/meta.rs'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
old_omni = """ let q = req.query.to_lowercase();
let tech_debts: Vec<_> = state
.tech_debts
.read()
.into_iter()
.filter(|d| {
req.namespace.as_ref().is_none_or(|ns| d.namespace == *ns)
&& (contains_ignore_ascii_case(&d.description, &q)
|| contains_ignore_ascii_case(&d.ideal_solution, &q))
})
.collect();
let memos: Vec<_> = state
.handoff_memos
.read()
.into_iter()
.filter(|m| {
req.namespace.as_ref().is_none_or(|ns| m.namespace == *ns)
&& contains_ignore_ascii_case(&m.content, &q)
})
.collect();
let error_fixes: Vec<_> = state
.error_fixes
.read()
.into_iter()
.filter(|f| {
contains_ignore_ascii_case(&f.signature, &q)
|| contains_ignore_ascii_case(&f.solution, &q)
})
.collect();"""
new_omni = """ let q = req.query.to_lowercase();
let tech_debts = state.tech_debts.read_with(|debts| {
debts.iter().filter(|d| {
req.namespace.as_ref().is_none_or(|ns| d.namespace == *ns)
&& (contains_ignore_ascii_case(&d.description, &q)
|| contains_ignore_ascii_case(&d.ideal_solution, &q))
}).cloned().collect::<Vec<_>>()
});
let memos = state.handoff_memos.read_with(|memos| {
memos.iter().filter(|m| {
req.namespace.as_ref().is_none_or(|ns| m.namespace == *ns)
&& contains_ignore_ascii_case(&m.content, &q)
}).cloned().collect::<Vec<_>>()
});
let error_fixes = state.error_fixes.read_with(|fixes| {
fixes.iter().filter(|f| {
contains_ignore_ascii_case(&f.signature, &q)
|| contains_ignore_ascii_case(&f.solution, &q)
}).cloned().collect::<Vec<_>>()
});"""
if old_omni in content:
content = content.replace(old_omni, new_omni)
print('Replaced omni_search vecs')
else:
print('Could not find old_omni')
old_health = """ let active_tasks = state
.tasks
.read()
.into_iter()
.filter(|t| t.status != "done")
.count();
let unresolved_debt = state
.tech_debts
.read()
.into_iter()
.filter(|d| d.namespace == req.namespace && !d.is_resolved)
.count();
let unread_memos = state
.handoff_memos
.read()
.into_iter()
.filter(|m| m.namespace == req.namespace)
.count();
let active_milestones = state
.milestones
.read()
.into_iter()
.filter(|m| m.namespace == req.namespace && m.status != "done")
.count();
let remaining_checklists = state
.pr_checklists
.read()
.into_iter()
.filter(|c| c.namespace == req.namespace)
.count();"""
new_health = """ let active_tasks = state.tasks.read_with(|tasks| tasks.iter().filter(|t| t.status != "done").count());
let unresolved_debt = state.tech_debts.read_with(|debts| debts.iter().filter(|d| d.namespace == req.namespace && !d.is_resolved).count());
let unread_memos = state.handoff_memos.read_with(|memos| memos.iter().filter(|m| m.namespace == req.namespace).count());
let active_milestones = state.milestones.read_with(|milestones| milestones.iter().filter(|m| m.namespace == req.namespace && m.status != "done").count());
let remaining_checklists = state.pr_checklists.read_with(|checklists| checklists.iter().filter(|c| c.namespace == req.namespace).count());"""
if old_health in content:
content = content.replace(old_health, new_health)
print('Replaced health counters')
else:
print('Could not find old_health')
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
fix_meta()
-44
View File
@@ -1,44 +0,0 @@
import os
import re
def fix_nvim_msgid():
filepath = 'nvim-core/src/lib.rs'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Add atomic import and static var if not exists
if 'static NEXT_MSGID' not in content:
atomic_def = "use std::sync::atomic::{AtomicU64, Ordering};\nstatic NEXT_MSGID: AtomicU64 = AtomicU64::new(1);\n"
# Find NVIM_CONN
conn_idx = content.find('static NVIM_CONN')
if conn_idx != -1:
content = content[:conn_idx] + atomic_def + content[conn_idx:]
# Replace all hardcoded msgid
# e.g., RmpValue::Integer(1.into()), // msgid
# with: let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst); ... RmpValue::Integer(msgid.into()),
# We need to insert `let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);` before `let req = RmpValue::Array(vec![`
# We will use regex to find the blocks
funcs = [
('send_nvim_command', '1'),
('get_nvim_active_buffer', '2'),
('get_nvim_cursor', '3'),
('get_nvim_visual_selection', '4'),
('set_nvim_diagnostics', '5'),
('execute_nvim_lua', '6'),
]
for func, old_id in funcs:
pattern = rf"let req = RmpValue::Array\(vec!\[\s*RmpValue::Integer\(0\.into\(\)\),\s*RmpValue::Integer\({old_id}\.into\(\)\), // msgid"
replacement = f"let msgid = NEXT_MSGID.fetch_add(1, Ordering::SeqCst);\n let req = RmpValue::Array(vec![\n RmpValue::Integer(0.into()),\n RmpValue::Integer(msgid.into()), // msgid"
content = re.sub(pattern, replacement, content)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
print('Fixed msgid allocations')
fix_nvim_msgid()
-40
View File
@@ -1,40 +0,0 @@
import os
def fix_notes():
filepath = 'server/src/handlers_v2/notes.rs'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
old_standup = """ let tasks = state
.tasks
.read()
.into_iter()
.filter(|t| t.updated_at >= cutoff)
.collect::<Vec<_>>();
let changes = state
.ledger
.read()
.into_iter()
.filter(|c| c.timestamp >= cutoff)
.collect::<Vec<_>>();
let summaries = state
.session_summaries
.read()
.into_iter()
.filter(|s| s.namespace == req.namespace && s.timestamp >= cutoff)
.collect::<Vec<_>>();"""
new_standup = """ let tasks = state.tasks.read_with(|items| items.iter().filter(|t| t.updated_at >= cutoff).cloned().collect::<Vec<_>>());
let changes = state.ledger.read_with(|items| items.iter().filter(|c| c.timestamp >= cutoff).cloned().collect::<Vec<_>>());
let summaries = state.session_summaries.read_with(|items| items.iter().filter(|s| s.namespace == req.namespace && s.timestamp >= cutoff).cloned().collect::<Vec<_>>());"""
if old_standup in content:
content = content.replace(old_standup, new_standup)
print('Replaced standup in notes.rs')
else:
print('Could not find old_standup in notes.rs')
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
fix_notes()
-66
View File
@@ -1,66 +0,0 @@
import os
def fix_stub_leaks():
filepath = 'stub/src/main.rs'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# 1. Fix connect_async to handle shutdown and timeout
old_connect = """ match tokio_tungstenite::connect_async(request).await {"""
new_connect = """ let connect_result = tokio::select! {
_ = shutdown_rx.recv() => {
tracing::info!("Shutdown received during connect");
break;
}
res = tokio::time::timeout(
tokio::time::Duration::from_secs(5),
tokio_tungstenite::connect_async(request)
) => res,
};
match connect_result {
Ok(Ok((ws_stream, _))) => {"""
if old_connect in content:
content = content.replace(old_connect, new_connect)
print("Replaced connect_async")
# Fix Err block to match the new match structure
old_err = """ Err(e) => {
tracing::error!("Failed to connect via WSS: {}", e);
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}"""
new_err = """ Ok(Err(e)) => {
tracing::error!("Failed to connect via WSS: {}", e);
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
Err(_) => {
tracing::error!("Connection attempt timed out");
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}"""
if old_err in content:
content = content.replace(old_err, new_err)
print("Replaced Err branch")
# 2. Fix the break in send_task that exits the stub instead of reconnecting
old_select_send = """ _ = &mut send_task => {
tracing::error!("Send task exited");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
recv_task.abort();
break;
}"""
new_select_send = """ _ = &mut send_task => {
tracing::error!("Send task exited");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
recv_task.abort();
}"""
if old_select_send in content:
content = content.replace(old_select_send, new_select_send)
print("Replaced select send_task")
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
fix_stub_leaks()
-197
View File
@@ -1,197 +0,0 @@
use crate::router::McpTool;
use crate::state::MemoryState;
use std::sync::Arc;
pub struct MemoryHandler {
pub state: Arc<MemoryState>,
pub tools: std::collections::HashMap<String, Box<dyn McpTool>>,
}
impl MemoryHandler {
pub fn new(state: Arc<MemoryState>) -> Self {
let mut tools: std::collections::HashMap<String, Box<dyn McpTool>> =
std::collections::HashMap::new();
macro_rules! register {
($module:ident::$handler:ident) => {
let h = crate::handlers_v2::$module::$handler;
tools.insert(h.name().to_string(), Box::new(h));
};
}
register!(graph::QueryGraphPathHandler);
register!(graph::CreateEntitiesHandler);
register!(graph::CreateRelationsHandler);
register!(graph::AddObservationsHandler);
register!(graph::DeleteEntitiesHandler);
register!(graph::DeleteObservationsHandler);
register!(graph::DeleteRelationsHandler);
register!(graph::ReadGraphHandler);
register!(graph::SearchNodesHandler);
register!(graph::OpenNodesHandler);
register!(graph::VisualizeGraphHandler);
register!(graph::CondenseEntityHandler);
register!(graph::MergeEntitiesHandler);
register!(graph::FindOrphansHandler);
register!(tasks::AddTaskHandler);
register!(tasks::DeleteTaskHandler);
register!(tasks::UpdateTaskStatusHandler);
register!(tasks::ListActiveTasksHandler);
register!(tasks::SetAcceptanceCriteriaHandler);
register!(tasks::VerifyAcceptanceCriteriaHandler);
register!(tasks::AddMilestoneHandler);
register!(tasks::UpdateMilestoneHandler);
register!(tasks::ListMilestonesHandler);
register!(notes::AddStickyNoteHandler);
register!(notes::ReadStickyNotesHandler);
register!(notes::DeleteStickyNoteHandler);
register!(notes::ClearStickyNotesHandler);
register!(notes::LeaveHandoffMemoHandler);
register!(notes::ReadHandoffMemosHandler);
register!(notes::ClearHandoffMemosHandler);
register!(notes::AddSessionSummaryHandler);
register!(notes::GenerateStandupReportHandler);
register!(meta::LogDecisionHandler);
register!(meta::QueryDecisionsHandler);
register!(meta::LogErrorFixHandler);
register!(meta::SearchErrorFixesHandler);
register!(meta::LogCodeChangeHandler);
register!(meta::QueryRecentChangesHandler);
register!(meta::LearnPreferenceHandler);
register!(meta::ReadPreferencesHandler);
register!(meta::LogTechDebtHandler);
register!(meta::ResolveTechDebtHandler);
register!(meta::ListTechDebtHandler);
register!(meta::OmniSearchHandler);
register!(meta::GetProjectHealthHandler);
register!(env::UpdateEnvFingerprintHandler);
register!(env::ReadEnvFingerprintHandler);
register!(env::LogEnvRequirementHandler);
register!(env::RegisterEnvironmentHandler);
register!(env::GetEnvironmentDetailsHandler);
register!(workspaces::PinFileHandler);
register!(workspaces::UnpinFileHandler);
register!(workspaces::ListPinnedFilesHandler);
register!(workspaces::StoreSnippetHandler);
register!(workspaces::SearchSnippetsHandler);
register!(workspaces::DeleteSnippetHandler);
register!(workspaces::SaveContextWorkspaceHandler);
register!(workspaces::LoadContextWorkspaceHandler);
register!(workspaces::ListContextWorkspacesHandler);
register!(workspaces::AddPrChecklistItemHandler);
register!(workspaces::GetPrChecklistHandler);
register!(workspaces::ClearPrChecklistHandler);
Self { state, tools }
}
pub async fn handle_request(&self, req: serde_json::Value) -> Option<serde_json::Value> {
let id = req.get("id").cloned().unwrap_or(serde_json::Value::Null);
let id_clone = id.clone();
let method = req.get("method").and_then(|m| m.as_str()).unwrap_or("");
match method {
"server/discover" => {
let payload = serde_json::json!({
"resultType": "complete",
"ttlMs": 0,
"cacheScope": "public",
"supportedVersions": ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"],
"capabilities": {
"tools": serde_json::json!({})
},
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "gemini-mcp-memory",
"version": "3.0.0"
}
}
});
Some(crate::mcp::success(id, payload))
}
"initialize" => {
let init = rmcp::model::InitializeResult::new(
rmcp::model::ServerCapabilities::builder()
.enable_tools()
.build(),
)
.with_server_info(rmcp::model::Implementation::new(
"gemini-mcp-memory",
"3.0.0",
));
Some(crate::mcp::success(
id,
serde_json::to_value(&init).unwrap_or_default(),
))
}
"notifications/initialized" => None,
"tools/list" => {
let mut tools: Vec<serde_json::Value> =
self.tools.values().map(|t| t.schema()).collect();
tools.sort_by_key(|t| {
t.get("name")
.and_then(|n| n.as_str())
.unwrap_or("")
.to_string()
});
Some(crate::mcp::success(
id,
serde_json::json!({ "tools": tools }),
))
}
"tools/call" => {
let params = req.get("params").unwrap_or(&serde_json::Value::Null);
let name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
let args = params
.get("arguments")
.cloned()
.unwrap_or(serde_json::Value::Object(Default::default()));
self.state
.broadcast_activity(&format!("Agent executed tool: {}", name));
let result: Result<String, String> = if let Some(tool) = self.tools.get(name) {
tool.execute(args, self.state.clone()).await
} else {
Err(format!("Unknown tool: {}", name))
};
match result {
Ok(text) => {
let payload = serde_json::json!({
"content": [{"type": "text", "text": text}],
"isError": false
});
Some(crate::mcp::success(id_clone, payload))
}
Err(e) => {
tracing::error!("Tool {} failed: {}", name, e);
let payload = serde_json::json!({
"content": [{"type": "text", "text": e}],
"isError": true
});
Some(crate::mcp::success(id_clone, payload))
}
}
}
m if m.starts_with("notifications/") => None,
"ping" => Some(crate::mcp::success(id, serde_json::json!({}))),
_ => {
if id.is_null() {
None
} else {
Some(crate::mcp::error(
id,
-32601,
&format!("Method {} not found", method),
))
}
}
}
}
}
-173
View File
@@ -1,173 +0,0 @@
import os
import re
GROUPS = {
"graph": [
"query_graph_path", "create_entities", "create_relations", "add_observations",
"delete_entities", "delete_observations", "delete_relations", "read_graph",
"search_nodes", "open_nodes", "visualize_graph", "condense_entity",
"merge_entities", "find_orphans"
],
"tasks": [
"add_task", "delete_task", "update_task_status", "list_active_tasks",
"set_acceptance_criteria", "verify_acceptance_criteria",
"add_milestone", "update_milestone", "list_milestones"
],
"notes": [
"add_sticky_note", "read_sticky_notes", "delete_sticky_note", "clear_sticky_notes",
"leave_handoff_memo", "read_handoff_memos", "clear_handoff_memos",
"add_session_summary", "generate_standup_report"
],
"meta": [
"log_decision", "query_decisions", "log_error_fix", "search_error_fixes",
"log_code_change", "query_recent_changes", "learn_preference", "read_preferences",
"log_tech_debt", "resolve_tech_debt", "list_tech_debt", "omni_search", "get_project_health"
],
"env": [
"update_env_fingerprint", "read_env_fingerprint", "log_env_requirement",
"register_environment", "get_environment_details"
],
"workspaces": [
"pin_file", "unpin_file", "list_pinned_files", "store_snippet", "search_snippets",
"delete_snippet", "save_context_workspace", "load_context_workspace",
"list_context_workspaces", "add_pr_checklist_item", "get_pr_checklist", "clear_pr_checklist"
]
}
def to_camel_case(snake_str):
components = snake_str.split('_')
return "".join(x.title() for x in components)
def parse_rust_match(file_path):
with open(file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
start_idx = -1
for i, line in enumerate(lines):
if "let result: Result<String, String> = match name {" in line:
start_idx = i
break
if start_idx == -1:
return {}
brace_depth = 1
i = start_idx + 1
tools = {}
current_tool = None
current_body = []
while i < len(lines):
line = lines[i]
if brace_depth == 1 and "=>" in line and '"' in line:
parts = line.strip().split('"')
if len(parts) >= 3:
tool_name = parts[1]
current_tool = tool_name
current_body = []
# Don't add the "name" => { line
if current_tool is not None and not (brace_depth == 1 and "=>" in line and '"' in line):
# check if this line closes the block
next_depth = brace_depth + line.count('{') - line.count('}')
if next_depth == 1 and current_tool is not None:
# This is the closing brace
tools[current_tool] = "".join(current_body)
current_tool = None
else:
current_body.append(line)
brace_depth += line.count('{')
brace_depth -= line.count('}')
if brace_depth == 0:
break
i += 1
return tools
def transform_body(body):
# Transform parse_tool!
body = re.sub(
r'let req = parse_tool!\(args, id, ([^)]+)\);',
r'let req: \1 = serde_json::from_value(args).map_err(|e| e.to_string())?;',
body
)
# Transform handle_list_with_namespace!
def repl_handle_list(m):
store = m.group(1)
tool_type = m.group(2)
return f"""
let req: {tool_type} = serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut items = state.{store}.read();
if let Some(ns) = req.namespace {{
items.retain(|i| i.namespace == ns);
}}
let data = serde_json::to_string(&items).unwrap_or_default();
return Ok(data.to_string());
"""
body = re.sub(
r'return handle_list_with_namespace!\(self, ([^,]+), ([^,]+), args, id\);',
repl_handle_list,
body
)
# Replace self.state with state
body = body.replace("self.state.", "state.")
return body
tools = parse_rust_match("server/src/handlers.rs")
for group, tool_names in GROUPS.items():
file_path = f"server/src/handlers_v2/{group}.rs"
with open(file_path, "w", encoding="utf-8") as f:
f.write("use crate::router::McpTool;\n")
f.write("use crate::state::MemoryState;\n")
f.write("use crate::tools::*;\n")
f.write("use async_trait::async_trait;\n")
f.write("use serde_json::Value;\n")
f.write("use std::sync::Arc;\n")
f.write("use std::time::{SystemTime, UNIX_EPOCH};\n\n")
for name in tool_names:
if name not in tools:
continue
body = tools[name]
# special case for query_graph_path which we already wrote properly?
# actually we will just overwrite it with the transformed body
body = transform_body(body)
struct_name = to_camel_case(name) + "Handler"
tool_type = to_camel_case(name) + "Tool"
f.write(f"pub struct {struct_name};\n\n")
f.write(f"#[async_trait]\n")
f.write(f"impl McpTool for {struct_name} {{\n")
f.write(f" fn name(&self) -> &'static str {{\n")
f.write(f' "{name}"\n')
f.write(f" }}\n\n")
f.write(f" fn schema(&self) -> Value {{\n")
# For schema description we can just put a generic one or extract it.
# I will use a generic one for now, or you can extract it from tools/list.
f.write(f' crate::mcp::tool_def::<{tool_type}>(\n')
f.write(f' "{name}",\n')
f.write(f' "Execute {name}",\n')
f.write(f' )\n')
f.write(f" }}\n\n")
f.write(f" async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {{\n")
f.write(body)
f.write(f" }}\n")
f.write(f"}}\n\n")
print("Generated handlers_v2 modules")
# generate mod.rs
with open("server/src/handlers_v2/mod.rs", "w", encoding="utf-8") as f:
for group in GROUPS.keys():
f.write(f"pub mod {group};\n")
-11
View File
@@ -1,11 +0,0 @@
use std::fs;
use std::io::Write;
fn main() {
let content = fs::read_to_string("server/src/handlers.rs").unwrap();
println!("Read {} bytes", content.len());
// Find the match name { block
let match_start = content.find("match name {").unwrap();
// naive extraction
println!("Found match block at {}", match_start);
}
-82
View File
@@ -1,82 +0,0 @@
import re
import os
with open("server/src/handlers.rs", "r", encoding="utf-8") as f:
lines = f.readlines()
list_start = -1
for i, line in enumerate(lines):
if '"tools/list" => {' in line:
list_start = i
break
# Find end of tools/call
call_start = -1
for i in range(list_start, len(lines)):
if '"tools/call" => {' in line:
call_start = i
break
# Find end of tools/call
# Match brace depth from call_start
brace_depth = 1
call_end = -1
for i in range(call_start + 1, len(lines)):
brace_depth += lines[i].count('{')
brace_depth -= lines[i].count('}')
if brace_depth == 0:
call_end = i
break
# replacement block
replacement = """ "tools/list" => {
let mut tools: Vec<serde_json::Value> = self.tools.values().map(|t| t.schema()).collect();
tools.sort_by_key(|t| t.get("name").and_then(|n| n.as_str()).unwrap_or("").to_string());
Some(crate::mcp::success(
id,
serde_json::json!({ "tools": tools }),
))
}
"tools/call" => {
let params = req.get("params").unwrap_or(&serde_json::Value::Null);
let name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
let args = params
.get("arguments")
.cloned()
.unwrap_or(serde_json::Value::Object(Default::default()));
self.state
.broadcast_activity(&format!("Agent executed tool: {}", name));
let result: Result<String, String> = if let Some(tool) = self.tools.get(name) {
tool.execute(args, self.state.clone()).await
} else {
Err(format!("Unknown tool: {}", name))
};
match result {
Ok(text) => {
let payload = serde_json::json!({
"content": [{"type": "text", "text": text}],
"isError": false
});
Some(crate::mcp::success(id_clone, payload))
}
Err(e) => {
tracing::error!("Tool {} failed: {}", name, e);
let payload = serde_json::json!({
"content": [{"type": "text", "text": e}],
"isError": true
});
Some(crate::mcp::success(id_clone, payload))
}
}
}
"""
new_lines = lines[:list_start] + [replacement] + lines[call_end+1:]
with open("server/src/handlers.rs", "w", encoding="utf-8") as f:
f.writelines(new_lines)
print("tools/list and tools/call replaced.")
-106
View File
@@ -1,106 +0,0 @@
import re
import os
with open("server/src/handlers.rs", "r", encoding="utf-8") as f:
content = f.read()
# Replace MemoryHandler struct
struct_pattern = r'pub struct MemoryHandler \{\s*pub state: Arc<MemoryState>,\s*\}'
new_struct = """use crate::router::McpTool;
pub struct MemoryHandler {
pub state: Arc<MemoryState>,
pub tools: std::collections::HashMap<String, Box<dyn McpTool>>,
}
impl MemoryHandler {
pub fn new(state: Arc<MemoryState>) -> Self {
let mut tools: std::collections::HashMap<String, Box<dyn McpTool>> = std::collections::HashMap::new();
macro_rules! register {
($module:ident::$handler:ident) => {
let h = crate::handlers_v2::$module::$handler;
tools.insert(h.name().to_string(), Box::new(h));
};
}
register!(graph::QueryGraphPathHandler);
register!(graph::CreateEntitiesHandler);
register!(graph::CreateRelationsHandler);
register!(graph::AddObservationsHandler);
register!(graph::DeleteEntitiesHandler);
register!(graph::DeleteObservationsHandler);
register!(graph::DeleteRelationsHandler);
register!(graph::ReadGraphHandler);
register!(graph::SearchNodesHandler);
register!(graph::OpenNodesHandler);
register!(graph::VisualizeGraphHandler);
register!(graph::CondenseEntityHandler);
register!(graph::MergeEntitiesHandler);
register!(graph::FindOrphansHandler);
register!(tasks::AddTaskHandler);
register!(tasks::DeleteTaskHandler);
register!(tasks::UpdateTaskStatusHandler);
register!(tasks::ListActiveTasksHandler);
register!(tasks::SetAcceptanceCriteriaHandler);
register!(tasks::VerifyAcceptanceCriteriaHandler);
register!(tasks::AddMilestoneHandler);
register!(tasks::UpdateMilestoneHandler);
register!(tasks::ListMilestonesHandler);
register!(notes::AddStickyNoteHandler);
register!(notes::ReadStickyNotesHandler);
register!(notes::DeleteStickyNoteHandler);
register!(notes::ClearStickyNotesHandler);
register!(notes::LeaveHandoffMemoHandler);
register!(notes::ReadHandoffMemosHandler);
register!(notes::ClearHandoffMemosHandler);
register!(notes::AddSessionSummaryHandler);
register!(notes::GenerateStandupReportHandler);
register!(meta::LogDecisionHandler);
register!(meta::QueryDecisionsHandler);
register!(meta::LogErrorFixHandler);
register!(meta::SearchErrorFixesHandler);
register!(meta::LogCodeChangeHandler);
register!(meta::QueryRecentChangesHandler);
register!(meta::LearnPreferenceHandler);
register!(meta::ReadPreferencesHandler);
register!(meta::LogTechDebtHandler);
register!(meta::ResolveTechDebtHandler);
register!(meta::ListTechDebtHandler);
register!(meta::OmniSearchHandler);
register!(meta::GetProjectHealthHandler);
register!(env::UpdateEnvFingerprintHandler);
register!(env::ReadEnvFingerprintHandler);
register!(env::LogEnvRequirementHandler);
register!(env::RegisterEnvironmentHandler);
register!(env::GetEnvironmentDetailsHandler);
register!(workspaces::PinFileHandler);
register!(workspaces::UnpinFileHandler);
register!(workspaces::ListPinnedFilesHandler);
register!(workspaces::StoreSnippetHandler);
register!(workspaces::SearchSnippetsHandler);
register!(workspaces::DeleteSnippetHandler);
register!(workspaces::SaveContextWorkspaceHandler);
register!(workspaces::LoadContextWorkspaceHandler);
register!(workspaces::ListContextWorkspacesHandler);
register!(workspaces::AddPrChecklistItemHandler);
register!(workspaces::GetPrChecklistHandler);
register!(workspaces::ClearPrChecklistHandler);
Self { state, tools }
}
"""
content = re.sub(struct_pattern, new_struct, content)
content = content.replace("impl MemoryHandler {\n pub async fn handle_request", " pub async fn handle_request")
with open("server/src/handlers.rs", "w", encoding="utf-8") as f:
f.write(content)
print("MemoryHandler struct updated.")
-45
View File
@@ -1,45 +0,0 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Create new entities in the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct CreateEntitiesTool {
pub entities: Vec<crate::models::Entity>,
}
/// Create new relations between entities in the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct CreateRelationsTool {
pub relations: Vec<crate::models::Relation>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ObservationInput {
#[serde(rename = "entityName")]
pub entity_name: String,
pub contents: Vec<String>,
}
/// Add new observations to existing entities in the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct AddObservationsTool {
pub observations: Vec<ObservationInput>,
}
/// Define a strict checklist of acceptance criteria for a given task or feature before starting work.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SetAcceptanceCriteriaTool {
/// The name or title of the task/feature being worked on.
pub task_title: String,
/// An array of specific, undeniable conditions that must be proven before claiming success.
pub criteria: Vec<String>,
}
/// Mark a previously defined acceptance criteria as met by providing cryptographic-like proof (logs, output, diffs).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct VerifyAcceptanceCriteriaTool {
/// The exact text of the criteria being met.
pub criteria: String,
/// The undeniable proof (e.g., test logs, terminal output, git diff) that proves the criteria is met.
pub proof: String,
}
-37
View File
@@ -1,37 +0,0 @@
import json
import os
eager_tools = [
"log_code_change",
"log_error_fix",
"log_tech_debt",
"omni_search",
"create_entities",
"learn_preference",
"list_active_tasks",
"add_task",
"set_acceptance_criteria",
"verify_acceptance_criteria",
"update_task_status",
"list_pinned_files",
"pin_file",
"unpin_file",
"get_project_health",
"list_tech_debt",
"resolve_tech_debt"
]
# Update Windows config
win_config_path = r'C:\Users\reazul.ashraf\.gemini\config\mcp_config.json'
with open(win_config_path, 'r', encoding='utf-8') as f:
win_data = json.load(f)
win_data['mcpServers']['mcp-memory']['eagerTools'] = eager_tools
if 'args' in win_data['mcpServers']['mcp-memory']:
# Ensure args are empty as we removed --target earlier
win_data['mcpServers']['mcp-memory']['args'] = []
with open(win_config_path, 'w', encoding='utf-8') as f:
json.dump(win_data, f, indent=2)
print("Updated Windows config")
-27
View File
@@ -1,27 +0,0 @@
local buf = vim.fn.bufnr('server/src/search.rs')
if buf == -1 then
vim.cmd('e server/src/search.rs')
buf = vim.api.nvim_get_current_buf()
end
vim.api.nvim_buf_set_lines(buf, 216, 222, false, {
' &self,',
' entities: &[Entity],',
' tasks: &[Task],',
' snippets: &[Snippet],',
' adrs: &[Adr],'
})
-- We need to change the loop variables inside the task from values to clones if they are passed as slices
-- Actually we can just clone the slice data before moving it into spawn_blocking
vim.api.nvim_buf_set_lines(buf, 222, 223, false, {
' ) -> tokio::task::JoinHandle<tantivy::Result<()>> {',
' let entities = entities.to_vec();',
' let tasks = tasks.to_vec();',
' let snippets = snippets.to_vec();',
' let adrs = adrs.to_vec();'
})
vim.cmd('write')
require('notify')('Updated search index_batch signature', 'info', { title = 'Antigravity' })
return 'ok'
-28
View File
@@ -1,28 +0,0 @@
local buf = vim.fn.bufnr('server/src/search.rs')
if buf == -1 then
vim.cmd('e server/src/search.rs')
buf = vim.api.nvim_get_current_buf()
end
vim.api.nvim_buf_set_lines(buf, 216, 222, false, {
' &self,',
' entities: &[Entity],',
' tasks: &[Task],',
' snippets: &[Snippet],',
' adrs: &[Adr],'
})
-- We need to change the loop variables inside the task from values to clones if they are passed as slices
-- Actually we can just clone the slice data before moving it into spawn_blocking
vim.api.nvim_buf_set_lines(buf, 222, 223, false, {
' ) -> tokio::task::JoinHandle<tantivy::Result<()>> {',
' let entities = entities.to_vec();',
' let tasks = tasks.to_vec();',
' let snippets = snippets.to_vec();',
' let adrs = adrs.to_vec();'
})
vim.cmd('write')
require('notify')('Updated search index_batch signature', 'info', { title = 'Antigravity' })
return 'ok'
-2
View File
@@ -1,2 +0,0 @@
local code = vim.fn.readfile('temp.lua')
return table.concat(code, '\n')
-6
View File
@@ -1,6 +0,0 @@
local buf = vim.fn.bufnr('server/src/handlers_v2/graph.rs')
if buf == -1 then
vim.cmd('e server/src/handlers_v2/graph.rs')
buf = vim.api.nvim_get_current_buf()
end
return vim.api.nvim_buf_get_name(buf)
-25
View File
@@ -1,25 +0,0 @@
--- server/src/main.rs
+++ server/src/main.rs
@@ -225,6 +225,20 @@
next_id: AtomicUsize::new(1),
});
+ let app_state_clone = Arc::clone(&app_state);
+ let mut rx = state.activity_tx.subscribe();
+ tokio::spawn(async move {
+ while let Ok(msg) = rx.recv().await {
+ let senders: Vec<_> = app_state_clone
+ .clients
+ .read()
+ .unwrap_or_else(|e| e.into_inner())
+ .values()
+ .cloned()
+ .collect();
+ for client_tx in senders {
+ let _ = client_tx.try_send(msg.clone());
+ }
+ }
+ });
+
let app = Router::new()
.route(