oh-memos-mcp 1.0.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.
Files changed (41) hide show
  1. package/.env.example +27 -0
  2. package/README.md +71 -0
  3. package/dist/api-client.d.ts +22 -0
  4. package/dist/api-client.js +127 -0
  5. package/dist/config.d.ts +30 -0
  6. package/dist/config.js +163 -0
  7. package/dist/cube-manager.d.ts +16 -0
  8. package/dist/cube-manager.js +357 -0
  9. package/dist/formatters.d.ts +16 -0
  10. package/dist/formatters.js +174 -0
  11. package/dist/handlers/admin.d.ts +13 -0
  12. package/dist/handlers/admin.js +331 -0
  13. package/dist/handlers/calendar.d.ts +8 -0
  14. package/dist/handlers/calendar.js +281 -0
  15. package/dist/handlers/graph.d.ts +11 -0
  16. package/dist/handlers/graph.js +502 -0
  17. package/dist/handlers/index.d.ts +7 -0
  18. package/dist/handlers/index.js +71 -0
  19. package/dist/handlers/memory.d.ts +12 -0
  20. package/dist/handlers/memory.js +270 -0
  21. package/dist/handlers/search.d.ts +11 -0
  22. package/dist/handlers/search.js +334 -0
  23. package/dist/handlers/utils.d.ts +22 -0
  24. package/dist/handlers/utils.js +75 -0
  25. package/dist/index.d.ts +13 -0
  26. package/dist/index.js +18 -0
  27. package/dist/keyword-enhancer.d.ts +18 -0
  28. package/dist/keyword-enhancer.js +335 -0
  29. package/dist/memory-analysis.d.ts +7 -0
  30. package/dist/memory-analysis.js +45 -0
  31. package/dist/models.d.ts +21 -0
  32. package/dist/models.js +101 -0
  33. package/dist/query-processing.d.ts +20 -0
  34. package/dist/query-processing.js +265 -0
  35. package/dist/server.d.ts +5 -0
  36. package/dist/server.js +114 -0
  37. package/dist/tools-registry.d.ts +303 -0
  38. package/dist/tools-registry.js +369 -0
  39. package/dist/types.d.ts +118 -0
  40. package/dist/types.js +5 -0
  41. package/package.json +53 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Query Processing Module
