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
package/.env.example ADDED
@@ -0,0 +1,27 @@
1
+ # ============================================================================
2
+ # oh-memos-mcp Environment Configuration
3
+ # ============================================================================
4
+ # Copy this file to .env in your project root and adjust values.
5
+ # Priority: CLI args > environment variables > defaults
6
+ # ============================================================================
7
+
8
+ # --- Required ---
9
+ MEMOS_URL=http://localhost:18000 # oh-memos API address
10
+ MEMOS_USER=dev_user # Default user ID
11
+ MEMOS_DEFAULT_CUBE=dev_cube # Default memory cube ID
12
+ MEMOS_CUBES_DIR=/path/to/cubes # Absolute path to cubes directory
13
+
14
+ # --- Timeouts (seconds) ---
15
+ # MEMOS_TIMEOUT_TOOL=300 # Tool call timeout (default: 300)
16
+ # MEMOS_TIMEOUT_STARTUP=30 # Startup timeout (default: 30)
17
+ # MEMOS_TIMEOUT_HEALTH=5 # Health check timeout (default: 5)
18
+ # MEMOS_API_WAIT_MAX=60 # Max API wait time (default: 60)
19
+
20
+ # --- Feature Flags ---
21
+ # MEMOS_ENABLE_DELETE=false # Enable memory deletion (default: false)
22
+ # MEMOS_LOG_LEVEL=WARNING # DEBUG | INFO | WARNING | ERROR
23
+
24
+ # --- Neo4j (optional, for direct graph queries) ---
25
+ # NEO4J_HTTP_URL=http://localhost:7474
26
+ # NEO4J_USER=neo4j
27
+ # NEO4J_PASSWORD=your_password
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # oh-memos-mcp
2
+
3
+ MCP Server for MemOS — Intelligent Memory Management for AI Assistants.
4
+
5
+ Node.js implementation. No Python required.
6
+
7
+ ## Quick Start
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "memos": {
13
+ "command": "npx",
14
+ "args": ["-y", "oh-memos-mcp"],
15
+ "env": {
16
+ "MEMOS_URL": "http://localhost:18000",
17
+ "MEMOS_USER": "dev_user",
18
+ "MEMOS_DEFAULT_CUBE": "dev_cube",
19
+ "MEMOS_CUBES_DIR": "/path/to/data/memos_cubes"
20
+ }
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## Environment Variables
27
+
28
+ | Variable | Required | Default | Description |
29
+ |----------|----------|---------|-------------|
30
+ | `MEMOS_URL` | Yes | — | MemOS API base URL |
31
+ | `MEMOS_USER` | Yes | — | Default user ID |
32
+ | `MEMOS_DEFAULT_CUBE` | Yes | — | Default memory cube ID |
33
+ | `MEMOS_CUBES_DIR` | Yes | — | Path to cubes storage directory |
34
+ | `NEO4J_HTTP_URL` | No | — | Neo4j HTTP endpoint for graph queries |
35
+ | `NEO4J_USER` | No | — | Neo4j username |
36
+ | `NEO4J_PASSWORD` | No | — | Neo4j password |
37
+ | `MEMOS_ENABLE_DELETE` | No | `false` | Enable delete functionality |
38
+
39
+ ## Tools (18)
40
+
41
+ - **memos_save** — Save memories with explicit type classification
42
+ - **memos_search** — Semantic search with keyword reranking
43
+ - **memos_search_context** — Context-aware search with conversation history
44
+ - **memos_list_v2** — List memories with compaction
45
+ - **memos_get** — Get full memory details by ID
46
+ - **memos_get_stats** — Memory type statistics
47
+ - **memos_suggest** — Smart search suggestions
48
+ - **memos_context_resume** — Recover context after compaction
49
+ - **memos_trace_path** — Trace reasoning paths between memories
50
+ - **memos_get_graph** — Knowledge graph with relationships
51
+ - **memos_export_schema** — Graph schema and statistics
52
+ - **memos_impact** — Forward blast radius analysis
53
+ - **memos_calendar** — Calendar view (project/student modes)
54
+ - **memos_list_cubes** — List available memory cubes
55
+ - **memos_register_cube** — Register a cube with the API
56
+ - **memos_create_user** — Create a MemOS user
57
+ - **memos_validate_cubes** — Validate and fix cube configs
58
+ - **memos_delete** — Delete memories (disabled by default)
59
+
60
+ ## Development
61
+
62
+ ```bash
63
+ npm install
64
+ npm run dev # Run with tsx (no build needed)
65
+ npm run build # Compile to dist/
66
+ ```
67
+
68
+ ## Requirements
69
+
70
+ - Node.js >= 18.0.0
71
+ - MemOS backend API running (Python FastAPI, port 18000)
@@ -0,0 +1,22 @@
1
+ /**
2
+ * MemOS MCP Server API Client Module
3
+ *
4
+ * HTTP fetch wrapper with timeout, retry, and health check.
5
+ */
6
+ export declare function fetchWithTimeout(url: string, options?: RequestInit & {
7
+ timeoutMs?: number;
8
+ }): Promise<Response>;
9
+ export declare function waitForApiReady(maxWait?: number, interval?: number): Promise<boolean>;
10
+ export type HttpMethod = "GET" | "POST" | "DELETE";
11
+ export interface ApiCallOptions {
12
+ body?: unknown;
13
+ params?: Record<string, string | number | boolean | undefined>;
14
+ headers?: Record<string, string>;
15
+ }
16
+ export interface ApiCallResult {
17
+ success: boolean;
18
+ data: Record<string, unknown> | null;
19
+ status: number;
20
+ }
21
+ export declare function apiCallWithRetry(method: HttpMethod, url: string, cubeId: string, options?: ApiCallOptions, ensureCubeRegistered?: (cubeId: string, force?: boolean) => Promise<[boolean, string | null]>): Promise<ApiCallResult>;
22
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1,127 @@
1
+ /**
2
+ * MemOS MCP Server API Client Module
3
+ *
4
+ * HTTP fetch wrapper with timeout, retry, and health check.
5
+ */
6
+ import { MEMOS_URL, MEMOS_API_WAIT_MAX, MEMOS_TIMEOUT_HEALTH, MEMOS_TIMEOUT_TOOL, logger, registeredCubes } from "./config.js";
7
+ // ============================================================================
8
+ // Fetch with Timeout
9
+ // ============================================================================
10
+ export async function fetchWithTimeout(url, options = {}) {
11
+ const timeoutMs = (options.timeoutMs ?? MEMOS_TIMEOUT_TOOL) * 1000;
12
+ const { timeoutMs: _, ...fetchOptions } = options;
13
+ const controller = new AbortController();
14
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
15
+ try {
16
+ const response = await fetch(url, {
17
+ ...fetchOptions,
18
+ signal: controller.signal,
19
+ });
20
+ return response;
21
+ }
22
+ finally {
23
+ clearTimeout(timer);
24
+ }
25
+ }
26
+ // ============================================================================
27
+ // API Readiness Check
28
+ // ============================================================================
29
+ export async function waitForApiReady(maxWait, interval = 2.0) {
30
+ const maxWaitMs = (maxWait ?? MEMOS_API_WAIT_MAX) * 1000;
31
+ const intervalMs = interval * 1000;
32
+ const start = Date.now();
33
+ while (Date.now() - start < maxWaitMs) {
34
+ try {
35
+ const response = await fetchWithTimeout(`${MEMOS_URL}/users`, {
36
+ timeoutMs: MEMOS_TIMEOUT_HEALTH,
37
+ });
38
+ if (response.ok) {
39
+ logger.debug("MemOS API is ready");
40
+ return true;
41
+ }
42
+ }
43
+ catch {
44
+ // Not ready yet
45
+ }
46
+ await sleep(intervalMs);
47
+ }
48
+ logger.warning(`MemOS API not ready after ${maxWait ?? MEMOS_API_WAIT_MAX}s`);
49
+ return false;
50
+ }
51
+ function buildUrl(base, params) {
52
+ if (!params)
53
+ return base;
54
+ const urlParams = new URLSearchParams();
55
+ for (const [k, v] of Object.entries(params)) {
56
+ if (v !== undefined)
57
+ urlParams.append(k, String(v));
58
+ }
59
+ const qs = urlParams.toString();
60
+ return qs ? `${base}?${qs}` : base;
61
+ }
62
+ async function doFetch(method, url, options) {
63
+ const finalUrl = buildUrl(url, options.params);
64
+ const headers = {
65
+ "Content-Type": "application/json",
66
+ ...options.headers,
67
+ };
68
+ const fetchOptions = {
69
+ method,
70
+ headers,
71
+ };
72
+ if (options.body !== undefined && method !== "GET") {
73
+ fetchOptions.body = JSON.stringify(options.body);
74
+ }
75
+ const response = await fetchWithTimeout(finalUrl, fetchOptions);
76
+ if (!response.ok && response.status !== 400) {
77
+ return { status: response.status, data: null };
78
+ }
79
+ try {
80
+ const data = await response.json();
81
+ return { status: response.status, data };
82
+ }
83
+ catch {
84
+ return { status: response.status, data: null };
85
+ }
86
+ }
87
+ export async function apiCallWithRetry(method, url, cubeId, options = {}, ensureCubeRegistered) {
88
+ const { status, data } = await doFetch(method, url, options);
89
+ if (status === 200 && data) {
90
+ if (data.code === 200) {
91
+ return { success: true, data, status: 200 };
92
+ }
93
+ // API returned error code - try re-registration if available
94
+ if (ensureCubeRegistered) {
95
+ registeredCubes.delete(cubeId);
96
+ const [regSuccess] = await ensureCubeRegistered(cubeId, true);
97
+ if (regSuccess) {
98
+ const retry = await doFetch(method, url, options);
99
+ if (retry.status === 200 && retry.data && retry.data.code === 200) {
100
+ return { success: true, data: retry.data, status: 200 };
101
+ }
102
+ return { success: false, data: retry.data, status: 200 };
103
+ }
104
+ }
105
+ return { success: false, data, status: 200 };
106
+ }
107
+ if (status === 400 && ensureCubeRegistered) {
108
+ // 400 often means cube not loaded
109
+ registeredCubes.delete(cubeId);
110
+ const [regSuccess] = await ensureCubeRegistered(cubeId, true);
111
+ if (regSuccess) {
112
+ const retry = await doFetch(method, url, options);
113
+ if (retry.status === 200 && retry.data && retry.data.code === 200) {
114
+ return { success: true, data: retry.data, status: 200 };
115
+ }
116
+ }
117
+ return { success: false, data: null, status: 400 };
118
+ }
119
+ return { success: false, data, status };
120
+ }
121
+ // ============================================================================
122
+ // Helper
123
+ // ============================================================================
124
+ function sleep(ms) {
125
+ return new Promise((resolve) => setTimeout(resolve, ms));
126
+ }
127
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * MemOS MCP Server Configuration Module (TypeScript)
3
+ *
4
+ * Configuration priority: CLI args > environment variables > defaults
5
+ */
6
+ export declare const logger: {
7
+ debug: (msg: string) => void;
8
+ info: (msg: string) => void;
9
+ warning: (msg: string) => void;
10
+ error: (msg: string) => void;
11
+ exception: (msg: string, err?: unknown) => void;
12
+ };
13
+ export declare const MEMOS_URL: string;
14
+ export declare const MEMOS_USER: string;
15
+ export declare const MEMOS_DEFAULT_CUBE: string;
16
+ export declare const MEMOS_CUBES_DIR: string;
17
+ export declare const MEMOS_TIMEOUT_TOOL: number;
18
+ export declare const MEMOS_TIMEOUT_STARTUP: number;
19
+ export declare const MEMOS_TIMEOUT_HEALTH: number;
20
+ export declare const MEMOS_API_WAIT_MAX: number;
21
+ export declare const MEMOS_ENABLE_DELETE: boolean;
22
+ export declare const NEO4J_HTTP_URL: string | undefined;
23
+ export declare const NEO4J_USER: string | undefined;
24
+ export declare const NEO4J_PASSWORD: string | undefined;
25
+ export declare const registeredCubes: Set<string>;
26
+ export declare const lastRegistrationAttempt: Map<string, number>;
27
+ export declare const REGISTRATION_RETRY_INTERVAL = 5;
28
+ export declare const MEMORY_TYPES: Set<string>;
29
+ export declare function isDefaultCubeFromEnv(): boolean;
30
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * MemOS MCP Server Configuration Module (TypeScript)
3
+ *
4
+ * Configuration priority: CLI args > environment variables > defaults
5
+ */
6
+ import { config as loadDotenv } from "dotenv";
7
+ import { resolve, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { existsSync } from "fs";
10
+ import { parseArgs } from "util";
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ // Load .env with priority: cwd > package root > dotenv default search
13
+ const cwdEnv = resolve(process.cwd(), ".env");
14
+ const pkgRoot = resolve(__dirname, "..", "..");
15
+ const pkgEnv = resolve(pkgRoot, ".env");
16
+ if (existsSync(cwdEnv)) {
17
+ loadDotenv({ path: cwdEnv, override: true });
18
+ }
19
+ else if (existsSync(pkgEnv)) {
20
+ loadDotenv({ path: pkgEnv, override: true });
21
+ }
22
+ else {
23
+ loadDotenv(); // fallback: search from cwd upward
24
+ }
25
+ // ============================================================================
26
+ // CLI Argument Parsing
27
+ // ============================================================================
28
+ function parseCliArgs() {
29
+ try {
30
+ const { values } = parseArgs({
31
+ args: process.argv.slice(2),
32
+ options: {
33
+ "memos-url": { type: "string" },
34
+ "memos-user": { type: "string" },
35
+ "memos-default-cube": { type: "string" },
36
+ "memos-cubes-dir": { type: "string" },
37
+ "memos-enable-delete": { type: "string" },
38
+ "memos-timeout-tool": { type: "string" },
39
+ "memos-timeout-startup": { type: "string" },
40
+ "memos-timeout-health": { type: "string" },
41
+ "memos-api-wait-max": { type: "string" },
42
+ },
43
+ strict: false,
44
+ });
45
+ return values;
46
+ }
47
+ catch {
48
+ return {};
49
+ }
50
+ }
51
+ const _args = parseCliArgs();
52
+ // ============================================================================
53
+ // Logging
54
+ // ============================================================================
55
+ const _logLevel = process.env.MEMOS_LOG_LEVEL?.toUpperCase() ?? "WARNING";
56
+ const LOG_LEVELS = {
57
+ DEBUG: 0,
58
+ INFO: 1,
59
+ WARNING: 2,
60
+ ERROR: 3,
61
+ };
62
+ const _currentLevel = LOG_LEVELS[_logLevel] ?? LOG_LEVELS.WARNING;
63
+ export const logger = {
64
+ debug: (msg) => {
65
+ if (_currentLevel <= LOG_LEVELS.DEBUG) {
66
+ process.stderr.write(`[DEBUG] ${msg}\n`);
67
+ }
68
+ },
69
+ info: (msg) => {
70
+ if (_currentLevel <= LOG_LEVELS.INFO) {
71
+ process.stderr.write(`[INFO] ${msg}\n`);
72
+ }
73
+ },
74
+ warning: (msg) => {
75
+ if (_currentLevel <= LOG_LEVELS.WARNING) {
76
+ process.stderr.write(`[WARN] ${msg}\n`);
77
+ }
78
+ },
79
+ error: (msg) => {
80
+ if (_currentLevel <= LOG_LEVELS.ERROR) {
81
+ process.stderr.write(`[ERROR] ${msg}\n`);
82
+ }
83
+ },
84
+ exception: (msg, err) => {
85
+ process.stderr.write(`[ERROR] ${msg}: ${err}\n`);
86
+ },
87
+ };
88
+ // ============================================================================
89
+ // Configuration Helpers
90
+ // ============================================================================
91
+ function getEnv(key, ...alternatives) {
92
+ const val = process.env[key]?.trim();
93
+ if (val)
94
+ return val;
95
+ for (const alt of alternatives) {
96
+ const altVal = process.env[alt]?.trim();
97
+ if (altVal)
98
+ return altVal;
99
+ }
100
+ return undefined;
101
+ }
102
+ function requireEnv(name, value) {
103
+ if (!value) {
104
+ throw new Error(`${name} is required (set ${name} in .env)`);
105
+ }
106
+ return value;
107
+ }
108
+ function parseFloat2(name, value, defaultVal) {
109
+ if (!value)
110
+ return defaultVal;
111
+ const n = parseFloat(value);
112
+ if (isNaN(n))
113
+ throw new Error(`${name} must be a float, got: ${value}`);
114
+ return n;
115
+ }
116
+ // ============================================================================
117
+ // Core Configuration (CLI args > env vars > defaults)
118
+ // ============================================================================
119
+ // -- API Connection --
120
+ export const MEMOS_URL = requireEnv("MEMOS_URL", _args["memos-url"] ?? getEnv("MEMOS_URL", "MEMOS_BASE_URL"));
121
+ export const MEMOS_USER = requireEnv("MEMOS_USER", _args["memos-user"] ?? getEnv("MEMOS_USER", "MOS_USER_ID"));
122
+ const _defaultCubeFromEnv = _args["memos-default-cube"] != null || process.env["MEMOS_DEFAULT_CUBE"] != null;
123
+ export const MEMOS_DEFAULT_CUBE = requireEnv("MEMOS_DEFAULT_CUBE", _args["memos-default-cube"] ?? getEnv("MEMOS_DEFAULT_CUBE"));
124
+ export const MEMOS_CUBES_DIR = requireEnv("MEMOS_CUBES_DIR", _args["memos-cubes-dir"] ?? getEnv("MEMOS_CUBES_DIR"));
125
+ // -- Timeouts (seconds) --
126
+ export const MEMOS_TIMEOUT_TOOL = parseFloat2("MEMOS_TIMEOUT_TOOL", _args["memos-timeout-tool"] ?? getEnv("MEMOS_TIMEOUT_TOOL"), 300.0);
127
+ export const MEMOS_TIMEOUT_STARTUP = parseFloat2("MEMOS_TIMEOUT_STARTUP", _args["memos-timeout-startup"] ?? getEnv("MEMOS_TIMEOUT_STARTUP"), 30.0);
128
+ export const MEMOS_TIMEOUT_HEALTH = parseFloat2("MEMOS_TIMEOUT_HEALTH", _args["memos-timeout-health"] ?? getEnv("MEMOS_TIMEOUT_HEALTH"), 5.0);
129
+ export const MEMOS_API_WAIT_MAX = parseFloat2("MEMOS_API_WAIT_MAX", _args["memos-api-wait-max"] ?? getEnv("MEMOS_API_WAIT_MAX"), 60.0);
130
+ // -- Feature Flags --
131
+ const _enableDeleteRaw = _args["memos-enable-delete"] ?? getEnv("MEMOS_ENABLE_DELETE") ?? "false";
132
+ export const MEMOS_ENABLE_DELETE = _enableDeleteRaw.toLowerCase() === "true";
133
+ // -- Neo4j (for fallback direct graph queries) --
134
+ export const NEO4J_HTTP_URL = getEnv("NEO4J_HTTP_URL");
135
+ export const NEO4J_USER = getEnv("NEO4J_USER");
136
+ export const NEO4J_PASSWORD = getEnv("NEO4J_PASSWORD");
137
+ // ============================================================================
138
+ // Cube Registration Tracking
139
+ // ============================================================================
140
+ export const registeredCubes = new Set();
141
+ export const lastRegistrationAttempt = new Map();
142
+ export const REGISTRATION_RETRY_INTERVAL = 5.0; // seconds
143
+ // ============================================================================
144
+ // Memory Types
145
+ // ============================================================================
146
+ export const MEMORY_TYPES = new Set([
147
+ "ERROR_PATTERN",
148
+ "DECISION",
149
+ "MILESTONE",
150
+ "BUGFIX",
151
+ "FEATURE",
152
+ "CONFIG",
153
+ "CODE_PATTERN",
154
+ "GOTCHA",
155
+ "PROGRESS",
156
+ ]);
157
+ // ============================================================================
158
+ // Helper
159
+ // ============================================================================
160
+ export function isDefaultCubeFromEnv() {
161
+ return _defaultCubeFromEnv;
162
+ }
163
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * MemOS MCP Server Cube Management Module
3
+ *
4
+ * Handles cube discovery, config, and registration.
5
+ */
6
+ import type { CubeInfo } from "./types.js";
7
+ export declare function getCubesBaseDir(): string;
8
+ export declare function getLocalCubesBaseDir(): string;
9
+ export declare function listAvailableCubes(): CubeInfo[];
10
+ export declare function getCubePath(cubeId: string): string | null;
11
+ export declare function validateAndFixCubeConfig(cubeId: string, configPath: string): [boolean, string | null];
12
+ export declare function ensureCubeDirectory(cubeId: string): [string | null, string | null];
13
+ export declare function verifyCubeLoaded(cubeId: string): Promise<boolean>;
14
+ export declare function ensureCubeRegistered(cubeId: string, force?: boolean): Promise<[boolean, string | null]>;
15
+ export declare function getDefaultCubeId(): string;
16
+ //# sourceMappingURL=cube-manager.d.ts.map