@bitbonsai/mcpvault 0.16.1 → 0.17.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
@@ -258,6 +258,21 @@ Add `--read-only` after the vault path to expose only read tools. Mutating tools
258
258
 
259
259
  The CLI also accepts `--read-only true` and `--read-only=true` for configuration systems that require explicit values. Omit the option, or set it to `false`, to keep normal read/write access.
260
260
 
261
+ **Default search and listing exclusions:**
262
+
263
+ Repeat `--exclude <subtree>` to hide vault-relative folders from `search_notes` and `list_directory` in every session. Per-call `excludePaths` add to these defaults. Direct read and write tools remain available for excluded paths.
264
+
265
+ ```json
266
+ "args": [
267
+ "@bitbonsai/mcpvault@latest",
268
+ "/Users/yourname/Documents/ResearchVault",
269
+ "--exclude", "Archive",
270
+ "--exclude", "Snapshots/Old"
271
+ ]
272
+ ```
273
+
274
+ MCPVault does not read Obsidian's `.obsidian/app.json`; exclusions stay explicit in the MCP server configuration.
275
+
261
276
  **Configuration File Locations:**
262
277
 
263
278
  - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
@@ -981,6 +996,8 @@ Only grant write access to clients and conversations you trust. Use `--read-only
981
996
  ## Architecture
982
997
 
983
998
  - `server.ts` - MCP server entry point
999
+ - `src/cli.ts` - CLI option parsing for vault path, read-only mode, and default exclusions
1000
+ - `src/createServer.ts` - MCP tool registration, request routing, and discovery defaults
984
1001
  - `src/frontmatter.ts` - YAML frontmatter handling with gray-matter
985
1002
  - `src/filesystem.ts` - File operations with path validation
986
1003
  - `src/pathfilter.ts` - Directory and file filtering
package/dist/server.js CHANGED
@@ -24,7 +24,7 @@ mcpvault v${VERSION}
24
24
  Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant
25
25
 
26
26
  Usage:
27
- npx @bitbonsai/mcpvault [vault-path] [--read-only[=true|false]]
27
+ npx @bitbonsai/mcpvault [vault-path] [--read-only[=true|false]] [--exclude <subtree>]...
28
28
 
29
29
  Arguments:
30
30
  [vault-path] Optional path to your Obsidian vault directory
@@ -35,12 +35,15 @@ Options:
35
35
  --help, -h Show this help message
36
36
  --read-only Expose read tools only and reject all vault mutations
37
37
  May be passed alone, with true/false, or as --read-only=true
38
+ --exclude PATH Hide a vault-relative subtree from search and directory listings
39
+ Repeat for multiple subtrees; direct read/write tools are unaffected
38
40
 
39
41
  Examples:
40
42
  npx @bitbonsai/mcpvault
41
43
  npx @bitbonsai/mcpvault ~/Documents/MyVault
42
44
  npx @bitbonsai/mcpvault ./Vault
43
45
  npx @bitbonsai/mcpvault ./Vault --read-only
46
+ npx @bitbonsai/mcpvault ./Vault --exclude Archive --exclude "Snapshots/Old"
44
47
  npx @bitbonsai/mcpvault /path/to/obsidian/vault
45
48
  npx @bitbonsai/mcpvault "/path/with spaces/Obsidian Vault"
46
49
  `);
@@ -48,11 +51,11 @@ Examples:
48
51
  }
49
52
  // Remove runtime options before joining trailing args, preserving support for
50
53
  // unquoted vault paths with spaces. When omitted, use the current directory.
51
- const { vaultPathArg, readOnly } = parseCliArgs(cliArgs);
54
+ const { vaultPathArg, readOnly, excludePaths } = parseCliArgs(cliArgs);
52
55
  const vaultPath = resolve(vaultPathArg || process.cwd());
53
56
  // Serve both the legacy handshake-based protocol and MCP 2026-07-28 from the
54
57
  // same process. The opening exchange selects the era for this connection.
