@bitbonsai/mcpvault 0.12.2 → 0.12.4

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.
@@ -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;AAI7C,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,CAoZzF"}
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"}
@@ -4,6 +4,7 @@ import { FileSystemService } from "./filesystem.js";
4
4
  import { FrontmatterHandler, parseFrontmatter } from "./frontmatter.js";
5
5
  import { PathFilter } from "./pathfilter.js";
6
6
  import { SearchService } from "./search.js";
7
+ import { handleWikiLinkTool } from "./wikilink/index.js";
7
8
  import { resolve } from "path";
8
9
  export function createServer(vaultPath, options = {}) {
9
10
  const { name = "mcpvault", version = "0.0.0", pathFilter = new PathFilter(), frontmatterHandler = new FrontmatterHandler(), } = options;
@@ -210,6 +211,25 @@ export function createServer(vaultPath, options = {}) {
210
211
  prettyPrint: { type: "boolean", description: "Format JSON response with indentation (default: false)", default: false }
211
212
  }
212
213
  }
214
+ },
215
+ {
216
+ name: "wiki_link",
217
+ description: "Read an Obsidian wiki link. Accepts the same syntax as Obsidian: [[Document Name]] or [[Document Name|Display Text]], including table-authored escapes like [[Document Name\\|Display]] and path-qualified links like [[folder/Document Name]]. A #fragment suffix in the input is ignored. Searches the vault for an exact basename match (or exact vault-relative path match when the name contains '/') and returns the file's content. When multiple files share the basename, picks the first (vault root first, then alphabetical by path) and lists the other paths in structuredContent.alternatives. Content is returned bare — ready for direct use in context.",
218
+ inputSchema: {
219
+ type: "object",
220
+ properties: {
221
+ document: {
222
+ type: "string",
223
+ description: "The document name — what goes inside [[ ]]. e.g. 'My-Document'. Brackets and display text (|...) are stripped if present. The .md extension is always appended (never include it)."
224
+ },
225
+ prettyPrint: {
226
+ type: "boolean",
227
+ description: "Format JSON response with indentation (default: false)",
228
+ default: false
229
+ }
230
+ },
231
+ required: ["document"]
232
+ }
213
233
  }
214
234
  ]
215
235
  };
@@ -372,6 +392,8 @@ export function createServer(vaultPath, options = {}) {
372
392
  content: [{ type: "text", text: JSON.stringify(tags, null, indent) }]
373
393
  };
374
394
  }
395
+ case "wiki_link":
396
+ return await handleWikiLinkTool(fileSystem, trimmedArgs);
375
397
  default:
376
398
  throw new Error(`Unknown tool: ${toolName}`);
377
399
  }
