memoryai-mcp 1.5.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +613 -14
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11,6 +11,10 @@ import { z } from "zod";
|
|
|
11
11
|
const API_URL = process.env.HM_ENDPOINT || "http://localhost:8420";
|
|
12
12
|
const API_KEY = process.env.HM_API_KEY || "";
|
|
13
13
|
const REQUEST_TIMEOUT_MS = 30_000; // P2 #6: 30s default timeout for API requests
|
|
14
|
+
// Context Guard — per-IDE settings via env vars
|
|
15
|
+
const CG_CONTEXT_CAP = parseInt(process.env.HM_CONTEXT_CAP || "0", 10); // IDE's context window (0 = let server detect)
|
|
16
|
+
const CG_COMPACT_PCT = parseInt(process.env.HM_COMPACT_AT || "0", 10); // % to warn (e.g. 30 = 30%)
|
|
17
|
+
const CG_CRITICAL_PCT = parseInt(process.env.HM_CRITICAL_AT || "0", 10); // % to force compact (e.g. 50 = 50%)
|
|
14
18
|
// --- HTTP helper ---
|
|
15
19
|
async function api(method, path, body) {
|
|
16
20
|
const resp = await fetch(`${API_URL}${path}`, {
|
|
@@ -36,19 +40,21 @@ function err(e) {
|
|
|
36
40
|
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
37
41
|
}
|
|
38
42
|
// --- MCP Server ---
|
|
39
|
-
const server = new McpServer({ name: "memoryai", version: "0.
|
|
43
|
+
const server = new McpServer({ name: "memoryai", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
40
44
|
// 1. memory_store
|
|
41
|
-
server.tool("memory_store", "Store information in persistent memory. Use when you learn something important — project context, user preferences, architectural decisions, patterns, or
|
|
45
|
+
server.tool("memory_store", "Store information in persistent memory. Use when you learn something important — project context, user preferences, architectural decisions, patterns, bugs, pricing/cost discussions, business plans, financial calculations, credit/billing info, revenue models, partnership details, or ANY information the user might ask about later. When in doubt, STORE — MemoryAI handles dedup automatically, so storing too much is always better than forgetting.", {
|
|
42
46
|
content: z.string().describe("What to remember"),
|
|
43
47
|
source: z.string().optional().describe("Source context (e.g. file path, conversation)"),
|
|
44
48
|
tags: z.array(z.string()).optional().describe("Categories: preferences, architecture, bugs, patterns, decisions"),
|
|
45
49
|
priority: z.enum(["hot", "warm", "cold"]).optional().describe("Memory priority (default: warm)"),
|
|
46
|
-
memory_type: z.enum(["fact", "decision", "preference", "error", "goal", "episodic", "identity"]).optional().describe("Memory type. 'preference', 'decision', 'identity' are DNA-protected — never decay, 1.5x recall boost. Default: fact"),
|
|
50
|
+
memory_type: z.enum(["fact", "decision", "preference", "error", "goal", "episodic", "identity", "pitfall", "life_event", "procedure"]).optional().describe("Memory type. 'preference', 'decision', 'identity', 'procedure' are DNA-protected — never decay, 1.5x recall boost. 'pitfall' for failure memories. 'procedure' for learned workflows/steps. Default: fact"),
|
|
47
51
|
retention: z.enum(["auto", "forever", "6m", "1y"]).optional().describe("Retention policy. 'forever' = never deleted. Default: auto"),
|
|
48
52
|
content_type: z.enum(["conversation", "code", "decision", "preference", "architecture", "lesson_learned", "todo", "entity", "pattern", "environment", "bug_fix", "action_log"]).optional().describe("Content type — helps with filtering and recall accuracy"),
|
|
49
53
|
metadata: z.record(z.string(), z.unknown()).optional().describe("Additional metadata (JSONB)"),
|
|
50
54
|
zone: z.enum(["critical", "important", "standard", "ephemeral"]).optional().describe("Memory zone (default: standard). critical=never evict, ephemeral=auto-expire"),
|
|
51
55
|
importance: z.number().min(0).max(1).optional().describe("Importance score 0.0-1.0 (default: 0.5). Higher = slower decay, prioritized in recall"),
|
|
56
|
+
project_id: z.string().optional().describe("Scope memory to a project/workspace. DNA memories (preference/decision/identity/pitfall) are always cross-project visible."),
|
|
57
|
+
thread_id: z.string().optional().describe("Scope memory to a conversation thread. Memories without thread_id are visible in all threads. Use for parallel topics (e.g. 'relationship', 'career')."),
|
|
52
58
|
}, async (args) => {
|
|
53
59
|
try {
|
|
54
60
|
const r = (await api("POST", "/v1/store", {
|
|
@@ -62,6 +68,8 @@ server.tool("memory_store", "Store information in persistent memory. Use when yo
|
|
|
62
68
|
metadata: args.metadata,
|
|
63
69
|
zone: args.zone || "standard",
|
|
64
70
|
importance: args.importance ?? 0.5,
|
|
71
|
+
project_id: args.project_id,
|
|
72
|
+
thread_id: args.thread_id,
|
|
65
73
|
}));
|
|
66
74
|
let msg = `Stored (id=${r.id}, type=${args.memory_type || "fact"})`;
|
|
67
75
|
if (r.deduplicated) {
|
|
@@ -81,12 +89,14 @@ server.tool("memory_store", "Store information in persistent memory. Use when yo
|
|
|
81
89
|
// 2. memory_recall
|
|
82
90
|
server.tool("memory_recall", "Search persistent memory for relevant context. Use before starting work to check what you already know about the project or task.", {
|
|
83
91
|
query: z.string().describe("What to search for"),
|
|
84
|
-
depth: z.enum(["fast", "deep", "exhaustive"]).optional().describe("Search depth (default
|
|
92
|
+
depth: z.enum(["fast", "instant", "deep", "exhaustive"]).optional().describe("Search depth. 'instant'=vector only (~50ms), 'fast'=FTS only, 'deep'=full fusion (default), 'exhaustive'=deep+more results"),
|
|
85
93
|
limit: z.number().optional().describe("Max results (default: 5)"),
|
|
86
94
|
min_score: z.number().optional().describe("Minimum relevance score 0-1 (default: 0)"),
|
|
87
95
|
tags: z.array(z.string()).optional().describe("Filter by tags"),
|
|
88
96
|
max_tokens: z.number().optional().describe("Token budget limit — results truncated to fit within this budget"),
|
|
89
97
|
priority_min: z.enum(["critical", "important", "standard", "ephemeral"]).optional().describe("Minimum zone priority filter (default: all zones)"),
|
|
98
|
+
project_id: z.string().optional().describe("Scope recall to a project/workspace. DNA memories are always visible cross-project."),
|
|
99
|
+
thread_id: z.string().optional().describe("Scope recall to a conversation thread. Memories without thread_id are always visible."),
|
|
90
100
|
}, async (args) => {
|
|
91
101
|
try {
|
|
92
102
|
const body = {
|
|
@@ -100,6 +110,10 @@ server.tool("memory_recall", "Search persistent memory for relevant context. Use
|
|
|
100
110
|
body.max_tokens = args.max_tokens;
|
|
101
111
|
if (args.priority_min)
|
|
102
112
|
body.priority_min = args.priority_min;
|
|
113
|
+
if (args.project_id)
|
|
114
|
+
body.project_id = args.project_id;
|
|
115
|
+
if (args.thread_id)
|
|
116
|
+
body.thread_id = args.thread_id;
|
|
103
117
|
const r = (await api("POST", "/v1/recall", body));
|
|
104
118
|
if (!r.results?.length)
|
|
105
119
|
return ok("No relevant memories found.");
|
|
@@ -804,15 +818,23 @@ server.tool("session_handoff_status", "Check current session handoff status —
|
|
|
804
818
|
// context_guard_check — universal guard check with DNA count
|
|
805
819
|
server.tool("context_guard_check", "Check context window health using Context Guard v6 — dynamic thresholds, DNA memory count, bootstrap readiness. Replaces memory_health with richer data.", {
|
|
806
820
|
estimated_tokens: z.number().describe("Current token count in context window"),
|
|
807
|
-
max_tokens: z.number().optional().describe("Max context window size (
|
|
821
|
+
max_tokens: z.number().optional().describe("Max context window size (uses HM_CONTEXT_CAP env if omitted)"),
|
|
808
822
|
model: z.string().optional().describe("Model name for auto-detecting context window size (e.g. claude-sonnet-4-6)"),
|
|
809
823
|
}, async (args) => {
|
|
810
824
|
try {
|
|
811
|
-
|
|
825
|
+
// Use env var HM_CONTEXT_CAP as default if max_tokens not provided
|
|
826
|
+
const maxTokens = args.max_tokens || CG_CONTEXT_CAP || 0;
|
|
827
|
+
const payload = {
|
|
812
828
|
estimated_tokens: args.estimated_tokens,
|
|
813
|
-
max_tokens:
|
|
829
|
+
max_tokens: maxTokens,
|
|
814
830
|
model: args.model || null,
|
|
815
|
-
}
|
|
831
|
+
};
|
|
832
|
+
// Send per-IDE threshold overrides if configured via env vars
|
|
833
|
+
if (CG_COMPACT_PCT > 0)
|
|
834
|
+
payload.compact_pct = CG_COMPACT_PCT / 100;
|
|
835
|
+
if (CG_CRITICAL_PCT > 0)
|
|
836
|
+
payload.critical_pct = CG_CRITICAL_PCT / 100;
|
|
837
|
+
const r = (await api("POST", "/v1/context/guard/check", payload));
|
|
816
838
|
const pct = r.usage_percent;
|
|
817
839
|
const barLen = 20;
|
|
818
840
|
const filled = Math.round(pct / 100 * barLen);
|
|
@@ -831,8 +853,8 @@ server.tool("context_guard_check", "Check context window health using Context Gu
|
|
|
831
853
|
}
|
|
832
854
|
});
|
|
833
855
|
// context_guard_compact — compact with DNA protection
|
|
834
|
-
server.tool("context_guard_compact", "Compact session context with DNA protection — DNA memories are never overwritten.
|
|
835
|
-
content: z.string().describe("
|
|
856
|
+
server.tool("context_guard_compact", "Compact session context with DNA protection — DNA memories are never overwritten. IMPORTANT: Send a REAL summary of the conversation (>500 chars) including topics discussed, decisions made, key numbers/facts, and current status. Do NOT send just a status string like 'context guard - 132%'. If you send useless content, the server will use its internal buffer as fallback, but a good summary from you produces better memories.", {
|
|
857
|
+
content: z.string().describe("Conversation summary — include topics, decisions, key facts, numbers. Must be >500 chars of real content."),
|
|
836
858
|
task_context: z.string().optional().describe("Task description for tagging"),
|
|
837
859
|
blocking: z.boolean().optional().describe("Wait for result (true) or return task_id (false, default)"),
|
|
838
860
|
}, async (args) => {
|
|
@@ -852,9 +874,9 @@ server.tool("context_guard_compact", "Compact session context with DNA protectio
|
|
|
852
874
|
return err(e);
|
|
853
875
|
}
|
|
854
876
|
});
|
|
855
|
-
// context_guard_bootstrap — DNA-first session bootstrap
|
|
856
|
-
server.tool("context_guard_bootstrap", "
|
|
857
|
-
task: z.string().describe("Task description for
|
|
877
|
+
// context_guard_bootstrap — DNA-first session bootstrap (IDE)
|
|
878
|
+
server.tool("context_guard_bootstrap", "Load context from previous sessions at session start. Returns preferences, recent activity, and task-relevant memories. Call once at the beginning of a session to restore context.", {
|
|
879
|
+
task: z.string().describe("Task description for context relevance"),
|
|
858
880
|
limit: z.number().optional().describe("Max memories to include (default: 10)"),
|
|
859
881
|
}, async (args) => {
|
|
860
882
|
try {
|
|
@@ -862,7 +884,7 @@ server.tool("context_guard_bootstrap", "Bootstrap a new session with DNA-first c
|
|
|
862
884
|
task: args.task,
|
|
863
885
|
limit: args.limit || 10,
|
|
864
886
|
}));
|
|
865
|
-
return ok(`
|
|
887
|
+
return ok(`Context restored: ${r.memories_restored || r.memories_included || 0} memories (${r.dna_memories || 0} DNA)\n` +
|
|
866
888
|
`Tokens used: ${r.tokens_used}\n\n` +
|
|
867
889
|
r.context_block);
|
|
868
890
|
}
|
|
@@ -870,6 +892,583 @@ server.tool("context_guard_bootstrap", "Bootstrap a new session with DNA-first c
|
|
|
870
892
|
return err(e);
|
|
871
893
|
}
|
|
872
894
|
});
|
|
895
|
+
// bot_guard_bootstrap — 3-tier wake-up for bots (800 tokens)
|
|
896
|
+
server.tool("bot_guard_bootstrap", "Bot-specific bootstrap — 3-tier wake-up (Identity→Context→Details) with 800 token budget. Use for chatbots, not IDEs.", {
|
|
897
|
+
task: z.string().describe("Task description for the new session"),
|
|
898
|
+
limit: z.number().optional().describe("Max memories to include (default: 10)"),
|
|
899
|
+
mode: z.enum(["default", "deep"]).optional().describe("'default' = 800 token 3-tier, 'deep' = full context with L2 chunks"),
|
|
900
|
+
token_budget: z.number().optional().describe("Token budget (default: 800)"),
|
|
901
|
+
}, async (args) => {
|
|
902
|
+
try {
|
|
903
|
+
const r = (await api("POST", "/v1/bot/guard/bootstrap", {
|
|
904
|
+
task: args.task,
|
|
905
|
+
limit: args.limit || 10,
|
|
906
|
+
mode: args.mode || "default",
|
|
907
|
+
token_budget: args.token_budget,
|
|
908
|
+
}));
|
|
909
|
+
return ok(`Bootstrap complete: ${r.memories_included} memories\n` +
|
|
910
|
+
`Tokens used: ${r.tokens_used}\n` +
|
|
911
|
+
`L2 sessions: ${r.l2_sessions_included || 0}\n\n` +
|
|
912
|
+
r.context_block);
|
|
913
|
+
}
|
|
914
|
+
catch (e) {
|
|
915
|
+
return err(e);
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
// bot_session_message — Rolling 3-session tracking (60 msg raw context)
|
|
919
|
+
server.tool("bot_session_message", "Track a message in the rolling session (rolling 3: keeps 60 messages raw in LLM context). Call on EVERY message (user + assistant). Returns rotate=true when session hits 20 messages. When should_compress=true, compress the oldest session via bot_session_compress.", {
|
|
920
|
+
message: z.object({
|
|
921
|
+
role: z.enum(["user", "assistant"]).describe("Message role"),
|
|
922
|
+
content: z.string().describe("Message content"),
|
|
923
|
+
}).describe("The message to track"),
|
|
924
|
+
rotation_size: z.number().optional().describe("Messages per session before rotation (default: 20, range: 5-50)"),
|
|
925
|
+
}, async (args) => {
|
|
926
|
+
try {
|
|
927
|
+
const r = (await api("POST", "/v1/bot/session/message", {
|
|
928
|
+
message: args.message,
|
|
929
|
+
rotation_size: args.rotation_size || 20,
|
|
930
|
+
}));
|
|
931
|
+
if (r.rotate) {
|
|
932
|
+
let output = `🔄 SESSION ROTATED\n` +
|
|
933
|
+
`New session: ${r.session_id} (msg ${r.message_count})\n` +
|
|
934
|
+
`Context: ${r.context_message_count} messages raw in LLM\n`;
|
|
935
|
+
if (r.should_compress) {
|
|
936
|
+
output += `\n⚠️ COMPRESS: session ${r.compress_session_id} (${r.compress_message_count} msgs)\n` +
|
|
937
|
+
`Action: Call bot_session_compress with session_id="${r.compress_session_id}"`;
|
|
938
|
+
}
|
|
939
|
+
return ok(output);
|
|
940
|
+
}
|
|
941
|
+
return ok(`Session ${r.session_id}: ${r.message_count}/20 messages | context: ${r.context_message_count} msgs`);
|
|
942
|
+
}
|
|
943
|
+
catch (e) {
|
|
944
|
+
return err(e);
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
// bot_guard_check — Bot-specific guard with spawn signal
|
|
948
|
+
server.tool("bot_guard_check", "Bot context guard — checks context pressure AND returns spawn signal. When should_spawn_new_session=true, bot should spawn a new session and compress the old one later. Use this instead of context_guard_check for bot/chatbot clients.", {
|
|
949
|
+
estimated_tokens: z.number().describe("Current token count in context window"),
|
|
950
|
+
max_tokens: z.number().optional().describe("Max context window size (default: 200000)"),
|
|
951
|
+
model: z.string().optional().describe("Model name for auto-detecting context window size"),
|
|
952
|
+
compress_threshold: z.number().optional().describe("Custom spawn threshold in tokens (default: 70% of max_tokens)"),
|
|
953
|
+
}, async (args) => {
|
|
954
|
+
try {
|
|
955
|
+
const payload = {
|
|
956
|
+
estimated_tokens: args.estimated_tokens,
|
|
957
|
+
max_tokens: args.max_tokens || CG_CONTEXT_CAP || 200000,
|
|
958
|
+
model: args.model || null,
|
|
959
|
+
};
|
|
960
|
+
if (args.compress_threshold)
|
|
961
|
+
payload.compress_threshold = args.compress_threshold;
|
|
962
|
+
const r = (await api("POST", "/v1/bot/guard/check", payload));
|
|
963
|
+
const pct = r.usage_percent;
|
|
964
|
+
const barLen = 20;
|
|
965
|
+
const filled = Math.round(pct / 100 * barLen);
|
|
966
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barLen - filled);
|
|
967
|
+
let output = `Bot Guard:\n` +
|
|
968
|
+
`[${bar}] ${pct.toFixed(1)}%\n` +
|
|
969
|
+
`Recommendation: ${r.recommendation.toUpperCase()}${r.should_compact ? " — compact now" : ""}\n` +
|
|
970
|
+
`Urgency: ${r.urgency}\n` +
|
|
971
|
+
`Spawn threshold: ${r.compress_threshold.toLocaleString()} tokens\n` +
|
|
972
|
+
`DNA memories: ${r.dna_memories} | Bootstrap ready: ${r.bootstrap_ready ? "yes" : "no"}\n`;
|
|
973
|
+
if (r.should_spawn_new_session) {
|
|
974
|
+
output += `\n⚠️ SPAWN NEW SESSION: ${r.spawn_reason}\n`;
|
|
975
|
+
output += `Action: Start new session → when new session reaches 20K tokens → compress old session via /v1/bot/session/compress`;
|
|
976
|
+
}
|
|
977
|
+
return ok(output);
|
|
978
|
+
}
|
|
979
|
+
catch (e) {
|
|
980
|
+
return err(e);
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
// ── Self-Thinking Tools ──────────────────────────────────────────────
|
|
984
|
+
// brain_thoughts — Get current active thoughts
|
|
985
|
+
server.tool("brain_thoughts", "Get the brain's current active thoughts — what it's thinking about autonomously.", {
|
|
986
|
+
limit: z.number().optional().describe("Max thoughts to return (default: 10)"),
|
|
987
|
+
}, async (args) => {
|
|
988
|
+
try {
|
|
989
|
+
const r = (await api("GET", `/v1/brain/thoughts?limit=${args.limit || 10}`));
|
|
990
|
+
if (!r.thoughts || r.thoughts.length === 0)
|
|
991
|
+
return ok("Brain has no active thoughts right now.");
|
|
992
|
+
const lines = r.thoughts.map((t) => `[${t.thought_type}] ${t.content} (confidence: ${t.confidence}, urgency: ${t.urgency})`);
|
|
993
|
+
return ok(`Active thoughts (${r.count}):\n${lines.join("\n")}`);
|
|
994
|
+
}
|
|
995
|
+
catch (e) {
|
|
996
|
+
return err(e);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
// brain_think_about — Request brain to think about a topic
|
|
1000
|
+
server.tool("brain_think_about", "Request the brain to think about a specific topic. The brain will deliberate on it in its next thinking cycle.", {
|
|
1001
|
+
topic: z.string().describe("What should the brain think about?"),
|
|
1002
|
+
}, async (args) => {
|
|
1003
|
+
try {
|
|
1004
|
+
const r = (await api("POST", "/v1/brain/think-about", { topic: args.topic }));
|
|
1005
|
+
return ok(`Queued for thinking: "${args.topic}"\nQueue size: ${r.queue_size}`);
|
|
1006
|
+
}
|
|
1007
|
+
catch (e) {
|
|
1008
|
+
return err(e);
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
// brain_hypotheses — Get active hypotheses
|
|
1012
|
+
server.tool("brain_hypotheses", "Get hypotheses the brain is currently testing — predictions about user behavior patterns.", {
|
|
1013
|
+
limit: z.number().optional().describe("Max hypotheses to return (default: 10)"),
|
|
1014
|
+
}, async (args) => {
|
|
1015
|
+
try {
|
|
1016
|
+
const r = (await api("GET", `/v1/brain/hypotheses?limit=${args.limit || 10}`));
|
|
1017
|
+
if (!r.hypotheses || r.hypotheses.length === 0)
|
|
1018
|
+
return ok("No active hypotheses being tested.");
|
|
1019
|
+
const lines = r.hypotheses.map((h) => `[${h.status}] ${h.hypothesis} (confidence: ${h.confidence})`);
|
|
1020
|
+
return ok(`Hypotheses (${r.count}):\n${lines.join("\n")}`);
|
|
1021
|
+
}
|
|
1022
|
+
catch (e) {
|
|
1023
|
+
return err(e);
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
// brain_feedback — Rate a thought
|
|
1027
|
+
server.tool("brain_feedback", "Rate a thought as useful or not — helps the brain learn what's worth thinking about.", {
|
|
1028
|
+
thought_id: z.number().describe("ID of the thought to rate"),
|
|
1029
|
+
useful: z.boolean().describe("Was this thought useful?"),
|
|
1030
|
+
}, async (args) => {
|
|
1031
|
+
try {
|
|
1032
|
+
await api("POST", "/v1/brain/thoughts/feedback", {
|
|
1033
|
+
thought_id: args.thought_id,
|
|
1034
|
+
useful: args.useful,
|
|
1035
|
+
});
|
|
1036
|
+
return ok(`Feedback recorded: thought #${args.thought_id} marked as ${args.useful ? "useful" : "not useful"}`);
|
|
1037
|
+
}
|
|
1038
|
+
catch (e) {
|
|
1039
|
+
return err(e);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
// brain_thinking_stats — Budget and efficiency
|
|
1043
|
+
server.tool("brain_thinking_stats", "Get thinking system statistics — token budget, efficiency, queue size, and meta-cognition report.", {}, async () => {
|
|
1044
|
+
try {
|
|
1045
|
+
const r = (await api("GET", "/v1/brain/thinking-stats"));
|
|
1046
|
+
return ok(`Budget: ${r.budget.remaining_tokens} tokens remaining (limit: ${r.budget.limit_per_hour}/hr)\n` +
|
|
1047
|
+
`Efficiency: ${(r.budget.efficiency * 100).toFixed(1)}%\n` +
|
|
1048
|
+
`Queue size: ${r.queue_size}\n` +
|
|
1049
|
+
`Total thoughts: ${r.meta.total_thoughts} (${r.meta.useful_thoughts} useful)\n` +
|
|
1050
|
+
`Interval: ${r.meta.recommended_interval_seconds}s\n` +
|
|
1051
|
+
`Best types: ${r.meta.best_types.join(", ") || "none yet"}\n` +
|
|
1052
|
+
`Suppressed: ${r.meta.suppressed_types.join(", ") || "none"}`);
|
|
1053
|
+
}
|
|
1054
|
+
catch (e) {
|
|
1055
|
+
return err(e);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
// ── Session Settings Tools ──────────────────────────────────────────
|
|
1059
|
+
// memory_auto_extract — LLM-based fact extraction from conversation
|
|
1060
|
+
server.tool("memory_auto_extract", "CRITICAL: Call this at the END of every conversation session to extract and store important facts automatically. Uses LLM analysis to identify pricing, decisions, plans, technical details, and anything worth remembering. This is MORE reliable than manual memory_store because it catches things you might forget to store. ALWAYS call this before the conversation ends — especially after discussions about money, pricing, plans, decisions, or business.", {
|
|
1061
|
+
conversation: z.string().describe("The conversation text to extract facts from (include both user and assistant messages)"),
|
|
1062
|
+
source: z.string().optional().describe("Source context (e.g. 'discord chat', 'slack thread')"),
|
|
1063
|
+
store: z.boolean().optional().describe("Whether to store extracted facts (default: true). Set false to preview what would be extracted."),
|
|
1064
|
+
}, async (args) => {
|
|
1065
|
+
try {
|
|
1066
|
+
const r = (await api("POST", "/v1/memory/auto-extract", {
|
|
1067
|
+
conversation: args.conversation,
|
|
1068
|
+
source: args.source || "auto-extract",
|
|
1069
|
+
store: args.store !== false,
|
|
1070
|
+
}));
|
|
1071
|
+
if (!r.facts?.length)
|
|
1072
|
+
return ok("No extractable facts found in conversation.");
|
|
1073
|
+
const factList = r.facts
|
|
1074
|
+
.map((f, i) => `${i + 1}. [${f.memory_type || 'fact'}] ${f.content}`)
|
|
1075
|
+
.join("\n");
|
|
1076
|
+
return ok(`Extracted ${r.facts.length} facts (added: ${r.added}, updated: ${r.updated}, skipped: ${r.skipped}):\n\n${factList}`);
|
|
1077
|
+
}
|
|
1078
|
+
catch (e) {
|
|
1079
|
+
return err(e);
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
// ── IDE Upgrade Tools ──────────────────────────────────────────────
|
|
1083
|
+
// memory_pitfall_check — Check pitfalls before risky actions
|
|
1084
|
+
server.tool("memory_pitfall_check", "IMPORTANT: Call this BEFORE executing risky actions (deploy, rm, git push, database changes). Returns known pitfalls (past failures + lessons) so you can avoid repeating mistakes. Pitfalls are DNA-protected and never expire.", {
|
|
1085
|
+
intent: z.string().describe("What you're about to do (e.g. 'deploy to production', 'delete user table')"),
|
|
1086
|
+
tags: z.array(z.string()).optional().describe("Filter by tags"),
|
|
1087
|
+
limit: z.number().optional().describe("Max results (default 5)"),
|
|
1088
|
+
}, async (args) => {
|
|
1089
|
+
try {
|
|
1090
|
+
const r = (await api("POST", "/v1/bot/pitfall/check", {
|
|
1091
|
+
intent: args.intent,
|
|
1092
|
+
tags: args.tags,
|
|
1093
|
+
limit: args.limit || 5,
|
|
1094
|
+
}));
|
|
1095
|
+
if (!r.has_pitfalls)
|
|
1096
|
+
return ok("No known pitfalls for this action. Proceed safely.");
|
|
1097
|
+
const list = r.pitfalls
|
|
1098
|
+
.map((p, i) => `${i + 1}. [score: ${p.score}] ${p.content}`)
|
|
1099
|
+
.join("\n");
|
|
1100
|
+
return ok(`⚠️ ${r.pitfalls.length} pitfall(s) found:\n\n${list}\n\nReview before proceeding.`);
|
|
1101
|
+
}
|
|
1102
|
+
catch (e) {
|
|
1103
|
+
return err(e);
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
// memory_plan_save — Save current plan/state for session resumption
|
|
1107
|
+
server.tool("memory_plan_save", "Save your current work state (plan steps, cursor position, active goals) so you can resume exactly where you left off in the next session. Call before session ends or when switching tasks.", {
|
|
1108
|
+
session_id: z.string().optional().describe("Session identifier (default: 'default')"),
|
|
1109
|
+
state: z.record(z.string(), z.unknown()).describe("State to save: {plan: [...], cursor: 3, active_goal: '...', last_action: '...', files_read: [...]}"),
|
|
1110
|
+
}, async (args) => {
|
|
1111
|
+
try {
|
|
1112
|
+
const r = (await api("POST", "/v1/bot/state/save", {
|
|
1113
|
+
session_id: args.session_id || "default",
|
|
1114
|
+
state: args.state,
|
|
1115
|
+
}));
|
|
1116
|
+
return ok(`State saved for session '${r.session_id}'. Will be restored on next bootstrap.`);
|
|
1117
|
+
}
|
|
1118
|
+
catch (e) {
|
|
1119
|
+
return err(e);
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
// memory_plan_resume — Restore saved state from previous session
|
|
1123
|
+
server.tool("memory_plan_resume", "Restore your work state from a previous session. Returns plan steps, cursor position, active goals — everything needed to continue where you left off.", {
|
|
1124
|
+
session_id: z.string().optional().describe("Session identifier (default: 'default')"),
|
|
1125
|
+
}, async (args) => {
|
|
1126
|
+
try {
|
|
1127
|
+
const r = (await api("GET", `/v1/bot/state/restore?session_id=${args.session_id || "default"}`));
|
|
1128
|
+
if (r.status === "not_found")
|
|
1129
|
+
return ok("No saved state found for this session. Starting fresh.");
|
|
1130
|
+
return ok(`State restored (saved at ${r.saved_at}):\n\n${JSON.stringify(r.state, null, 2)}`);
|
|
1131
|
+
}
|
|
1132
|
+
catch (e) {
|
|
1133
|
+
return err(e);
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
// memory_goal_track — Create/update/query goals
|
|
1137
|
+
server.tool("memory_goal_track", "Track goals across sessions. Create new goals, update progress, or query active goals. Goals with status='active' are DNA-protected (never decay).", {
|
|
1138
|
+
action: z.enum(["create", "update", "list"]).describe("Action to perform"),
|
|
1139
|
+
title: z.string().optional().describe("Goal title (for create)"),
|
|
1140
|
+
progress: z.number().optional().describe("Progress 0.0-1.0 (for update)"),
|
|
1141
|
+
goal_id: z.number().optional().describe("Goal ID (for update)"),
|
|
1142
|
+
status: z.enum(["active", "achieved", "abandoned"]).optional().describe("New status (for update)"),
|
|
1143
|
+
}, async (args) => {
|
|
1144
|
+
try {
|
|
1145
|
+
if (args.action === "create") {
|
|
1146
|
+
const r = (await api("POST", "/v1/store", {
|
|
1147
|
+
content: args.title,
|
|
1148
|
+
memory_type: "goal",
|
|
1149
|
+
zone: "important",
|
|
1150
|
+
tags: ["goal", "active"],
|
|
1151
|
+
}));
|
|
1152
|
+
return ok(`Goal created: "${args.title}" (id: ${r.id}). DNA-protected while active.`);
|
|
1153
|
+
}
|
|
1154
|
+
else if (args.action === "list") {
|
|
1155
|
+
const r = (await api("POST", "/v1/recall", {
|
|
1156
|
+
query: "active goals and objectives",
|
|
1157
|
+
memory_type: "goal",
|
|
1158
|
+
depth: "deep",
|
|
1159
|
+
limit: 10,
|
|
1160
|
+
}));
|
|
1161
|
+
if (!r.results?.length)
|
|
1162
|
+
return ok("No active goals found.");
|
|
1163
|
+
const list = r.results.map((g, i) => `${i + 1}. ${g.content}`).join("\n");
|
|
1164
|
+
return ok(`Active goals:\n\n${list}`);
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
return ok("Goal update: use memory_store with memory_type='goal' to update goal content.");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
catch (e) {
|
|
1171
|
+
return err(e);
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
// memory_thought_log — Query what the brain has been thinking about
|
|
1175
|
+
server.tool("memory_thought_log", "See what the brain has been thinking about autonomously. Returns recent thoughts, hypotheses, and insights generated during idle time.", {
|
|
1176
|
+
limit: z.number().optional().describe("Max thoughts to return (default 5)"),
|
|
1177
|
+
}, async (args) => {
|
|
1178
|
+
try {
|
|
1179
|
+
const r = (await api("GET", `/v1/brain/thoughts?limit=${args.limit || 5}`));
|
|
1180
|
+
if (!r.thoughts?.length)
|
|
1181
|
+
return ok("No recent thoughts. The brain thinks during idle periods.");
|
|
1182
|
+
const list = r.thoughts
|
|
1183
|
+
.map((t, i) => `${i + 1}. [${t.thought_type}] ${t.content} (urgency: ${t.urgency})`)
|
|
1184
|
+
.join("\n");
|
|
1185
|
+
return ok(`Recent brain thoughts:\n\n${list}`);
|
|
1186
|
+
}
|
|
1187
|
+
catch (e) {
|
|
1188
|
+
return err(e);
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
// memory_feedback — Report recall quality for self-improvement
|
|
1192
|
+
server.tool("memory_feedback", "Report whether recall results were helpful. This feeds the neuroplasticity system — over time, the brain learns what works for YOUR specific patterns and improves recall quality.", {
|
|
1193
|
+
query: z.string().describe("The recall query that was made"),
|
|
1194
|
+
chunk_ids: z.array(z.number()).describe("IDs of chunks that were returned"),
|
|
1195
|
+
helpful: z.boolean().describe("Were the results helpful for your task?"),
|
|
1196
|
+
action_succeeded: z.boolean().optional().describe("Did the action using these memories succeed? (default: true)"),
|
|
1197
|
+
}, async (args) => {
|
|
1198
|
+
try {
|
|
1199
|
+
const r = (await api("POST", "/v1/bot/feedback", {
|
|
1200
|
+
query: args.query,
|
|
1201
|
+
chunk_ids: args.chunk_ids,
|
|
1202
|
+
helpful: args.helpful,
|
|
1203
|
+
action_succeeded: args.action_succeeded !== false,
|
|
1204
|
+
}));
|
|
1205
|
+
return ok(r.message || "Feedback recorded. Brain will adapt over time.");
|
|
1206
|
+
}
|
|
1207
|
+
catch (e) {
|
|
1208
|
+
return err(e);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
// memory_predict — Predictive recall (push intent, get predicted memories)
|
|
1212
|
+
server.tool("memory_predict", "Predictive recall — tell the brain what you're about to do and get relevant memories pre-loaded. Call this when you can anticipate what context will be needed next.", {
|
|
1213
|
+
intent: z.string().describe("What you/user are about to do"),
|
|
1214
|
+
context: z.string().optional().describe("Current conversation context (helps prediction accuracy)"),
|
|
1215
|
+
limit: z.number().optional().describe("Max predictions (default 5)"),
|
|
1216
|
+
}, async (args) => {
|
|
1217
|
+
try {
|
|
1218
|
+
const r = (await api("POST", "/v1/bot/predict", {
|
|
1219
|
+
intent: args.intent,
|
|
1220
|
+
context: args.context || "",
|
|
1221
|
+
limit: args.limit || 5,
|
|
1222
|
+
}));
|
|
1223
|
+
if (!r.predictions?.length)
|
|
1224
|
+
return ok("No relevant predictions for this intent.");
|
|
1225
|
+
const list = r.predictions
|
|
1226
|
+
.map((p, i) => `${i + 1}. [${p.memory_type || 'memory'}] ${p.content}\n (score: ${p.score}, reason: ${p.reason})`)
|
|
1227
|
+
.join("\n\n");
|
|
1228
|
+
return ok(`Predicted ${r.count} relevant memories:\n\n${list}`);
|
|
1229
|
+
}
|
|
1230
|
+
catch (e) {
|
|
1231
|
+
return err(e);
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
// memory_changelog — What changed since last session
|
|
1235
|
+
server.tool("memory_changelog", "See what changed in your memory since your last session. Shows new memories, updates, invalidations, and insights from overnight consolidation. Call at session start after bootstrap to understand what the brain learned while you were away.", {
|
|
1236
|
+
since: z.string().describe("ISO datetime — show changes after this time (e.g. '2026-05-20T10:00:00Z')"),
|
|
1237
|
+
project_id: z.string().optional().describe("Filter to specific project"),
|
|
1238
|
+
limit: z.number().optional().describe("Max changes to return (default 50)"),
|
|
1239
|
+
}, async (args) => {
|
|
1240
|
+
try {
|
|
1241
|
+
const r = (await api("POST", "/v1/memory/changelog", {
|
|
1242
|
+
since: args.since,
|
|
1243
|
+
project_id: args.project_id,
|
|
1244
|
+
limit: args.limit || 50,
|
|
1245
|
+
}));
|
|
1246
|
+
if (!r.changes?.length)
|
|
1247
|
+
return ok("No changes since last session. Memory is up to date.");
|
|
1248
|
+
const list = r.changes
|
|
1249
|
+
.map((c, i) => `${i + 1}. [${c.type}] ${c.content}${c.source ? ` (source: ${c.source})` : ""}`)
|
|
1250
|
+
.join("\n");
|
|
1251
|
+
return ok(`${r.count} changes since ${args.since}:\n\n${list}`);
|
|
1252
|
+
}
|
|
1253
|
+
catch (e) {
|
|
1254
|
+
return err(e);
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
// memory_cognitive_profile — Complete self-model (metacognition)
|
|
1258
|
+
server.tool("memory_cognitive_profile", "Get the brain's complete self-model: who the user is, their mood, active goals, top entities (people/places), learned procedures, and recent topics. Use for complete context awareness. No LLM cost — pure aggregation (~50ms).", {}, async () => {
|
|
1259
|
+
try {
|
|
1260
|
+
const r = (await api("GET", "/v1/personality/cognitive-profile"));
|
|
1261
|
+
let out = `## Cognitive Profile\n\n`;
|
|
1262
|
+
if (r.persona)
|
|
1263
|
+
out += `**Persona:** ${r.persona}\n\n`;
|
|
1264
|
+
if (r.mood)
|
|
1265
|
+
out += `**Mood:** ${r.mood.current} (trend: ${r.mood.trend})\n\n`;
|
|
1266
|
+
if (r.active_goals?.length)
|
|
1267
|
+
out += `**Active Goals:**\n${r.active_goals.map((g) => `- ${g}`).join("\n")}\n\n`;
|
|
1268
|
+
if (r.top_entities?.length)
|
|
1269
|
+
out += `**Top Entities:** ${r.top_entities.map((e) => e.name || e).join(", ")}\n\n`;
|
|
1270
|
+
if (r.procedures?.length)
|
|
1271
|
+
out += `**Procedures:**\n${r.procedures.map((p) => `- ${p.slice(0, 100)}`).join("\n")}\n\n`;
|
|
1272
|
+
if (r.recent_topics?.length)
|
|
1273
|
+
out += `**Recent Topics:**\n${r.recent_topics.map((t) => `- ${t}`).join("\n")}\n`;
|
|
1274
|
+
return ok(out.trim());
|
|
1275
|
+
}
|
|
1276
|
+
catch (e) {
|
|
1277
|
+
return err(e);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
// memory_entity_profile — Get everything known about an entity
|
|
1281
|
+
server.tool("memory_entity_profile", "Get complete profile for a specific entity (person, place, concept). Returns: frequency stats, linked memories, and relationships. Use when you need context about a specific person or topic the user has discussed.", {
|
|
1282
|
+
name: z.string().describe("Entity name to look up (e.g. 'Sarah', 'React', 'AuthService')"),
|
|
1283
|
+
}, async (args) => {
|
|
1284
|
+
try {
|
|
1285
|
+
const r = (await api("GET", `/v1/entities/${encodeURIComponent(args.name)}/profile`));
|
|
1286
|
+
if (!r.stats && !r.memories?.length)
|
|
1287
|
+
return ok(`No information found about "${args.name}".`);
|
|
1288
|
+
let out = `## Entity: ${args.name}\n\n`;
|
|
1289
|
+
if (r.stats) {
|
|
1290
|
+
out += `**Stats:** mentioned ${r.stats.frequency}x, recalled ${r.stats.recall_count}x`;
|
|
1291
|
+
if (r.stats.first_seen)
|
|
1292
|
+
out += `, first seen ${r.stats.first_seen.slice(0, 10)}`;
|
|
1293
|
+
out += `\n\n`;
|
|
1294
|
+
}
|
|
1295
|
+
if (r.memories?.length) {
|
|
1296
|
+
out += `**Linked Memories (${r.memory_count}):**\n`;
|
|
1297
|
+
out += r.memories.map((m) => `- [${m.memory_type}] ${m.content}`).join("\n");
|
|
1298
|
+
out += `\n\n`;
|
|
1299
|
+
}
|
|
1300
|
+
if (r.relationships?.length) {
|
|
1301
|
+
out += `**Relationships (${r.relationship_count}):**\n`;
|
|
1302
|
+
out += r.relationships.map((rel) => `- ${rel.source} → ${rel.relationship} → ${rel.target}`).join("\n");
|
|
1303
|
+
}
|
|
1304
|
+
return ok(out.trim());
|
|
1305
|
+
}
|
|
1306
|
+
catch (e) {
|
|
1307
|
+
return err(e);
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1311
|
+
// PHASE 5 (2026-05-28) — DNA-aligned tools:
|
|
1312
|
+
// • brain_export / brain_import — DNA #3 vendor neutrality
|
|
1313
|
+
// • benchmark_recall_vs_full — DNA #2 retina (measurable moat)
|
|
1314
|
+
// • benchmark_pricing — public model pricing reference
|
|
1315
|
+
// • trust_agents / trust_chunk — DNA #1.5 trust graph
|
|
1316
|
+
// • twin_respond / twin_status — Cognitive Twin (promax+ tier)
|
|
1317
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1318
|
+
// brain_export
|
|
1319
|
+
server.tool("brain_export", "Export the entire brain to a portable JSON bundle (vendor-neutral). Use when the user wants to back up their brain, migrate to another instance (e.g. lite-build on-prem), or comply with data-portability rights. The bundle is self-contained — chunks, edges, entities, L2 sessions, mood, agents — and includes a sha256 checksum. Returns the bundle JSON.", {
|
|
1320
|
+
scope: z.enum(["full", "dna_only", "since"]).optional().describe("'full'=everything (default), 'dna_only'=just preferences/decisions/identity (lightweight portable identity), 'since'=incremental (requires `since` ISO datetime)"),
|
|
1321
|
+
since: z.string().optional().describe("ISO8601 datetime, only used when scope='since'"),
|
|
1322
|
+
}, async (args) => {
|
|
1323
|
+
try {
|
|
1324
|
+
const r = (await api("POST", "/v1/brain/export", {
|
|
1325
|
+
scope: args.scope || "full",
|
|
1326
|
+
since: args.since,
|
|
1327
|
+
}));
|
|
1328
|
+
const counts = r?.manifest?.counts || {};
|
|
1329
|
+
const summary = `Exported brain bundle (format=${r.format} v${r.version}):\n` +
|
|
1330
|
+
`- chunks: ${counts.chunks ?? 0}\n` +
|
|
1331
|
+
`- memory_edges: ${counts.memory_edges ?? 0}\n` +
|
|
1332
|
+
`- entities: ${counts.entities ?? 0}\n` +
|
|
1333
|
+
`- l2_sessions: ${counts.l2_sessions ?? 0}\n` +
|
|
1334
|
+
`- agents: ${counts.agents ?? 0}\n` +
|
|
1335
|
+
`- checksum: ${r?.manifest?.checksum?.slice(0, 16)}...\n\n` +
|
|
1336
|
+
`Bundle JSON ready (truncated preview):\n\`\`\`json\n${JSON.stringify(r, null, 2).slice(0, 1200)}...\n\`\`\``;
|
|
1337
|
+
return ok(summary);
|
|
1338
|
+
}
|
|
1339
|
+
catch (e) {
|
|
1340
|
+
return err(e);
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
// brain_import
|
|
1344
|
+
server.tool("brain_import", "Import a MemoryAI bundle (from brain_export) into the current tenant. Idempotent — chunks deduped by content_hash; edges/entities upserted. Embeddings are reused if the bundle's embedding_model matches the local one; otherwise dropped (chunks re-embed lazily).", {
|
|
1345
|
+
bundle: z.record(z.string(), z.unknown()).describe("The bundle JSON produced by brain_export (must contain format='memoryai-bundle', version, manifest, etc.)"),
|
|
1346
|
+
keep_embeddings: z.boolean().optional().describe("Reuse bundle embeddings if model matches (default: true)"),
|
|
1347
|
+
}, async (args) => {
|
|
1348
|
+
try {
|
|
1349
|
+
const r = (await api("POST", `/v1/brain/import?keep_embeddings=${args.keep_embeddings === false ? "false" : "true"}`, args.bundle));
|
|
1350
|
+
const rep = r?.report || {};
|
|
1351
|
+
const out = `Brain import complete (format=${rep.bundle_format} v${rep.bundle_version}):\n` +
|
|
1352
|
+
`- chunks: ${rep.chunks?.inserted ?? 0} inserted, ${rep.chunks?.skipped_duplicate ?? 0} skipped (dup), ${rep.chunks?.skipped_invalid ?? 0} invalid\n` +
|
|
1353
|
+
`- memory_edges: ${rep.memory_edges?.upserted ?? 0} upserted (${rep.memory_edges?.skipped ?? 0} skipped)\n` +
|
|
1354
|
+
`- entities: ${rep.entities?.upserted ?? 0} upserted\n` +
|
|
1355
|
+
`- l2_sessions: ${rep.l2_sessions?.inserted ?? 0} inserted\n` +
|
|
1356
|
+
`- agents: ${rep.agents?.upserted ?? 0} upserted\n` +
|
|
1357
|
+
`- embedding_model_match: ${rep.embedding_model_match}\n` +
|
|
1358
|
+
(rep.warnings?.length ? `\nWarnings:\n ${rep.warnings.join("\n ")}` : "");
|
|
1359
|
+
return ok(out);
|
|
1360
|
+
}
|
|
1361
|
+
catch (e) {
|
|
1362
|
+
return err(e);
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
// benchmark_recall_vs_full
|
|
1366
|
+
server.tool("benchmark_recall_vs_full", "Run a public benchmark: smart recall vs full-context dump on the calling brain. DNA #2 — proves the 'retina for AI' moat with measurable numbers (cost, latency, signal density). Available on every tier; safe to share results publicly.", {
|
|
1367
|
+
query: z.string().describe("The query to benchmark (e.g. 'what does the user prefer?')"),
|
|
1368
|
+
model: z.string().optional().describe("Model whose pricing to apply (default: claude-opus-4-6). Affects $cost only."),
|
|
1369
|
+
naive_budget_tokens: z.number().optional().describe("Cap on full-context dump (default: 200K = Claude window)"),
|
|
1370
|
+
smart_top_k: z.number().optional().describe("Top-K chunks for smart mode (default: 8)"),
|
|
1371
|
+
smart_depth: z.enum(["instant", "fast", "deep"]).optional().describe("Smart recall depth (default: deep)"),
|
|
1372
|
+
}, async (args) => {
|
|
1373
|
+
try {
|
|
1374
|
+
const r = (await api("POST", "/v1/benchmark/recall-vs-fullcontext", {
|
|
1375
|
+
query: args.query,
|
|
1376
|
+
model: args.model || "claude-opus-4-6",
|
|
1377
|
+
naive_budget_tokens: args.naive_budget_tokens,
|
|
1378
|
+
smart_top_k: args.smart_top_k,
|
|
1379
|
+
smart_depth: args.smart_depth,
|
|
1380
|
+
}));
|
|
1381
|
+
const out = `Benchmark — query: ${JSON.stringify(r.query)}\n` +
|
|
1382
|
+
`Model: ${r.model} ($${r.price_per_m_tokens_usd}/M tokens)\n\n` +
|
|
1383
|
+
`NAIVE: ${r.naive.chunks_used} chunks · ${r.naive.input_tokens} tok · $${r.naive.estimated_cost_usd} · ${r.naive.latency_ms}ms\n` +
|
|
1384
|
+
`SMART: ${r.smart.chunks_used} chunks · ${r.smart.input_tokens} tok · $${r.smart.estimated_cost_usd} · ${r.smart.latency_ms}ms\n\n` +
|
|
1385
|
+
`→ ${r.headline}`;
|
|
1386
|
+
return ok(out);
|
|
1387
|
+
}
|
|
1388
|
+
catch (e) {
|
|
1389
|
+
return err(e);
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
// benchmark_pricing
|
|
1393
|
+
server.tool("benchmark_pricing", "Get the assumed $/1M-input-tokens pricing for each LLM (used by benchmark_recall_vs_full). No auth required; list prices only.", {}, async () => {
|
|
1394
|
+
try {
|
|
1395
|
+
const r = (await api("GET", "/v1/benchmark/pricing"));
|
|
1396
|
+
const lines = Object.entries(r.prices || {}).map(([k, v]) => `- ${k}: $${v}`);
|
|
1397
|
+
return ok(`Model pricing (${r.currency} ${r.unit}, as of ${r.as_of}):\n${lines.join("\n")}\n\nNote: ${r.note}`);
|
|
1398
|
+
}
|
|
1399
|
+
catch (e) {
|
|
1400
|
+
return err(e);
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
// trust_agents
|
|
1404
|
+
server.tool("trust_agents", "Get the agent reputation leaderboard (sorted by reputation_score desc). DNA #1.5 — when 20 agents share memory and disagree, this tells you whose claims to trust. Requires team+ plan.", {
|
|
1405
|
+
limit: z.number().optional().describe("Max agents to return (default: 50, max: 500)"),
|
|
1406
|
+
}, async (args) => {
|
|
1407
|
+
try {
|
|
1408
|
+
const r = (await api("GET", `/v1/trust/agents?limit=${args.limit ?? 50}`));
|
|
1409
|
+
if (!r.agents?.length)
|
|
1410
|
+
return ok("No agent reputation snapshots yet. Run /v1/trust/recompute-all to populate.");
|
|
1411
|
+
const lines = r.agents.map((a) => `- ${a.agent_id}: ${a.reputation_score.toFixed(3)} [${a.label}] — V=${a.verified_count}/C=${a.contradicted_count}/N=${a.feedback_n}`);
|
|
1412
|
+
return ok(`Agent reputation (${r.agents.length} agents):\n${lines.join("\n")}`);
|
|
1413
|
+
}
|
|
1414
|
+
catch (e) {
|
|
1415
|
+
return err(e);
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
// trust_chunk
|
|
1419
|
+
server.tool("trust_chunk", "Get per-chunk trust info: which agent claimed it, that agent's reputation, helpful/unhelpful counts. Use after a recall to decide whether to trust a specific result. Available on every paid tier.", {
|
|
1420
|
+
chunk_id: z.number().describe("The chunk ID returned by recall"),
|
|
1421
|
+
}, async (args) => {
|
|
1422
|
+
try {
|
|
1423
|
+
const r = (await api("GET", `/v1/trust/chunks/${args.chunk_id}`));
|
|
1424
|
+
const stats = r.agent_stats || {};
|
|
1425
|
+
const fb = r.feedback || {};
|
|
1426
|
+
return ok(`Chunk #${r.chunk_id} (${r.memory_type})\n` +
|
|
1427
|
+
`Source agent: ${r.source_agent_id ?? "(none)"}\n` +
|
|
1428
|
+
`Agent trust: ${r.agent_trust?.toFixed(3) ?? "(none)"} — V=${stats.verified_count ?? 0}/C=${stats.contradicted_count ?? 0}/total=${stats.claim_count ?? 0}\n` +
|
|
1429
|
+
`Per-chunk trust: ${r.chunk_trust_score?.toFixed(3) ?? "(none)"} (helpful=${fb.helpful ?? 0}, unhelpful=${fb.unhelpful ?? 0})`);
|
|
1430
|
+
}
|
|
1431
|
+
catch (e) {
|
|
1432
|
+
return err(e);
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
// twin_respond
|
|
1436
|
+
server.tool("twin_respond", "Ask the Cognitive Twin to predict how the user would respond to a given query. The twin uses the user's personality + mood + DNA + procedures to simulate their voice. Returns the predicted response, a confidence score 0-1, and the chunk IDs that informed it (provenance trail). Auto-refuses with confidence=0 if the brain has fewer than 5 DNA memories. Requires promax+ plan.", {
|
|
1437
|
+
query: z.string().describe("The free-form question to ask the twin (e.g. 'what database for a chat app?')"),
|
|
1438
|
+
operation: z.enum(["respond", "decide"]).optional().describe("'respond'=free-form answer (default), 'decide'=pick one option from the query"),
|
|
1439
|
+
}, async (args) => {
|
|
1440
|
+
try {
|
|
1441
|
+
const path = args.operation === "decide" ? "/v1/twin/decide" : "/v1/twin/respond";
|
|
1442
|
+
const r = (await api("POST", path, { query: args.query }));
|
|
1443
|
+
const out = `Cognitive Twin (${r.operation}, confidence ${r.confidence}):\n` +
|
|
1444
|
+
`Persona: ${r.persona_summary || "(not synthesized)"}\n` +
|
|
1445
|
+
`Mood: ${r.mood || "unknown"}\n` +
|
|
1446
|
+
`Provenance chunks: ${r.provenance_chunks?.length ?? 0}\n\n` +
|
|
1447
|
+
`Response:\n${r.response}\n\n` +
|
|
1448
|
+
`(reason: ${r.confidence_reason})`;
|
|
1449
|
+
return ok(out);
|
|
1450
|
+
}
|
|
1451
|
+
catch (e) {
|
|
1452
|
+
return err(e);
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
// twin_status
|
|
1456
|
+
server.tool("twin_status", "Check whether the Cognitive Twin is ready for the calling tenant. Cheap — no LLM call. Returns DNA count, personality/mood presence, and a `ready` boolean. Useful before invoking twin_respond.", {}, async () => {
|
|
1457
|
+
try {
|
|
1458
|
+
const r = (await api("GET", "/v1/twin/status"));
|
|
1459
|
+
return ok(`Twin ready: ${r.ready ? "YES" : "NO"} (need ≥${r.min_dna_required} DNA, have ${r.dna_count})\n` +
|
|
1460
|
+
`- has_personality: ${r.has_personality}\n` +
|
|
1461
|
+
`- has_mood: ${r.has_mood}\n` +
|
|
1462
|
+
`- procedures: ${r.procedures_count}\n` +
|
|
1463
|
+
`- active_goals: ${r.active_goals_count}\n` +
|
|
1464
|
+
`- top_entities: ${r.top_entities_count}\n` +
|
|
1465
|
+
(r.persona_summary ? `\nPersona: ${r.persona_summary}\n` : "") +
|
|
1466
|
+
(r.mood ? `Mood: ${r.mood}\n` : ""));
|
|
1467
|
+
}
|
|
1468
|
+
catch (e) {
|
|
1469
|
+
return err(e);
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
873
1472
|
async function main() {
|
|
874
1473
|
const transport = new StdioServerTransport();
|
|
875
1474
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memoryai-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "MCP server for MemoryAI
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "MCP server for MemoryAI v2.0 — One brain. ∞ agents. Forever. Adds Brain Export/Import (vendor-neutral bundles), Public Benchmark (smart recall vs full context), Trust Graph (per-agent reputation), Cognitive Twin (simulate user voice). Plus the v1.5 base: 11 biological behaviors, DNA-protected memories, Multi-Agent Mesh.",
|
|
5
5
|
"homepage": "https://memoryai.dev",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|