3
+ *
4
+ * Intent detection, keyword extraction, reranking, type extraction.
5
+ */
6
+ import type { SearchData, MemoryNode, QueryIntent } from "./types.js";
7
+ export declare const INTENT_TO_GRAPHS: Record<string, string[]>;
8
+ export declare function detectQueryIntent(query: string): QueryIntent;
9
+ export declare function getGraphsForIntent(intent: QueryIntent): string[];
10
+ export declare function getIntentDescription(intent: QueryIntent): string;
11
+ export declare function parseMemoryTypePrefix(query: string): [string | null, string];
12
+ export declare function extractKeywords(query: string): string[];
13
+ export declare function keywordMatchScore(text: string, keywords: string[], metadata?: Record<string, unknown>, enableFuzzy?: boolean): number;
14
+ export declare function applyKeywordRerank(data: SearchData, query: string, enableFuzzy?: boolean): SearchData;
15
+ export declare function filterEdgesByIntent(data: SearchData, intent: QueryIntent): SearchData;
16
+ export declare function filterMemoriesByType(data: SearchData, memType: string | null): SearchData;
17
+ export declare function extractMemoriesFromResponse(data: SearchData): MemoryNode[];
18
+ export declare function extractMcpType(memory: MemoryNode | Record<string, unknown>): string;
19
+ export declare function computeMemoryStats(data: SearchData): [Record<string, number>, number];
20
+ //# sourceMappingURL=query-processing.d.ts.map
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Query Processing Module
3
+ *
4
+ * Intent detection, keyword extraction, reranking, type extraction.
5
+ */
6
+ import { MEMORY_TYPES } from "./config.js";
7
+ import { ALL_STOPWORDS, extractKeywordsEnhanced, keywordMatchScoreEnhanced } from "./keyword-enhancer.js";
8
+ // ============================================================================
9
+ // Intent to Graph Type Mapping
10
+ // ============================================================================
11
+ export const INTENT_TO_GRAPHS = {
12
+ causal: ["CAUSE", "CONDITION"],
13
+ related: ["RELATE"],
14
+ conflict: ["CONFLICT"],
15
+ temporal: ["FOLLOWS"],
16
+ default: ["CAUSE", "RELATE", "CONDITION"],
17
+ };
18
+ export function detectQueryIntent(query) {
19
+ const q = query.toLowerCase();
20
+ const causalPatterns = [
21
+ /为什么/, /why/, /原因/, /cause/, /导致/, /因为/,
22
+ /根本原因/, /root\s*cause/, /怎么.*出错/, /how.*fail/,
23
+ /什么.*引起/, /what.*caused/, /出了什么问题/,
24
+ ];
25
+ if (causalPatterns.some((p) => p.test(q)))
26
+ return "causal";
27
+ const conflictPatterns = [
28
+ /冲突/, /conflict/, /矛盾/, /contradict/, /不一致/,
29
+ /inconsisten/, /问题.*之间/, /clash/,
30
+ ];
31
+ if (conflictPatterns.some((p) => p.test(q)))
32
+ return "conflict";
33
+ const temporalPatterns = [
34
+ /什么时候/, /when/, /之前/, /before/, /之后/, /after/,
35
+ /最近/, /recent/, /先.*后/, /顺序/, /timeline/, /历史/,
36
+ /上次/, /last\s*time/, /earlier/, /previously/,
37
+ ];
38
+ if (temporalPatterns.some((p) => p.test(q)))
39
+ return "temporal";
40
+ const relatedPatterns = [
41
+ /相关/, /related/, /关联/, /有关/, /涉及/,
42
+ /association/, /connect/, /link/,
43
+ ];
44
+ if (relatedPatterns.some((p) => p.test(q)))
45
+ return "related";
46
+ return "default";
47
+ }
48
+ export function getGraphsForIntent(intent) {
49
+ return INTENT_TO_GRAPHS[intent] ?? INTENT_TO_GRAPHS.default;
50
+ }
51
+ export function getIntentDescription(intent) {
52
+ const descriptions = {
53
+ causal: "🔍 因果分析 (Causal Analysis)",
54
+ related: "🔗 关联查询 (Related Search)",
55
+ conflict: "⚠️ 冲突检测 (Conflict Detection)",
56
+ temporal: "📅 时序查询 (Temporal Search)",
57
+ default: "📚 综合查询 (General Search)",
58
+ };
59
+ return descriptions[intent] ?? descriptions.default;
60
+ }
61
+ // ============================================================================
62
+ // Memory Type Prefix Parsing
63
+ // ============================================================================
64
+ export function parseMemoryTypePrefix(query) {
65
+ if (!query)
66
+ return [null, ""];
67
+ const m = query.match(/^\s*\[?([A-Z_]+)\]?\s*[:\-]?\s*(.*)$/);
68
+ if (!m)
69
+ return [null, query.trim()];
70
+ const memType = m[1];
71
+ const rest = (m[2] ?? "").trim();
72
+ if (MEMORY_TYPES.has(memType)) {
73
+ return [memType, rest];
74
+ }
75
+ return [null, query.trim()];
76
+ }
77
+ // ============================================================================
78
+ // Keyword Extraction
79
+ // ============================================================================
80
+ export function extractKeywords(query) {
81
+ return extractKeywordsEnhanced(query, ALL_STOPWORDS);
82
+ }
83
+ // ============================================================================
84
+ // Keyword Match Score
85
+ // ============================================================================
86
+ export function keywordMatchScore(text, keywords, metadata, enableFuzzy = true) {
87
+ return keywordMatchScoreEnhanced(text, keywords, metadata, enableFuzzy);
88
+ }
89
+ // ============================================================================
90
+ // Reranking
91
+ // ============================================================================
92
+ export function applyKeywordRerank(data, query, enableFuzzy = true) {
93
+ const keywords = extractKeywords(query);
94
+ if (keywords.length === 0)
95
+ return data;
96
+ const textMems = data.text_mem ?? [];
97
+ for (const cubeData of textMems) {
98
+ const memData = cubeData.memories;
99
+ if (memData && !Array.isArray(memData) && memData.nodes) {
100
+ memData.nodes = [...memData.nodes].sort((a, b) => {
101
+ const scoreA = (a.metadata?.relativity ?? 0) + keywordMatchScore(a.memory ?? "", keywords, a.metadata, enableFuzzy);
102
+ const scoreB = (b.metadata?.relativity ?? 0) + keywordMatchScore(b.memory ?? "", keywords, b.metadata, enableFuzzy);
103
+ return scoreB - scoreA;
104
+ });
105
+ }
106
+ else if (Array.isArray(memData)) {
107
+ memData.sort((a, b) => {
108
+ const scoreA = (a.metadata?.relativity ?? 0) + keywordMatchScore(a.memory ?? "", keywords, a.metadata, enableFuzzy);
109
+ const scoreB = (b.metadata?.relativity ?? 0) + keywordMatchScore(b.memory ?? "", keywords, b.metadata, enableFuzzy);
110
+ return scoreB - scoreA;
111
+ });
112
+ }
113
+ }
114
+ return data;
115
+ }
116
+ // ============================================================================
117
+ // Edge Filtering by Intent
118
+ // ============================================================================
119
+ export function filterEdgesByIntent(data, intent) {
120
+ const allowedEdgeTypes = getGraphsForIntent(intent);
121
+ const textMems = data.text_mem ?? [];
122
+ for (const cubeData of textMems) {
123
+ const memData = cubeData.memories;
124
+ if (memData && !Array.isArray(memData) && memData.edges && memData.nodes) {
125
+ const filteredEdges = memData.edges.filter((edge) => edge.type && allowedEdgeTypes.includes(edge.type));
126
+ memData.edges = filteredEdges;
127
+ if (filteredEdges.length > 0) {
128
+ const boostedIds = new Set();
129
+ for (const edge of filteredEdges) {
130
+ if (edge.source)
131
+ boostedIds.add(edge.source);
132
+ if (edge.target)
133
+ boostedIds.add(edge.target);
134
+ }
135
+ for (const node of memData.nodes) {
136
+ if (node.id && boostedIds.has(node.id) && node.metadata) {
137
+ node.metadata.relativity = (node.metadata.relativity ?? 0) + 0.5;
138
+ node.metadata.intent_matched = true;
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ return data;
145
+ }
146
+ // ============================================================================
147
+ // Memory Type Filter
148
+ // ============================================================================
149
+ export function filterMemoriesByType(data, memType) {
150
+ if (!memType)
151
+ return data;
152
+ const textMems = data.text_mem ?? [];
153
+ for (const cubeData of textMems) {
154
+ const memData = cubeData.memories;
155
+ if (memData && !Array.isArray(memData) && memData.nodes) {
156
+ const filtered = memData.nodes.filter((n) => new RegExp(`^\\[${memType}\\]`).test(n.memory ?? ""));
157
+ if (memData.edges) {
158
+ const keptIds = new Set(filtered.map((n) => n.id));
159
+ memData.edges = memData.edges.filter((e) => e.source && keptIds.has(e.source) && e.target && keptIds.has(e.target));
160
+ }
161
+ memData.nodes = filtered;
162
+ }
163
+ else if (Array.isArray(memData)) {
164
+ cubeData.memories = memData.filter((m) => new RegExp(`^\\[${memType}\\]`).test(m.memory ?? ""));
165
+ }
166
+ }
167
+ return data;
168
+ }
169
+ // ============================================================================
170
+ // Memory Extraction
171
+ // ============================================================================
172
+ export function extractMemoriesFromResponse(data) {
173
+ const memories = [];
174
+ const textMems = data.text_mem ?? [];
175
+ for (const cubeData of textMems) {
176
+ const memData = cubeData.memories;
177
+ if (memData && !Array.isArray(memData) && memData.nodes) {
178
+ memories.push(...memData.nodes);
179
+ }
180
+ else if (Array.isArray(memData)) {
181
+ memories.push(...memData);
182
+ }
183
+ }
184
+ return memories;
185
+ }
186
+ // ============================================================================
187
+ // MCP Type Extraction
188
+ // ============================================================================
189
+ const VALID_MEMORY_TYPES = new Set([
190
+ "BUGFIX", "ERROR_PATTERN", "DECISION", "GOTCHA", "CODE_PATTERN",
191
+ "CONFIG", "FEATURE", "MILESTONE", "PROGRESS",
192
+ ]);
193
+ const TYPE_PATTERN = /^\[([A-Z_]+)\]/;
194
+ function extractTypeFromText(text) {
195
+ const m = TYPE_PATTERN.exec(text);
196
+ if (m && VALID_MEMORY_TYPES.has(m[1]))
197
+ return m[1];
198
+ return null;
199
+ }
200
+ function decodeSourceEntry(entry) {
201
+ if (entry && typeof entry === "object")
202
+ return entry;
203
+ if (typeof entry !== "string")
204
+ return null;
205
+ try {
206
+ let result = JSON.parse(entry);
207
+ if (typeof result === "string")
208
+ result = JSON.parse(result);
209
+ return typeof result === "object" && result !== null ? result : null;
210
+ }
211
+ catch {
212
+ return null;
213
+ }
214
+ }
215
+ function extractTypeFromSources(sources) {
216
+ if (!sources || sources.length === 0)
217
+ return null;
218
+ for (const entry of sources) {
219
+ const decoded = decodeSourceEntry(entry);
220
+ if (decoded) {
221
+ const content = String(decoded.content ?? "");
222
+ const found = extractTypeFromText(content);
223
+ if (found)
224
+ return found;
225
+ }
226
+ }
227
+ return null;
228
+ }
229
+ export function extractMcpType(memory) {
230
+ // 1. [TYPE] prefix in memory text
231
+ const memText = memory.memory ?? "";
232
+ const fromText = extractTypeFromText(memText);
233
+ if (fromText)
234
+ return fromText;
235
+ // 2. [TYPE] prefix in sources (tree_text mode)
236
+ const sources = memory.sources ??
237
+ (memory.metadata?.sources) ??
238
+ [];
239
+ const fromSources = extractTypeFromSources(sources);
240
+ if (fromSources)
241
+ return fromSources;
242
+ // 3. LLM-inferred nodes
243
+ const meta = memory.metadata ?? memory;
244
+ const nodeType = typeof meta === "object" ? meta.type : null;
245
+ const key = memory.key ?? (typeof meta === "object" ? String(meta.key ?? "") : "");
246
+ if (nodeType === "reasoning" || String(key).startsWith("InferredFact")) {
247
+ return "INFERRED";
248
+ }
249
+ return "PROGRESS";
250
+ }
251
+ // ============================================================================
252
+ // Stats
253
+ // ============================================================================
254
+ export function computeMemoryStats(data) {
255
+ const memories = extractMemoriesFromResponse(data);
256
+ const stats = {};
257
+ let total = 0;
258
+ for (const mem of memories) {
259
+ total++;
260
+ const memType = extractMcpType(mem);
261
+ stats[memType] = (stats[memType] ?? 0) + 1;
262
+ }
263
+ return [stats, total];
264
+ }
265
+ //# sourceMappingURL=query-processing.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * MemOS MCP Server — McpServer setup + background init
3
+ */
4
+ export declare function runServer(): Promise<void>;
5
+ //# sourceMappingURL=server.d.ts.map
package/dist/server.js ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * MemOS MCP Server — McpServer setup + background init
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { MEMOS_DEFAULT_CUBE, MEMOS_ENABLE_DELETE, logger } from "./config.js";
7
+ import { waitForApiReady } from "./api-client.js";
8
+ import { ensureCubeRegistered } from "./cube-manager.js";
9
+ import { toolSchemas } from "./tools-registry.js";
10
+ import { dispatchTool, handleApiUnreachable } from "./handlers/index.js";
11
+ // ============================================================================
12
+ // Register All Tools
13
+ // ============================================================================
14
+ function registerTools(server) {
15
+ for (const [name, schema] of Object.entries(toolSchemas)) {
16
+ // Skip delete tool if not enabled
17
+ if (name === "memos_delete" && !MEMOS_ENABLE_DELETE) {
18
+ logger.debug("Delete tool skipped (MEMOS_ENABLE_DELETE=false)");
19
+ continue;
20
+ }
21
+ server.registerTool(name, {
22
+ description: schema.description,
23
+ inputSchema: schema.inputSchema,
24
+ }, async (args) => {
25
+ try {
26
+ const result = await dispatchTool(name, args);
27
+ return {
28
+ content: result.map((r) => ({ type: "text", text: r.text })),
29
+ };
30
+ }
31
+ catch (err) {
32
+ const errStr = String(err);
33
+ if (errStr.includes("ECONNREFUSED") || errStr.includes("fetch failed")) {
34
+ const unreachable = await handleApiUnreachable();
35
+ return {
36
+ content: unreachable.map((r) => ({ type: "text", text: r.text })),
37
+ };
38
+ }
39
+ logger.exception("Tool call failed", err);
40
+ return {
41
+ content: [
42
+ {
43
+ type: "text",
44
+ text: [
45
+ `❌ [UNEXPECTED_ERROR] ${errStr}`,
46
+ "",
47
+ "💡 Suggestions:",
48
+ "- Check MCP server logs for details",
49
+ "- Verify MemOS API is healthy: `curl http://localhost:18000/health/detail`",
50
+ ].join("\n"),
51
+ },
52
+ ],
53
+ };
54
+ }
55
+ });
56
+ }
57
+ if (MEMOS_ENABLE_DELETE) {
58
+ logger.debug("Delete tool enabled (MEMOS_ENABLE_DELETE=true)");
59
+ }
60
+ }
61
+ // ============================================================================
62
+ // Background Init
63
+ // ============================================================================
64
+ async function backgroundInit() {
65
+ try {
66
+ const apiReady = await waitForApiReady();
67
+ if (apiReady) {
68
+ for (let attempt = 0; attempt < 3; attempt++) {
69
+ try {
70
+ const [regSuccess, regError] = await ensureCubeRegistered(MEMOS_DEFAULT_CUBE, true);
71
+ if (regSuccess) {
72
+ logger.debug(`Default cube '${MEMOS_DEFAULT_CUBE}' ready`);
73
+ return;
74
+ }
75
+ logger.warning(`Cube registration attempt ${attempt + 1} failed: ${regError}`);
76
+ await sleep(2000);
77
+ }
78
+ catch (err) {
79
+ logger.warning(`Registration attempt ${attempt + 1} error: ${err}`);
80
+ await sleep(2000);
81
+ }
82
+ }
83
+ }
84
+ else {
85
+ logger.warning("API not ready, will register cube on first tool call");
86
+ }
87
+ }
88
+ catch (err) {
89
+ logger.warning(`Background init failed: ${err}`);
90
+ }
91
+ }
92
+ // ============================================================================
93
+ // Run Server
94
+ // ============================================================================
95
+ export async function runServer() {
96
+ const server = new McpServer({
97
+ name: "memos-memory",
98
+ version: "1.0.0",
99
+ });
100
+ registerTools(server);
101
+ const transport = new StdioServerTransport();
102
+ // Connect FIRST (completes MCP handshake immediately),
103
+ // then do background init to prevent Claude Code timeout.
104
+ await server.connect(transport);
105
+ // Background init — don't await
106
+ backgroundInit().catch((err) => {
107
+ logger.error(`Background init error: ${err}`);
108
+ });
109
+ logger.debug("MemOS MCP Server (Node.js) started");
110
+ }
111
+ function sleep(ms) {
112
+ return new Promise((resolve) => setTimeout(resolve, ms));
113
+ }
114
+ //# sourceMappingURL=server.js.map