neuro-vault-mcp 3.3.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -15
- package/dist/cli.js +79 -7
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Neuro Vault MCP
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
> 🧠💾 **Plug your knowledge in like a USB drive.** Your Obsidian vault, available in every AI conversation — search it, query it, write to it. Same vault in Claude Code, Cursor, Windsurf — no copy-paste, no context-switching, no re-indexing.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/neuro-vault-mcp)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Your second brain stops being a folder you open between contexts and becomes a first-class participant in every project. Ask, recall, decide, write back — all without leaving the conversation.
|
|
10
|
+
|
|
11
|
+
> _"What did I write about that idea last month?"_ — and now your assistant can actually answer.
|
|
10
12
|
|
|
11
13
|
---
|
|
12
14
|
|
|
@@ -22,30 +24,35 @@
|
|
|
22
24
|
|
|
23
25
|
---
|
|
24
26
|
|
|
25
|
-
##
|
|
27
|
+
## 🧰 Two superpowers, one server
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
flowchart LR
|
|
29
|
-
You([You]) --> AI[AI assistant]
|
|
30
|
-
AI <-->|MCP| NV[Neuro Vault]
|
|
31
|
-
NV <--> Vault[(Obsidian vault)]
|
|
32
|
-
```
|
|
29
|
+
Most "vault MCP" servers give you one or the other. Neuro Vault gives you both, and lets your assistant pick the right one per question:
|
|
33
30
|
|
|
34
|
-
|
|
31
|
+
| | 🔭 **Semantic recall** | 🛠 **Vault operations** |
|
|
32
|
+
| ---------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
33
|
+
| **What it does** | Finds notes by meaning, not keywords. Surfaces neighbours and duplicates. | Reads, writes, edits notes; manages frontmatter, tags, daily notes. |
|
|
34
|
+
| **Best for** | _"What did I think about X?"_, fuzzy recall, exploratory research. | Structured queries, capturing decisions, updating tasks, batch reads. |
|
|
35
|
+
| **Powered by** | Smart Connections embeddings (already in your vault). | The official Obsidian CLI — Smart Connections, sync, plugins all stay in sync. |
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
The two work together: semantic search finds the right region of the vault, vault operations let the assistant actually _do something_ with what it found.
|
|
37
38
|
|
|
38
39
|
---
|
|
39
40
|
|
|
40
|
-
##
|
|
41
|
+
## ✨ What it looks like in practice
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
**Before:** _"Could you check my notes about that LangGraph experiment?"_
|
|
44
|
+
→ Assistant lists `Notes/`, opens 12 files, greps for "LangGraph", gives up halfway, you paste the relevant note manually.
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
**After:** _"Could you check my notes about that LangGraph experiment?"_
|
|
47
|
+
→ One semantic search, top-3 ranked notes back, follow-up question already grounded in your own writing.
|
|
48
|
+
|
|
49
|
+
A few more questions Neuro Vault makes one-shot:
|
|
45
50
|
|
|
46
51
|
> _"What are my active projects tagged #ai with a deadline this quarter?"_
|
|
47
|
-
> _"Show meeting notes from `Work/` from the last two weeks."_
|
|
52
|
+
> _"Show meeting notes from `Work/` from the last two weeks, newest first."_
|
|
48
53
|
> _"Find notes similar to this one I'm reading."_
|
|
54
|
+
> _"Append today's decision to the daily note."_
|
|
55
|
+
> _"What did past-me write about retrieval policy before I started building it?"_
|
|
49
56
|
|
|
50
57
|
One question, one answer. Your assistant stops being a file browser and starts being an actual second brain.
|
|
51
58
|
|
|
@@ -53,6 +60,21 @@ One question, one answer. Your assistant stops being a file browser and starts b
|
|
|
53
60
|
|
|
54
61
|
---
|
|
55
62
|
|
|
63
|
+
## 🏗 How it works
|
|
64
|
+
|
|
65
|
+
```mermaid
|
|
66
|
+
flowchart LR
|
|
67
|
+
You([You]) --> AI[AI assistant]
|
|
68
|
+
AI <-->|MCP| NV[Neuro Vault]
|
|
69
|
+
NV <--> Vault[(Obsidian vault)]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You ask, the assistant calls Neuro Vault, Neuro Vault reads your vault — semantic search uses embeddings already in `.smart-env/`, vault operations go through the `obsidian` CLI. No database, no background processes.
|
|
73
|
+
|
|
74
|
+
For module wiring and internal data flow, see [docs/architecture/module-structure.md](./docs/architecture/module-structure.md).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
56
78
|
## ⚡ Quickstart
|
|
57
79
|
|
|
58
80
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -14263,6 +14263,16 @@ function date4(params) {
|
|
|
14263
14263
|
config(en_default());
|
|
14264
14264
|
|
|
14265
14265
|
// src/lib/input-coercion.ts
|
|
14266
|
+
var CoerceError = class extends Error {
|
|
14267
|
+
fieldName;
|
|
14268
|
+
bareMessage;
|
|
14269
|
+
constructor(fieldName, bareMessage) {
|
|
14270
|
+
super(`${fieldName}: ${bareMessage}`);
|
|
14271
|
+
this.name = "CoerceError";
|
|
14272
|
+
this.fieldName = fieldName;
|
|
14273
|
+
this.bareMessage = bareMessage;
|
|
14274
|
+
}
|
|
14275
|
+
};
|
|
14266
14276
|
function unwrap(schema) {
|
|
14267
14277
|
let current = schema;
|
|
14268
14278
|
while (current instanceof external_exports.ZodOptional || current instanceof external_exports.ZodNullable || current instanceof external_exports.ZodDefault) {
|
|
@@ -14275,35 +14285,87 @@ function unwrap(schema) {
|
|
|
14275
14285
|
function isPlainObject2(value) {
|
|
14276
14286
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14277
14287
|
}
|
|
14278
|
-
function
|
|
14288
|
+
function describeJsonShape(parsed) {
|
|
14289
|
+
if (parsed === null) return "null";
|
|
14290
|
+
if (Array.isArray(parsed)) return "array";
|
|
14291
|
+
return typeof parsed;
|
|
14292
|
+
}
|
|
14293
|
+
function getUnionOptions(union2) {
|
|
14294
|
+
const def = union2._def;
|
|
14295
|
+
return def.options ?? [];
|
|
14296
|
+
}
|
|
14297
|
+
function isStringArraySchema(schema) {
|
|
14298
|
+
const inner = unwrap(schema);
|
|
14299
|
+
if (!(inner instanceof external_exports.ZodArray)) return false;
|
|
14300
|
+
const def = inner._def;
|
|
14301
|
+
const element = def.element ?? def.type;
|
|
14302
|
+
if (!element) return false;
|
|
14303
|
+
return unwrap(element) instanceof external_exports.ZodString;
|
|
14304
|
+
}
|
|
14305
|
+
function coerceFieldValue(schema, value, fieldName = "value") {
|
|
14279
14306
|
if (value === null || value === void 0) return value;
|
|
14280
14307
|
const inner = unwrap(schema);
|
|
14281
14308
|
if (inner instanceof external_exports.ZodNumber) {
|
|
14282
|
-
if (typeof value === "
|
|
14309
|
+
if (typeof value === "number") return value;
|
|
14310
|
+
if (typeof value === "string") {
|
|
14311
|
+
if (value.trim() === "") return value;
|
|
14283
14312
|
const n = Number(value);
|
|
14284
14313
|
if (Number.isFinite(n)) return n;
|
|
14314
|
+
throw new CoerceError(
|
|
14315
|
+
fieldName,
|
|
14316
|
+
`expected number or numeric string, got ${JSON.stringify(value)}`
|
|
14317
|
+
);
|
|
14285
14318
|
}
|
|
14286
14319
|
return value;
|
|
14287
14320
|
}
|
|
14288
14321
|
if (inner instanceof external_exports.ZodBoolean) {
|
|
14322
|
+
if (typeof value === "boolean") return value;
|
|
14289
14323
|
if (value === "true") return true;
|
|
14290
14324
|
if (value === "false") return false;
|
|
14325
|
+
if (typeof value === "string") {
|
|
14326
|
+
throw new CoerceError(
|
|
14327
|
+
fieldName,
|
|
14328
|
+
`expected boolean or "true"/"false", got ${JSON.stringify(value)}`
|
|
14329
|
+
);
|
|
14330
|
+
}
|
|
14291
14331
|
return value;
|
|
14292
14332
|
}
|
|
14293
14333
|
if (inner instanceof external_exports.ZodObject || inner instanceof external_exports.ZodRecord) {
|
|
14334
|
+
if (isPlainObject2(value)) return value;
|
|
14294
14335
|
if (typeof value === "string") {
|
|
14336
|
+
let parsed;
|
|
14337
|
+
try {
|
|
14338
|
+
parsed = JSON.parse(value);
|
|
14339
|
+
} catch {
|
|
14340
|
+
throw new CoerceError(
|
|
14341
|
+
fieldName,
|
|
14342
|
+
`expected object or JSON-string of one, failed to parse: ${JSON.stringify(value)}`
|
|
14343
|
+
);
|
|
14344
|
+
}
|
|
14345
|
+
if (isPlainObject2(parsed)) return parsed;
|
|
14346
|
+
throw new CoerceError(
|
|
14347
|
+
fieldName,
|
|
14348
|
+
`expected object, parsed JSON resolved to ${describeJsonShape(parsed)}`
|
|
14349
|
+
);
|
|
14350
|
+
}
|
|
14351
|
+
return value;
|
|
14352
|
+
}
|
|
14353
|
+
if (inner instanceof external_exports.ZodUnion && typeof value === "string") {
|
|
14354
|
+
const options = getUnionOptions(inner);
|
|
14355
|
+
if (options.some(isStringArraySchema)) {
|
|
14295
14356
|
try {
|
|
14296
14357
|
const parsed = JSON.parse(value);
|
|
14297
|
-
if (
|
|
14358
|
+
if (Array.isArray(parsed) && parsed.every((x) => typeof x === "string")) {
|
|
14359
|
+
return parsed;
|
|
14360
|
+
}
|
|
14298
14361
|
} catch {
|
|
14299
14362
|
}
|
|
14300
|
-
return value;
|
|
14301
14363
|
}
|
|
14302
14364
|
return value;
|
|
14303
14365
|
}
|
|
14304
14366
|
return value;
|
|
14305
14367
|
}
|
|
14306
|
-
function wrapField(field) {
|
|
14368
|
+
function wrapField(field, fieldName) {
|
|
14307
14369
|
let inner = field;
|
|
14308
14370
|
let isOptional = false;
|
|
14309
14371
|
while (inner instanceof external_exports.ZodOptional || inner instanceof external_exports.ZodNullable || inner instanceof external_exports.ZodDefault) {
|
|
@@ -14312,7 +14374,17 @@ function wrapField(field) {
|
|
|
14312
14374
|
if (!next) break;
|
|
14313
14375
|
inner = next;
|
|
14314
14376
|
}
|
|
14315
|
-
const wrapped = external_exports.preprocess((v) =>
|
|
14377
|
+
const wrapped = external_exports.preprocess((v, ctx) => {
|
|
14378
|
+
try {
|
|
14379
|
+
return coerceFieldValue(inner, v, fieldName);
|
|
14380
|
+
} catch (err) {
|
|
14381
|
+
if (err instanceof CoerceError) {
|
|
14382
|
+
ctx.addIssue({ code: "custom", message: err.bareMessage });
|
|
14383
|
+
return external_exports.NEVER;
|
|
14384
|
+
}
|
|
14385
|
+
throw err;
|
|
14386
|
+
}
|
|
14387
|
+
}, inner);
|
|
14316
14388
|
return isOptional ? wrapped.optional() : wrapped;
|
|
14317
14389
|
}
|
|
14318
14390
|
function wrapSchemaWithCoercion(schema) {
|
|
@@ -14320,7 +14392,7 @@ function wrapSchemaWithCoercion(schema) {
|
|
|
14320
14392
|
const shape = schema.shape;
|
|
14321
14393
|
const newShape = {};
|
|
14322
14394
|
for (const [key, field] of Object.entries(shape)) {
|
|
14323
|
-
newShape[key] = wrapField(field);
|
|
14395
|
+
newShape[key] = wrapField(field, key);
|
|
14324
14396
|
}
|
|
14325
14397
|
return external_exports.object(newShape);
|
|
14326
14398
|
}
|