feat: Add Brain Monitor dashboard and live API stats
This commit is contained in:
1 parent
3239e32866
commit
18fd05bb11
10 files changed
+665
-73
No files matched your search
Generated
+67
-1
@@ -1171,6 +1171,12 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "levenshtein_automata"
|
||||
version = "0.2.1"
|
||||
@@ -1267,10 +1273,11 @@ dependencies = [
|
||||
"rust-mcp-sdk",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strsim",
|
||||
"tantivy",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -1358,6 +1365,15 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.8"
|
||||
@@ -2026,6 +2042,15 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
@@ -2335,6 +2360,15 @@ dependencies = [
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.55"
|
||||
@@ -2531,6 +2565,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2642,6 +2702,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.64s
|
||||
@@ -0,0 +1,312 @@
|
||||
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)
|
||||
@@ -0,0 +1,59 @@
|
||||
import urllib.request
|
||||
import json
|
||||
import threading
|
||||
import sys
|
||||
import time
|
||||
|
||||
sse_url = "http://localhost:3000/sse"
|
||||
|
||||
def call_mcp(method, params):
|
||||
req = urllib.request.Request(sse_url)
|
||||
with urllib.request.urlopen(req) as response:
|
||||
endpoint = None
|
||||
# read the SSE stream
|
||||
for line_bytes in response:
|
||||
line = line_bytes.decode('utf-8').strip()
|
||||
if line.startswith("data: /messages"):
|
||||
endpoint = "http://localhost:3000" + line.split("data: ")[1].strip()
|
||||
break
|
||||
|
||||
if not endpoint:
|
||||
print("Failed to get endpoint")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Got endpoint: {endpoint}")
|
||||
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": method,
|
||||
"arguments": params
|
||||
}
|
||||
}
|
||||
|
||||
post_req = urllib.request.Request(endpoint, data=json.dumps(payload).encode('utf-8'), headers={'Content-Type': 'application/json'})
|
||||
with urllib.request.urlopen(post_req) as post_resp:
|
||||
print(f"POST status: {post_resp.status}")
|
||||
|
||||
# continue reading SSE for response
|
||||
for line_bytes in response:
|
||||
line = line_bytes.decode('utf-8').strip()
|
||||
if line.startswith("data: "):
|
||||
data = line[6:]
|
||||
if data == "ping": continue
|
||||
try:
|
||||
msg = json.loads(data)
|
||||
if msg.get("id") == 1:
|
||||
print("RESULT:")
|
||||
print(json.dumps(msg, indent=2))
|
||||
return
|
||||
except:
|
||||
pass
|
||||
|
||||
import sys
|
||||
if __name__ == "__main__":
|
||||
method = sys.argv[1]
|
||||
params = json.loads(sys.argv[2])
|
||||
call_mcp(method, params)
|
||||
+2
-1
@@ -15,8 +15,9 @@ rust-mcp-axum = "2.0.0"
|
||||
rust-mcp-sdk = "2.0.0"
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde_json = "1.0.151"
|
||||
strsim = "0.11.1"
|
||||
tantivy = "0.26.1"
|
||||
tokio = { version = "1.53.1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.19", features = ["io"] }
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = "0.3.23"
|
||||
uuid = { version = "1.26.0", features = ["v4"] }
|
||||
@@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Brain Monitor</title>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; padding: 40px; background-color: #f0f2f5; color: #333; }
|
||||
h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; max-width: 800px; margin: 0 auto; }
|
||||
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); text-align: center; transition: transform 0.2s ease; }
|
||||
.stat-card:hover { transform: translateY(-5px); }
|
||||
.stat-value { font-size: 2.5em; font-weight: bold; color: #3498db; margin: 10px 0; }
|
||||
.stat-label { font-size: 1.1em; color: #7f8c8d; text-transform: uppercase; letter-spacing: 1px; }
|
||||
.status-dot { display: inline-block; width: 10px; height: 10px; background-color: #2ecc71; border-radius: 50%; margin-right: 8px; box-shadow: 0 0 5px #2ecc71; }
|
||||
.header-bar { max-width: 800px; margin: 0 auto 20px; display: flex; justify-content: space-between; align-items: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header-bar">
|
||||
<h1>🧠 Antigravity Brain Monitor</h1>
|
||||
<div><span class="status-dot"></span>System Online</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Entities</div>
|
||||
<div class="stat-value" id="val-entities">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Relations</div>
|
||||
<div class="stat-value" id="val-relations">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Active Tasks</div>
|
||||
<div class="stat-value" id="val-tasks">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Snippets</div>
|
||||
<div class="stat-value" id="val-snippets">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Tech Debt</div>
|
||||
<div class="stat-value" id="val-tech_debts">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">ADRs</div>
|
||||
<div class="stat-value" id="val-adrs">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function fetchStats() {
|
||||
try {
|
||||
const response = await fetch('/api/stats');
|
||||
const data = await response.json();
|
||||
|
||||
document.getElementById('val-entities').innerText = data.entities;
|
||||
document.getElementById('val-relations').innerText = data.relations;
|
||||
document.getElementById('val-tasks').innerText = data.tasks;
|
||||
document.getElementById('val-snippets').innerText = data.snippets;
|
||||
document.getElementById('val-tech_debts').innerText = data.tech_debts;
|
||||
document.getElementById('val-adrs').innerText = data.adrs;
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch stats:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
fetchStats();
|
||||
// Poll every 2 seconds
|
||||
setInterval(fetchStats, 2000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+56
-64
@@ -120,6 +120,9 @@ impl ServerHandler for MemoryHandler {
|
||||
} {
|
||||
let entity: Entity = e;
|
||||
if !entity.name.is_empty() {
|
||||
if let Ok(idx) = self.state.search_index.read() {
|
||||
let _ = idx.index_entity(&entity);
|
||||
}
|
||||
g.entities.insert(entity.name.clone(), entity);
|
||||
}
|
||||
}
|
||||
@@ -267,29 +270,19 @@ impl ServerHandler for MemoryHandler {
|
||||
}
|
||||
"search_nodes" => {
|
||||
let req: SearchNodesTool = parse_args(args)?;
|
||||
let query = req.query.to_lowercase();
|
||||
let tokens: Vec<&str> = query.split_whitespace().collect();
|
||||
let matches = if let Ok(idx) = self.state.search_index.read() {
|
||||
idx.search(&req.query, req.namespace.as_deref()).unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let mut result = KnowledgeGraph::default();
|
||||
for (name, e) in self.state.get_full_graph().entities {
|
||||
if let Some(ns) = &req.namespace
|
||||
&& e.namespace != *ns
|
||||
{
|
||||
continue;
|
||||
let full = self.state.get_full_graph();
|
||||
for (id, doc_type) in matches {
|
||||
if doc_type == "entity" {
|
||||
if let Some(e) = full.entities.get(&id) {
|
||||
result.entities.insert(id, e.clone());
|
||||
}
|
||||
let name_low = name.to_lowercase();
|
||||
let type_low = e.entity_type.to_lowercase();
|
||||
let full_txt = format!(
|
||||
"{} {} {}",
|
||||
name_low,
|
||||
type_low,
|
||||
e.observations.join(" ").to_lowercase()
|
||||
);
|
||||
let matched = full_txt.contains(&query)
|
||||
|| (!tokens.is_empty() && tokens.iter().all(|&t| full_txt.contains(t)))
|
||||
|| strsim::jaro_winkler(&name_low, &query) > 0.85
|
||||
|| strsim::jaro_winkler(&type_low, &query) > 0.85;
|
||||
if matched {
|
||||
result.entities.insert(name, e);
|
||||
}
|
||||
}
|
||||
let data = serde_json::to_string(&result).unwrap_or_default();
|
||||
@@ -456,8 +449,7 @@ impl ServerHandler for MemoryHandler {
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
self.state.tasks.modify(|tasks| {
|
||||
tasks.push(Task {
|
||||
let task = Task {
|
||||
id: id.clone(),
|
||||
title: req.title,
|
||||
status: "pending".to_string(),
|
||||
@@ -465,7 +457,12 @@ impl ServerHandler for MemoryHandler {
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
git_branch: req.git_branch,
|
||||
});
|
||||
};
|
||||
if let Ok(idx) = self.state.search_index.read() {
|
||||
let _ = idx.index_task(&task);
|
||||
}
|
||||
self.state.tasks.modify(|tasks| {
|
||||
tasks.push(task);
|
||||
});
|
||||
Ok(ServerResult::from(CallToolResult::text_content(vec![
|
||||
format!("Task added with ID: {}", id).into(),
|
||||
@@ -1098,47 +1095,42 @@ impl ServerHandler for MemoryHandler {
|
||||
}
|
||||
"omni_search" => {
|
||||
let req: OmniSearchTool = parse_args(args)?;
|
||||
let matches = if let Ok(idx) = self.state.search_index.read() {
|
||||
idx.search(&req.query, req.namespace.as_deref()).unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let mut kg = KnowledgeGraph::default();
|
||||
let mut tasks = Vec::new();
|
||||
let mut snippets = Vec::new();
|
||||
let mut adrs = Vec::new();
|
||||
|
||||
let full = self.state.get_full_graph();
|
||||
for (id, doc_type) in &matches {
|
||||
if doc_type == "entity" {
|
||||
if let Some(e) = full.entities.get(id) {
|
||||
kg.entities.insert(id.clone(), e.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for t in self.state.tasks.read() {
|
||||
if matches.iter().any(|(id, typ)| id == &t.id && typ == "task") {
|
||||
tasks.push(t);
|
||||
}
|
||||
}
|
||||
for s in self.state.snippets.read() {
|
||||
if matches.iter().any(|(id, typ)| id == &s.name && typ == "snippet") {
|
||||
snippets.push(s);
|
||||
}
|
||||
}
|
||||
for a in self.state.adrs.read() {
|
||||
if matches.iter().any(|(id, typ)| id == &a.id && typ == "adr") {
|
||||
adrs.push(a);
|
||||
}
|
||||
}
|
||||
|
||||
let q = req.query.to_lowercase();
|
||||
|
||||
let mut kg = self.state.get_full_graph();
|
||||
kg.entities.retain(|name, e| {
|
||||
(req.namespace.is_none() || e.namespace == *req.namespace.as_ref().unwrap())
|
||||
&& (name.to_lowercase().contains(&q)
|
||||
|| e.entity_type.to_lowercase().contains(&q)
|
||||
|| e.observations.join(" ").to_lowercase().contains(&q))
|
||||
});
|
||||
|
||||
let tasks: Vec<_> = self
|
||||
.state
|
||||
.tasks
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|t| {
|
||||
t.title.to_lowercase().contains(&q)
|
||||
|| t.description.to_lowercase().contains(&q)
|
||||
})
|
||||
.collect();
|
||||
let snippets: Vec<_> = self
|
||||
.state
|
||||
.snippets
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|s| {
|
||||
s.name.to_lowercase().contains(&q)
|
||||
|| s.description.to_lowercase().contains(&q)
|
||||
})
|
||||
.collect();
|
||||
let adrs: Vec<_> = self
|
||||
.state
|
||||
.adrs
|
||||
.read()
|
||||
.into_iter()
|
||||
.filter(|a| {
|
||||
a.title.to_lowercase().contains(&q)
|
||||
|| a.context.to_lowercase().contains(&q)
|
||||
|| a.decision.to_lowercase().contains(&q)
|
||||
})
|
||||
.collect();
|
||||
let tech_debts: Vec<_> = self
|
||||
.state
|
||||
.tech_debts
|
||||
|
||||
+35
-1
@@ -3,6 +3,7 @@ mod models;
|
||||
mod state;
|
||||
mod store;
|
||||
mod tools;
|
||||
mod search;
|
||||
|
||||
use crate::handlers::MemoryHandler;
|
||||
use crate::models::*;
|
||||
@@ -84,6 +85,7 @@ async fn reconcile_worker(state: Arc<MemoryState>) {
|
||||
let has_files = glob::glob(&pattern).map(|p| p.count() > 0).unwrap_or(false);
|
||||
if has_local || has_files {
|
||||
state.apply_sync_write(|_master| {});
|
||||
state.rebuild_index();
|
||||
}
|
||||
|
||||
let now = SystemTime::now()
|
||||
@@ -152,6 +154,35 @@ fn run_server(state: Arc<MemoryState>) -> SdkResult<()> {
|
||||
axum::routing::get(|| async move {
|
||||
axum::http::StatusCode::OK
|
||||
}),
|
||||
).with_route(
|
||||
"/dashboard",
|
||||
axum::routing::get(|| async move {
|
||||
axum::response::Html(include_str!("dashboard.html"))
|
||||
}),
|
||||
).with_route(
|
||||
"/api/stats",
|
||||
axum::routing::get({
|
||||
let state_clone = state.clone();
|
||||
move || async move {
|
||||
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();
|
||||
|
||||
axum::Json(serde_json::json!({
|
||||
"entities": entities,
|
||||
"relations": relations,
|
||||
"tasks": tasks,
|
||||
"snippets": snippets,
|
||||
"tech_debts": tech_debts,
|
||||
"adrs": adrs
|
||||
}))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
println!("MCP Memory Server running on http://0.0.0.0:3000/sse");
|
||||
@@ -190,7 +221,7 @@ fn main() -> SdkResult<()> {
|
||||
{
|
||||
// This shouldn't be executed on linux natively anymore due to workspace split,
|
||||
// but keeping it as a fallback.
|
||||
let target_url = cli.target.as_deref().unwrap_or("http://host.docker.internal:3000");
|
||||
let target_url = cli.target.as_deref().unwrap_or("http://127.0.0.1:3000");
|
||||
let _ = proxy::run_proxy(target_url);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -211,6 +242,7 @@ fn main() -> SdkResult<()> {
|
||||
session_graph: RwLock::new(KnowledgeGraph::default()),
|
||||
base_dir: base.clone(),
|
||||
master_cache: RwLock::new((KnowledgeGraph::default(), SystemTime::UNIX_EPOCH)),
|
||||
search_index: RwLock::new(crate::search::MemoryIndex::new().unwrap()),
|
||||
ledger: Store::new(base.join("audit_ledger.json")),
|
||||
sticky: Store::new(base.join("sticky_notes.json")),
|
||||
tasks: Store::new(base.join("tasks.json")),
|
||||
@@ -231,6 +263,8 @@ fn main() -> SdkResult<()> {
|
||||
context_workspaces: Store::new(base.join("context_workspaces.json")),
|
||||
});
|
||||
|
||||
state.rebuild_index();
|
||||
|
||||
if let Some(command) = cli.command {
|
||||
match command {
|
||||
Commands::Gate { subcmd } => {
|
||||
|
||||
+36
-6
@@ -1,7 +1,7 @@
|
||||
use tantivy::schema::*;
|
||||
use tantivy::{doc, Index, IndexWriter, IndexReader, ReloadPolicy};
|
||||
use std::sync::Mutex;
|
||||
use crate::models::{Entity, Task, StickyNote, Adr, Snippet};
|
||||
use crate::models::{Entity, Task, Adr, Snippet};
|
||||
|
||||
pub struct MemoryIndex {
|
||||
index: Index,
|
||||
@@ -71,21 +71,51 @@ impl MemoryIndex {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn search(&self, query: &str, namespace: Option<&str>) -> tantivy::Result<Vec<String>> {
|
||||
pub fn search(&self, query: &str, namespace: Option<&str>) -> tantivy::Result<Vec<(String, String)>> {
|
||||
let searcher = self.reader.searcher();
|
||||
let query_parser = tantivy::query::QueryParser::for_index(&self.index, vec![self.title_field, self.body_field]);
|
||||
let q = query_parser.parse_query(query)?;
|
||||
|
||||
let top_docs = searcher.search(&q, &tantivy::collector::TopDocs::with_limit(50))?;
|
||||
let top_docs = searcher.search(&q, &tantivy::collector::TopDocs::with_limit(50).order_by_score())?;
|
||||
let mut results = Vec::new();
|
||||
for (_score, doc_address) in top_docs {
|
||||
let retrieved_doc = searcher.doc::<tantivy::TantivyDocument>(doc_address)?;
|
||||
if let Some(val) = retrieved_doc.get_first(self.title_field) {
|
||||
if let Some(t) = val.as_str() {
|
||||
results.push(t.to_string());
|
||||
let id = retrieved_doc.get_first(self.id_field).and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let doc_type = retrieved_doc.get_first(self.type_field).and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let doc_ns = retrieved_doc.get_first(self.namespace_field).and_then(|v| v.as_str()).unwrap_or("");
|
||||
if let Some(ns) = namespace {
|
||||
if doc_ns != ns && doc_ns != "global" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
results.push((id, doc_type));
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub fn index_snippet(&self, s: &Snippet) -> tantivy::Result<()> {
|
||||
let mut writer = self.writer.lock().unwrap();
|
||||
writer.add_document(doc!(
|
||||
self.id_field => s.name.clone(),
|
||||
self.title_field => s.name.clone(),
|
||||
self.body_field => format!("{} {}", s.language, s.description),
|
||||
self.type_field => "snippet",
|
||||
self.namespace_field => "global"
|
||||
))?;
|
||||
writer.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn index_adr(&self, a: &Adr) -> tantivy::Result<()> {
|
||||
let mut writer = self.writer.lock().unwrap();
|
||||
writer.add_document(doc!(
|
||||
self.id_field => a.id.clone(),
|
||||
self.title_field => a.title.clone(),
|
||||
self.body_field => format!("{} {} {}", a.context, a.decision, a.consequence),
|
||||
self.type_field => "adr",
|
||||
self.namespace_field => "global"
|
||||
))?;
|
||||
writer.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::models::*;
|
||||
use crate::store::{Store, read_json_file, write_json_atomic};
|
||||
use crate::search::MemoryIndex;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
@@ -11,6 +12,7 @@ pub struct MemoryState {
|
||||
pub master_path: PathBuf,
|
||||
pub session_graph: RwLock<KnowledgeGraph>,
|
||||
pub master_cache: RwLock<(KnowledgeGraph, SystemTime)>,
|
||||
pub search_index: RwLock<MemoryIndex>,
|
||||
pub ledger: Store<Vec<CodeChange>>,
|
||||
pub sticky: Store<Vec<StickyNote>>,
|
||||
pub tasks: Store<Vec<Task>>,
|
||||
@@ -136,5 +138,27 @@ impl MemoryState {
|
||||
|
||||
let _ = fs::remove_file(&lock_path);
|
||||
}
|
||||
|
||||
pub fn rebuild_index(&self) {
|
||||
if let Ok(new_idx) = MemoryIndex::new() {
|
||||
let full = self.get_full_graph();
|
||||
for (_, e) in &full.entities {
|
||||
let _ = new_idx.index_entity(e);
|
||||
}
|
||||
for t in self.tasks.read() {
|
||||
let _ = new_idx.index_task(&t);
|
||||
}
|
||||
for s in self.snippets.read() {
|
||||
let _ = new_idx.index_snippet(&s);
|
||||
}
|
||||
for a in self.adrs.read() {
|
||||
let _ = new_idx.index_adr(&a);
|
||||
}
|
||||
if let Ok(mut w) = self.search_index.write() {
|
||||
*w = new_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user