@bitbonsai/mcpvault 0.12.6 → 0.14.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 CHANGED
@@ -1,6 +1,4 @@
1
- <div align="center">
2
- <img width="256" height="256" alt="image" src="https://github.com/user-attachments/assets/1e21d898-811b-42c2-a810-bf921dde0f58" />
3
- </div>
1
+ <div align="center"> <img width="256" height="256" alt="image" src="https://github.com/user-attachments/assets/1e21d898-811b-42c2-a810-bf921dde0f58" /> </div>
4
2
 
5
3
  # MCPVault
6
4
 
@@ -18,12 +16,7 @@ A universal AI bridge for Obsidian vaults using the Model Context Protocol (MCP)
18
16
 
19
17
  <div align="center">
20
18
 
21
- [![GitHub Stars](https://img.shields.io/github/stars/bitbonsai/mcpvault?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/bitbonsai/mcpvault)
22
- [![npm version](https://img.shields.io/npm/v/%40bitbonsai%2Fmcpvault?style=flat&logo=npm&logoColor=white&color=9065ea&labelColor=262626)](https://www.npmjs.com/package/@bitbonsai/mcpvault)
23
- [![npm downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fmcpvault.org%2Fapi%2Fdownloads.json&style=flat&logo=npm&logoColor=white&color=9065ea&labelColor=262626)](https://www.npmjs.com/package/@bitbonsai/mcpvault)
24
- [![GitHub Sponsors](https://img.shields.io/github/sponsors/BitBonsai?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/sponsors/bitbonsai)
25
- [![Ko-Fi](https://img.shields.io/badge/Ko--fi-Support%20Me-9065ea?style=flat&logo=ko-fi&logoColor=white&labelColor=262626)](https://ko-fi.com/bitbonsai)
26
- [![Liberapay](https://img.shields.io/badge/Liberapay-Weekly%20Support-9065ea?style=flat&logo=liberapay&logoColor=white&labelColor=262626)](https://liberapay.com/bitbonsai/)
19
+ [![GitHub Stars](https://img.shields.io/github/stars/bitbonsai/mcpvault?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/bitbonsai/mcpvault) [![npm version](https://img.shields.io/npm/v/%40bitbonsai%2Fmcpvault?style=flat&logo=npm&logoColor=white&color=9065ea&labelColor=262626)](https://www.npmjs.com/package/@bitbonsai/mcpvault) [![npm downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fmcpvault.org%2Fapi%2Fdownloads.json&style=flat&logo=npm&logoColor=white&color=9065ea&labelColor=262626)](https://www.npmjs.com/package/@bitbonsai/mcpvault) [![GitHub Sponsors](https://img.shields.io/github/sponsors/BitBonsai?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/sponsors/bitbonsai) [![Ko-Fi](https://img.shields.io/badge/Ko--fi-Support%20Me-9065ea?style=flat&logo=ko-fi&logoColor=white&labelColor=262626)](https://ko-fi.com/bitbonsai) [![Liberapay](https://img.shields.io/badge/Liberapay-Weekly%20Support-9065ea?style=flat&logo=liberapay&logoColor=white&labelColor=262626)](https://liberapay.com/bitbonsai/)
27
20
 
28
21
  </div>
29
22
 
@@ -126,8 +119,9 @@ MCP is an open protocol. You're not tied to any specific vendor or platform. You
126
119
 
127
120
  - ✅ Safe frontmatter parsing and validation using gray-matter with AST-aware updates that preserve raw formatting for unmodified fields
128
121
  - ✅ Path filtering to exclude `.obsidian` directory and other system files
129
- - ✅ **Complete MCP toolkit**: 16 tools covering all vault operations
122
+ - ✅ **Complete MCP toolkit**: 18 tools covering all vault operations
130
123
  - 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)
131
125
  - Directory operations: `list_directory`
132
126
  - Batch operations: `read_multiple_notes`
133
127
  - Search: `search_notes` with multi-word matching and BM25 relevance reranking
@@ -283,8 +277,7 @@ _Note: ChatGPT Desktop's MCP integration is currently limited to enterprise subs
283
277
 
284
278
  Claude Code uses `.claude.json` configuration file:
285
279
 
286
- **User-scoped (recommended):**
287
- Edit `~/.claude.json`:
280
+ **User-scoped (recommended):** Edit `~/.claude.json`:
288
281
 
289
282
  ```json
290
283
  {
@@ -298,8 +291,7 @@ Edit `~/.claude.json`:
298
291
  }
299
292
  ```
300
293
 
301
- **Project-scoped:**
302
- Edit `.claude.json` in your project or add to the projects section:
294
+ **Project-scoped:** Edit `.claude.json` in your project or add to the projects section:
303
295
 
304
296
  ```json
305
297
  {
@@ -1 +1 @@
1
- {"version":3,"file":"createServer.d.ts","sourceRoot":"","sources":["../../src/createServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAMnE,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;CACzC;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CA0azF"}
1
+ {"version":3,"file":"createServer.d.ts","sourceRoot":"","sources":["../../src/createServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAMnE,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;CACzC;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAudzF"}
@@ -230,6 +230,32 @@ export function createServer(vaultPath, options = {}) {
230
230
  },
231
231
  required: ["document"]
232
232
  }
233
+ },
234
+ {
235
+ name: "get_note_outline",
236
+ description: "Get the heading structure of a note without reading its full content. Returns headings with level, text, and line number. Use this first to navigate large notes efficiently, then call read_note_lines to read only the section you need.",
237
+ inputSchema: {
238
+ type: "object",
239
+ properties: {
240
+ path: { type: "string", description: "Path to the note relative to vault root" },
241
+ prettyPrint: { type: "boolean", description: "Format JSON response with indentation (default: false)", default: false }
242
+ },
243
+ required: ["path"]
244
+ }
245
+ },
246
+ {
247
+ name: "read_note_lines",
248
+ description: "Read a specific line range from a note. Use after get_note_outline to read only the section you need instead of the full file.",
249
+ inputSchema: {
250
+ type: "object",
251
+ properties: {
252
+ path: { type: "string", description: "Path to the note relative to vault root" },
253
+ startLine: { type: "number", description: "First line to read (1-indexed, inclusive)" },
254
+ endLine: { type: "number", description: "Last line to read (1-indexed, inclusive)" },
255
+ prettyPrint: { type: "boolean", description: "Format JSON response with indentation (default: false)", default: false }
256
+ },
257
+ required: ["path", "startLine", "endLine"]
258
+ }
233
259
  }
234
260
  ]
235
261
  };
@@ -394,6 +420,23 @@ export function createServer(vaultPath, options = {}) {
394
420
  }
395
421
  case "wiki_link":
396
422
  return await handleWikiLinkTool(fileSystem, trimmedArgs);
423
+ case "get_note_outline": {
424
+ const headings = await fileSystem.getNoteOutline(trimmedArgs.path);
425
+ const indent = trimmedArgs.prettyPrint ? 2 : undefined;
426
+ return {
427
+ content: [{ type: "text", text: JSON.stringify(headings, null, indent) }]
428
+ };
429
+ }
430
+ case "read_note_lines": {
431
+ const text = await fileSystem.readNoteLines({
432
+ path: trimmedArgs.path,
433
+ startLine: trimmedArgs.startLine,
434
+ endLine: trimmedArgs.endLine
435
+ });
436
+ return {
437
+ content: [{ type: "text", text }]
438
+ };
439
+ }
397
440
  default:
398
441
  throw new Error(`Unknown tool: ${toolName}`);
399
442
  }
@@ -1,6 +1,6 @@
1
1
  import { FrontmatterHandler } from './frontmatter.js';
2
2
  import { PathFilter } from './pathfilter.js';
3
- import type { ParsedNote, DirectoryListing, NoteWriteParams, DeleteNoteParams, DeleteResult, MoveNoteParams, MoveFileParams, MoveResult, BatchReadParams, BatchReadResult, UpdateFrontmatterParams, NoteInfo, TagManagementParams, TagManagementResult, PatchNoteParams, PatchNoteResult, VaultStats } from './types.js';
3
+ import type { ParsedNote, DirectoryListing, NoteWriteParams, DeleteNoteParams, DeleteResult, MoveNoteParams, MoveFileParams, MoveResult, BatchReadParams, BatchReadResult, UpdateFrontmatterParams, NoteInfo, TagManagementParams, TagManagementResult, PatchNoteParams, PatchNoteResult, VaultStats, NoteHeading, ReadNoteLinesParams } from './types.js';
4
4
  /**
5
5
  * Map a filesystem write failure to a clear, accurate Error.
6
6
  *
@@ -53,6 +53,8 @@ export declare class FileSystemService {
53
53
  * Throws only on caller misuse (empty name).
54
54
  */
55
55
  findPathForWikiLink(wikiLinkName: string): Promise<string[]>;
56
+ getNoteOutline(path: string): Promise<NoteHeading[]>;
57
+ readNoteLines(params: ReadNoteLinesParams): Promise<string>;
56
58
  getVaultStats(recentCount?: number): Promise<VaultStats>;
57
59
  listAllTags(): Promise<Array<{
58
60
  tag: string;
@@ -1 +1 @@
1
- {"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/filesystem.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzT;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAiBtE;AAED,qBAAa,iBAAiB;IAK1B,OAAO,CAAC,SAAS;IAJnB,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,UAAU,CAAa;IAE/B,YACU,SAAS,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,UAAU,EACvB,kBAAkB,CAAC,EAAE,kBAAkB,EAWxC;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,WAAW;IAkDb,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA+BhD;IAEK,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EtD;IAEK,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA4FjE;IAEK,aAAa,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4DhE;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc3C;IAEK,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAchD;IAEK,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAyGhE;IAEK,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAoF1D;IAEK,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CA+H1D;IAEK,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA+CzE;IAEK,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCtE;IAEK,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CA0CvD;IAEK,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqG1E;IAED,YAAY,IAAI,MAAM,CAErB;IAED;;;;;;;;;;;;;;OAcG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA+CjE;IAEK,aAAa,CAAC,WAAW,GAAE,MAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAuDhE;IAEK,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAiDlE;CACF"}
1
+ {"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/filesystem.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE3V;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAiBtE;AAoBD,qBAAa,iBAAiB;IAK1B,OAAO,CAAC,SAAS;IAJnB,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,UAAU,CAAa;IAE/B,YACU,SAAS,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,UAAU,EACvB,kBAAkB,CAAC,EAAE,kBAAkB,EAWxC;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,WAAW;IAkDb,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA+BhD;IAEK,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EtD;IAEK,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA4FjE;IAEK,aAAa,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4DhE;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc3C;IAEK,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAchD;IAEK,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAyGhE;IAEK,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAoF1D;IAEK,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CA+H1D;IAEK,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA+CzE;IAEK,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCtE;IAEK,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CA0CvD;IAEK,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqG1E;IAED,YAAY,IAAI,MAAM,CAErB;IAED;;;;;;;;;;;;;;OAcG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA+CjE;IAEK,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA+EzD;IAEK,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAehE;IAEK,aAAa,CAAC,WAAW,GAAE,MAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAuDhE;IAEK,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAiDlE;CACF"}
@@ -33,6 +33,23 @@ export function classifyWriteError(error, path) {
33
33
  }
34
34
  return new Error(`Failed to write file: ${path} - ${error instanceof Error ? error.message : 'Unknown error'}`);
35
35
  }
36
+ /**
37
+ * Strip an ATX heading's optional closing sequence of #s per CommonMark: it
38
+ * must be preceded by a space (or the text is nothing but #s, i.e. an empty
39
+ * heading with a closer and no content) and followed only by trailing spaces
40
+ * (already removed by the caller's trim). A closer with no preceding space
41
+ * (e.g. "Heading###") is not a valid closer and stays as literal text.
42
+ */
43
+ function stripAtxClosingSequence(text) {
44
+ const withPrecedingSpace = /^(.*[ \t])#+$/.exec(text);
45
+ if (withPrecedingSpace) {
46
+ return withPrecedingSpace[1].replace(/[ \t]+$/, '');
47
+ }
48
+ if (/^#+$/.test(text)) {
49
+ return '';
50
+ }
51
+ return text;
52
+ }
36
53
  export class FileSystemService {
37
54
  vaultPath;
38
55
  frontmatterHandler;
@@ -946,6 +963,100 @@ export class FileSystemService {
946
963
  });
947
964
  return matches;
948
965
  }
966
+ async getNoteOutline(path) {
967
+ path = this.normalizePath(path);
968
+ if (!this.pathFilter.isAllowed(path)) {
969
+ throw new Error(`Access denied: ${path}. This path is restricted (system files like .obsidian, .git, and dotfiles are not accessible).`);
970
+ }
971
+ const fullPath = this.resolvePath(path);
972
+ const raw = await readFile(fullPath, 'utf-8');
973
+ const lines = raw.split('\n');
974
+ const headings = [];
975
+ // Per CommonMark ATX headings: up to 3 leading spaces are allowed before
976
+ // the #s; the heading may have no text at all (bare `#`); and an optional
977
+ // closing sequence of #s (preceded by a space, followed only by trailing
978
+ // spaces) is stripped from the returned text rather than kept literally.
979
+ const headingRegex = /^ {0,3}(#{1,6})(?:[ \t]+(.*))?$/;
980
+ // Frontmatter delimiters (---) can themselves look like content but never
981
+ // contain real headings; skip the block so YAML comments (# ...) inside it
982
+ // can't be misdetected as headings. Handles both LF and CRLF line endings,
983
+ // since split('\n') leaves a trailing \r on each line for CRLF files.
984
+ let inFrontmatter = false;
985
+ let frontmatterEnded = false;
986
+ // Fenced code blocks (``` or ~~~) can contain lines that look like
987
+ // headings (e.g. a shell comment or markdown example) but aren't real
988
+ // structure; track fence state and skip everything inside one.
989
+ // Per CommonMark: a fence marker may be indented up to 3 spaces; the
990
+ // opener records both its character and its length, and only a line
991
+ // with the *same* character, at least as many markers, and nothing but
992
+ // trailing whitespace after them closes it (mismatched length, a
993
+ // different character, or trailing content like a language tag on a
994
+ // would-be closer must NOT end the block).
995
+ let inFence = false;
996
+ let fenceChar = '';
997
+ let fenceLength = 0;
998
+ const fenceRegex = /^ {0,3}(`{3,}|~{3,})(.*)$/;
999
+ for (let i = 0; i < lines.length; i++) {
1000
+ const line = lines[i];
1001
+ const trimmed = line.replace(/\r$/, '');
1002
+ if (!frontmatterEnded && i === 0 && trimmed === '---') {
1003
+ inFrontmatter = true;
1004
+ continue;
1005
+ }
1006
+ if (inFrontmatter) {
1007
+ if (trimmed === '---') {
1008
+ inFrontmatter = false;
1009
+ frontmatterEnded = true;
1010
+ }
1011
+ continue;
1012
+ }
1013
+ frontmatterEnded = true;
1014
+ const fenceMatch = fenceRegex.exec(trimmed);
1015
+ if (fenceMatch) {
1016
+ const markers = fenceMatch[1];
1017
+ const trailing = fenceMatch[2];
1018
+ const char = markers.charAt(0);
1019
+ if (!inFence) {
1020
+ inFence = true;
1021
+ fenceChar = char;
1022
+ fenceLength = markers.length;
1023
+ }
1024
+ else if (char === fenceChar && markers.length >= fenceLength && trailing.trim() === '') {
1025
+ inFence = false;
1026
+ fenceChar = '';
1027
+ fenceLength = 0;
1028
+ }
1029
+ // Any other fence-like line while inFence (mismatched char, too
1030
+ // short, or has trailing content) is just code-block content.
1031
+ continue;
1032
+ }
1033
+ if (inFence) {
1034
+ continue;
1035
+ }
1036
+ const match = headingRegex.exec(trimmed);
1037
+ if (match) {
1038
+ const rawText = (match[2] ?? '').trim();
1039
+ headings.push({ level: match[1].length, text: stripAtxClosingSequence(rawText), line: i + 1 });
1040
+ }
1041
+ }
1042
+ return headings;
1043
+ }
1044
+ async readNoteLines(params) {
1045
+ const path = this.normalizePath(params.path);
1046
+ if (!this.pathFilter.isAllowed(path)) {
1047
+ throw new Error(`Access denied: ${path}. This path is restricted (system files like .obsidian, .git, and dotfiles are not accessible).`);
1048
+ }
1049
+ const fullPath = this.resolvePath(path);
1050
+ const raw = await readFile(fullPath, 'utf-8');
1051
+ const lines = raw.split('\n');
1052
+ // Both bounds are clamped into [1, lines.length] rather than trusting
1053
+ // caller-supplied indices directly - out-of-range start/end (0, negative,
1054
+ // or past EOF) previously either threw or silently wrapped via
1055
+ // Array.slice's negative-index behavior instead of clamping like end did.
1056
+ const clampedStart = Math.min(Math.max(params.startLine, 1), lines.length);
1057
+ const clampedEnd = Math.min(Math.max(params.endLine, clampedStart), lines.length);
1058
+ return lines.slice(clampedStart - 1, clampedEnd).join('\n');
1059
+ }
949
1060
  async getVaultStats(recentCount = 5) {
950
1061
  let totalNotes = 0;
951
1062
  let totalFolders = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"pathfilter.d.ts","sourceRoot":"","sources":["../../src/pathfilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,qBAAa,UAAU;IAOrB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAMxC;IAEH,OAAO,CAAC,eAAe,CAAW;IAClC,OAAO,CAAC,iBAAiB,CAAW;IAEpC,YAAY,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAuB7C;IAED,OAAO,CAAC,eAAe;IAoBvB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAQ5B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAuB/B;IAED,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUzC;IAED,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,MAAM;IA0Bd,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAErC;CACF"}
1
+ {"version":3,"file":"pathfilter.d.ts","sourceRoot":"","sources":["../../src/pathfilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,qBAAa,UAAU;IAOrB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAMxC;IAEH,OAAO,CAAC,eAAe,CAAW;IAClC,OAAO,CAAC,iBAAiB,CAAW;IAEpC,YAAY,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAuB7C;IAED,OAAO,CAAC,eAAe;IAoBvB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAQ5B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAuB/B;IAED,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUzC;IAED,OAAO,CAAC,aAAa;IA+BrB,OAAO,CAAC,MAAM;IA0Bd,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAErC;CACF"}
@@ -99,10 +99,12 @@ export class PathFilter {
99
99
  // (".Git/config", ".git./config", ".git /config") cannot bypass the deny-list
100
100
  // on case-insensitive / Windows filesystems.
101
101
  const canonicalPath = this.canonicalizeForMatch(normalizedPath);
102
- // Deny restricted directories/files at any depth: if any path segment is a
103
- // restricted name, the path is ignored regardless of nesting (#128).
102
+ // Deny hidden paths and restricted directories/files at any depth. Dotfiles
103
+ // are outside the public vault-note surface even when their suffix looks
104
+ // like an allowed note extension (for example, `.secrets.md`).
104
105
  for (const segment of canonicalPath.split('/')) {
105
- if (PathFilter.RESTRICTED_SEGMENTS.has(segment.toLowerCase())) {
106
+ if (segment.startsWith('.') ||
107
+ PathFilter.RESTRICTED_SEGMENTS.has(segment.toLowerCase())) {
106
108
  return true;
107
109
  }
108
110
  }
@@ -128,6 +128,16 @@ export interface TagManagementResult {
128
128
  success: boolean;
129
129
  message?: string;
130
130
  }
131
+ export interface NoteHeading {
132
+ level: number;
133
+ text: string;
134
+ line: number;
135
+ }
136
+ export interface ReadNoteLinesParams {
137
+ path: string;
138
+ startLine: number;
139
+ endLine: number;
140
+ }
131
141
  export interface VaultStats {
132
142
  totalNotes: number;
133
143
  totalFolders: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitbonsai/mcpvault",
3
- "version": "0.12.6",
3
+ "version": "0.14.1",
4
4
  "description": "Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant",
5
5
  "homepage": "https://mcpvault.org",
6
6
  "author": "bitbonsai",