@mauricio.wolff/mcp-obsidian 0.6.1 → 0.6.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.
package/README.md CHANGED
@@ -14,10 +14,10 @@ A universal AI bridge for Obsidian vaults using the Model Context Protocol (MCP)
14
14
 
15
15
  <div align="center">
16
16
 
17
- [![GitHub Stars](https://img.shields.io/github/stars/bitbonsai/mcp-obsidian?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/bitbonsai/mcp-obsidian)
18
- [![GitHub Sponsors](https://img.shields.io/github/sponsors/BitBonsai?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/sponsors/bitbonsai)
19
- [![GitHub Sponsors](https://img.shields.io/github/sponsors/BitBonsai?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/sponsors/bitbonsai)
20
- [![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)
17
+ [![GitHub Stars](https://img.shields.io/github/stars/bitbonsai/mcp-obsidian?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/bitbonsai/mcp-obsidian)
18
+ [![npm version](https://img.shields.io/npm/v/@mauricio.wolff/mcp-obsidian?style=flat&logo=npm&logoColor=white&color=9065ea&labelColor=262626)](https://www.npmjs.com/package/@mauricio.wolff/mcp-obsidian)
19
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/BitBonsai?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/sponsors/bitbonsai)
20
+ [![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)
21
21
  [![Liberapay](https://img.shields.io/badge/Liberapay-Weekly%20Support-9065ea?style=flat&logo=liberapay&logoColor=white&labelColor=262626)](https://liberapay.com/bitbonsai/)
22
22
 
23
23
  </div>
@@ -110,6 +110,8 @@ MCP is an open protocol. You're not tied to any specific vendor or platform. You
110
110
  - ✅ Automatic path trimming to handle whitespace in inputs
111
111
  - ✅ TypeScript support with Node.js runtime (using tsx for execution)
112
112
  - ✅ Comprehensive error handling and validation
113
+ - ✅ **Token-optimized responses**: 40-60% smaller responses with minified field names and compact JSON (v0.6.3+)
114
+ - ✅ **Optional pretty-printing**: Set `prettyPrint: true` for human-readable debugging
113
115
  - ✅ **Performance optimized**: No unnecessary token consumption, efficient for large vaults
114
116
  - ✅ **Zero dependencies**: No Obsidian plugins required, works with any vault structure
115
117
  - ✅ **Intelligent link handling**: Smart processing of internal links and references
@@ -362,16 +364,21 @@ Read a note from the vault with parsed frontmatter.
362
364
  {
363
365
  "name": "read_note",
364
366
  "arguments": {
365
- "path": "project-ideas.md"
367
+ "path": "project-ideas.md",
368
+ "prettyPrint": false
366
369
  }
367
370
  }
368
371
  ```
369
372
 
370
- **Response:**
373
+ **Response (optimized for tokens):**
374
+ ```json
375
+ {"fm":{"title":"Project Ideas","tags":["projects","brainstorming"],"created":"2023-01-15T10:30:00.000Z"},"content":"# Project Ideas\n\n## AI Tools\n- MCP server for Obsidian\n- Voice note transcription\n\n## Web Apps\n- Task management system"}
376
+ ```
377
+
378
+ **Response (with prettyPrint: true):**
371
379
  ```json
372
380
  {
373
- "path": "project-ideas.md",
374
- "frontmatter": {
381
+ "fm": {
375
382
  "title": "Project Ideas",
376
383
  "tags": ["projects", "brainstorming"],
377
384
  "created": "2023-01-15T10:30:00.000Z"
@@ -432,24 +439,15 @@ List files and directories in the vault.
432
439
  {
433
440
  "name": "list_directory",
434
441
  "arguments": {
435
- "path": "Projects"
442
+ "path": "Projects",
443
+ "prettyPrint": false
436
444
  }
437
445
  }
438
446
  ```
439
447
 
440
- **Response:**
448
+ **Response (optimized):**
441
449
  ```json
442
- {
443
- "path": "Projects",
444
- "directories": [
445
- "AI-Tools",
446
- "Web-Development"
447
- ],
448
- "files": [
449
- "project-template.md",
450
- "roadmap.md"
451
- ]
452
- }
450
+ {"dirs":["AI-Tools","Web-Development"],"files":["project-template.md","roadmap.md"]}
453
451
  ```
454
452
 
455
453
  ### `delete_note`
@@ -494,21 +492,15 @@ Extract only the frontmatter from a note without reading the full content.
494
492
  {
495
493
  "name": "get_frontmatter",
496
494
  "arguments": {
497
- "path": "project-ideas.md"
495
+ "path": "project-ideas.md",
496
+ "prettyPrint": false
498
497
  }
499
498
  }
500
499
  ```
501
500
 
502
- **Response:**
501
+ **Response (optimized, returns frontmatter directly):**
503
502
  ```json
504
- {
505
- "path": "project-ideas.md",
506
- "frontmatter": {
507
- "title": "Project Ideas",
508
- "tags": ["projects", "brainstorming"],
509
- "created": "2023-01-15T10:30:00.000Z"
510
- }
511
- }
503
+ {"title":"Project Ideas","tags":["projects","brainstorming"],"created":"2023-01-15T10:30:00.000Z"}
512
504
  ```
513
505
 
514
506
  ### `manage_tags`
@@ -572,28 +564,24 @@ Search for notes in the vault by content or frontmatter.
572
564
  "limit": 5,
573
565
  "searchContent": true,
574
566
  "searchFrontmatter": false,
575
- "caseSensitive": false
567
+ "caseSensitive": false,
568
+ "prettyPrint": false
576
569
  }
577
570
  }
578
571
  ```
579
572
 
580
- **Response:**
573
+ **Response (optimized with minified field names):**
581
574
  ```json
582
- {
583
- "query": "machine learning",
584
- "resultCount": 3,
585
- "results": [
586
- {
587
- "path": "ai-research.md",
588
- "title": "AI Research Notes",
589
- "excerpt": "...machine learning algorithms are...",
590
- "matchCount": 2,
591
- "lineNumber": 15
592
- }
593
- ]
594
- }
575
+ [{"p":"ai-research.md","t":"AI Research Notes","ex":"...machine learning...","mc":2,"ln":15}]
595
576
  ```
596
577
 
578
+ **Field names:**
579
+ - `p` = path
580
+ - `t` = title
581
+ - `ex` = excerpt (21 chars context)
582
+ - `mc` = match count
583
+ - `ln` = line number
584
+
597
585
  ### `move_note`
598
586
  Move or rename a note in the vault.
599
587
 
@@ -629,34 +617,21 @@ Read multiple notes in a batch (maximum 10 files).
629
617
  "arguments": {
630
618
  "paths": ["note1.md", "note2.md", "note3.md"],
631
619
  "includeContent": true,
632
- "includeFrontmatter": true
620
+ "includeFrontmatter": true,
621
+ "prettyPrint": false
633
622
  }
634
623
  }
635
624
  ```
636
625
 
637
- **Response:**
626
+ **Response (optimized, shortened field names):**
638
627
  ```json
639
- {
640
- "successful": [
641
- {
642
- "path": "note1.md",
643
- "frontmatter": {"title": "Note 1"},
644
- "content": "# Note 1\n\nContent here..."
645
- }
646
- ],
647
- "failed": [
648
- {
649
- "path": "note2.md",
650
- "error": "File not found"
651
- }
652
- ],
653
- "summary": {
654
- "successCount": 1,
655
- "failureCount": 1
656
- }
657
- }
628
+ {"ok":[{"path":"note1.md","frontmatter":{"title":"Note 1"},"content":"# Note 1\n\nContent here..."}],"err":[{"path":"note2.md","error":"File not found"}]}
658
629
  ```
659
630
 
631
+ **Field names:**
632
+ - `ok` = successful reads
633
+ - `err` = failed reads
634
+
660
635
  ### `update_frontmatter`
661
636
  Update frontmatter of a note without changing content.
662
637
 
@@ -690,24 +665,15 @@ Get metadata for notes without reading full content.
690
665
  {
691
666
  "name": "get_notes_info",
692
667
  "arguments": {
693
- "paths": ["note1.md", "note2.md"]
668
+ "paths": ["note1.md", "note2.md"],
669
+ "prettyPrint": false
694
670
  }
695
671
  }
696
672
  ```
697
673
 
698
- **Response:**
674
+ **Response (optimized, returns array directly):**
699
675
  ```json
700
- {
701
- "notes": [
702
- {
703
- "path": "note1.md",
704
- "size": 1024,
705
- "modified": 1695456000000,
706
- "hasFrontmatter": true
707
- }
708
- ],
709
- "count": 1
710
- }
676
+ [{"path":"note1.md","size":1024,"modified":1695456000000,"hasFrontmatter":true}]
711
677
  ```
712
678
 
713
679
  ## Security Considerations
package/dist/server.js CHANGED
@@ -6,9 +6,46 @@ import { FileSystemService } from "./src/filesystem.js";
6
6
  import { FrontmatterHandler } from "./src/frontmatter.js";
7
7
  import { PathFilter } from "./src/pathfilter.js";
8
8
  import { SearchService } from "./src/search.js";
9
- const vaultPath = process.argv[2];
9
+ import { readFileSync } from "fs";
10
+ import { fileURLToPath } from "url";
11
+ import { dirname, join } from "path";
12
+ // Get package.json version
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
16
+ const VERSION = packageJson.version;
17
+ // Handle --version and --help flags
18
+ const arg = process.argv[2];
19
+ if (arg === "--version" || arg === "-v") {
20
+ console.log(VERSION);
21
+ process.exit(0);
22
+ }
23
+ if (arg === "--help" || arg === "-h") {
24
+ console.log(`
25
+ @mauricio.wolff/mcp-obsidian v${VERSION}
26
+
27
+ Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant
28
+
29
+ Usage:
30
+ npx @mauricio.wolff/mcp-obsidian <vault-path>
31
+
32
+ Arguments:
33
+ <vault-path> Path to your Obsidian vault directory
34
+
35
+ Options:
36
+ --version, -v Show version number
37
+ --help, -h Show this help message
38
+
39
+ Examples:
40
+ npx @mauricio.wolff/mcp-obsidian ~/Documents/MyVault
41
+ npx @mauricio.wolff/mcp-obsidian /path/to/obsidian/vault
42
+ `);
43
+ process.exit(0);
44
+ }
45
+ const vaultPath = arg;
10
46
  if (!vaultPath) {
11
47
  console.error("Usage: npx @mauricio.wolff/mcp-obsidian /path/to/vault");
48
+ console.error("Run 'npx @mauricio.wolff/mcp-obsidian --help' for more information");
12
49
  process.exit(1);
13
50
  }
14
51
  // Initialize services
@@ -18,7 +55,7 @@ const fileSystem = new FileSystemService(vaultPath, pathFilter, frontmatterHandl
18
55
  const searchService = new SearchService(vaultPath, pathFilter);
19
56
  const server = new Server({
20
57
  name: "mcp-obsidian",
21
- version: "0.6.1"
58
+ version: VERSION
22
59
  }, {
23
60
  capabilities: {
24
61
  tools: {},
@@ -36,6 +73,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
36
73
  path: {
37
74
  type: "string",
38
75
  description: "Path to the note relative to vault root"
76
+ },
77
+ prettyPrint: {
78
+ type: "boolean",
79
+ description: "Format JSON response with indentation (default: false)",
80
+ default: false
39
81
  }
40
82
  },
41
83
  required: ["path"]
@@ -106,6 +148,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
106
148
  type: "string",
107
149
  description: "Path relative to vault root (default: '/')",
108
150
  default: "/"
151
+ },
152
+ prettyPrint: {
153
+ type: "boolean",
154
+ description: "Format JSON response with indentation (default: false)",
155
+ default: false
109
156
  }
110
157
  }
111
158
  }
@@ -157,6 +204,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
157
204
  type: "boolean",
158
205
  description: "Case sensitive search (default: false)",
159
206
  default: false
207
+ },
208
+ prettyPrint: {
209
+ type: "boolean",
210
+ description: "Format JSON response with indentation (default: false)",
211
+ default: false
160
212
  }
161
213
  },
162
214
  required: ["query"]
@@ -206,6 +258,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
206
258
  type: "boolean",
207
259
  description: "Include frontmatter (default: true)",
208
260
  default: true
261
+ },
262
+ prettyPrint: {
263
+ type: "boolean",
264
+ description: "Format JSON response with indentation (default: false)",
265
+ default: false
209
266
  }
210
267
  },
211
268
  required: ["paths"]
@@ -244,6 +301,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
244
301
  type: "array",
245
302
  items: { type: "string" },
246
303
  description: "Array of note paths to get info for"
304
+ },
305
+ prettyPrint: {
306
+ type: "boolean",
307
+ description: "Format JSON response with indentation (default: false)",
308
+ default: false
247
309
  }
248
310
  },
249
311
  required: ["paths"]
@@ -258,6 +320,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
258
320
  path: {
259
321
  type: "string",
260
322
  description: "Path to the note relative to vault root"
323
+ },
324
+ prettyPrint: {
325
+ type: "boolean",
326
+ description: "Format JSON response with indentation (default: false)",
327
+ default: false
261
328
  }
262
329
  },
263
330
  required: ["path"]
@@ -319,15 +386,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
319
386
  switch (name) {
320
387
  case "read_note": {
321
388
  const note = await fileSystem.readNote(trimmedArgs.path);
389
+ const indent = trimmedArgs.prettyPrint ? 2 : undefined;
322
390
  return {
323
391
  content: [
324
392
  {
325
393
  type: "text",
326
394
  text: JSON.stringify({
327
- path: trimmedArgs.path,
328
- frontmatter: note.frontmatter,
395
+ fm: note.frontmatter,
329
396
  content: note.content
330
- }, null, 2)
397
+ }, null, indent)
331
398
  }
332
399
  ]
333
400
  };
@@ -367,15 +434,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
367
434
  }
368
435
  case "list_directory": {
369
436
  const listing = await fileSystem.listDirectory(trimmedArgs.path || '');
437
+ const indent = trimmedArgs.prettyPrint ? 2 : undefined;
370
438
  return {
371
439
  content: [
372
440
  {
373
441
  type: "text",
374
442
  text: JSON.stringify({
375
- path: trimmedArgs.path || '/',
376
- directories: listing.directories,
443
+ dirs: listing.directories,
377
444
  files: listing.files
378
- }, null, 2)
445
+ }, null, indent)
379
446
  }
380
447
  ]
381
448
  };
@@ -403,15 +470,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
403
470
  searchFrontmatter: trimmedArgs.searchFrontmatter,
404
471
  caseSensitive: trimmedArgs.caseSensitive
405
472
  });
473
+ const indent = trimmedArgs.prettyPrint ? 2 : undefined;
406
474
  return {
407
475
  content: [
408
476
  {
409
477
  type: "text",
410
- text: JSON.stringify({
411
- query: trimmedArgs.query,
412
- resultCount: results.length,
413
- results: results
414
- }, null, 2)
478
+ text: JSON.stringify(results, null, indent)
415
479
  }
416
480
  ]
417
481
  };
@@ -438,18 +502,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
438
502
  includeContent: trimmedArgs.includeContent,
439
503
  includeFrontmatter: trimmedArgs.includeFrontmatter
440
504
  });
505
+ const indent = trimmedArgs.prettyPrint ? 2 : undefined;
441
506
  return {
442
507
  content: [
443
508
  {
444
509
  type: "text",
445
510
  text: JSON.stringify({
446
- successful: result.successful,
447
- failed: result.failed,
448
- summary: {
449
- successCount: result.successful.length,
450
- failureCount: result.failed.length
451
- }
452
- }, null, 2)
511
+ ok: result.successful,
512
+ err: result.failed
513
+ }, null, indent)
453
514
  }
454
515
  ]
455
516
  };
@@ -471,28 +532,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
471
532
  }
472
533
  case "get_notes_info": {
473
534
  const result = await fileSystem.getNotesInfo(trimmedArgs.paths);
535
+ const indent = trimmedArgs.prettyPrint ? 2 : undefined;
474
536
  return {
475
537
  content: [
476
538
  {
477
539
  type: "text",
478
- text: JSON.stringify({
479
- notes: result,
480
- count: result.length
481
- }, null, 2)
540
+ text: JSON.stringify(result, null, indent)
482
541
  }
483
542
  ]
484
543
  };
485
544
  }
486
545
  case "get_frontmatter": {
487
546
  const note = await fileSystem.readNote(trimmedArgs.path);
547
+ const indent = trimmedArgs.prettyPrint ? 2 : undefined;
488
548
  return {
489
549
  content: [
490
550
  {
491
551
  type: "text",
492
- text: JSON.stringify({
493
- path: trimmedArgs.path,
494
- frontmatter: note.frontmatter
495
- }, null, 2)
552
+ text: JSON.stringify(note.frontmatter, null, indent)
496
553
  }
497
554
  ]
498
555
  };
@@ -45,8 +45,8 @@ export class SearchService {
45
45
  const index = searchIn.indexOf(searchQuery);
46
46
  if (index !== -1) {
47
47
  // Extract excerpt around first match
48
- const excerptStart = Math.max(0, index - 50);
49
- const excerptEnd = Math.min(searchableText.length, index + searchQuery.length + 50);
48
+ const excerptStart = Math.max(0, index - 21);
49
+ const excerptEnd = Math.min(searchableText.length, index + searchQuery.length + 21);
50
50
  let excerpt = searchableText.slice(excerptStart, excerptEnd).trim();
51
51
  // Add ellipsis if excerpt is truncated
52
52
  if (excerptStart > 0)
@@ -66,11 +66,11 @@ export class SearchService {
66
66
  // Extract title from filename
67
67
  const title = relativePath.split('/').pop()?.replace(/\.md$/, '') || relativePath;
68
68
  results.push({
69
- path: relativePath,
70
- title: title,
71
- excerpt: excerpt,
72
- matchCount: matchCount,
73
- lineNumber: lineNumber
69
+ p: relativePath,
70
+ t: title,
71
+ ex: excerpt,
72
+ mc: matchCount,
73
+ ln: lineNumber
74
74
  });
75
75
  }
76
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauricio.wolff/mcp-obsidian",
3
- "version": "0.6.1",
3
+ "version": "0.6.4",
4
4
  "description": "Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant",
5
5
  "author": "bitbonsai",
6
6
  "license": "MIT",