@@ -37,6 +37,22 @@ export declare class FileSystemService {
37
37
  getNotesInfo(paths: string[]): Promise<NoteInfo[]>;
38
38
  manageTags(params: TagManagementParams): Promise<TagManagementResult>;
39
39
  getVaultPath(): string;
40
+ /**
41
+ * Resolve an Obsidian wiki link name to its vault-relative paths.
42
+ * Scans the vault for exact filename matches (name + .md).
43
+ *
44
+ * A name containing `/` is path-qualified (Obsidian emits these when a
45
+ * basename is ambiguous, e.g. [[folder/Note]]): it must match the full
46
+ * vault-relative path instead of just the basename.
47
+ *
48
+ * Returns all matches sorted root-first (by path depth ascending), with
49
+ * alphabetical tiebreak at equal depth. Empty array on zero matches.
50
+ * The caller decides how to handle zero/single/multi — this function does
51
+ * not throw on lookup outcomes.
52
+ *
53
+ * Throws only on caller misuse (empty name).
54
+ */
55
+ findPathForWikiLink(wikiLinkName: string): Promise<string[]>;
40
56
  getVaultStats(recentCount?: number): Promise<VaultStats>;
41
57
  listAllTags(): Promise<Array<{
42
58
  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;gBAGrB,SAAS,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,UAAU,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAazC;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,WAAW;IAkDb,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiC3C,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA4EjD,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IA8F5D,aAAa,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8D3D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBtC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB3C,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC;IA2G3D,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAsFrD,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAiIrD,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IAiDpE,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IA4ClD,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAuG3E,YAAY,IAAI,MAAM;IAIhB,aAAa,CAAC,WAAW,GAAE,MAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAyD3D,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAkDpE"}
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;gBAGrB,SAAS,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,UAAU,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAazC;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,WAAW;IAkDb,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiC3C,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA4EjD,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IA8F5D,aAAa,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8D3D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBtC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB3C,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC;IA2G3D,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAsFrD,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAiIrD,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IAiDpE,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IA4ClD,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAuG3E,YAAY,IAAI,MAAM;IAItB;;;;;;;;;;;;;;OAcG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiD5D,aAAa,CAAC,WAAW,GAAE,MAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAyD3D,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAkDpE"}
@@ -891,6 +891,61 @@ export class FileSystemService {
891
891
  getVaultPath() {
892
892
  return this.vaultPath;
893
893
  }
894
+ /**
895
+ * Resolve an Obsidian wiki link name to its vault-relative paths.
896
+ * Scans the vault for exact filename matches (name + .md).
897
+ *
898
+ * A name containing `/` is path-qualified (Obsidian emits these when a
899
+ * basename is ambiguous, e.g. [[folder/Note]]): it must match the full
900
+ * vault-relative path instead of just the basename.
901
+ *
902
+ * Returns all matches sorted root-first (by path depth ascending), with
903
+ * alphabetical tiebreak at equal depth. Empty array on zero matches.
904
+ * The caller decides how to handle zero/single/multi — this function does
905
+ * not throw on lookup outcomes.
906
+ *
907
+ * Throws only on caller misuse (empty name).
908
+ */
909
+ async findPathForWikiLink(wikiLinkName) {
910
+ if (!wikiLinkName.trim()) {
911
+ throw new Error('Empty wiki link — provide a document name inside [[ ]].');
912
+ }
913
+ const normalizedName = `${wikiLinkName}.md`;
914
+ const isPathQualified = wikiLinkName.includes('/');
915
+ const matches = [];
916
+ const scan = async (dirPath, relativePath = '') => {
917
+ const entries = await readdir(dirPath, { withFileTypes: true });
918
+ for (const entry of entries) {
919
+ const entryRelativePath = relativePath
920
+ ? `${relativePath}/${entry.name}`
921
+ : entry.name;
922
+ if (!this.pathFilter.isAllowed(entryRelativePath)) {
923
+ continue;
924
+ }
925
+ if (entry.isDirectory()) {
926
+ if (!this.pathFilter.isAllowed(`${entryRelativePath}/test.md`)) {
927
+ continue;
928
+ }
929
+ await scan(join(dirPath, entry.name), entryRelativePath);
930
+ }
931
+ else if (entry.isFile() &&
932
+ (isPathQualified
933
+ ? entryRelativePath === normalizedName
934
+ : entry.name === normalizedName)) {
935
+ matches.push(entryRelativePath);
936
+ }
937
+ }
938
+ };
939
+ await scan(this.vaultPath);
940
+ // Depth-ascending (root-first), alphabetical tiebreak at equal depth.
941
+ // No current-folder context exists for a standalone MCP tool.
942
+ matches.sort((a, b) => {
943
+ const da = a.split('/').length;
944
+ const db = b.split('/').length;
945
+ return da !== db ? da - db : a.localeCompare(b);
946
+ });
947
+ return matches;
948
+ }
894
949
  async getVaultStats(recentCount = 5) {
895
950
  let totalNotes = 0;
896
951
  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;gBAExB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAuB9C,OAAO,CAAC,eAAe;IAoBvB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAQ5B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAyBhC,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAY1C,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,MAAM;IA0Bd,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;CAGvC"}
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;gBAExB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAyB9C,OAAO,CAAC,eAAe;IAoBvB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAQ5B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAyBhC,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAY1C,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,MAAM;IA0Bd,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;CAGvC"}
@@ -20,6 +20,8 @@ export class PathFilter {
20
20
  '.obsidian/**',
21
21
  '.git',
22
22
  '.git/**',
23
+ '.trash',
24
+ '.trash/**',
23
25
  'node_modules',
24
26
  'node_modules/**',
25
27
  '.DS_Store',
@@ -0,0 +1,5 @@
1
+ export { parseWikiLink } from './resolveWikiLink.js';
2
+ export { handleWikiLinkTool } from './wikiLinkTool.js';
3
+ export type { ParsedWikiLink } from './resolveWikiLink.js';
4
+ export type { WikiLinkToolArgs } from './wikiLinkTool.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/wikilink/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { parseWikiLink } from './resolveWikiLink.js';
2
+ export { handleWikiLinkTool } from './wikiLinkTool.js';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The parsed components of an Obsidian wiki link.
3
+ *
4
+ * @example
5
+ * parseWikiLink('[[Doc Name#Heading|Display]]')
6
+ * // { document: 'Doc Name' }
7
+ */
8
+ export interface ParsedWikiLink {
9
+ /**
10
+ * The document name as it appears inside [[ ]] — without brackets,
11
+ * without display text, without any `#fragment` suffix.
12
+ * Append `.md` to get the filesystem filename.
13
+ */
14
+ document: string;
15
+ }
16
+ /**
17
+ * Parse an Obsidian wiki link string into its components.
18
+ *
19
+ * Handles all wiki link forms:
20
+ * - `[[Document Name]]`
21
+ * - `[[Document Name|Display Text]]`
22
+ * - `[[Document Name\|Display]]` — table-authored escape; `\|` unescapes to `|`
23
+ * - Bare names without brackets (backslashes preserved as-is)
24
+ *
25
+ * A `#Heading` or `#^block-id` suffix is accepted but ignored: the fragment
26
+ * is stripped and only the document name is returned.
27
+ *
28
+ * Throws on malformed input: if the parsed `document` still contains a
29
+ * literal `\` after processing, the input is rejected rather than
30
+ * heuristically interpreted.
31
+ *
32
+ * @param wikiLinkText - The raw wiki link string, with or without brackets
33
+ * @returns The parsed basename
34
+ * @throws Error when parsed components contain an unexpected backslash
35
+ *
36
+ * @see {@link ParsedWikiLink}
37
+ */
38
+ export declare const parseWikiLink: (wikiLinkText: string) => ParsedWikiLink;
39
+ //# sourceMappingURL=resolveWikiLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveWikiLink.d.ts","sourceRoot":"","sources":["../../../src/wikilink/resolveWikiLink.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,aAAa,GACxB,cAAc,MAAM,KACnB,cAmCF,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Parse an Obsidian wiki link string into its components.
3
+ *
4
+ * Handles all wiki link forms:
5
+ * - `[[Document Name]]`
6
+ * - `[[Document Name|Display Text]]`
7
+ * - `[[Document Name\|Display]]` — table-authored escape; `\|` unescapes to `|`
8
+ * - Bare names without brackets (backslashes preserved as-is)
9
+ *
10
+ * A `#Heading` or `#^block-id` suffix is accepted but ignored: the fragment
11
+ * is stripped and only the document name is returned.
12
+ *
13
+ * Throws on malformed input: if the parsed `document` still contains a
14
+ * literal `\` after processing, the input is rejected rather than
15
+ * heuristically interpreted.
16
+ *
17
+ * @param wikiLinkText - The raw wiki link string, with or without brackets
18
+ * @returns The parsed basename
19
+ * @throws Error when parsed components contain an unexpected backslash
20
+ *
21
+ * @see {@link ParsedWikiLink}
22
+ */
23
+ export const parseWikiLink = (wikiLinkText) => {
24
+ let text = wikiLinkText.trim();
25
+ const wasBracketed = text.startsWith('[[');
26
+ if (wasBracketed) {
27
+ text = text.slice(2);
28
+ }
29
+ if (text.endsWith(']]')) {
30
+ text = text.slice(0, -2);
31
+ }
32
+ // Inside [[ ]] only: unescape Obsidian's table-authoring artifact `\|` → `|`.
33
+ // Pipe is a reserved grammar character, so `\|` is unambiguous there.
34
+ if (wasBracketed) {
35
+ text = text.replace(/\\\|/g, '|');
36
+ }
37
+ // Strip |display text if present
38
+ const pipeIndex = text.indexOf('|');
39
+ if (pipeIndex !== -1) {
40
+ text = text.slice(0, pipeIndex);
41
+ }
42
+ // Strip and ignore any #fragment suffix
43
+ const hashIndex = text.indexOf('#');
44
+ const rest = hashIndex !== -1 ? text.slice(hashIndex + 1) : '';
45
+ const document = (hashIndex !== -1 ? text.slice(0, hashIndex) : text).trim();
46
+ if (document.includes('\\') || rest.includes('\\')) {
47
+ throw new Error(`Invalid wiki-link syntax: unexpected backslash in [[${wikiLinkText}]]. Only \\| inside [[ ]] is a recognized escape.`);
48
+ }
49
+ return { document };
50
+ };
@@ -0,0 +1,21 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { FileSystemService } from '../filesystem.js';
3
+ export interface WikiLinkToolArgs {
4
+ document: string;
5
+ prettyPrint?: boolean;
6
+ }
7
+ /**
8
+ * Handle the `wiki_link` MCP tool call.
9
+ *
10
+ * Resolves an Obsidian wiki-link reference against the vault and returns the
11
+ * matching note. Designed to keep the MCP server request handler slim — all
12
+ * wiki-link specific concerns live here.
13
+ *
14
+ * Response channels per MCP spec 2025-11-25:
15
+ * - Invalid syntax or no match → `isError: true` with an actionable message.
16
+ * - One or more matches → success; `structuredContent` carries `document`,
17
+ * `path` (the resolved pick), and `alternatives` (the other matched paths,
18
+ * present only when more than one file shares the basename).
19
+ */
20
+ export declare function handleWikiLinkTool(fileSystem: FileSystemService, args: WikiLinkToolArgs): Promise<CallToolResult>;
21
+ //# sourceMappingURL=wikiLinkTool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wikiLinkTool.d.ts","sourceRoot":"","sources":["../../../src/wikilink/wikiLinkTool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,iBAAiB,EAC7B,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,cAAc,CAAC,CAiDzB"}
@@ -0,0 +1,60 @@
1
+ import { parseWikiLink } from './resolveWikiLink.js';
2
+ /**
3
+ * Handle the `wiki_link` MCP tool call.
4
+ *
5
+ * Resolves an Obsidian wiki-link reference against the vault and returns the
6
+ * matching note. Designed to keep the MCP server request handler slim — all
7
+ * wiki-link specific concerns live here.
8
+ *
9
+ * Response channels per MCP spec 2025-11-25:
10
+ * - Invalid syntax or no match → `isError: true` with an actionable message.
11
+ * - One or more matches → success; `structuredContent` carries `document`,
12
+ * `path` (the resolved pick), and `alternatives` (the other matched paths,
13
+ * present only when more than one file shares the basename).
14
+ */
15
+ export async function handleWikiLinkTool(fileSystem, args) {
16
+ const indent = args.prettyPrint ? 2 : undefined;
17
+ let parsed;
18
+ try {
19
+ parsed = parseWikiLink(args.document);
20
+ }
21
+ catch (err) {
22
+ const message = err instanceof Error ? err.message : 'Invalid wiki-link syntax';
23
+ return {
24
+ content: [{ type: 'text', text: message }],
25
+ structuredContent: { rawInput: args.document },
26
+ isError: true,
27
+ };
28
+ }
29
+ const paths = await fileSystem.findPathForWikiLink(parsed.document);
30
+ if (paths.length === 0) {
31
+ return {
32
+ content: [{
33
+ type: 'text',
34
+ text: `No file found for [[${parsed.document}]]. Use search_notes or list_directory to find the correct name.`,
35
+ }],
36
+ structuredContent: {
37
+ document: parsed.document,
38
+ },
39
+ isError: true,
40
+ };
41
+ }
42
+ const resolvedPath = paths[0];
43
+ const alternatives = paths.slice(1);
44
+ const note = await fileSystem.readNote(resolvedPath);
45
+ return {
46
+ content: [{
47
+ type: 'text',
48
+ text: JSON.stringify({
49
+ path: resolvedPath,
50
+ fm: note.frontmatter,
51
+ content: note.content,
52
+ }, null, indent),
53
+ }],
54
+ structuredContent: {
55
+ document: parsed.document,
56
+ path: resolvedPath,
57
+ ...(alternatives.length > 0 && { alternatives }),
58
+ },
59
+ };
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitbonsai/mcpvault",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant",
5
5
  "homepage": "https://mcpvault.org",
6
6
  "author": "bitbonsai",