55
- const serverHandle = serveStdio(() => createServer(vaultPath, { version: VERSION, readOnly }), { onerror: (error) => console.error(error) });
58
+ const serverHandle = serveStdio(() => createServer(vaultPath, { version: VERSION, readOnly, excludePaths }), { onerror: (error) => console.error(error) });
56
59
  // Exit when the client disconnects (stdin EOF) or the process is asked to
57
60
  // terminate. Hosts that don't send an MCP shutdown request otherwise leave
58
61
  // this process running forever, orphaned once stdin closes (#159).
package/dist/src/cli.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export interface ParsedCliArgs {
2
2
  vaultPathArg: string;
3
3
  readOnly: boolean;
4
+ excludePaths: string[];
4
5
  }
5
6
  /**
6
7
  * Parse runtime options without importing server.ts, which starts the MCP
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAkC1D"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAuD1D"}
package/dist/src/cli.js CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  export function parseCliArgs(args) {
7
7
  const pathArgs = [];
8
+ const excludePaths = [];
8
9
  let readOnly = false;
9
10
  for (let index = 0; index < args.length; index += 1) {
10
11
  const arg = args[index];
@@ -27,10 +28,28 @@ export function parseCliArgs(args) {
27
28
  readOnly = value === "true";
28
29
  continue;
29
30
  }
31
+ if (arg === "--exclude") {
32
+ const value = args[index + 1]?.trim();
33
+ if (!value || value.startsWith("--")) {
34
+ throw new Error("--exclude requires a subtree path");
35
+ }
36
+ excludePaths.push(value);
37
+ index += 1;
38
+ continue;
39
+ }
40
+ if (arg.startsWith("--exclude=")) {
41
+ const value = arg.slice("--exclude=".length).trim();
42
+ if (!value) {
43
+ throw new Error("--exclude requires a subtree path");
44
+ }
45
+ excludePaths.push(value);
46
+ continue;
47
+ }
30
48
  pathArgs.push(arg);
31
49
  }
32
50
  return {
33
51
  vaultPathArg: pathArgs.join(" ").trim(),
34
52
  readOnly,
53
+ excludePaths,
35
54
  };
36
55
  }
@@ -8,6 +8,8 @@ export interface CreateServerOptions {
8
8
  frontmatterHandler?: FrontmatterHandler;
9
9
  /** Expose read tools only and reject direct calls to mutating tools. */
10
10
  readOnly?: boolean;
11
+ /** Vault-relative subtrees hidden from search and directory listings. */
12
+ excludePaths?: string[];
11
13
  }
12
14
  export declare function createServer(vaultPath: string, options?: CreateServerOptions): Server;
13
15
  //# sourceMappingURL=createServer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createServer.d.ts","sourceRoot":"","sources":["../../src/createServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAa,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAoB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAK7C,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAYD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAsezF"}
