@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 +17 -0
- package/dist/server.js +6 -3
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +19 -0
- package/dist/src/createServer.d.ts +2 -0
- package/dist/src/createServer.d.ts.map +1 -1
- package/dist/src/createServer.js +36 -5
- package/package.json +1 -1
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
package/dist/src/cli.d.ts.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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"}
|
package/dist/src/createServer.js
CHANGED
|
@@ -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
|
|
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:
|
|
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