neuro-vault-mcp 7.0.0 → 8.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🧠💾 **Make your personal vault usable by agents.** Low-token retrieval, explicit provenance, and safe writes for your Obsidian notes — in Claude Code, Cursor, Windsurf, and any MCP client.
4
4
 
5
- https://github.com/user-attachments/assets/25c1bafb-7b90-43ac-aa50-50e85705fb5b
5
+ [https://github.com/user-attachments/assets/25c1bafb-7b90-43ac-aa50-50e85705fb5b](https://github.com/user-attachments/assets/5ac86cd7-2d67-420d-b940-dba4ae1a2ccb)
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/neuro-vault-mcp)](https://www.npmjs.com/package/neuro-vault-mcp)
8
8
  [![Node.js](https://img.shields.io/node/v/neuro-vault-mcp)](https://nodejs.org)
@@ -140,7 +140,7 @@ Two vaults cannot share the same directory basename — the basename doubles as
140
140
  With multiple vaults registered:
141
141
 
142
142
  - **Every tool** accepts an optional `vault: "<name>"` parameter to target a specific vault.
143
- - **`search_notes`, `query_notes`, and `get_vault_overview`** fan out across all registered vaults when `vault` is omitted. The response shape switches to `results_by_vault: [...]` (one entry per vault) plus `skipped_vaults: [...]` for any vault the tool could not reach.
143
+ - **`search_notes`, `query_notes`, `get_vault_overview`, `list_tags`, and `list_properties`** fan out across all registered vaults when `vault` is omitted. The response shape switches to `results_by_vault: [...]` (one entry per vault) plus `skipped_vaults: [...]` for any vault the tool could not reach and `failed_vaults: [...]` for per-vault runtime errors (`{ vault, error: { code, message, details? } }`). A single failed vault does not abort the whole call.
144
144
  - **All other tools** (writes, reads of specific paths, single-vault diagnostics) require an explicit `vault` in multi-vault mode. Omitting it returns `VAULT_REQUIRED`.
145
145
  - **Semantic fan-out** silently skips vaults whose Smart Connections `.smart-env/multi/` index is unavailable. Targeting such a vault explicitly with `vault: "<name>"` returns `SEMANTIC_INDEX_NOT_FOUND`.
146
146
 
package/dist/cli.js CHANGED
@@ -14228,6 +14228,16 @@ var ToolHandlerError = class extends Error {
14228
14228
  }
14229
14229
  }
14230
14230
  };
14231
+ var FATAL_TOOL_ERROR_CODES = /* @__PURE__ */ new Set([
14232
+ "INVALID_ARGUMENT",
14233
+ "INVALID_PARAMS",
14234
+ "INVALID_FILTER",
14235
+ "VAULT_REQUIRED",
14236
+ "VAULT_NOT_FOUND"
14237
+ ]);
14238
+ function isFatalToolError(error48) {
14239
+ return error48 instanceof ToolHandlerError && FATAL_TOOL_ERROR_CODES.has(error48.code);
14240
+ }
14231
14241
  function isPlainRecord(value) {
14232
14242
  return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
14233
14243
  }
@@ -15715,20 +15725,73 @@ function buildGetStatsTool(deps) {
15715
15725
  }
15716
15726
 
15717
15727
  // src/lib/fan-out.ts
15728
+ function findFatalRejection(settled) {
15729
+ for (const outcome of settled) {
15730
+ if (outcome.status === "rejected" && isFatalToolError(outcome.reason)) {
15731
+ return outcome.reason;
15732
+ }
15733
+ }
15734
+ return void 0;
15735
+ }
15736
+ function mapRejectionToFailedVault(vault, reason) {
15737
+ if (reason instanceof ToolHandlerError) {
15738
+ return {
15739
+ vault,
15740
+ error: {
15741
+ code: reason.code,
15742
+ message: reason.message,
15743
+ ...reason.details ? { details: reason.details } : {}
15744
+ }
15745
+ };
15746
+ }
15747
+ return {
15748
+ vault,
15749
+ error: {
15750
+ code: "INTERNAL_ERROR",
15751
+ message: reason instanceof Error ? reason.message : String(reason)
15752
+ }
15753
+ };
15754
+ }
15718
15755
  async function runFanOut(registry2, fn) {
15719
15756
  const entries = registry2.list();
15720
- const results = await Promise.all(
15721
- entries.map(async (entry) => ({ vault: entry.name, ...await fn(entry) }))
15722
- );
15723
- return { results_by_vault: results, skipped_vaults: [] };
15757
+ const settled = await Promise.allSettled(entries.map((entry) => fn(entry)));
15758
+ const fatalError = findFatalRejection(settled);
15759
+ if (fatalError) {
15760
+ throw fatalError;
15761
+ }
15762
+ const results = [];
15763
+ const failed = [];
15764
+ for (let i = 0; i < settled.length; i++) {
15765
+ const outcome = settled[i];
15766
+ const vault = entries[i].name;
15767
+ if (outcome.status === "fulfilled") {
15768
+ results.push({ vault, ...outcome.value });
15769
+ } else {
15770
+ failed.push(mapRejectionToFailedVault(vault, outcome.reason));
15771
+ }
15772
+ }
15773
+ return { results_by_vault: results, skipped_vaults: [], failed_vaults: failed };
15724
15774
  }
15725
15775
  async function runSemanticFanOut(registry2, fn) {
15726
15776
  const eligible = registry2.semanticAvailableEntries();
15727
15777
  const skipped = registry2.list().filter((e) => !e.semanticAvailable).map((e) => ({ vault: e.name, reason: "SEMANTIC_INDEX_NOT_FOUND" }));
15728
- const results = await Promise.all(
15729
- eligible.map(async (entry) => ({ vault: entry.name, ...await fn(entry) }))
15730
- );
15731
- return { results_by_vault: results, skipped_vaults: skipped };
15778
+ const settled = await Promise.allSettled(eligible.map((entry) => fn(entry)));
15779
+ const fatalError = findFatalRejection(settled);
15780
+ if (fatalError) {
15781
+ throw fatalError;
15782
+ }
15783
+ const results = [];
15784
+ const failed = [];
15785
+ for (let i = 0; i < settled.length; i++) {
15786
+ const outcome = settled[i];
15787
+ const vault = eligible[i].name;
15788
+ if (outcome.status === "fulfilled") {
15789
+ results.push({ vault, ...outcome.value });
15790
+ } else {
15791
+ failed.push(mapRejectionToFailedVault(vault, outcome.reason));
15792
+ }
15793
+ }
15794
+ return { results_by_vault: results, skipped_vaults: skipped, failed_vaults: failed };
15732
15795
  }
15733
15796
 
15734
15797
  // src/modules/semantic/retrieval-policy.ts
@@ -17473,7 +17536,7 @@ async function buildServerInstructions(registry2) {
17473
17536
 
17474
17537
  ## Multi-vault mode
17475
17538
 
17476
- This server is registered with multiple vaults: ${names}. Every tool accepts an optional \`vault\` parameter. For broad recall, \`search_notes\`, \`query_notes\`, and \`get_vault_overview\` fan out across all vaults when \`vault\` is omitted; other tools (reads of specific paths, writes, single-vault diagnostics) require an explicit \`vault\` \u2014 omitting it returns \`VAULT_REQUIRED\`. Path-shaped parameters (\`path\`, \`paths\`, \`path_prefix\`) remain vault-relative; vault identity is always carried by \`vault\`.`;
17539
+ This server is registered with multiple vaults: ${names}. Every tool accepts an optional \`vault\` parameter. For broad recall, \`search_notes\`, \`query_notes\`, \`get_vault_overview\`, \`list_tags\`, and \`list_properties\` fan out across all vaults when \`vault\` is omitted; other tools (reads of specific paths, writes, single-vault diagnostics) require an explicit \`vault\` \u2014 omitting it returns \`VAULT_REQUIRED\`. Fan-out responses include \`results_by_vault\`, \`skipped_vaults\` (vaults pre-filtered out, e.g. missing semantic index), and \`failed_vaults\` (per-vault runtime errors with \`{ code, message, details? }\`) \u2014 a single failed vault does not abort the whole call. Path-shaped parameters (\`path\`, \`paths\`, \`path_prefix\`) remain vault-relative; vault identity is always carried by \`vault\`.`;
17477
17540
  }
17478
17541
  for (const entry of registry2.list()) {
17479
17542
  const extra = await readExternalAgentInstructions(entry.path);