1
+ {"version":3,"file":"createServer.d.ts","sourceRoot":"","sources":["../../src/createServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAa,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAoB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAK7C,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAoCD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAofzF"}
@@ -4,7 +4,7 @@ import { FrontmatterHandler, parseFrontmatter } from "./frontmatter.js";
4
4
  import { PathFilter } from "./pathfilter.js";
5
5
  import { SearchService } from "./search.js";
6
6
  import { handleWikiLinkTool } from "./wikilink/index.js";
7
- import { resolve } from "path";
7
+ import { posix, resolve } from "path";
8
8
  const MUTATING_TOOLS = new Set([
9
9
  "write_note",
10
10
  "patch_note",
@@ -14,8 +14,29 @@ const MUTATING_TOOLS = new Set([
14
14
  "update_frontmatter",
15
15
  "manage_tags",
16
16
  ]);
17
+ function normalizeSubtree(path) {
18
+ let relativePath = path.trim().replace(/\\/g, "/");
19
+ while (relativePath.startsWith("/"))
20
+ relativePath = relativePath.slice(1);
21
+ const normalizedPath = posix.normalize(relativePath);
22
+ if (normalizedPath === ".")
23
+ return "";
24
+ return normalizedPath.endsWith("/") ? normalizedPath.slice(0, -1) : normalizedPath;
25
+ }
26
+ function mergeExcludePaths(defaults, requested) {
27
+ const provided = Array.isArray(requested) ? requested : [];
28
+ return [...new Set([...defaults, ...provided]
29
+ .filter((path) => typeof path === "string")
30
+ .map(normalizeSubtree)
31
+ .filter(Boolean))];
32
+ }
33
+ function isExcluded(path, excludePaths) {
34
+ const normalizedPath = normalizeSubtree(path);
35
+ return excludePaths.some((excluded) => normalizedPath === excluded || normalizedPath.startsWith(`${excluded}/`));
36
+ }
17
37
  export function createServer(vaultPath, options = {}) {
18
- const { name = "mcpvault", version = "0.0.0", pathFilter = new PathFilter(), frontmatterHandler = new FrontmatterHandler(), readOnly = false, } = options;
38
+ const { name = "mcpvault", version = "0.0.0", pathFilter = new PathFilter(), frontmatterHandler = new FrontmatterHandler(), readOnly = false, excludePaths = [], } = options;
39
+ const defaultExcludePaths = mergeExcludePaths([], excludePaths);
19
40
  const resolvedVaultPath = resolve(vaultPath);
20
41
  const fileSystem = new FileSystemService(resolvedVaultPath, pathFilter, frontmatterHandler);
21
42
  const searchService = new SearchService(resolvedVaultPath, pathFilter);
@@ -317,10 +338,17 @@ export function createServer(vaultPath, options = {}) {
317
338
  };
318
339
  }
319
340
  case "list_directory": {
320
- const listing = await fileSystem.listDirectory(trimmedArgs.path || '');
341
+ const requestedPath = normalizeSubtree(trimmedArgs.path || "");
342
+ if (isExcluded(requestedPath, defaultExcludePaths)) {
343
+ throw new Error(`Directory excluded by server configuration: ${requestedPath}`);
344
+ }
345
+ const listing = await fileSystem.listDirectory(trimmedArgs.path || "");
346
+ const prefix = requestedPath ? `${requestedPath}/` : "";
347
+ const directories = listing.directories.filter((name) => !isExcluded(`${prefix}${name}`, defaultExcludePaths));
348
+ const files = listing.files.filter((name) => !isExcluded(`${prefix}${name}`, defaultExcludePaths));
321
349
  const indent = trimmedArgs.prettyPrint ? 2 : undefined;
322
350
  return {
323
- content: [{ type: "text", text: JSON.stringify({ dirs: listing.directories, files: listing.files }, null, indent) }]
351
+ content: [{ type: "text", text: JSON.stringify({ dirs: directories, files }, null, indent) }]
324
352
  };
325
353
  }
326
354
  case "delete_note": {
@@ -342,7 +370,7 @@ export function createServer(vaultPath, options = {}) {
342
370
  searchFrontmatter: trimmedArgs.searchFrontmatter,
343
371
  caseSensitive: trimmedArgs.caseSensitive,
344
372
  pathPrefix: trimmedArgs.pathPrefix,
345
- excludePaths: trimmedArgs.excludePaths
373
+ excludePaths: mergeExcludePaths(defaultExcludePaths, trimmedArgs.excludePaths)
346
374
  });
347
375
  const indent = trimmedArgs.prettyPrint ? 2 : undefined;
348
376
  return {
@@ -487,5 +515,8 @@ function trimPaths(args) {
487
515
  if (trimmed.paths && Array.isArray(trimmed.paths)) {
488
516
  trimmed.paths = trimmed.paths.map((p) => typeof p === 'string' ? p.trim() : p);
489
517
  }
518
+ if (trimmed.excludePaths && Array.isArray(trimmed.excludePaths)) {
519
+ trimmed.excludePaths = trimmed.excludePaths.map((p) => typeof p === 'string' ? p.trim() : p);
520
+ }
490
521
  return trimmed;
491
522
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitbonsai/mcpvault",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant",
5
5
  "homepage": "https://mcpvault.org",
6
6
  "author": "bitbonsai",