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 +95 -0
- package/README.md +185 -30
- package/dist/config.js +1 -0
- package/dist/cube-manager.js +119 -20
- package/dist/handlers/admin.d.ts +2 -2
- package/dist/handlers/admin.js +23 -18
- package/dist/handlers/arg-contract.d.ts +30 -0
- package/dist/handlers/arg-contract.js +148 -0
- package/dist/handlers/calendar.d.ts +1 -1
- package/dist/handlers/calendar.js +2 -2
- package/dist/handlers/graph.d.ts +1 -1
- package/dist/handlers/graph.js +9 -9
- package/dist/handlers/index.d.ts +3 -0
- package/dist/handlers/index.js +41 -26
- package/dist/handlers/memory.d.ts +1 -1
- package/dist/handlers/memory.js +26 -9
- package/dist/handlers/search.d.ts +3 -2
- package/dist/handlers/search.js +44 -14
- package/dist/handlers/think.d.ts +19 -0
- package/dist/handlers/think.js +250 -0
- package/dist/handlers/utils.js +14 -6
- package/dist/handlers/wiki-export.d.ts +18 -0
- package/dist/handlers/wiki-export.js +327 -0
- package/dist/models.js +1 -0
- package/dist/query-processing.js +25 -11
- package/dist/server.js +51 -3
- package/dist/tools-registry.d.ts +124 -138
- package/dist/tools-registry.js +147 -285
- package/package.json +4 -1
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
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/oh-memos-mcp)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
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": "/
|
|
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 | — |
|
|
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 | — |
|
|
34
|
-
| `
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
-
|
|
226
|
+
- oh-memos backend API running (`scripts\local\start.bat`)
|
package/dist/config.js
CHANGED
package/dist/cube-manager.js
CHANGED
|
@@ -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
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
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}`];
|
package/dist/handlers/admin.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Admin Handlers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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[]>;
|
package/dist/handlers/admin.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Admin Handlers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
//
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
|
|
57
|
-
'`
|
|
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
|
|
62
|
-
|
|
61
|
+
// Try to find cube path, or auto-create if not found
|
|
62
|
+
let localPath = getCubePath(cubeId);
|
|
63
63
|
if (!localPath) {
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 =
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|