@bitbonsai/mcpvault 0.16.0 → 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
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![MCP Toplist](https://mcptoplist.com/badge/glama%2Fbitbonsai%2Fmcpvault.svg)](https://mcptoplist.com/server/glama%2Fbitbonsai%2Fmcpvault)
6
6
 
7
- A universal AI bridge for Obsidian vaults using the Model Context Protocol (MCP) standard. Connect any MCP-compatible AI assistant to your knowledge base - works with Claude, ChatGPT, and future AI tools. This server provides safe read/write access to your notes while preventing YAML frontmatter corruption.
7
+ A local MCP server that lets compatible clients read, search, and edit notes in an Obsidian vault. MCPVault works directly with vault files, restricts file operations to the configured vault root, and preserves formatting for unchanged frontmatter fields.
8
8
 
9
9
  <div align="center">
10
10
 
@@ -20,18 +20,18 @@ A universal AI bridge for Obsidian vaults using the Model Context Protocol (MCP)
20
20
 
21
21
  </div>
22
22
 
23
- ## Universal Compatibility
23
+ ## Supported clients
24
24
 
25
- Works with any MCP-compatible AI assistant including Claude Desktop, Claude Code, ChatGPT Desktop (Enterprise+), OpenCode, Gemini CLI, OpenAI Codex, IntelliJ IDEA 2025.1+, Cursor IDE, Windsurf IDE, Ontheia, and future AI platforms that adopt the MCP standard.
25
+ Configuration examples are available for Claude Desktop, Claude Code, ChatGPT Desktop (Enterprise+), OpenCode, Gemini CLI, OpenAI Codex, IntelliJ IDEA 2025.1+, Cursor, Windsurf, and Ontheia. Other clients can use MCPVault if they support local stdio MCP servers.
26
26
 
27
27
  https://github.com/user-attachments/assets/657ac4c6-1cd2-4cc3-829f-fd095a32f71c
28
28
 
29
- ## Quick Start (5 minutes)
29
+ ## Quick start
30
30
 
31
31
  1. **Install Node.js runtime:**
32
32
 
33
33
  ```bash
34
- # Download from https://nodejs.org (v18.0.0 or later)
34
+ # Download from https://nodejs.org (v20.0.0 or later)
35
35
  # or use a package manager like nvm, brew, apt, etc.
36
36
  ```
37
37
 
@@ -99,57 +99,41 @@ https://github.com/user-attachments/assets/657ac4c6-1cd2-4cc3-829f-fd095a32f71c
99
99
  - "Read my note called 'project-ideas.md'"
100
100
  - "Create a new note with today's date"
101
101
 
102
- **Success indicators:** Your AI should be able to list files and read notes from your vault.
102
+ To verify the connection, ask your client to list MCPVault tools or read a known note.
103
103
 
104
- ## Why MCPVault?
104
+ ## How it connects
105
105
 
106
- ### Universal AI Compatibility
107
-
108
- Built on the open Model Context Protocol standard, MCPVault is not locked to any single AI provider. As more AI assistants adopt MCP, your investment in this tool grows more valuable. Today it works with Claude and ChatGPT - tomorrow it will work with whatever AI tools emerge.
109
-
110
- ### Future-Proof Your Knowledge Base
111
-
112
- Instead of waiting for each AI company to build Obsidian integrations, MCPVault provides a universal adapter that works with any MCP-compatible assistant. One tool, endless possibilities.
113
-
114
- ### Open Standard, No Lock-in
115
-
116
- MCP is an open protocol. You're not tied to any specific vendor or platform. Your notes remain yours, accessible through any compatible AI assistant.
106
+ An MCP client starts MCPVault as a local stdio process and passes the vault path. MCPVault exposes the same tools to each supported client, so the server is not tied to one AI provider. Obsidian does not need to be running, and no Obsidian plugin is required.
117
107
 
118
108
  ## Features
119
109
 
120
- - ✅ Safe frontmatter parsing and validation using gray-matter with AST-aware updates that preserve raw formatting for unmodified fields
121
- - ✅ Path filtering to exclude `.obsidian` directory and other system files
122
- - ✅ **Complete MCP toolkit**: 18 tools covering all vault operations
110
+ - AST-aware frontmatter updates preserve formatting for unchanged YAML fields.
111
+ - Path checks block traversal, symlink escapes, dotfiles, `.obsidian`, `.git`, and `node_modules`.
112
+ - Eighteen MCP tools cover note and file operations:
123
113
  - File operations: `read_note`, `write_note`, `patch_note`, `delete_note`, `move_note`, `move_file`
124
- - Partial reads: `get_note_outline` (headings without loading the full file), `read_note_lines` (a specific line range)
125
- - Directory operations: `list_directory`
126
- - Batch operations: `read_multiple_notes`
127
- - Search: `search_notes` with multi-word matching and BM25 relevance reranking
128
- - Metadata: `get_frontmatter`, `update_frontmatter`, `get_notes_info`, `get_vault_stats`
129
- - Tag management: `manage_tags` (add, remove, list), `list_all_tags`
130
- - Wiki links: `wiki_link` picks the shallowest match first (vault root before nested folders), then uses locale-aware path sorting at the same depth. Other matches are returned as alternatives; punctuation may sort before digits, so `_Inbox` can come before `00.projects`.
131
- - ✅ Write modes: `overwrite`, `append`, `prepend` for flexible content editing
132
- - ✅ Tag management: add, remove, and list tags in notes
133
- - ✅ Safe deletion with confirmation requirement to prevent accidents
134
- - ✅ Automatic path trimming to handle whitespace in inputs
135
- - ✅ TypeScript support with Node.js runtime (using tsx for execution)
136
- - ✅ Comprehensive error handling and validation
137
- - ✅ **Token-optimized responses**: 40-60% smaller responses with minified field names and compact JSON (v0.6.3+)
138
- - ✅ **Optional pretty-printing**: Set `prettyPrint: true` for human-readable debugging
139
- - ✅ **Performance optimized**: No unnecessary token consumption, efficient for large vaults
140
- - ✅ **Zero dependencies**: No Obsidian plugins required, works with any vault structure
114
+ - Partial reads: `get_note_outline`, `read_note_lines`
115
+ - Directory and batch reads: `list_directory`, `read_multiple_notes`
116
+ - Search: `search_notes` with multi-word matching and BM25 reranking
117
+ - Metadata and tags: `get_frontmatter`, `update_frontmatter`, `get_notes_info`, `get_vault_stats`, `manage_tags`, `list_all_tags`
118
+ - Wiki links: `wiki_link` resolves names and returns alternative paths when a name is ambiguous
119
+ - `write_note` supports overwrite, append, and prepend modes.
120
+ - `delete_note` and `move_file` require matching confirmation paths.
121
+ - Path arguments are trimmed before validation.
122
+ - Search and batch tools return compact fields by default; set `prettyPrint: true` for expanded output.
123
+ - The package exports TypeScript declarations and public types.
124
+ - MCPVault requires no Obsidian plugin.
141
125
 
142
126
  ## Prerequisites
143
127
 
144
- - [Node.js](https://nodejs.org) runtime (v18.0.0 or later)
128
+ - [Node.js](https://nodejs.org) runtime (v20.0.0 or later)
145
129
  - An Obsidian vault (local directory with `.md`, `.markdown`, `.txt`, `.base`, or `.canvas` files)
146
130
  - MCP-compatible AI client (Claude Desktop, ChatGPT Desktop, Claude Code, etc.)
147
131
 
148
132
  ## Installation
149
133
 
150
- ### For End Users (Recommended)
134
+ ### For end users
151
135
 
152
- No installation needed! Use `npx` to run directly:
136
+ `npx` downloads and runs the package:
153
137
 
154
138
  ```bash
155
139
  npx @bitbonsai/mcpvault@latest /path/to/your/obsidian/vault
@@ -157,7 +141,7 @@ npx @bitbonsai/mcpvault@latest /path/to/your/obsidian/vault
157
141
 
158
142
  If you omit the vault path, the server uses your current working directory as the vault root.
159
143
 
160
- ### For Developers
144
+ ### For developers
161
145
 
162
146
  1. Clone this repository
163
147
  2. Use the correct Node.js version:
@@ -178,7 +162,7 @@ npm install # Corepack automatically uses npm 10.9.0
178
162
  npx @modelcontextprotocol/inspector npm start /path/to/your/vault
179
163
  ```
180
164
 
181
- **Pro tip:** Use MCP Inspector to test all server functionality before configuring with AI clients:
165
+ Use MCP Inspector to test the server before adding it to a client:
182
166
 
183
167
  ```bash
184
168
  # Install globally for easier access
@@ -274,6 +258,21 @@ Add `--read-only` after the vault path to expose only read tools. Mutating tools
274
258
 
275
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.
276
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
+
277
276
  **Configuration File Locations:**
278
277
 
279
278
  - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
@@ -375,12 +374,12 @@ Most modern MCP clients use similar JSON configuration patterns. Refer to your s
375
374
  - "List all markdown files in my 'Projects' folder"
376
375
  - "Delete the old draft note 'draft-ideas.md' (with confirmation)"
377
376
 
378
- #### Advanced Use Cases:
377
+ #### Example workflows
379
378
 
380
- - **Knowledge Synthesis**: "Summarize all my research notes tagged with 'machine-learning' from the last month"
381
- - **Project Management**: "Update the status in all project notes to 'completed' and add today's date"
382
- - **Content Analysis**: "Find all notes that mention 'API design' and create a comprehensive guide"
383
- - **Smart Tagging**: "Review my untagged notes and suggest appropriate tags based on content"
379
+ - "Summarize my research notes tagged with 'machine-learning' from the last month"
380
+ - "Update the status in my project notes to 'completed' and add today's date"
381
+ - "Find notes that mention 'API design' and draft a guide from them"
382
+ - "Review my untagged notes and suggest tags based on their content"
384
383
 
385
384
  ## Troubleshooting
386
385
 
@@ -459,7 +458,7 @@ Read a note from the vault with parsed frontmatter.
459
458
  }
460
459
  ```
461
460
 
462
- **Response (optimized for tokens):**
461
+ **Compact response:**
463
462
 
464
463
  ```json
465
464
  {
@@ -536,7 +535,7 @@ Write a note to the vault with optional frontmatter and write mode.
536
535
 
537
536
  ### `patch_note`
538
537
 
539
- Efficiently replace an exact string inside an existing note without rewriting the full file.
538
+ Replace an exact string inside an existing note without rewriting the full file.
540
539
 
541
540
  **Request:**
542
541
 
@@ -592,7 +591,7 @@ Note: this includes non-note filenames (for example `pdf`, `png`, `jpg`) so AI a
592
591
  }
593
592
  ```
594
593
 
595
- **Response (optimized):**
594
+ **Compact response:**
596
595
 
597
596
  ```json
598
597
  {
@@ -643,7 +642,7 @@ Delete a note from the vault (requires confirmation for safety).
643
642
  }
644
643
  ```
645
644
 
646
- **⚠️ Safety Note:** The `confirmPath` parameter must exactly match the `path` parameter to proceed with deletion. This prevents accidental deletions.
645
+ **Confirmation:** `confirmPath` must exactly match `path` before deletion proceeds.
647
646
 
648
647
  ### `get_frontmatter`
649
648
 
@@ -661,7 +660,7 @@ Extract only the frontmatter from a note without reading the full content.
661
660
  }
662
661
  ```
663
662
 
664
- **Response (optimized, returns frontmatter directly):**
663
+ **Compact response, returning frontmatter directly:**
665
664
 
666
665
  ```json
667
666
  {
@@ -745,7 +744,7 @@ Search for notes in the vault by content or frontmatter with multi-word matching
745
744
  }
746
745
  ```
747
746
 
748
- **Response (optimized with minified field names):**
747
+ **Compact response:**
749
748
 
750
749
  ```json
751
750
  [
@@ -827,7 +826,7 @@ Move or rename any file in the vault with binary-safe file operations (file-only
827
826
  }
828
827
  ```
829
828
 
830
- **Safety Note:** `confirmOldPath` must exactly match `oldPath`, and `confirmNewPath` must exactly match `newPath`, otherwise the move is rejected.
829
+ **Confirmation:** `confirmOldPath` must match `oldPath`, and `confirmNewPath` must match `newPath`.
831
830
 
832
831
  ### `read_multiple_notes`
833
832
 
@@ -847,7 +846,7 @@ Read multiple notes in a batch (maximum 10 files).
847
846
  }
848
847
  ```
849
848
 
850
- **Response (optimized, shortened field names):**
849
+ **Compact response:**
851
850
 
852
851
  ```json
853
852
  {
@@ -911,7 +910,7 @@ Get metadata for notes without reading full content.
911
910
  }
912
911
  ```
913
912
 
914
- **Response (optimized, returns array directly):**
913
+ **Compact response, returning an array directly:**
915
914
 
916
915
  ```json
917
916
  [
@@ -940,7 +939,7 @@ Get high-level vault statistics without reading note contents.
940
939
  }
941
940
  ```
942
941
 
943
- **Response (optimized):**
942
+ **Compact response:**
944
943
 
945
944
  ```json
946
945
  {
@@ -957,9 +956,9 @@ Get high-level vault statistics without reading note contents.
957
956
  }
958
957
  ```
959
958
 
960
- ## Security Considerations
959
+ ## Security boundaries
961
960
 
962
- This MCP server implements several security measures to protect your Obsidian vault:
961
+ MCPVault applies these checks before file operations:
963
962
 
964
963
  ### Path Security
965
964
 
@@ -992,13 +991,15 @@ This MCP server implements several security measures to protect your Obsidian va
992
991
  - **Vault Structure:** Directory structure is visible to AI assistants
993
992
  - **File Metadata:** Creation times, file sizes, etc. are accessible
994
993
 
995
- **⚠️ Important:** Only grant vault access to trusted AI conversations. The server provides full read/write access to your notes within the security boundaries above.
994
+ Only grant write access to clients and conversations you trust. Use `--read-only` when the client does not need to modify notes.
996
995
 
997
996
  ## Architecture
998
997
 
999
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
1000
1001
  - `src/frontmatter.ts` - YAML frontmatter handling with gray-matter
1001
- - `src/filesystem.ts` - Safe file operations with path validation
1002
+ - `src/filesystem.ts` - File operations with path validation
1002
1003
  - `src/pathfilter.ts` - Directory and file filtering
1003
1004
  - `src/search.ts` - Note search functionality with content and frontmatter support
1004
1005
  - `src/uri.ts` - Obsidian URI generation for deep links
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.0",
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",
@@ -39,18 +39,18 @@
39
39
  "ship:update": "bun scripts/ship.ts --update"
40
40
  },
41
41
  "dependencies": {
42
- "@modelcontextprotocol/server": "^2.0.0",
42
+ "@modelcontextprotocol/server": "^2.3.1",
43
43
  "gray-matter": "^4.0.3",
44
- "trash": "^10.1.1",
44
+ "trash": "9.0.0",
45
45
  "yaml": "^2.9.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@clack/prompts": "^0.7.0",
49
- "@modelcontextprotocol/client": "^2.0.0",
48
+ "@clack/prompts": "^1.7.0",
49
+ "@modelcontextprotocol/client": "^2.3.1",
50
50
  "@types/node": "^26.1.2",
51
51
  "tsx": "^4.23.9",
52
52
  "typescript": "^7.0.2",
53
- "vitest": "^4.1.10"
53
+ "vitest": "^4.1.12"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=20.0.0"