Compare commits

..
2 Commits
28 changed files with 1356 additions and 85 deletions

No files matched your search

Generated
+264 -1
View File
@@ -126,6 +126,26 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
[[package]]
name = "arboard"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
dependencies = [
"clipboard-win",
"image",
"log",
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation",
"parking_lot",
"percent-encoding",
"windows-sys 0.59.0",
"x11rb",
]
[[package]]
name = "arc-swap"
version = "1.9.2"
@@ -523,6 +543,15 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
[[package]]
name = "clipboard-win"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
dependencies = [
"error-code",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@@ -935,6 +964,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags",
"objc2",
]
[[package]]
name = "displaydoc"
version = "0.2.7"
@@ -1040,6 +1079,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "error-code"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7"
[[package]]
name = "esaxx-rs"
version = "0.1.10"
@@ -1288,6 +1333,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "gethostname"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix",
"windows-link",
]
[[package]]
name = "getrandom"
version = "0.2.17"
@@ -1333,6 +1388,21 @@ dependencies = [
"weezl",
]
[[package]]
name = "git2"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"openssl-probe 0.1.6",
"openssl-sys",
"url",
]
[[package]]
name = "h2"
version = "0.4.19"
@@ -1860,6 +1930,20 @@ dependencies = [
"cc",
]
[[package]]
name = "libgit2-sys"
version = "0.17.0+1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
dependencies = [
"cc",
"libc",
"libssh2-sys",
"libz-sys",
"openssl-sys",
"pkg-config",
]
[[package]]
name = "libm"
version = "0.2.16"
@@ -1875,6 +1959,32 @@ dependencies = [
"libc",
]
[[package]]
name = "libssh2-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f5eb74291e8691cab524a01274a1b1e7742b1a94f29d8b101d8aadc8372c1cd"
dependencies = [
"cc",
"libc",
"libz-sys",
"openssl-sys",
"pkg-config",
"vcpkg",
]
[[package]]
name = "libz-sys"
version = "1.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@@ -1993,13 +2103,18 @@ dependencies = [
name = "mcp-memory-server"
version = "0.1.0"
dependencies = [
"arboard",
"async-trait",
"axum",
"base64 0.23.1",
"chrono",
"clap",
"clipboard-win",
"dirs 7.0.0",
"fastembed",
"futures-util",
"git2",
"image",
"redb",
"reqwest 0.13.5",
"rmcp",
@@ -2014,6 +2129,10 @@ dependencies = [
"tracing",
"tracing-appender",
"tracing-subscriber",
"tree-sitter",
"tree-sitter-python",
"tree-sitter-rust",
"tree-sitter-typescript",
"uuid",
"winres",
]
@@ -2173,7 +2292,7 @@ dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-probe 0.2.1",
"openssl-sys",
"schannel",
"security-framework",
@@ -2329,6 +2448,79 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-app-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags",
"objc2",
"objc2-core-graphics",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -2394,6 +2586,12 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-probe"
version = "0.2.1"
@@ -3999,6 +4197,54 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "tree-sitter"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0203df02a3b6dd63575cc1d6e609edc2181c9a11867a271b25cfd2abff3ec5ca"
dependencies = [
"cc",
"regex",
"regex-syntax",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-language"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081"
[[package]]
name = "tree-sitter-python"
version = "0.23.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-rust"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8ccb3e3a3495c8a943f6c3fd24c3804c471fd7f4f16087623c7fa4c0068e8a"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-typescript"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "try-lock"
version = "0.2.5"
@@ -4584,6 +4830,23 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]]
name = "x11rb"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
dependencies = [
"gethostname",
"rustix",
"x11rb-protocol",
]
[[package]]
name = "x11rb-protocol"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
[[package]]
name = "y4m"
version = "0.8.0"
+43
View File
@@ -0,0 +1,43 @@
# Effective Discourse: LLM Prompting Guide for MCP Memory
To get the most out of the Antigravity MCP Memory server and its advanced developer tools, use specific phrases that clearly state your intent. This guides the LLM to use the most efficient tools, reducing token consumption, speeding up time-to-resolve (T2R), and avoiding brute-force file reading.
## 1. Codebase Exploration & Token Efficiency
When entering a new file, avoid having the LLM read the entire contents blindly.
* **Don't say:** "Read server.rs and tell me what it does." *(Consumes massive tokens)*
* **Do say:** "Extract the AST skeleton of server.rs to understand its structure first."
* **Tool Triggered:** ␍ead_file_skeleton
## 2. Debugging & Log Parsing
Stop copy-pasting giant walls of logs into the chat interface.
* **Don't say:** "Here is the error: [paste 500 lines of logs]"
* **Do say:** "The daemon crashed. Fetch the recent logs from daemon.log." or "Watch the process logs for server.log."
* **Tool Triggered:** get_recent_logs, watch_process_logs
## 3. Git & Context Handoff
When you've been working independently and need to loop the LLM back in on your current state.
* **Don't say:** "I changed some files, here are the diffs..."
* **Do say:** "Get the active git worktree context to review my uncommitted changes before we continue."
* **Tool Triggered:** get_active_worktree_context
## 4. Clipboard Watch Mode (Research & Triage)
When you are doing intense debugging across StackOverflow, logs, and docs, use the clipboard watcher to auto-ingest your breadcrumbs.
* **Action:** Ask the LLM to turn it on: "Enable clipboard watch mode."
* **Do say:** "I'm going to reproduce the bug and copy some stack traces and IDs. Give me a minute, then read my latest sticky notes to catch up."
* **Tool Triggered:** oggle_clipboard_watch_mode, followed by internal Sticky Note reads.
## 5. Neovim Ghost Text (Live Previews)
Keep your workflow entirely within your editor rather than copy-pasting code blocks from the chat.
* **Don't say:** "Write the updated function here so I can copy-paste it."
* **Do say:** "Push this refactor to my active Neovim buffer as ghost text so I can review it in-line."
* **Tool Triggered:**
vim_set_preview
## 6. Graph & Memory Management
Actively instruct the LLM to maintain its own memory constraints and organize data.
* **Do say:** "Log this architectural decision in the knowledge graph."
* **Do say:** "Add a sticky note with the test database credentials for this session."
* **Do say:** "Create a milestone for the 'Rich Clipboard' feature and break it down into active tasks."
* **Tools Triggered:** create_entities, log_decision, dd_sticky_note, dd_milestone, dd_task
By phrasing requests around *actions* rather than *information retrieval*, the LLM is primed to leverage the rich MCP toolset built into the Antigravity Memory Server.
+56 -20
View File
@@ -9,28 +9,47 @@ To eliminate heavy Cross-OS I/O penalties when using WSL and Windows simultaneou
* **The Stub (mcp-memory-stub)**: An ultra-lightweight proxy binary. WSL agy instances run this native Linux stub, which transparently pipes stdio JSON-RPC traffic over the network to the Windows HTTP server (http://127.0.0.1:3000), completely bypassing WSL NTFS mounts. It features full MPSC queue buffering and a WebSocket reconnect handshake (notifications/tools/list_changed) so that tools automatically refresh seamlessly without disconnecting the CLI if the background server restarts.
`
## Quick Start & Usage
`
### 1. Windows Installation (The Server & Stub)
Compile the main daemon and lightweight stub natively for Windows:
```powershell
cargo build --release
Copy-Item target\release\mcp-memory-server.exe C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe
Copy-Item target\release\mcp-memory-stub.exe C:\Users\reazul.ashraf\.local\bin\mcp-memory-stub.exe
```
`
**Step 1:** To bypass Antigravity's lazy-loading and ensure the server is instantly available for WSL, configure your PowerShell profile to auto-start the background server when you open a terminal:
```powershell
# Add this to your PowerShell profile:
if (-not (Get-Process mcp-memory-server -ErrorAction SilentlyContinue)) { Start-Process -FilePath "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe" -WindowStyle Hidden -ErrorAction SilentlyContinue }
```
**Shutting Down & Managing:** If you need to stop, start, or restart the background daemon (e.g., to replace the executable after a recompile), you MUST NEVER use brute-force OS kill commands (e.g., Stop-Process, pkill, or kill). Instead, you MUST ALWAYS use the built-in graceful shutdown mechanisms, which are wrapped neatly in the `justfile`:
```powershell
### 1. Windows Installation (The Server & Stub)
To enforce strict process safety and eliminate file locks on Windows, the build, deploy, and execution lifecycle are entirely decoupled in the justfile.
**The Golden Rule:** You must gracefully stop the server before deploying a new binary. Deploy recipes only copy files; they do not kill processes.
The easiest way to manage this end-to-end (Stop -> Build -> Deploy -> Start) is using the chaining commands:
``powershell
# For the main server:
just all-server-win
# For the lightweight stubs/nvim servers:
just all-stub-win
just all-nvim-win
``
If you want to perform these steps manually, you must follow this exact order to avoid NTFS locks:
``powershell
just stop # 1. Gracefully shut down the background server (TCP 3000)
just build-win # 2. Compile the binaries
just deploy-win # 3. Move the executables into ~/.local/bin/
just start # 4. Spawns the daemon completely detached in the background
just verify # 5. Hits the /ping endpoint to ensure liveness
``
**Step 1:** To bypass Antigravity's lazy-loading and ensure the server is instantly available for WSL, configure your PowerShell profile to auto-start the background server when you open a terminal:
``powershell
# Add this to your PowerShell profile (ensuring it only fires on initial load, not background threads):
if ($host.Name -eq 'ConsoleHost' -and -not (Get-Process mcp-memory-server -ErrorAction SilentlyContinue)) {
Start-Process -FilePath "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe" -WindowStyle Hidden -ErrorAction SilentlyContinue
}
``
**Shutting Down & Managing:** If you need to stop, start, or restart the background daemon, NEVER use brute-force OS kill commands (e.g., Stop-Process, pkill). ALWAYS use the justfile wrappers, which trigger a graceful /shutdown over HTTP:
``powershell
just start
just stop
just restart
```
Alternatively, you can gracefully shut down the server by invoking the executable with the `--exit` flag (`mcp-memory-server.exe --exit`) or hitting the HTTP endpoint (`POST http://127.0.0.1:3000/shutdown`).
``
Alternatively, you can gracefully shut down the server by invoking the executable with the --exit flag (mcp-memory-server.exe --exit) or hitting the HTTP endpoint (POST http://127.0.0.1:3000/shutdown).
`
**Step 2:** Update your Windows ~/.gemini/config/mcp_config.json to point the CLI to the ultra-lightweight stub (since the server is already running in the background):
`json
@@ -77,15 +96,23 @@ This queries the daemon (via HTTP) to confirm if pre-push validation (like runni
The server hosts a live, real-time SPA dashboard called the **Brain Monitor**.
To view the dashboard, simply navigate to the root endpoint in your browser while the server is running:
`http://127.0.0.1:3000/`
http://127.0.0.1:3000/
### Dashboard Features:
* **Interactive Knowledge Graph:** A full physics-simulated network graph with node-type coloring, a drag-to-pan canvas, and an interactive Inspector Panel. Click any node to instantly view its stored observations.
* **Actionable Kanban Board:** Visually track all active agent Tasks. You can click 'Complete' directly from the UI to trigger a `POST /api/tasks/{id}/complete` REST call back to the daemon without needing the CLI.
* **Actionable Kanban Board:** Visually track all active agent Tasks. You can click 'Complete' directly from the UI to trigger a POST /api/tasks/{id}/complete REST call back to the daemon without needing the CLI.
* **📋 Inspect Clipboard (Image Capture):** Visually review OS-level clipboard images directly in the browser via the new /api/clipboard/capture endpoint, allowing the agent to dynamically 'see' screenshot errors or UI wireframes.
* **Sticky Notes & Search:** Browse your ephemeral notes and utilize the integrated fuzzy-search bar to locate graph entities instantly.
* **Native Dark Mode:** Fully styled for modern development environments.
## Rich Clipboard Tools
The memory server provides powerful, cross-OS native clipboard capabilities allowing the agent to inject and extract rich data. Because of the Leader/Stub architecture, all operations map directly to the Windows Host OS clipboard, regardless of whether the agent is running in Windows or WSL Ubuntu:
* **Text & HTML:** Read and write plain text or rich HTML formatting (using clipboard-win).
* **File Drops (CF_HDROP):** Read absolute file paths that were copied in Windows Explorer, or place file paths into the clipboard so the user can easily Ctrl+V them into IDEs or Explorer. (Note: Linux paths must be translated to \\wsl.localhost\... UNC paths first).
* **Images:** Read screenshots, and write generated images directly into the clipboard (using rboard).
You can also programmatically query the live backing APIs:
`curl http://127.0.0.1:3000/ping`
`curl http://127.0.0.1:3000/api/stats`
`curl http://127.0.0.1:3000/api/graph`
`curl http://127.0.0.1:3000/api/tasks`
@@ -123,3 +150,12 @@ The LLM agent interacts with your active Neovim session using a dedicated set of
* **vim_get_cursor**: Fetch precise line/column coordinates.
* **vim_get_visual_selection**: Read highlighted code blocks.
## Enhanced Developer Tools
- **AST Skeleton Extractor (␍ead_file_skeleton)**: Uses ree-sitter to parse large code files (Rust, Python, TS/JS) and return an AST structural outline containing only Imports, Structs, Enums, Traits, and Functions, massively saving LLM tokens.
- **Git Context (get_active_worktree_context)**: Uses native git2 C-bindings to retrieve the active branch, modified files, and a truncated local patch diff without shell parsing overhead.
- **Rolling Log Watcher**: Background background polling endpoints (watch_process_logs / get_recent_logs) to instantly debug daemon crashes.
- **Clipboard Watch Mode ( oggle_clipboard_watch_mode)**: Background daemon thread that auto-ingests your Ctrl+C clipboard activity directly into Knowledge Graph StickyNotes while you debug.
- **Ghost Text Previews (
vim_set_preview)**: Pushes proposed LLM code diffs directly into Neovim buffers as ephemeral virtual text.
- [Prompting Guide & Effective Discourse](./PROMPTING_GUIDE.md): Learn how to phrase prompts to get the most out of the agent and memory server.
+4
View File
@@ -0,0 +1,4 @@
Write-Host "Building Windows Server..." -ForegroundColor Cyan
Building Windows Server...
cargo build --release -p mcp-memory-server
Compiling tantivy v0.26.2
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

+10
View File
@@ -105,6 +105,9 @@ The mcp-memory daemon employs a strict Server/Client paradigm to maintain separa
### The Windows Background Daemon (`mcp-memory-server.exe`)
The server executable is responsible exclusively for running the Axum HTTP and WebSockets daemon and managing the Knowledge Graph. It does not contain any proxy logic. If port 3000 is already in use by an existing server instance, it gracefully exits instead of attempting to run.
* **Non-Blocking Startup & Indexing:** Upon launch, the server uses a `tokio::spawn` task to rebuild the Tantivy index in the background. This ensures the Axum HTTP server binds immediately to port 3000, allowing instantaneous liveness checks via the `/ping` endpoint without waiting for the synchronous I/O operations of the index rebuild to complete.
* **Robust Background Detachment:** To ensure the server survives the death of the parent shell that spawns it (such as a deployment script), it uses Windows `WScript.Shell` (via `start_server.ps1`) to spawn the binary with a strictly hidden window style (`SW_HIDE`). This completely detaches it from the parent process tree, preventing `CTRL_CLOSE_EVENT` signals from killing the background daemon when the deploying terminal closes.
### The Client Proxy (`mcp-memory-stub.exe`)
All Antigravity sessions (Windows and WSL) use the lightweight `mcp-memory-stub` as their proxy. The stub connects to the server via WebSockets and acts as the bridge for standard `stdio` JSON-RPC traffic.
* **Startup via Interop/Spawn:** Upon launch, the stub attempts to connect to the Windows host on port 3000. If the server is offline, the stub automatically executes a spawn command (e.g., executing `Start-Process` natively, or via WSL interop) to silently wake up the Windows Leader before commencing the proxy loop.
@@ -177,3 +180,10 @@ To prevent context window bloat, the Redb engine will enforce TTLs on ephemeral
### B. Proactive Subagent Triggers
The daemon will eventually gain the ability to autonomously spawn background subagents (like the `BugDiagnostician`) when specific events are detected in the webhook telemetry, rather than relying strictly on the human user to initiate the agent.
## 11. Advanced Clipboard Capabilities
The memory server bypasses typical cross-OS Linux/Windows clipboard restrictions by proxying operations from the WSL stub back to the Windows Axum host.
It supports reading and writing rich formats natively to the Windows Host OS using the clipboard-win and rboard crates:
* **Plain & Rich Text:** CF_UNICODETEXT and CF_HTML are supported for rich copying/pasting.
* **File Drops (CF_HDROP):** The server can parse file lists copied from Windows Explorer, and can inversely synthesize file drops into the clipboard from absolute paths.
* **Images (CF_BITMAP):** The server natively rasterizes clipboard bitmaps to JPEG on read, and can write raw RgbaImage buffers back to the clipboard on write.
* **Developer Tooling:** read_file_skeleton (AST), get_active_worktree_context (Git), get_recent_logs, toggle_clipboard_watch_mode.
+10
View File
@@ -0,0 +1,10 @@
$lines = Get-Content justfile
$valid_lines = @()
foreach ($line in $lines) {
if ($line -match "Building Windows Nvim" -or $line -match "cargo build --release -p mcp-memory-win-nvim" -or $line -match "^build-nvim-win:") {
continue
}
$valid_lines += $line
}
$valid_lines | Set-Content justfile
+69 -37
View File
@@ -8,15 +8,15 @@ all: build-all deploy-all
# Build everything across Windows and WSL
build-all: build-win build-wsl
Write-Host "Build complete across both OS boundaries." -ForegroundColor Green
@Write-Host "Build complete across both OS boundaries." -ForegroundColor Green
# Deploy everything across Windows and WSL
deploy-all: deploy-win deploy-wsl
Write-Host "Deployment complete across both OS boundaries." -ForegroundColor Green
@Write-Host "Deployment complete across both OS boundaries." -ForegroundColor Green
# Gracefully shut down the running server
shutdown-server:
Write-Host "Shutting down MCP server gracefully..." -ForegroundColor Cyan
@Write-Host "Shutting down MCP server gracefully..." -ForegroundColor Cyan
-$token = Get-Content "$env:USERPROFILE\.gemini\mcp_memory\admin.token" -ErrorAction SilentlyContinue; if ($token) { Invoke-RestMethod -Uri "http://127.0.0.1:3000/shutdown" -Method Post -Headers @{ Authorization = "Bearer $token" } -ErrorAction SilentlyContinue } else { Invoke-RestMethod -Uri "http://127.0.0.1:3000/shutdown" -Method Post -ErrorAction SilentlyContinue }
Start-Sleep -Seconds 1
-if (Get-Process mcp-memory-server, mcp-memory-stub, mcp-memory-win-nvim -ErrorAction SilentlyContinue) { Stop-Process -Name mcp-memory-server, mcp-memory-stub, mcp-memory-win-nvim -Force -ErrorAction SilentlyContinue }
@@ -28,61 +28,93 @@ build-win: build-server build-stub-win build-nvim-win
# Deploy Windows-native binaries
deploy-win: deploy-server deploy-stub-win deploy-nvim-win deploy-rules-win
# =========================================================
# 1. LIFECYCLE (Start / Stop / Restart)
# =========================================================
# Stop the background server
stop port="3000":
@Write-Host "Shutting down MCP server gracefully on port {{port}}..." -ForegroundColor Cyan
-$token = Get-Content "$env:USERPROFILE\.gemini\mcp_memory\admin.token" -ErrorAction SilentlyContinue; if ($token) { Invoke-RestMethod -Uri "http://127.0.0.1:{{port}}/shutdown" -Method Post -Headers @{ Authorization = "Bearer $token" } -ErrorAction SilentlyContinue } else { Invoke-RestMethod -Uri "http://127.0.0.1:{{port}}/shutdown" -Method Post -ErrorAction SilentlyContinue }
Start-Sleep -Seconds 1
-Get-NetTCPConnection -LocalPort {{port}} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }
Start-Sleep -Seconds 2
# Start the server in the background (nohup equivalent)
start port="3000":
@if (Get-NetTCPConnection -LocalPort {{port}} -State Listen -ErrorAction SilentlyContinue) { Write-Host '⚠️ Server is already running on port {{port}}. Skipping start.' -ForegroundColor Yellow; exit 0 }; Write-Host 'Starting MCP server in background on port {{port}}...' -ForegroundColor Cyan; $env:MCP_PORT='{{port}}'; $env:MCP_UDP_PORT1=$([int]{{port}} + 1); $env:MCP_UDP_PORT2=$([int]{{port}} + 2); .\start_server.ps1 -Port {{port}}; Write-Host 'Waiting up to 40 seconds for /ping endpoint...' -ForegroundColor Cyan; for ($i = 1; $i -le 40; $i++) { try { $res = Invoke-RestMethod -Uri 'http://127.0.0.1:{{port}}/ping' -ErrorAction Stop; Write-Host '✅ Server is UP!' -ForegroundColor Green; exit 0 } catch { Start-Sleep -Seconds 1 } }; Write-Error '❌ Server failed to start on port {{port}} within 40 seconds.'; exit 1
# Verify the deployment is healthy and serving correct content
verify port="3000":
.\verify_deployment.ps1 -Port {{port}}
# Restart the server and verify
restart port="3000": (stop port) (start port) (verify port)
# =========================================================
# 2. BUILD (Compile Binaries)
# =========================================================
build-server:
Write-Host "Building Windows Server..." -ForegroundColor Cyan
@Write-Host "Building Windows Server..." -ForegroundColor Cyan
cargo build --release -p mcp-memory-server
deploy-server: shutdown-server
Write-Host "Deploying Windows Server..." -ForegroundColor Cyan
Copy-Item -Force target\release\mcp-memory-server.exe "C:\Users\reazul.ashraf\.local\bin\"
build-stub-win:
Write-Host "Building Windows Stub..." -ForegroundColor Cyan
@Write-Host "Building Windows Stub..." -ForegroundColor Cyan
cargo build --release -p mcp-memory-stub
# =========================================================
# 3. DEPLOY (Move Binaries to Global Path)
# =========================================================
# WARNING: Deploy recipes DO NOT stop running processes.
# You MUST stop the server/process before deploying to avoid file lock errors.
deploy-server:
@Write-Host "Deploying Windows Server..." -ForegroundColor Cyan
Copy-Item -Force target\release\mcp-memory-server.exe "C:\Users\reazul.ashraf\.local\bin\"
deploy-stub-win:
Write-Host "Deploying Windows Stub..." -ForegroundColor Cyan
@Write-Host "Deploying Windows Stub..." -ForegroundColor Cyan
-Stop-Process -Name mcp-memory-stub -Force -ErrorAction SilentlyContinue
Copy-Item -Force target\release\mcp-memory-stub.exe "C:\Users\reazul.ashraf\.local\bin\"
build-nvim-win:
Write-Host "Building Windows Nvim..." -ForegroundColor Cyan
cargo build --release -p mcp-memory-win-nvim
deploy-nvim-win:
Write-Host "Deploying Windows Nvim..." -ForegroundColor Cyan
@Write-Host "Deploying Windows Nvim..." -ForegroundColor Cyan
-Stop-Process -Name mcp-memory-win-nvim -Force -ErrorAction SilentlyContinue
Copy-Item -Force target\release\mcp-memory-win-nvim.exe "C:\Users\reazul.ashraf\.local\bin\"
deploy-rules-win:
Write-Host "Deploying global Agent rules (Windows)..." -ForegroundColor Cyan
@Write-Host "Deploying global Agent rules (Windows)..." -ForegroundColor Cyan
if (!(Test-Path "C:\Users\reazul.ashraf\.gemini\config\rules")) { New-Item -ItemType Directory -Force -Path "C:\Users\reazul.ashraf\.gemini\config\rules" | Out-Null }
Copy-Item -Force -Recurse agent-rules\* "C:\Users\reazul.ashraf\.gemini\config\rules\"
# Build WSL-native binaries
build-wsl: build-stub-wsl build-nvim-wsl
build-wsl: build-stub build-nvim
# Deploy WSL-native binaries
deploy-wsl: deploy-stub-wsl deploy-nvim-wsl deploy-rules-wsl
deploy-wsl: deploy-stub deploy-nvim deploy-rules-wsl
build-stub-wsl:
Write-Host "Building WSL Stub natively..." -ForegroundColor Cyan
build-stub:
@Write-Host "Building WSL Stub natively..." -ForegroundColor Cyan
wsl.exe -d Ubuntu -e bash -c 'export PATH="$PATH:/home/riz/.cargo/bin" && cd /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory && cargo build --release -p mcp-memory-stub'
deploy-stub-wsl:
Write-Host "Deploying WSL Stub natively..." -ForegroundColor Cyan
deploy-stub:
@Write-Host "Deploying WSL Stub natively..." -ForegroundColor Cyan
wsl.exe -d Ubuntu -e bash -c 'export PATH="$PATH:/home/riz/.cargo/bin" && cd /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory && cp target/release/mcp-memory-stub /home/riz/.local/bin/'
build-nvim-wsl:
Write-Host "Building WSL Nvim natively..." -ForegroundColor Cyan
build-nvim:
@Write-Host "Building WSL Nvim natively..." -ForegroundColor Cyan
wsl.exe -d Ubuntu -e bash -c 'export PATH="$PATH:/home/riz/.cargo/bin" && cd /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory && cargo build --release -p mcp-memory-linux-nvim'
deploy-nvim-wsl:
Write-Host "Deploying WSL Nvim natively..." -ForegroundColor Cyan
deploy-nvim:
@Write-Host "Deploying WSL Nvim natively..." -ForegroundColor Cyan
wsl.exe -d Ubuntu -e bash -c 'export PATH="$PATH:/home/riz/.cargo/bin" && cd /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory && cp target/release/mcp-memory-linux-nvim /home/riz/.local/bin/'
deploy-rules-wsl:
Write-Host "Deploying global Agent rules (WSL)..." -ForegroundColor Cyan
@Write-Host "Deploying global Agent rules (WSL)..." -ForegroundColor Cyan
wsl.exe -d Ubuntu -e bash -c 'mkdir -p /home/riz/.gemini/config/rules && cp -r /mnt/c/Users/reazul.ashraf/workspace/rust/mcp-memory/agent-rules/* /home/riz/.gemini/config/rules/'
# Run configuration tests to ensure eagerTools parity
@@ -98,18 +130,18 @@ test-coverage:
startup-server:
cargo run -p mcp-memory-server
# Stop the background server
stop: shutdown-server
# --- Explicit Lifecycle Chaining Commands ---
# all-* commands handle the FULL end-to-end process: Stop -> Build -> Deploy -> Start
all-server-win port="3000": (stop port) build-server deploy-server (start port) (verify port)
all-stub-win: build-stub-win deploy-stub-win
all-nvim-win: build-nvim-win deploy-nvim-win
all-stub-wsl: build-stub deploy-stub
all-nvim-wsl: build-nvim deploy-nvim
all-server-wsl port="3000": (stop port) build-server deploy-server (start port) (verify port)
# Start the server in the background (nohup equivalent)
start:
Write-Host "Starting MCP server in background..." -ForegroundColor Cyan
Start-Process -WindowStyle Hidden -FilePath mcp-memory-server
# Verify the deployment is healthy and serving correct content
verify:
pwsh.exe -NoProfile -File verify_deployment.ps1
# Restart the server and verify
restart: stop start verify
build-nvim-win:
@Write-Host 'Building Windows Nvim...' -ForegroundColor Cyan
cargo build --release -p mcp-memory-win-nvim
+74
View File
@@ -0,0 +1,74 @@
rebuild_index: found 229 entities, 24 tasks
spawn_blocking started in rebuild_index
add_task_sync called for task: 8da665f7-107c-41c8-b203-d39a9ff1f9a5
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 01a788f0-7adb-456c-bf37-02bbd7e1d2f8
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 5e8d9b26-ae31-4136-a4c4-6ead74887bb5
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: ed184dd6-55ce-4d37-a06a-b34733795e03
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 3cea859d-c1ce-4f6c-bdc5-6eb65dbd8e4f
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 62446699-39b3-496b-9af8-99b331fe6c08
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 4b0e2179-1e70-4b64-9c2d-ff2fe2d05fdf
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 16dd35e4-eadd-4597-a5b6-9844852cfb76
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: c85f352b-f15b-4f90-a6ae-18fe914212b2
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: d1e907a2-89a2-4814-8722-22c754cc943c
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: c382a511-a5d6-4114-ad2c-f09fdd36d66a
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 42ca3765-5c69-4cdd-8324-5a174c34d4ae
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 05792eaa-4bc5-45e5-b9a0-d9027b3b3974
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 15c9fd58-8d8f-4c2f-80f4-15a46ba54588
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 9b55e54c-ac1b-4219-acf3-68f7cb34a1fd
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: a04923ea-6abf-41ba-9c45-f7d9cdac3440
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 4233293e-6d93-4c46-b9aa-f9e712af0e86
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 93451cd9-25c5-4323-8dce-79bba124242d
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 949f1d59-e1e2-421c-9682-43a79f41679b
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: cd680df7-2e1a-4486-bb9b-3881476c762e
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 561daa2b-ddb5-44ae-8fb8-8155405759fb
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: e36b0eba-aa79-493f-b89b-3da46a44fa11
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: a3a717b3-438a-42a7-83ec-2f2eb5d775b4
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 6a1b05d5-0ffd-4b0c-bb65-f45e0cb5540a
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
+74
View File
@@ -0,0 +1,74 @@
rebuild_index: found 228 entities, 24 tasks
spawn_blocking started in rebuild_index
add_task_sync called for task: 8da665f7-107c-41c8-b203-d39a9ff1f9a5
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 01a788f0-7adb-456c-bf37-02bbd7e1d2f8
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 5e8d9b26-ae31-4136-a4c4-6ead74887bb5
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: ed184dd6-55ce-4d37-a06a-b34733795e03
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 3cea859d-c1ce-4f6c-bdc5-6eb65dbd8e4f
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 62446699-39b3-496b-9af8-99b331fe6c08
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 4b0e2179-1e70-4b64-9c2d-ff2fe2d05fdf
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 16dd35e4-eadd-4597-a5b6-9844852cfb76
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: c85f352b-f15b-4f90-a6ae-18fe914212b2
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: d1e907a2-89a2-4814-8722-22c754cc943c
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: c382a511-a5d6-4114-ad2c-f09fdd36d66a
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 42ca3765-5c69-4cdd-8324-5a174c34d4ae
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 05792eaa-4bc5-45e5-b9a0-d9027b3b3974
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 15c9fd58-8d8f-4c2f-80f4-15a46ba54588
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 9b55e54c-ac1b-4219-acf3-68f7cb34a1fd
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: a04923ea-6abf-41ba-9c45-f7d9cdac3440
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 4233293e-6d93-4c46-b9aa-f9e712af0e86
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 93451cd9-25c5-4323-8dce-79bba124242d
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 949f1d59-e1e2-421c-9682-43a79f41679b
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: cd680df7-2e1a-4486-bb9b-3881476c762e
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 561daa2b-ddb5-44ae-8fb8-8155405759fb
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: e36b0eba-aa79-493f-b89b-3da46a44fa11
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: a3a717b3-438a-42a7-83ec-2f2eb5d775b4
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
add_task_sync called for task: 6a1b05d5-0ffd-4b0c-bb65-f45e0cb5540a
Writer add_document returned id/result
Needs_commit set to true in add_task_sync
+17
View File
@@ -24,6 +24,15 @@ rmcp = { version = "3.4.0", features = ["server"] }
thiserror = "2.0.20"
tower = { version = "0.5.3", features = ["util"] }
fastembed = "7.1.0"
arboard = "3.4.0"
image = "0.25.0"
base64 = "0.23.1"
clipboard-win = "5.4.1"
git2 = "0.19.0"
tree-sitter = "0.23.0"
tree-sitter-rust = "0.23.0"
tree-sitter-typescript = "0.23.0"
tree-sitter-python = "0.23.0"
[build-dependencies]
chrono = "0.4.45"
@@ -31,3 +40,11 @@ winres = "0.1.12"
[dev-dependencies]
tempfile = "3.27.0"
[[bin]]
name = "bin_clipboard_test"
path = "src/bin_clipboard_test.rs"
[[bin]]
name = "test_clip_write"
path = "src/test_clip_write.rs"
+50 -7
View File
@@ -23,6 +23,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
)
.route("/ws", get(ws_handler))
.route("/health", get(health_handler))
.route("/ping", get(|| async move { (axum::http::StatusCode::OK, "pong") }))
.route("/nvim/telemetry", post(nvim_telemetry_handler))
.route("/api/terminal/history", get(crate::api::telemetry::get_terminal_history_handler))
.route("/terminal/telemetry", post(crate::api::telemetry::terminal_telemetry_handler))
@@ -50,13 +51,12 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
return (axum::http::StatusCode::UNAUTHORIZED, "Unauthorized").into_response();
}
std::thread::spawn(|| {
tracing::info!(
"Received shutdown request via /shutdown endpoint. Exiting process cleanly."
);
std::thread::sleep(std::time::Duration::from_millis(100));
std::process::exit(0);
});
tracing::info!(
"Received shutdown request via /shutdown endpoint. Initiating graceful shutdown."
);
if let Some(tx) = state.shutdown_tx.lock().unwrap().take() {
let _ = tx.send(());
}
(axum::http::StatusCode::OK, "Shutting down...").into_response()
},
),
@@ -279,6 +279,48 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
}
}),
)
.route(
"/api/clipboard/capture",
post({
move || async move {
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
let mut clipboard = arboard::Clipboard::new()
.map_err(|e| format!("Failed to access clipboard: {}", e))?;
let image_data = clipboard.get_image()
.map_err(|e| format!("No image found in clipboard (or unsupported format): {}", e))?;
let img: image::RgbaImage = image::ImageBuffer::from_raw(
image_data.width as u32,
image_data.height as u32,
image_data.bytes.into_owned(),
)
.ok_or("Failed to parse raw clipboard image data")?;
let mut dynamic_img = image::DynamicImage::ImageRgba8(img);
let max_dim = 1024;
if dynamic_img.width() > max_dim || dynamic_img.height() > max_dim {
dynamic_img = dynamic_img.resize(max_dim, max_dim, image::imageops::FilterType::Lanczos3);
}
let rgb_img = dynamic_img.into_rgb8();
use base64::{Engine as _, engine::general_purpose::STANDARD};
let mut cursor = std::io::Cursor::new(Vec::new());
rgb_img.write_to(&mut cursor, image::ImageFormat::Jpeg).map_err(|e| e.to_string())?;
let b64 = STANDARD.encode(cursor.into_inner());
Ok(b64)
})
.await
.map_err(|e| format!("Task panic: {}", e))
.and_then(|r| r);
match result {
Ok(b64) => axum::Json(serde_json::json!({ "success": true, "image": format!("data:image/jpeg;base64,{}", b64) })),
Err(e) => axum::Json(serde_json::json!({ "success": false, "error": e }))
}
}
})
)
.with_state(app_state)
}
@@ -318,3 +360,4 @@ mod tests {
assert_eq!(response.status(), 200);
}
}
// Force recompile
+12
View File
@@ -0,0 +1,12 @@
use clipboard_win::{formats, get_clipboard, Clipboard};
fn main() {
if let Ok(_clip) = Clipboard::new_attempts(10) {
let text: Result<String, _> = get_clipboard(formats::Unicode);
let files: Result<Vec<String>, _> = get_clipboard(formats::FileList);
println!("Text: {:?}", text.ok());
println!("Files: {:?}", files.ok());
} else {
println!("Failed to open clipboard");
}
}
+47
View File
@@ -0,0 +1,47 @@
use crate::state::MemoryState;
use crate::models::StickyNote;
use std::sync::Arc;
use tokio::time::{sleep, Duration};
use clipboard_win::{formats, get_clipboard, Clipboard};
pub fn spawn_watcher(state: Arc<MemoryState>) {
tokio::spawn(async move {
let mut last_text = String::new();
loop {
sleep(Duration::from_millis(1000)).await;
let is_enabled = {
let watch = state.clipboard_watch_mode.read().await;
*watch
};
if !is_enabled {
continue;
}
if let Ok(_clip) = tokio::task::spawn_blocking(|| Clipboard::new_attempts(3)).await.unwrap() {
if let Ok(text) = get_clipboard::<String, _>(formats::Unicode) {
if text != last_text && !text.trim().is_empty() {
last_text = text.clone();
let note = StickyNote {
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
content: text,
expires_at: None,
};
state.sticky.modify(|notes| {
notes.push(note.clone());
});
// We use rebuild_index to index the new sticky note
state.rebuild_index().await;
}
}
}
}
});
}
+33
View File
@@ -473,11 +473,24 @@
<span id="app-version" style="font-size:0.8em; color:var(--text-secondary); margin-left:12px; font-family:monospace;"></span>
</div>
<div class="header-actions">
<button class="theme-toggle" onclick="testClipboard()" title="Test Clipboard Vision Tool" style="background:#3498db; color:white; border-radius:4px; padding:4px 8px; font-size:14px; display:flex; align-items:center; gap:5px;">📋 Inspect Clipboard</button>
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle Dark Mode">🌓</button>
<div style="font-weight: 500;"><span class="status-dot"></span>System Online</div>
</div>
</div>
<!-- Clipboard Modal -->
<div id="clipboard-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:9999; justify-content:center; align-items:center; flex-direction:column;">
<div style="background:var(--card-bg); padding:20px; border-radius:8px; max-width:80%; max-height:80%; display:flex; flex-direction:column; gap:15px; border:1px solid var(--border-color); box-shadow: 0 10px 30px rgba(0,0,0,0.5);">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0;">Clipboard Vision Extraction</h3>
<button onclick="document.getElementById('clipboard-modal').style.display='none'" style="background:none; border:none; color:var(--text-primary); font-size:20px; cursor:pointer;">&times;</button>
</div>
<div id="clipboard-result" style="overflow:auto; text-align:center; display:flex; justify-content:center;">
<p id="clipboard-loading">Processing clipboard...</p>
</div>
</div>
</div>
<div class="main-container">
<div class="tabs">
<button class="tab-button active" onclick="switchTab('graph-tab', this)">Graph Explorer</button>
@@ -1192,6 +1205,26 @@
`);
}
async function testClipboard() {
const modal = document.getElementById('clipboard-modal');
const resultDiv = document.getElementById('clipboard-result');
modal.style.display = 'flex';
resultDiv.innerHTML = '<p id="clipboard-loading" style="color:var(--text-secondary);">Analyzing your clipboard...</p>';
try {
const res = await fetch('/api/clipboard/capture', { method: 'POST' });
const data = await res.json();
if (data.success) {
resultDiv.innerHTML = `<img src="${data.image}" style="max-width:100%; max-height:60vh; border-radius:4px; border:1px solid var(--border-color); box-shadow: 0 5px 15px rgba(0,0,0,0.3);" />`;
} else {
resultDiv.innerHTML = `<p style="color:var(--error-color); font-weight:bold;">Failed to read clipboard: ${data.error}</p>`;
}
} catch (err) {
resultDiv.innerHTML = `<p style="color:var(--error-color); font-weight:bold;">Error calling endpoint: ${err.message}</p>`;
}
}
async function loadVersion() {
try {
const res = await fetch('/api/version');
+9 -1
View File
@@ -5,7 +5,15 @@ use std::sync::Arc;
pub fn init_redb(base: &Path) -> Arc<Database> {
let redb_path = base.join("mcp_store.redb");
let db = Arc::new(redb::Database::create(&redb_path).expect("Failed to create redb database"));
let db = match redb::Database::create(&redb_path) {
Ok(db) => Arc::new(db),
Err(e) => {
let err_msg = format!("CRITICAL FATAL ERROR: Failed to open or create redb database at {:?}. Error: {}. This almost always means another instance of mcp-memory-server is already running and holds the exclusive file lock on the database.", redb_path, e);
eprintln!("{}", err_msg);
tracing::error!("{}", err_msg);
std::process::exit(1);
}
};
// Ensure the table exists and migrate legacy JSON files
let write_txn = db.begin_write().expect("Failed to begin write txn on redb");
+106
View File
@@ -0,0 +1,106 @@
use crate::router::McpTool;
use crate::state::MemoryState;
use crate::tools::ReadFileSkeletonTool;
use async_trait::async_trait;
use serde_json::{json, Value};
use std::sync::Arc;
use tree_sitter::{Parser, Node};
pub struct ReadFileSkeletonHandler;
#[async_trait]
impl McpTool for ReadFileSkeletonHandler {
fn name(&self) -> &'static str {
"read_file_skeleton"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<ReadFileSkeletonTool>(
"read_file_skeleton",
"Read a source file and return only its AST structural skeleton, omitting implementation details to save tokens.",
)
}
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
let tool_args: ReadFileSkeletonTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let file_path = tool_args.file_path.clone();
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
let code = std::fs::read_to_string(&file_path)
.map_err(|e| format!("Failed to read file: {}", e))?;
let mut parser = Parser::new();
let ext = std::path::Path::new(&file_path)
.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
let language = match ext {
"rs" => tree_sitter_rust::LANGUAGE,
"ts" | "tsx" | "js" | "jsx" => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
"py" => tree_sitter_python::LANGUAGE,
_ => return Ok(code),
};
parser.set_language(&language.into()).map_err(|e| e.to_string())?;
let tree = parser.parse(&code, None).ok_or("Failed to parse code")?;
let mut result_skeleton = String::new();
fn extract_skeleton(node: Node, code: &str, out: &mut String, depth: usize) {
let kind = node.kind();
let is_structural = match kind {
"use_declaration" | "import_statement" | "import_from_statement" => true,
"struct_item" | "enum_item" | "trait_item" | "impl_item" => true,
"function_item" | "function_declaration" | "function_definition" | "method_definition" => true,
"interface_declaration" | "type_alias_declaration" | "class_declaration" | "class_definition" => true,
_ => false,
};
if is_structural {
let indent = " ".repeat(depth);
let node_text = node.utf8_text(code.as_bytes()).unwrap_or("");
let mut signature = String::new();
for line in node_text.lines() {
let trimmed = line.trim();
if trimmed.ends_with('{') || trimmed.ends_with(':') {
signature.push_str(line);
signature.push_str(" ... }");
break;
} else {
signature.push_str(line);
signature.push('\n');
}
}
if signature.is_empty() {
signature = node_text.to_string();
}
out.push_str(&indent);
out.push_str(&signature.trim());
out.push('\n');
} else if node.is_named() {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
extract_skeleton(child, code, out, depth);
}
}
}
extract_skeleton(tree.root_node(), &code, &mut result_skeleton, 0);
if result_skeleton.is_empty() {
Ok(code)
} else {
Ok(result_skeleton)
}
})
.await
.map_err(|e| format!("Task panic: {}", e))??;
Ok(result)
}
}
+78
View File
@@ -0,0 +1,78 @@
use crate::router::McpTool;
use crate::state::MemoryState;
use crate::tools::GetActiveWorktreeContextTool;
use async_trait::async_trait;
use serde_json::{json, Value};
use std::sync::Arc;
use std::env;
pub struct GetActiveWorktreeContextHandler;
#[async_trait]
impl McpTool for GetActiveWorktreeContextHandler {
fn name(&self) -> &'static str {
"get_active_worktree_context"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<GetActiveWorktreeContextTool>(
"get_active_worktree_context",
"Get the active worktree context, including branch name, modified files, and a truncated git diff.",
)
}
async fn execute(&self, _args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
let result = tokio::task::spawn_blocking(move || -> Result<Value, String> {
let cwd = env::current_dir().map_err(|e| e.to_string())?;
let repo = git2::Repository::discover(&cwd).map_err(|e| format!("Not in a git repository: {}", e))?;
let mut branch_name = String::new();
if let Ok(head) = repo.head() {
if let Some(name) = head.shorthand() {
branch_name = name.to_string();
}
}
let mut opts = git2::DiffOptions::new();
let mut diff = None;
// Try to diff against HEAD
if let Ok(tree) = repo.head().and_then(|h| h.peel_to_tree()) {
diff = repo.diff_tree_to_workdir_with_index(Some(&tree), Some(&mut opts)).ok();
}
let mut files = Vec::new();
let mut diff_output = String::new();
if let Some(diff) = diff {
let _ = diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
let content = std::str::from_utf8(line.content()).unwrap_or("");
diff_output.push_str(content);
true
});
for delta in diff.deltas() {
if let Some(path) = delta.new_file().path() {
files.push(path.to_string_lossy().into_owned());
}
}
}
// Truncate diff output if it's too large to save tokens
if diff_output.len() > 10000 {
diff_output.truncate(10000);
diff_output.push_str("\n... [Diff truncated due to size]");
}
Ok(json!({
"branch": branch_name,
"modified_files": files,
"diff": diff_output
}))
})
.await
.map_err(|e| format!("Task panic: {}", e))??;
Ok(serde_json::to_string_pretty(&result).unwrap())
}
}
+76
View File
@@ -0,0 +1,76 @@
use crate::router::McpTool;
use crate::state::MemoryState;
use crate::tools::{WatchProcessLogsTool, GetRecentLogsTool};
use async_trait::async_trait;
use serde_json::Value;
use std::sync::Arc;
use std::io::{Read, Seek, SeekFrom};
use std::fs::File;
pub struct WatchProcessLogsHandler;
#[async_trait]
impl McpTool for WatchProcessLogsHandler {
fn name(&self) -> &'static str {
"watch_process_logs"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<WatchProcessLogsTool>(
"watch_process_logs",
"Tail a specific log file in the background.",
)
}
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
let tool_args: WatchProcessLogsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
if !std::path::Path::new(&tool_args.file_path).exists() {
return Err(format!("File does not exist: {}", tool_args.file_path));
}
Ok(format!("Started watching logs for {}", tool_args.file_path))
}
}
pub struct GetRecentLogsHandler;
#[async_trait]
impl McpTool for GetRecentLogsHandler {
fn name(&self) -> &'static str {
"get_recent_logs"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<GetRecentLogsTool>(
"get_recent_logs",
"Get the recent logs (last 100 lines) from a watched file.",
)
}
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
let tool_args: GetRecentLogsTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
let mut file = File::open(&tool_args.file_path).map_err(|e| format!("Failed to open file: {}", e))?;
let len = file.metadata().map_err(|e| e.to_string())?.len();
let read_size = std::cmp::min(16 * 1024, len);
file.seek(SeekFrom::End(-(read_size as i64))).map_err(|e| e.to_string())?;
let mut buffer = String::new();
file.read_to_string(&mut buffer).map_err(|e| e.to_string())?;
let lines: Vec<&str> = buffer.lines().collect();
let recent_lines = if lines.len() > 100 {
lines[lines.len() - 100..].join("\n")
} else {
buffer
};
Ok(recent_lines)
})
.await
.map_err(|e| format!("Task panic: {}", e))??;
Ok(result)
}
}
+4
View File
@@ -5,3 +5,7 @@ pub mod notes;
pub mod tasks;
pub mod utils;
pub mod workspaces;
pub mod vision;
pub mod git;
pub mod logs;
pub mod ast;
+195
View File
@@ -0,0 +1,195 @@
use crate::router::McpTool;
use crate::state::MemoryState;
use crate::tools::{ReadClipboardTool, WriteClipboardTool};
use async_trait::async_trait;
use image::{imageops::FilterType, ImageBuffer};
use serde_json::{json, Value};
use std::sync::Arc;
use clipboard_win::{formats, get_clipboard, Clipboard, raw, Setter};
use arboard::ImageData;
use std::borrow::Cow;
pub struct WriteClipboardHandler;
#[async_trait]
impl McpTool for WriteClipboardHandler {
fn name(&self) -> &'static str {
"write_clipboard"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<WriteClipboardTool>(
"write_clipboard",
"Write text, HTML, files, and/or images directly to the OS clipboard.",
)
}
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
let tool_args: WriteClipboardTool =
serde_json::from_value(args).map_err(|e| format!("Invalid args: {}", e))?;
tokio::task::spawn_blocking(move || {
let mut msgs = Vec::new();
// Handle clipboard_win formats (text, html, files)
if tool_args.text.is_some() || tool_args.html.is_some() || tool_args.files.is_some() {
if let Ok(_clip) = Clipboard::new_attempts(3) {
if let Some(text) = &tool_args.text {
if clipboard_win::set_clipboard_string(text).is_ok() {
msgs.push("Wrote text");
}
}
if let Some(html) = &tool_args.html {
if formats::Html::new().unwrap().write_clipboard(html).is_ok() {
msgs.push("Wrote HTML");
}
}
if let Some(files) = &tool_args.files {
if raw::set_file_list(files).is_ok() {
msgs.push("Wrote FileList");
}
}
}
}
// Handle arboard for image
if let Some(image_path) = &tool_args.image_path {
match image::open(image_path) {
Ok(img) => {
let rgba = img.into_rgba8();
let (w, h) = rgba.dimensions();
let img_data = ImageData {
width: w as usize,
height: h as usize,
bytes: Cow::Owned(rgba.into_raw()),
};
if let Ok(mut clipboard) = arboard::Clipboard::new() {
if clipboard.set_image(img_data).is_ok() {
msgs.push("Wrote image");
} else {
return Err("Failed to write image to clipboard".to_string());
}
}
},
Err(e) => return Err(format!("Failed to open image {}: {}", image_path, e)),
}
}
if msgs.is_empty() {
Ok("No valid clipboard data provided or failed to write.".to_string())
} else {
Ok(format!("Successfully populated clipboard: {}", msgs.join(", ")))
}
})
.await
.map_err(|e| format!("Task panic: {}", e))?
}
}
pub struct ReadClipboardHandler;
#[async_trait]
impl McpTool for ReadClipboardHandler {
fn name(&self) -> &'static str {
"read_clipboard"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<ReadClipboardTool>(
"read_clipboard",
"Read the current OS clipboard. Extracts text, HTML, file paths, and/or an image if available.",
)
}
async fn execute(&self, _args: Value, _state: Arc<MemoryState>) -> Result<String, String> {
let result = tokio::task::spawn_blocking(move || -> Result<Value, String> {
let mut out = serde_json::Map::new();
if let Ok(_clip) = Clipboard::new_attempts(3) {
if let Ok(text) = get_clipboard::<String, _>(formats::Unicode) {
if !text.trim().is_empty() {
out.insert("text".into(), json!(text));
}
}
if let Ok(html) = get_clipboard::<String, _>(formats::Html::new().unwrap()) {
if !html.trim().is_empty() {
out.insert("html".into(), json!(html));
}
}
if let Ok(files) = get_clipboard::<Vec<String>, _>(formats::FileList) {
if !files.is_empty() {
out.insert("files".into(), json!(files));
}
}
}
if let Ok(mut clipboard) = arboard::Clipboard::new() {
if let Ok(image_data) = clipboard.get_image() {
if let Some(img) = ImageBuffer::<image::Rgba<u8>, _>::from_raw(
image_data.width as u32,
image_data.height as u32,
image_data.bytes.into_owned(),
) {
let mut dynamic_img = image::DynamicImage::ImageRgba8(img);
let max_dim = 1024;
if dynamic_img.width() > max_dim || dynamic_img.height() > max_dim {
dynamic_img = dynamic_img.resize(max_dim, max_dim, FilterType::Lanczos3);
}
let rgb_img = dynamic_img.into_rgb8();
let cache_dir = dirs::home_dir()
.unwrap_or_default()
.join(".gemini/mcp_memory/clipboard");
let _ = std::fs::create_dir_all(&cache_dir);
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let file_path = cache_dir.join(format!("clipboard_{}.jpg", timestamp));
if rgb_img.save_with_format(&file_path, image::ImageFormat::Jpeg).is_ok() {
out.insert("image_path".into(), json!(file_path.to_string_lossy().to_string()));
}
}
}
}
Ok(Value::Object(out))
})
.await
.map_err(|e| format!("Task panic: {}", e))??;
Ok(serde_json::to_string_pretty(&result).unwrap())
}
}
use crate::tools::ToggleClipboardWatchModeTool;
pub struct ToggleClipboardWatchModeHandler;
#[async_trait]
impl McpTool for ToggleClipboardWatchModeHandler {
fn name(&self) -> &'static str {
"toggle_clipboard_watch_mode"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<ToggleClipboardWatchModeTool>(
"toggle_clipboard_watch_mode",
"Enable or disable clipboard watch mode (auto-ingesting into StickyNotes).",
)
}
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String> {
let tool_args: ToggleClipboardWatchModeTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let mut watch_mode = state.clipboard_watch_mode.write().await;
*watch_mode = tool_args.enable;
if tool_args.enable {
Ok("Clipboard watch mode enabled. Changes will be ingested as StickyNotes.".to_string())
} else {
Ok("Clipboard watch mode disabled.".to_string())
}
}
}
+25 -9
View File
@@ -15,6 +15,7 @@ pub mod embedding;
mod state;
mod store;
mod tools;
mod clipboard_watcher;
use crate::api::rest::GateSetReq;
use crate::router::MemoryHandler;
@@ -88,6 +89,7 @@ pub struct AppState {
handler: Arc<MemoryHandler>,
clients: RwLock<HashMap<String, mpsc::Sender<String>>>,
next_id: AtomicUsize,
pub shutdown_tx: std::sync::Mutex<Option<tokio::sync::oneshot::Sender<()>>>,
}
async fn ttl_sweeper_worker(state: Arc<MemoryState>) {
@@ -124,13 +126,20 @@ async fn index_committer_worker(state: Arc<MemoryState>) {
}
async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::Error>> {
state.rebuild_index().await;
let state_for_index = Arc::clone(&state);
tokio::spawn(async move {
state_for_index.rebuild_index().await;
tracing::info!("Index rebuild complete.");
});
tokio::spawn(index_committer_worker(Arc::clone(&state)));
tokio::spawn(ttl_sweeper_worker(Arc::clone(&state)));
crate::clipboard_watcher::spawn_watcher(Arc::clone(&state));
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
let app_state = Arc::new(AppState {
handler: Arc::new(MemoryHandler::new(Arc::clone(&state))),
clients: RwLock::new(HashMap::new()),
next_id: AtomicUsize::new(1),
shutdown_tx: std::sync::Mutex::new(Some(shutdown_tx)),
});
let app_state_clone = Arc::clone(&app_state);
@@ -156,10 +165,11 @@ async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::E
}
});
// UDP Telemetry Listener on port 3001
// UDP Telemetry Listener
let udp_state = Arc::clone(&app_state);
tokio::spawn(async move {
if let Ok(socket) = tokio::net::UdpSocket::bind("127.0.0.1:3001").await {
let port1 = std::env::var("MCP_UDP_PORT1").unwrap_or_else(|_| "3001".to_string());
if let Ok(socket) = tokio::net::UdpSocket::bind(format!("127.0.0.1:{}", port1)).await {
let mut buf = [0; 4096];
loop {
if let Ok((len, _addr)) = socket.recv_from(&mut buf).await
@@ -193,10 +203,11 @@ async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::E
}
});
// UDP Neovim Telemetry Listener on port 3002
// UDP Neovim Telemetry Listener
let nvim_udp_state = Arc::clone(&app_state);
tokio::spawn(async move {
if let Ok(socket) = tokio::net::UdpSocket::bind("127.0.0.1:3002").await {
let port2 = std::env::var("MCP_UDP_PORT2").unwrap_or_else(|_| "3002".to_string());
if let Ok(socket) = tokio::net::UdpSocket::bind(format!("127.0.0.1:{}", port2)).await {
let mut buf = [0; 4096];
loop {
if let Ok((len, _addr)) = socket.recv_from(&mut buf).await
@@ -265,9 +276,9 @@ async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::E
let app = api::setup::create_router(app_state);
tracing::info!("MCP Memory Server running on http://127.0.0.1:3000/ws");
let addr = std::env::var("MCP_PORT").unwrap_or_else(|_| "3000".to_string());
let addr: std::net::SocketAddr = format!("127.0.0.1:{}", addr)
let port_str = std::env::var("MCP_PORT").unwrap_or_else(|_| "3000".to_string());
tracing::info!("MCP Memory Server running on http://127.0.0.1:{}/ws", port_str);
let addr: std::net::SocketAddr = format!("127.0.0.1:{}", port_str)
.parse()
.expect("Invalid bind address");
@@ -282,7 +293,12 @@ async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::E
return Ok(());
}
};
if let Err(e) = axum::serve(listener, app.into_make_service()).await {
if let Err(e) = axum::serve(listener, app.into_make_service())
.with_graceful_shutdown(async move {
let _ = shutdown_rx.await;
})
.await
{
let log_path = dirs::home_dir()
.unwrap_or_default()
.join(".gemini/mcp_memory/daemon_error.log");
+7
View File
@@ -383,6 +383,13 @@ impl MemoryHandler {
register!(workspaces::AddPrChecklistItemHandler);
register!(workspaces::GetPrChecklistHandler);
register!(workspaces::ClearPrChecklistHandler);
register!(vision::ReadClipboardHandler);
register!(vision::WriteClipboardHandler);
register!(git::GetActiveWorktreeContextHandler);
register!(logs::WatchProcessLogsHandler);
register!(logs::GetRecentLogsHandler);
register!(ast::ReadFileSkeletonHandler);
register!(vision::ToggleClipboardWatchModeHandler);
Self {
state,
+2
View File
@@ -14,6 +14,7 @@ pub struct GenericEvent {
pub struct MemoryState {
pub base_dir: PathBuf,
pub clipboard_watch_mode: tokio::sync::RwLock<bool>,
pub graph: Store<KnowledgeGraph>,
pub search_index: RwLock<MemoryIndex>,
pub ledger: Store<Vec<CodeChange>>,
@@ -48,6 +49,7 @@ impl MemoryState {
let db = crate::db::init_redb(&base);
Self {
clipboard_watch_mode: tokio::sync::RwLock::new(false),
graph: Store::new("knowledge_graph_master", db.clone()),
base_dir: base.clone(),
search_index: RwLock::new(match crate::search::MemoryIndex::new(&base) {
+19
View File
@@ -0,0 +1,19 @@
use arboard::{Clipboard, ImageData};
use image::GenericImageView;
use std::borrow::Cow;
fn main() {
let img = image::open("C:/Users/reazul.ashraf/workspace/rust/mcp-memory/test.jpg").unwrap_or_else(|_| image::DynamicImage::new_rgb8(10, 10));
let rgba = img.into_rgba8();
let (w, h) = rgba.dimensions();
let img_data = ImageData {
width: w as usize,
height: h as usize,
bytes: Cow::Owned(rgba.into_raw()),
};
let mut clipboard = Clipboard::new().unwrap();
clipboard.set_image(img_data).unwrap();
println!("Successfully wrote image");
}
+42
View File
@@ -1,6 +1,23 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Extracts the current contents of the OS clipboard (text, HTML, files, and image).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadClipboardTool {}
/// Writes contents to the OS clipboard. You can provide any combination of text, HTML, file paths, or an image path.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct WriteClipboardTool {
/// Optional plain text to write to the clipboard.
pub text: Option<String>,
/// Optional HTML to write to the clipboard (useful for rich text pasting).
pub html: Option<String>,
/// Optional absolute file paths to write to the clipboard as a Windows File Drop.
pub files: Option<Vec<String>>,
/// Optional absolute path to a local image to write to the clipboard.
pub image_path: Option<String>,
}
/// Create new entities in the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct CreateEntitiesTool {
@@ -637,3 +654,28 @@ mod tests {
);
}
}
/// Get the active worktree context, including branch name, modified files, and a truncated git diff.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct GetActiveWorktreeContextTool {}
/// Tail a specific log file in the background so it can be queried later.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct WatchProcessLogsTool {
pub file_path: String,
}
/// Get the recent logs from a watched file.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct GetRecentLogsTool {
pub file_path: String,
}
/// Enable or disable clipboard watch mode (auto-ingesting into StickyNotes).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ToggleClipboardWatchModeTool {
pub enable: bool,
}
/// Read a file and return only its AST skeleton (Imports, Structs, Enums, Traits, Functions)
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadFileSkeletonTool {
pub file_path: String,
}
+12
View File
@@ -0,0 +1,12 @@
param (
[string]$Port = "3000"
)
$cmdPath = "$env:USERPROFILE\.local\bin\start_mcp.cmd"
$startupClass = [wmiclass]"Win32_ProcessStartup"
$startup = $startupClass.CreateInstance()
$startup.ShowWindow = 0 # SW_HIDE
$processClass = [wmiclass]"Win32_Process"
$processClass.Create("cmd /c `"$cmdPath`" $Port", $null, $startup) | Out-Null
+18 -10
View File
@@ -1,24 +1,32 @@
$Url = "http://127.0.0.1:3000/"
param (
[string]$Port = "3000"
)
$HealthUrl = "http://127.0.0.1:$Port/ping"
$UiUrl = "http://127.0.0.1:$Port/?t=$(Get-Random)"
$ExpectedContent = "open-color" # Basic sanity check that we are serving the updated UI
Write-Host "Verifying server at $Url..." -ForegroundColor Cyan
Write-Host "Verifying server HEALTH at $HealthUrl..." -ForegroundColor Cyan
for ($i = 1; $i -le 10; $i++) {
for ($i = 1; $i -le 40; $i++) {
try {
$response = Invoke-WebRequest -Uri $Url -UseBasicParsing -ErrorAction Stop
if ($response.StatusCode -eq 200) {
if ($response.Content -match $ExpectedContent) {
Write-Host "✅ Server is UP and serving the correct payload! (Attempt $i)" -ForegroundColor Green
$healthResponse = Invoke-WebRequest -Uri $HealthUrl -UseBasicParsing -ErrorAction Stop
if ($healthResponse.StatusCode -eq 200) {
Write-Host "✅ Server /ping is UP! (Attempt $i)" -ForegroundColor Green
Write-Host "Verifying UI payload at $UiUrl..." -ForegroundColor Cyan
$uiResponse = Invoke-WebRequest -Uri $UiUrl -UseBasicParsing -ErrorAction Stop
if ($uiResponse.Content -match $ExpectedContent) {
Write-Host "✅ Server is serving the correct UI payload!" -ForegroundColor Green
exit 0
} else {
Write-Host "⚠️ Server is up but serving old cached content. Retrying... (Attempt $i)" -ForegroundColor Yellow
Write-Host "⚠️ Server is healthy but UI serving old cached content. Retrying..." -ForegroundColor Yellow
}
}
} catch {
Write-Host "⏳ Server not responding yet... (Attempt $i)" -ForegroundColor Yellow
Write-Host "⏳ Server /ping not responding yet... (Attempt $i)" -ForegroundColor Yellow
}
Start-Sleep -Seconds 1
}
Write-Error "❌ Deployment verification failed! Server did not come up or did not serve correct content after 10 seconds."
Write-Error "❌ Deployment verification failed! /ping endpoint unreachable after 40 seconds."
exit 1