oh-memos-mcp 1.0.0 → 2.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,95 @@
1
+ # Changelog
2
+
3
+ All notable changes to `oh-memos-mcp` are documented here.
4
+
5
+ This project follows [Semantic Versioning](https://semver.org/).
6
+
7
+ ---
8
+
9
+ ## [2.0.1] — 2026-08-02
10
+
11
+ ### Fixed
12
+
13
+ - MCP `serverInfo` was hardcoded in `server.ts` and reported
14
+ `{name: "memos-memory", version: "1.0.1"}` — a name matching neither the
15
+ package (`oh-memos-mcp`) nor the conventional server key (`oh-memos`), and a
16
+ version that 2.0.0 shipped stale. Clients display this during the handshake.
17
+ It is now read from `package.json` at startup, so it cannot drift again.
18
+
19
+ ---
20
+
21
+ ## [2.0.0] — 2026-08-02
22
+
23
+ ### ⚠️ Breaking — tool surface consolidated from 18 to 10
24
+
25
+ Eleven tools were merged into three dispatching tools. **There is no compatibility
26
+ shim**: the MCP SDK only dispatches registered tools, so calling a removed name now
27
+ returns `Unknown tool`.
28
+
29
+ If your MCP client config pins tool names (e.g. Claude Code's `alwaysAllow` list),
30
+ you must update it before upgrading.
31
+
32
+ #### Migration
33
+
34
+ | 1.x tool | 2.0 replacement |
35
+ |----------|-----------------|
36
+ | `memos_search_context` | `memos_search` — pass the `context` array (recent turns) |
37
+ | `memos_get_graph` | `memos_graph(mode="related")` |
38
+ | `memos_trace_path` | `memos_graph(mode="path")` |
39
+ | `memos_impact` | `memos_graph(mode="impact")` |
40
+ | `memos_export_schema` | `memos_graph(mode="schema")` |
41
+ | `memos_list_cubes` | `memos_admin(action="list_cubes")` |
42
+ | `memos_register_cube` | `memos_admin(action="register_cube")` |
43
+ | `memos_create_user` | `memos_admin(action="create_user")` |
44
+ | `memos_validate_cubes` | `memos_admin(action="validate_cubes")` |
45
+ | `memos_get_stats` | `memos_admin(action="stats")` |
46
+ | `memos_calendar` | `memos_admin(action="calendar")` |
47
+
48
+ Unchanged: `memos_context_resume`, `memos_search`, `memos_save`, `memos_list_v2`,
49
+ `memos_get`, `memos_suggest`, `memos_delete`.
50
+
51
+ `alwaysAllow` for 2.0:
52
+
53
+ ```json
54
+ "alwaysAllow": [
55
+ "memos_context_resume", "memos_search", "memos_save",
56
+ "memos_list_v2", "memos_get", "memos_suggest",
57
+ "memos_think", "memos_graph", "memos_admin", "memos_export_wiki"
58
+ ]
59
+ ```
60
+
61
+ ### Added
62
+
63
+ - **`memos_think`** — evidence pack for a question. Runs semantic retrieval plus a
64
+ recent-72h temporal pass, deduplicates, and returns numbered evidence with graph
65
+ relationships between items, contradiction/evolution candidates, staleness
66
+ candidates, and gap analysis. Synthesis is deliberately left to the calling model;
67
+ the server emits no prose. Results can be persisted back as `SYNTHESIS`.
68
+ - **`memos_export_wiki`** — export a cube as an interlinked markdown wiki: one page
69
+ per memory (YAML frontmatter, `[[wikilink]]` relations), plus `index.md` and a
70
+ mermaid `graph.md`. Only files carrying the generator marker are ever replaced;
71
+ foreign files in the output directory are preserved. Defaults to
72
+ `<project_path>/docs/memory-wiki`.
73
+ - **`SYNTHESIS`** memory type, for answers synthesized from retrieved evidence.
74
+
75
+ ### Changed
76
+
77
+ - Tool-surface cost dropped ~51%: `tools/list` payload 22.9 KB → 11.2 KB
78
+ (≈5850 → ≈2856 tokens). Shared `project_path` / `cube_id` parameter descriptions
79
+ were deduplicated; the full cube-routing rules are now stated once, on
80
+ `memos_save` and `memos_search`.
81
+ - Runtime error `suggestions` now name the 2.0 call forms, so a model reading a
82
+ failure message is no longer told to call a tool that does not exist.
83
+
84
+ ### Fixed
85
+
86
+ - `memos_admin`'s `cube_id` no longer carries a `MEMOS_DEFAULT_CUBE` default.
87
+ Previously, calling `register_cube` without `cube_id` would silently register the
88
+ default cube instead of failing.
89
+
90
+ ---
91
+
92
+ ## [1.0.0] — 2026-03-04
93
+
94
+ Initial npm release. 18 tools, Node-only MCP server for oh-memos — no Python
95
+ required, runs via `npx`.
package/README.md CHANGED
@@ -1,71 +1,226 @@
1
1
  # oh-memos-mcp
2
2
 
3
- MCP Server for MemOS — Intelligent Memory Management for AI Assistants.
3
+ [![npm version](https://img.shields.io/npm/v/oh-memos-mcp.svg)](https://www.npmjs.com/package/oh-memos-mcp)
4
+ [![Node.js >= 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
6
 
5
- Node.js implementation. No Python required.
7
+ MCP Server for **oh-memos** — Intelligent Persistent Memory for AI Assistants.
8
+
9
+ Pure Node.js. No Python required. Works with `npx` out of the box.
10
+
11
+ ---
12
+
13
+ ## Prerequisites
14
+
15
+ oh-memos backend API must be running before connecting this MCP server:
16
+
17
+ - **oh-memos API** on `http://localhost:18000`
18
+ - **Neo4j** on `localhost:7687` (Knowledge Graph)
19
+ - **Qdrant** on `localhost:6333` (Vector Search)
20
+
21
+ > Start all services: run `scripts\local\start.bat` from the [oh-memos repo](https://github.com/xigou/oh-memos).
22
+
23
+ ---
6
24
 
7
25
  ## Quick Start
8
26
 
27
+ Add to your Claude Code `~/.claude/settings.json`:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "oh-memos": {
33
+ "command": "npx",
34
+ "args": ["-y", "oh-memos-mcp"],
35
+ "env": {
36
+ "MEMOS_URL": "http://localhost:18000",
37
+ "MEMOS_USER": "dev_user",
38
+ "MEMOS_DEFAULT_CUBE": "dev_cube",
39
+ "MEMOS_CUBES_DIR": "G:/test/oh-memos/data/oh-memos_cubes"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ Or use a `.env` file in your working directory (the server auto-discovers it):
47
+
48
+ ```bash
49
+ cp node_modules/oh-memos-mcp/.env.example .env
50
+ # Edit .env with your paths
51
+ npx oh-memos-mcp
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Configuration Examples
57
+
58
+ ### Linux / macOS
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "oh-memos": {
64
+ "command": "npx",
65
+ "args": ["-y", "oh-memos-mcp"],
66
+ "env": {
67
+ "MEMOS_URL": "http://localhost:18000",
68
+ "MEMOS_USER": "dev_user",
69
+ "MEMOS_DEFAULT_CUBE": "dev_cube",
70
+ "MEMOS_CUBES_DIR": "/home/user/oh-memos/data/oh-memos_cubes"
71
+ }
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### Windows
78
+
9
79
  ```json
10
80
  {
11
81
  "mcpServers": {
12
- "memos": {
82
+ "oh-memos": {
13
83
  "command": "npx",
14
84
  "args": ["-y", "oh-memos-mcp"],
15
85
  "env": {
16
86
  "MEMOS_URL": "http://localhost:18000",
17
87
  "MEMOS_USER": "dev_user",
18
88
  "MEMOS_DEFAULT_CUBE": "dev_cube",
19
- "MEMOS_CUBES_DIR": "/path/to/data/memos_cubes"
89
+ "MEMOS_CUBES_DIR": "G:/test/oh-memos/data/oh-memos_cubes"
20
90
  }
21
91
  }
22
92
  }
23
93
  }
24
94
  ```
25
95
 
96
+ ### WSL2
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "oh-memos": {
102
+ "command": "npx",
103
+ "args": ["-y", "oh-memos-mcp"],
104
+ "env": {
105
+ "MEMOS_URL": "http://localhost:18000",
106
+ "MEMOS_USER": "dev_user",
107
+ "MEMOS_DEFAULT_CUBE": "dev_cube",
108
+ "MEMOS_CUBES_DIR": "/mnt/g/test/oh-memos/data/oh-memos_cubes"
109
+ }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### With `alwaysAllow` (skip per-tool confirmation)
116
+
117
+ ```json
118
+ {
119
+ "mcpServers": {
120
+ "oh-memos": {
121
+ "type": "stdio",
122
+ "command": "npx",
123
+ "args": ["-y", "oh-memos-mcp"],
124
+ "env": {
125
+ "MEMOS_URL": "http://localhost:18000",
126
+ "MEMOS_USER": "dev_user",
127
+ "MEMOS_DEFAULT_CUBE": "dev_cube",
128
+ "MEMOS_CUBES_DIR": "/path/to/oh-memos/data/oh-memos_cubes",
129
+ "MEMOS_ENABLE_DELETE": "true"
130
+ },
131
+ "alwaysAllow": [
132
+ "memos_context_resume",
133
+ "memos_search",
134
+ "memos_search",
135
+ "memos_save",
136
+ "memos_list_v2",
137
+ "memos_get",
138
+ "memos_suggest",
139
+ "memos_admin(action=list_cubes)",
140
+ "memos_admin(action=stats)",
141
+ "memos_graph(mode=related)",
142
+ "memos_graph(mode=path)",
143
+ "memos_graph(mode=schema)",
144
+ "memos_admin(action=register_cube)",
145
+ "memos_admin(action=create_user)",
146
+ "memos_admin(action=validate_cubes)",
147
+ "memos_graph(mode=impact)",
148
+ "memos_admin(action=calendar)",
149
+ "memos_delete"
150
+ ]
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
156
+ ---
157
+
26
158
  ## Environment Variables
27
159
 
28
160
  | Variable | Required | Default | Description |
29
161
  |----------|----------|---------|-------------|
30
- | `MEMOS_URL` | Yes | — | MemOS API base URL |
162
+ | `MEMOS_URL` | Yes | — | oh-memos API base URL |
31
163
  | `MEMOS_USER` | Yes | — | Default user ID |
32
164
  | `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 |
165
+ | `MEMOS_CUBES_DIR` | Yes | — | Absolute path to cubes storage directory |
166
+ | `MEMOS_TIMEOUT_TOOL` | No | `300` | Tool call timeout (seconds) |
167
+ | `MEMOS_TIMEOUT_STARTUP` | No | `30` | API startup wait timeout (seconds) |
168
+ | `MEMOS_TIMEOUT_HEALTH` | No | `5` | Health check timeout (seconds) |
169
+ | `MEMOS_API_WAIT_MAX` | No | `60` | Max time to wait for API on startup (seconds) |
170
+ | `MEMOS_ENABLE_DELETE` | No | `false` | Enable `memos_delete` tool |
171
+ | `NEO4J_HTTP_URL` | No | — | Neo4j HTTP endpoint (for direct graph queries) |
35
172
  | `NEO4J_USER` | No | — | Neo4j username |
36
173
  | `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)
174
+
175
+ > **Tip**: `.env` file in your working directory is loaded automatically with highest priority.
176
+ > Copy `.env.example` to get started.
177
+
178
+ ---
179
+
180
+ ## Tools (10)
181
+
182
+ | Tool | Description |
183
+ |------|-------------|
184
+ | `memos_context_resume` | Recover project context after compaction (recent 24h + project state) |
185
+ | `memos_search` | Semantic search with keyword reranking. Pass `context` (recent turns) to enable LLM intent analysis and query expansion |
186
+ | `memos_save` | Save memories with explicit type (BUGFIX, DECISION, MILESTONE, SYNTHESIS…) |
187
+ | `memos_list_v2` | List memories with auto-compaction |
188
+ | `memos_get` | Get full memory details by ID |
189
+ | `memos_suggest` | Smart search query suggestions + `memory_type` decision tree |
190
+ | `memos_think` | Evidence pack for a question: retrieval + contradiction/staleness flags + gap analysis. The caller synthesizes the answer and may persist it as `SYNTHESIS` |
191
+ | `memos_graph` | Knowledge graph queries — `mode`: `related` / `path` / `impact` / `schema` |
192
+ | `memos_admin` | Maintenance — `action`: `list_cubes` / `register_cube` / `create_user` / `validate_cubes` / `stats` / `calendar` |
193
+ | `memos_export_wiki` | Export a cube as an interlinked markdown wiki (page per memory + index + mermaid graph) |
194
+
195
+ Plus `memos_delete`, hidden from `tools/list` unless `MEMOS_ENABLE_DELETE=true`.
196
+
197
+ ---
198
+
199
+ ## How .env Loading Works
200
+
201
+ Priority order (highest first):
202
+
203
+ 1. **`process.cwd()/.env`** — your project working directory
204
+ 2. **Package root `.env`** — where oh-memos-mcp is installed
205
+ 3. **dotenv default search** — walks up from cwd
206
+
207
+ This means you can place a `.env` in your project root and `npx oh-memos-mcp` will pick it up automatically — no need to repeat env vars in every MCP config.
208
+
209
+ ---
59
210
 
60
211
  ## Development
61
212
 
62
213
  ```bash
214
+ git clone https://github.com/xigou/oh-memos.git
215
+ cd oh-memos/mcp-server-node
63
216
  npm install
64
217
  npm run dev # Run with tsx (no build needed)
65
218
  npm run build # Compile to dist/
66
219
  ```
67
220
 
221
+ ---
222
+
68
223
  ## Requirements
69
224
 
70
225
  - Node.js >= 18.0.0
71
- - MemOS backend API running (Python FastAPI, port 18000)
226
+ - oh-memos backend API running (`scripts\local\start.bat`)
package/dist/config.js CHANGED
@@ -153,6 +153,7 @@ export const MEMORY_TYPES = new Set([
153
153
  "CODE_PATTERN",
154
154
  "GOTCHA",
155
155
  "PROGRESS",
156
+ "SYNTHESIS",
156
157
  ]);
157
158
  // ============================================================================
158
159
  // Helper
@@ -153,6 +153,120 @@ function updateConfigForCube(config, cubeId) {
153
153
  }
154
154
  return config;
155
155
  }
156
+ // Fallback cube config built from environment variables — ported from the Python
157
+ // server's _build_fallback_cube_config. Throws a descriptive error when a required
158
+ // var is missing (caller surfaces it), instead of silently producing a broken cube.
159
+ function requireEnv(key) {
160
+ const v = process.env[key]?.trim();
161
+ if (!v)
162
+ throw new Error(`${key} is required to build a fallback cube config (set ${key} in .env)`);
163
+ return v;
164
+ }
165
+ function envFloat(key) {
166
+ const n = Number(requireEnv(key));
167
+ if (Number.isNaN(n))
168
+ throw new Error(`${key} must be a number`);
169
+ return n;
170
+ }
171
+ function envInt(key) {
172
+ const n = parseInt(requireEnv(key), 10);
173
+ if (Number.isNaN(n))
174
+ throw new Error(`${key} must be an int`);
175
+ return n;
176
+ }
177
+ // Optional boolean switches — fall back to a sane default when unset, so a cube
178
+ // can still be built in environments that don't define every strategy flag
179
+ // (missing BM25_CALL/VEC_COT_CALL/NEO4J_* switches must not fail registration).
180
+ function envBool(key, def) {
181
+ const v = process.env[key]?.trim().toLowerCase();
182
+ if (v === undefined || v === "")
183
+ return def;
184
+ return v === "true" || v === "1" || v === "yes";
185
+ }
186
+ function buildFallbackCubeConfig(cubeId) {
187
+ const openaiConfig = {
188
+ model_name_or_path: requireEnv("MOS_CHAT_MODEL"),
189
+ temperature: envFloat("MOS_CHAT_TEMPERATURE"),
190
+ max_tokens: envInt("MOS_MAX_TOKENS"),
191
+ top_p: envFloat("MOS_TOP_P"),
192
+ top_k: envInt("MOS_TOP_K"),
193
+ remove_think_prefix: true,
194
+ api_key: requireEnv("OPENAI_API_KEY"),
195
+ api_base: requireEnv("OPENAI_API_BASE"),
196
+ };
197
+ const embedderConfig = {
198
+ backend: requireEnv("MOS_EMBEDDER_BACKEND"),
199
+ config: {
200
+ provider: requireEnv("MOS_EMBEDDER_PROVIDER"),
201
+ api_key: requireEnv("OPENAI_API_KEY"),
202
+ model_name_or_path: requireEnv("MOS_EMBEDDER_MODEL"),
203
+ base_url: process.env.MOS_EMBEDDER_API_BASE?.trim() || requireEnv("OPENAI_API_BASE"),
204
+ embedding_dims: envInt("EMBEDDING_DIMENSION"),
205
+ },
206
+ };
207
+ const neo4jBackend = requireEnv("NEO4J_BACKEND").toLowerCase();
208
+ let graphConfig;
209
+ if (neo4jBackend === "neo4j") {
210
+ graphConfig = {
211
+ uri: requireEnv("NEO4J_URI"),
212
+ user: requireEnv("NEO4J_USER"),
213
+ db_name: requireEnv("NEO4J_DB_NAME"),
214
+ password: requireEnv("NEO4J_PASSWORD"),
215
+ auto_create: envBool("NEO4J_AUTO_CREATE", false),
216
+ use_multi_db: envBool("NEO4J_USE_MULTI_DB", false),
217
+ user_name: cubeId,
218
+ embedding_dimension: envInt("EMBEDDING_DIMENSION"),
219
+ };
220
+ }
221
+ else {
222
+ const qdrantUrl = process.env.QDRANT_URL?.trim() || null;
223
+ graphConfig = {
224
+ uri: requireEnv("NEO4J_URI"),
225
+ user: requireEnv("NEO4J_USER"),
226
+ db_name: requireEnv("NEO4J_DB_NAME"),
227
+ password: requireEnv("NEO4J_PASSWORD"),
228
+ user_name: cubeId,
229
+ auto_create: false,
230
+ use_multi_db: false,
231
+ embedding_dimension: envInt("EMBEDDING_DIMENSION"),
232
+ vec_config: {
233
+ backend: "qdrant",
234
+ config: {
235
+ collection_name: `${cubeId}_graph`,
236
+ vector_dimension: envInt("EMBEDDING_DIMENSION"),
237
+ distance_metric: "cosine",
238
+ host: qdrantUrl ? null : requireEnv("QDRANT_HOST"),
239
+ port: qdrantUrl ? null : envInt("QDRANT_PORT"),
240
+ path: process.env.QDRANT_PATH?.trim() || null,
241
+ url: qdrantUrl,
242
+ api_key: process.env.QDRANT_API_KEY?.trim() || null,
243
+ },
244
+ },
245
+ };
246
+ }
247
+ return {
248
+ model_schema: "memos.configs.mem_cube.GeneralMemCubeConfig",
249
+ user_id: MEMOS_USER,
250
+ cube_id: cubeId,
251
+ config_filename: "config.json",
252
+ text_mem: {
253
+ backend: "tree_text",
254
+ config: {
255
+ extractor_llm: { backend: "openai", config: openaiConfig },
256
+ dispatcher_llm: { backend: "openai", config: openaiConfig },
257
+ embedder: embedderConfig,
258
+ graph_db: { backend: neo4jBackend, config: graphConfig },
259
+ reorganize: envBool("MOS_ENABLE_REORGANIZE", false),
260
+ search_strategy: {
261
+ bm25: envBool("BM25_CALL", false),
262
+ cot: envBool("VEC_COT_CALL", false),
263
+ },
264
+ },
265
+ },
266
+ act_mem: {},
267
+ para_mem: {},
268
+ };
269
+ }
156
270
  function buildCubeConfig(cubeId) {
157
271
  const templatePath = getCubePath(MEMOS_DEFAULT_CUBE);
158
272
  if (templatePath !== null) {
@@ -166,15 +280,9 @@ function buildCubeConfig(cubeId) {
166
280
  logger.warning(`Failed to read template cube config: ${err}`);
167
281
  }
168
282
  }
169
- // Minimal fallback config (will likely fail API registration without proper fields)
170
- return {
171
- user_id: MEMOS_USER,
172
- cube_id: cubeId,
173
- config_filename: "config.json",
174
- text_mem: {},
175
- act_mem: {},
176
- para_mem: {},
177
- };
283
+ // No usable template — build a complete config from env (throws with a clear
284
+ // message if required vars are missing; caller reports it as a registration error).
285
+ return buildFallbackCubeConfig(cubeId);
178
286
  }
179
287
  export function validateAndFixCubeConfig(cubeId, configPath) {
180
288
  try {
@@ -249,7 +357,7 @@ export function ensureCubeDirectory(cubeId) {
249
357
  // ============================================================================
250
358
  export async function verifyCubeLoaded(cubeId) {
251
359
  try {
252
- const response = await fetchWithTimeout(`${MEMOS_URL}/memories?user_id=${encodeURIComponent(MEMOS_USER)}&mem_cube_id=${encodeURIComponent(cubeId)}`, { method: "GET", timeoutMs: 5 });
360
+ const response = await fetchWithTimeout(`${MEMOS_URL}/memories?user_id=${encodeURIComponent(MEMOS_USER)}&mem_cube_id=${encodeURIComponent(cubeId)}&limit=1`, { method: "GET", timeoutMs: 5 });
253
361
  if (response.ok) {
254
362
  const data = await response.json();
255
363
  return data.code === 200;
@@ -281,17 +389,8 @@ export async function ensureCubeRegistered(cubeId, force = false) {
281
389
  // Get or create cube path
282
390
  let cubePath = getCubeApiPath(cubeId);
283
391
  if (!cubePath) {
284
- // Try to auto-create
392
+ // Try to auto-create (ensureCubeDirectory handles both template-clone and fallback)
285
393
  logger.debug(`Cube '${cubeId}' not found, attempting auto-creation...`);
286
- const templatePath = getCubePath(MEMOS_DEFAULT_CUBE);
287
- if (!templatePath) {
288
- const available = listAvailableCubes();
289
- const availableIds = available.map((c) => c.id);
290
- if (availableIds.length > 0) {
291
- return [false, `Cube '${cubeId}' not found. Available cubes: ${availableIds.join(", ")}`];
292
- }
293
- return [false, `Cube '${cubeId}' not found and no cubes available.`];
294
- }
295
394
  const [newDir, createErr] = ensureCubeDirectory(cubeId);
296
395
  if (!newDir) {
297
396
  return [false, `Failed to auto-create cube '${cubeId}': ${createErr}`];
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Admin Handlers
3
3
  *
4
- * memos_list_cubes, memos_register_cube, memos_create_user,
5
- * memos_validate_cubes, memos_delete
4
+ * memos_admin(action="list_cubes"), memos_admin(action="register_cube"), memos_admin(action="create_user"),
5
+ * memos_admin(action="validate_cubes"), memos_delete
6
6
  */
7
7
  import type { TextContent } from "../types.js";
8
8
  export declare function handleMemosListCubes(arguments_: Record<string, unknown>): Promise<TextContent[]>;
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * Admin Handlers
3
3
  *
4
- * memos_list_cubes, memos_register_cube, memos_create_user,
5
- * memos_validate_cubes, memos_delete
4
+ * memos_admin(action="list_cubes"), memos_admin(action="register_cube"), memos_admin(action="create_user"),
5
+ * memos_admin(action="validate_cubes"), memos_delete
6
6
  */
7
7
  import * as fs from "fs";
8
8
  import * as path from "path";
9
9
  import { MEMOS_URL, MEMOS_USER, MEMOS_DEFAULT_CUBE, MEMOS_CUBES_DIR, MEMOS_ENABLE_DELETE, MEMOS_TIMEOUT_TOOL, registeredCubes, } from "../config.js";
10
10
  import { fetchWithTimeout } from "../api-client.js";
11
- import { ensureCubeRegistered, getCubePath, getCubesBaseDir, getLocalCubesBaseDir, listAvailableCubes, validateAndFixCubeConfig, verifyCubeLoaded, } from "../cube-manager.js";
11
+ import { ensureCubeDirectory, ensureCubeRegistered, getCubePath, getCubesBaseDir, getLocalCubesBaseDir, listAvailableCubes, validateAndFixCubeConfig, verifyCubeLoaded, } from "../cube-manager.js";
12
12
  import { ERR_CUBE_NOT_FOUND, ERR_DELETE_DISABLED, ERR_PARAM_MISSING, apiErrorResponse, cubeRegistrationError, errorResponse, getCubeIdFromArgs, } from "./utils.js";
13
13
  // ============================================================================
14
- // memos_list_cubes
14
+ // memos_admin(action="list_cubes")
15
15
  // ============================================================================
16
16
  export async function handleMemosListCubes(arguments_) {
17
17
  const includeStatus = arguments_.include_status === true;
@@ -42,30 +42,35 @@ export async function handleMemosListCubes(arguments_) {
42
42
  }
43
43
  }
44
44
  result.push(`\n**Default cube**: \`${MEMOS_DEFAULT_CUBE}\``);
45
- result.push("\n*Use `memos_list_cubes(include_status=True)` to check registration status.*");
45
+ result.push('\n*Use `memos_admin(action="list_cubes", include_status=true)` to check registration status.*');
46
46
  return [{ type: "text", text: result.join("\n") }];
47
47
  }
48
48
  // ============================================================================
49
- // memos_register_cube
49
+ // memos_admin(action="register_cube")
50
50
  // ============================================================================
51
51
  export async function handleMemosRegisterCube(arguments_) {
52
52
  const cubeId = String(arguments_.cube_id ?? "");
53
53
  let cubePath = arguments_.cube_path ? String(arguments_.cube_path) : undefined;
54
54
  if (!cubeId) {
55
55
  return errorResponse("cube_id parameter is required", ERR_PARAM_MISSING, [
56
- "Use memos_list_cubes() to find available cubes",
57
- '`memos_register_cube(cube_id="dev_cube")`',
56
+ 'Use memos_admin(action="list_cubes") to find available cubes',
57
+ '`memos_admin(action="register_cube", cube_id="dev_cube")`',
58
58
  ]);
59
59
  }
60
60
  if (!cubePath) {
61
- // Try to find cube path (returns Windows/original path for API)
62
- const localPath = getCubePath(cubeId);
61
+ // Try to find cube path, or auto-create if not found
62
+ let localPath = getCubePath(cubeId);
63
63
  if (!localPath) {
64
- const available = listAvailableCubes();
65
- const hint = available.length > 0
66
- ? `\n\n**Available cubes:**\n${available.map((c) => `- \`${c.id}\``).join("\n")}`
67
- : "";
68
- return [{ type: "text", text: `❌ Cube \`${cubeId}\` not found in cubes directory.\n\nCubes directory: \`${getCubesBaseDir()}\`${hint}` }];
64
+ // Auto-create cube directory with config
65
+ const [newDir, createErr] = ensureCubeDirectory(cubeId);
66
+ if (!newDir) {
67
+ const available = listAvailableCubes();
68
+ const hint = available.length > 0
69
+ ? `\n\n**Available cubes:**\n${available.map((c) => `- \`${c.id}\``).join("\n")}`
70
+ : "";
71
+ return errorResponse(`Cube \`${cubeId}\` not found and auto-creation failed: ${createErr}`, ERR_CUBE_NOT_FOUND, [`Cubes directory: \`${getCubesBaseDir()}\`${hint}`]);
72
+ }
73
+ localPath = newDir;
69
74
  }
70
75
  // Convert back to Windows path if needed
71
76
  const cubesDir = MEMOS_CUBES_DIR;
@@ -114,7 +119,7 @@ export async function handleMemosRegisterCube(arguments_) {
114
119
  hint = "Edit the cube's config.json and change `reranker.backend` to `http_bge` or `noop`.";
115
120
  }
116
121
  else if (errorMsg.toLowerCase().includes("user")) {
117
- hint = "Use `memos_create_user` tool to create the user first.";
122
+ hint = 'Use `memos_admin(action="create_user")` to create the user first.';
118
123
  }
119
124
  return errorResponse(`Registration failed: ${errorMsg}`, errorMsg.toLowerCase().includes("not found") ? ERR_CUBE_NOT_FOUND : undefined, hint ? [hint] : []);
120
125
  }
@@ -128,7 +133,7 @@ export async function handleMemosRegisterCube(arguments_) {
128
133
  }
129
134
  }
130
135
  // ============================================================================
131
- // memos_create_user
136
+ // memos_admin(action="create_user")
132
137
  // ============================================================================
133
138
  export async function handleMemosCreateUser(arguments_) {
134
139
  const userId = String(arguments_.user_id ?? MEMOS_USER);
@@ -173,7 +178,7 @@ export async function handleMemosCreateUser(arguments_) {
173
178
  }
174
179
  }
175
180
  // ============================================================================
176
- // memos_validate_cubes
181
+ // memos_admin(action="validate_cubes")
177
182
  // ============================================================================
178
183
  export async function handleMemosValidateCubes(arguments_) {
179
184
  const fixIssues = arguments_.fix !== false; // Default true
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Parameter contract — disclose arguments the schema silently discarded.
3
+ *
4
+ * zod strips keys it does not declare. That is a sane default for a validator
5
+ * and a bad one for a memory server: a call carrying `path="/mnt/g/foo"`
6
+ * instead of `project_path` validates cleanly, loses the key, falls back to the
7
+ * default cube, searches the wrong project, and returns nothing. The model then
8
+ * reads that empty result as "this project has no memories" — a wrong answer
9
+ * with no error anywhere in the chain. (This is how `dev_cube` accumulated
10
+ * memories belonging to other projects.)
11
+ *
12
+ * So: capture the argument keys as they arrive at the transport, before zod
13
+ * touches them, and say plainly which ones were dropped.
14
+ *
15
+ * The call is never rejected. An unknown key has always been accepted, and
16
+ * turning a recoverable mistake into a hard failure trades a slightly wrong
17
+ * answer for no answer at all. Disclosure lets the model correct itself on the
18
+ * next call; rejection just ends the conversation with the memory store.
19
+ */
20
+ export declare function recordRawArgKeys(requestId: unknown, args: unknown): void;
21
+ export interface ArgContractResult {
22
+ /** Keys the caller sent that the schema does not declare (and so discarded). */
23
+ ignored: string[];
24
+ /** Whether any ignored key looks like it was meant to route the cube. */
25
+ affectsRouting: boolean;
26
+ /** Human-readable warning, or null when everything was declared. */
27
+ warning: string | null;
28
+ }
29
+ export declare function checkArgContract(toolName: string, requestId: unknown): ArgContractResult;
30
+ //# sourceMappingURL=arg-contract.d.ts.map