neuro-vault-mcp 3.1.0 → 3.2.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/README.md +21 -4
- package/dist/cli.js +159 -73
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
## ✨ Why Neuro Vault?
|
|
14
14
|
|
|
15
|
-
- 🧠 **Semantic search
|
|
16
|
-
- 🎯 **
|
|
17
|
-
-
|
|
18
|
-
- 🔎 **
|
|
15
|
+
- 🧠 **Semantic search that already knows your vault** — reuses [Smart Connections](https://github.com/brianpetro/obsidian-smart-connections) embeddings. No re-indexing, no API keys, no extra infrastructure.
|
|
16
|
+
- 🎯 **Quick or deep, your call** — fast direct lookups for "find that note", or exploratory mode with related-note expansion when the question is fuzzy.
|
|
17
|
+
- 🧭 **A real navigation toolkit for your agent** — instead of grepping files and opening notes one by one, your assistant walks the vault like a database: filter by tags and properties, batch-read metadata, discover the structure, jump to semantic neighbours.
|
|
18
|
+
- 🔎 **Ask structured questions in plain language** — _"active projects tagged #ai"_, _"todo tasks with a deadline this week"_, _"meeting notes from `Work/` newest first"_ — one call, ranked answer, no chains of reads.
|
|
19
|
+
- ✍️ **Read and write through Obsidian itself** — create, append, edit notes, manage frontmatter and daily notes via the [Obsidian CLI](https://github.com/AlexMost/obsidian-cli), so Smart Connections, sync, and other plugins stay in the loop.
|
|
19
20
|
- ⚡ **Zero infrastructure** — local stdio MCP server, in-memory index, no database, no background processes, no watchers.
|
|
20
21
|
- 🔌 **Drop-in for any MCP client** — Claude Code, Cursor, Windsurf — configuration is a single JSON block.
|
|
21
22
|
|
|
@@ -36,6 +37,22 @@ For module wiring and internal data flow, see [docs/architecture/module-structur
|
|
|
36
37
|
|
|
37
38
|
---
|
|
38
39
|
|
|
40
|
+
## 🧭 Your vault, but queryable
|
|
41
|
+
|
|
42
|
+
A plain-text vault is great for humans and rough on agents. Without the right tools, an assistant ends up listing folders, opening dozens of notes, and grepping inside each one just to answer a simple question.
|
|
43
|
+
|
|
44
|
+
Neuro Vault gives your assistant a way to **ask the vault directly** — by tag, by frontmatter property, by folder, by similarity. Things like:
|
|
45
|
+
|
|
46
|
+
> _"What are my active projects tagged #ai with a deadline this quarter?"_
|
|
47
|
+
> _"Show meeting notes from `Work/` from the last two weeks."_
|
|
48
|
+
> _"Find notes similar to this one I'm reading."_
|
|
49
|
+
|
|
50
|
+
One question, one answer. Your assistant stops being a file browser and starts being an actual second brain.
|
|
51
|
+
|
|
52
|
+
→ See [docs/guide/vault-operations.md](./docs/guide/vault-operations.md#query_notes) for the full query language and examples.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
39
56
|
## ⚡ Quickstart
|
|
40
57
|
|
|
41
58
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -494,76 +494,6 @@ function getEmbeddingDimension(sources) {
|
|
|
494
494
|
return expectedDimension ?? 0;
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
// src/lib/tool-response.ts
|
|
498
|
-
var ToolHandlerError = class extends Error {
|
|
499
|
-
code;
|
|
500
|
-
details;
|
|
501
|
-
constructor(code, message, options) {
|
|
502
|
-
super(message);
|
|
503
|
-
this.name = "ToolHandlerError";
|
|
504
|
-
this.code = code;
|
|
505
|
-
this.details = options?.details;
|
|
506
|
-
if (options?.cause !== void 0) {
|
|
507
|
-
this.cause = options.cause;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
function toToolResponse(value) {
|
|
512
|
-
const text = value === void 0 ? "ok" : JSON.stringify(value, null, 2);
|
|
513
|
-
const result = {
|
|
514
|
-
content: [{ type: "text", text }]
|
|
515
|
-
};
|
|
516
|
-
if (value !== void 0 && value !== null) {
|
|
517
|
-
result.structuredContent = value;
|
|
518
|
-
}
|
|
519
|
-
return result;
|
|
520
|
-
}
|
|
521
|
-
function toToolErrorResponse(error48) {
|
|
522
|
-
if (error48 instanceof ToolHandlerError) {
|
|
523
|
-
return {
|
|
524
|
-
content: [{ type: "text", text: error48.message }],
|
|
525
|
-
structuredContent: {
|
|
526
|
-
code: error48.code,
|
|
527
|
-
message: error48.message,
|
|
528
|
-
details: error48.details
|
|
529
|
-
},
|
|
530
|
-
isError: true
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
const message = error48 instanceof Error ? error48.message : "Unknown tool error";
|
|
534
|
-
return {
|
|
535
|
-
content: [{ type: "text", text: message }],
|
|
536
|
-
structuredContent: { message },
|
|
537
|
-
isError: true
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
async function invokeTool(handler) {
|
|
541
|
-
try {
|
|
542
|
-
const value = await handler();
|
|
543
|
-
return toToolResponse(value);
|
|
544
|
-
} catch (error48) {
|
|
545
|
-
return toToolErrorResponse(error48);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// src/lib/tool-registry.ts
|
|
550
|
-
function registerTool(tool) {
|
|
551
|
-
return {
|
|
552
|
-
name: tool.name,
|
|
553
|
-
spec: {
|
|
554
|
-
...tool.title !== void 0 ? { title: tool.title } : {},
|
|
555
|
-
description: tool.description,
|
|
556
|
-
inputSchema: tool.inputSchema,
|
|
557
|
-
...tool.outputSchema !== void 0 ? { outputSchema: tool.outputSchema } : {},
|
|
558
|
-
...tool.annotations !== void 0 ? { annotations: tool.annotations } : {}
|
|
559
|
-
},
|
|
560
|
-
handler: async (args) => invokeTool(async () => {
|
|
561
|
-
const parsed = tool.inputSchema.parse(args);
|
|
562
|
-
return tool.handler(parsed);
|
|
563
|
-
})
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
|
|
567
497
|
// node_modules/zod/v4/classic/external.js
|
|
568
498
|
var external_exports = {};
|
|
569
499
|
__export(external_exports, {
|
|
@@ -14332,6 +14262,150 @@ function date4(params) {
|
|
|
14332
14262
|
// node_modules/zod/v4/classic/external.js
|
|
14333
14263
|
config(en_default());
|
|
14334
14264
|
|
|
14265
|
+
// src/lib/input-coercion.ts
|
|
14266
|
+
function unwrap(schema) {
|
|
14267
|
+
let current = schema;
|
|
14268
|
+
while (current instanceof external_exports.ZodOptional || current instanceof external_exports.ZodNullable || current instanceof external_exports.ZodDefault) {
|
|
14269
|
+
const unwrapped = current.unwrap();
|
|
14270
|
+
if (!unwrapped) break;
|
|
14271
|
+
current = unwrapped;
|
|
14272
|
+
}
|
|
14273
|
+
return current;
|
|
14274
|
+
}
|
|
14275
|
+
function isPlainObject2(value) {
|
|
14276
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14277
|
+
}
|
|
14278
|
+
function coerceField(schema, value) {
|
|
14279
|
+
if (value === null || value === void 0) return value;
|
|
14280
|
+
const inner = unwrap(schema);
|
|
14281
|
+
if (inner instanceof external_exports.ZodNumber) {
|
|
14282
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
14283
|
+
const n = Number(value);
|
|
14284
|
+
if (Number.isFinite(n)) return n;
|
|
14285
|
+
}
|
|
14286
|
+
return value;
|
|
14287
|
+
}
|
|
14288
|
+
if (inner instanceof external_exports.ZodBoolean) {
|
|
14289
|
+
if (value === "true") return true;
|
|
14290
|
+
if (value === "false") return false;
|
|
14291
|
+
return value;
|
|
14292
|
+
}
|
|
14293
|
+
if (inner instanceof external_exports.ZodObject || inner instanceof external_exports.ZodRecord) {
|
|
14294
|
+
if (typeof value === "string") {
|
|
14295
|
+
try {
|
|
14296
|
+
const parsed = JSON.parse(value);
|
|
14297
|
+
if (isPlainObject2(parsed)) return parsed;
|
|
14298
|
+
} catch {
|
|
14299
|
+
}
|
|
14300
|
+
return value;
|
|
14301
|
+
}
|
|
14302
|
+
return value;
|
|
14303
|
+
}
|
|
14304
|
+
return value;
|
|
14305
|
+
}
|
|
14306
|
+
function coerceInput(schema, value) {
|
|
14307
|
+
const inner = unwrap(schema);
|
|
14308
|
+
if (!(inner instanceof external_exports.ZodObject)) return value;
|
|
14309
|
+
if (!isPlainObject2(value)) return value;
|
|
14310
|
+
const shape = inner.shape;
|
|
14311
|
+
const out = { ...value };
|
|
14312
|
+
for (const key of Object.keys(shape)) {
|
|
14313
|
+
if (key in out) {
|
|
14314
|
+
out[key] = coerceField(shape[key], out[key]);
|
|
14315
|
+
}
|
|
14316
|
+
}
|
|
14317
|
+
return out;
|
|
14318
|
+
}
|
|
14319
|
+
|
|
14320
|
+
// src/lib/tool-response.ts
|
|
14321
|
+
var ToolHandlerError = class extends Error {
|
|
14322
|
+
code;
|
|
14323
|
+
details;
|
|
14324
|
+
constructor(code, message, options) {
|
|
14325
|
+
super(message);
|
|
14326
|
+
this.name = "ToolHandlerError";
|
|
14327
|
+
this.code = code;
|
|
14328
|
+
this.details = options?.details;
|
|
14329
|
+
if (options?.cause !== void 0) {
|
|
14330
|
+
this.cause = options.cause;
|
|
14331
|
+
}
|
|
14332
|
+
}
|
|
14333
|
+
};
|
|
14334
|
+
function isPlainRecord(value) {
|
|
14335
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
14336
|
+
}
|
|
14337
|
+
function toToolResponse(value) {
|
|
14338
|
+
const text = value === void 0 ? "ok" : JSON.stringify(value, null, 2);
|
|
14339
|
+
const result = {
|
|
14340
|
+
content: [{ type: "text", text }]
|
|
14341
|
+
};
|
|
14342
|
+
if (isPlainRecord(value)) {
|
|
14343
|
+
result.structuredContent = value;
|
|
14344
|
+
}
|
|
14345
|
+
return result;
|
|
14346
|
+
}
|
|
14347
|
+
function toToolErrorResponse(error48) {
|
|
14348
|
+
if (error48 instanceof ToolHandlerError) {
|
|
14349
|
+
return {
|
|
14350
|
+
content: [{ type: "text", text: error48.message }],
|
|
14351
|
+
structuredContent: {
|
|
14352
|
+
code: error48.code,
|
|
14353
|
+
message: error48.message,
|
|
14354
|
+
details: error48.details
|
|
14355
|
+
},
|
|
14356
|
+
isError: true
|
|
14357
|
+
};
|
|
14358
|
+
}
|
|
14359
|
+
const message = error48 instanceof Error ? error48.message : "Unknown tool error";
|
|
14360
|
+
return {
|
|
14361
|
+
content: [{ type: "text", text: message }],
|
|
14362
|
+
structuredContent: { message },
|
|
14363
|
+
isError: true
|
|
14364
|
+
};
|
|
14365
|
+
}
|
|
14366
|
+
async function invokeTool(handler) {
|
|
14367
|
+
try {
|
|
14368
|
+
const value = await handler();
|
|
14369
|
+
return toToolResponse(value);
|
|
14370
|
+
} catch (error48) {
|
|
14371
|
+
return toToolErrorResponse(error48);
|
|
14372
|
+
}
|
|
14373
|
+
}
|
|
14374
|
+
|
|
14375
|
+
// src/lib/tool-registry.ts
|
|
14376
|
+
function formatZodError(error48) {
|
|
14377
|
+
const issues = error48.issues.map((issue2) => {
|
|
14378
|
+
const path6 = issue2.path.length > 0 ? issue2.path.join(".") : "<root>";
|
|
14379
|
+
const expected = issue2.expected;
|
|
14380
|
+
const formatted = { path: path6, message: issue2.message };
|
|
14381
|
+
if (typeof expected === "string") formatted.expected = expected;
|
|
14382
|
+
return formatted;
|
|
14383
|
+
});
|
|
14384
|
+
const message = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
|
|
14385
|
+
return { message, issues };
|
|
14386
|
+
}
|
|
14387
|
+
function registerTool(tool) {
|
|
14388
|
+
return {
|
|
14389
|
+
name: tool.name,
|
|
14390
|
+
spec: {
|
|
14391
|
+
...tool.title !== void 0 ? { title: tool.title } : {},
|
|
14392
|
+
description: tool.description,
|
|
14393
|
+
inputSchema: tool.inputSchema,
|
|
14394
|
+
...tool.outputSchema !== void 0 ? { outputSchema: tool.outputSchema } : {},
|
|
14395
|
+
...tool.annotations !== void 0 ? { annotations: tool.annotations } : {}
|
|
14396
|
+
},
|
|
14397
|
+
handler: async (args) => invokeTool(async () => {
|
|
14398
|
+
const coerced = coerceInput(tool.inputSchema, args);
|
|
14399
|
+
const result = tool.inputSchema.safeParse(coerced);
|
|
14400
|
+
if (!result.success) {
|
|
14401
|
+
const { message, issues } = formatZodError(result.error);
|
|
14402
|
+
throw new ToolHandlerError("INVALID_PARAMS", message, { details: { issues } });
|
|
14403
|
+
}
|
|
14404
|
+
return tool.handler(result.data);
|
|
14405
|
+
})
|
|
14406
|
+
};
|
|
14407
|
+
}
|
|
14408
|
+
|
|
14335
14409
|
// src/modules/semantic/tool-helpers.ts
|
|
14336
14410
|
import path3 from "path";
|
|
14337
14411
|
var WINDOWS_ABSOLUTE_PATH_RE = /^[A-Za-z]:[\\/]/;
|
|
@@ -15578,7 +15652,7 @@ var ALLOWED_OPERATORS = /* @__PURE__ */ new Set([
|
|
|
15578
15652
|
"$not"
|
|
15579
15653
|
]);
|
|
15580
15654
|
function validateFilter(filter) {
|
|
15581
|
-
if (!
|
|
15655
|
+
if (!isPlainObject3(filter)) {
|
|
15582
15656
|
throw invalidFilter("filter must be a JSON object");
|
|
15583
15657
|
}
|
|
15584
15658
|
walk(filter, []);
|
|
@@ -15588,7 +15662,7 @@ function walk(value, pathSegments) {
|
|
|
15588
15662
|
value.forEach((entry, idx) => walk(entry, [...pathSegments, `[${idx}]`]));
|
|
15589
15663
|
return;
|
|
15590
15664
|
}
|
|
15591
|
-
if (!
|
|
15665
|
+
if (!isPlainObject3(value)) return;
|
|
15592
15666
|
for (const [key, child] of Object.entries(value)) {
|
|
15593
15667
|
if (key.startsWith("$") && !ALLOWED_OPERATORS.has(key)) {
|
|
15594
15668
|
const where = pathSegments.length === 0 ? key : `${pathSegments.join(".")}.${key}`;
|
|
@@ -15597,7 +15671,7 @@ function walk(value, pathSegments) {
|
|
|
15597
15671
|
walk(child, [...pathSegments, key]);
|
|
15598
15672
|
}
|
|
15599
15673
|
}
|
|
15600
|
-
function
|
|
15674
|
+
function isPlainObject3(value) {
|
|
15601
15675
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15602
15676
|
}
|
|
15603
15677
|
function invalidFilter(message) {
|
|
@@ -15993,6 +16067,18 @@ var require2 = createRequire(import.meta.url);
|
|
|
15993
16067
|
var { name: SERVER_NAME, version: SERVER_VERSION } = require2("../package.json");
|
|
15994
16068
|
var SERVER_INSTRUCTIONS = `This server provides two capability sets for an Obsidian vault: semantic search (when enabled) and direct vault operations (when enabled). Use the right one based on the user's intent.
|
|
15995
16069
|
|
|
16070
|
+
## Role: a second brain alongside the project
|
|
16071
|
+
|
|
16072
|
+
This vault is the user's personal knowledge base \u2014 planning notes, brainstorm artifacts, decisions, reflections, drafts \u2014 that often predates and outlives any single project they bring to you. Before brainstorming new directions, drafting retrospectives, or answering "why did we decide X", check the vault first; the answer is often there but lives nowhere else.
|
|
16073
|
+
|
|
16074
|
+
You don't know on your own how the user scopes notes for the current project \u2014 the same vault is typically connected to many, and different users organise differently (a tag, a folder, a frontmatter property like \`project\`, or a combination). The user's project-level instructions should name that scope and tell you how to query for it. If they don't, you have three ways forward, in order of preference:
|
|
16075
|
+
|
|
16076
|
+
1. **Probe the vault structure** via \`list_tags\`, \`list_properties\`, and exploratory \`query_notes\` calls \u2014 common conventions (a \`project\` frontmatter field, a \`Projects/\` folder, a recurring tag) usually surface immediately and tell you how this vault is organised.
|
|
16077
|
+
2. **Use \`search_notes\`** with the project name and key concepts as a fuzzy entry point \u2014 relevant material may exist under unrelated names.
|
|
16078
|
+
3. **Ask the user**, and the first time they save project-specific notes propose a scoping scheme so future sessions have an explicit entry point.
|
|
16079
|
+
|
|
16080
|
+
When operating directly inside the vault itself (not from an external project that connects to it), you have both filesystem access and MCP access to the same files. Prefer MCP tools \u2014 \`query_notes\` for structured filters, \`search_notes\` for semantic recall \u2014 over manually scanning files; they exist so frontmatter and similarity queries don't require reading every note.
|
|
16081
|
+
|
|
15996
16082
|
## When to use vault operations
|
|
15997
16083
|
|
|
15998
16084
|
### Notes (body)
|