@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 +46 -80
- package/dist/server.js +85 -28
- package/dist/src/search.js +7 -7
- package/package.json +1 -1
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
|
-
[](https://github.com/bitbonsai/mcp-obsidian)
|
|
18
|
-
[](https://github.com/sponsors/bitbonsai)
|
|
20
|
-
[](https://ko-fi.com/bitbonsai)
|
|
17
|
+
[](https://github.com/bitbonsai/mcp-obsidian)
|
|
18
|
+
[](https://www.npmjs.com/package/@mauricio.wolff/mcp-obsidian)
|
|
19
|
+
[](https://github.com/sponsors/bitbonsai)
|
|
20
|
+
[](https://ko-fi.com/bitbonsai)
|
|
21
21
|
[](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
|
-
"
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
328
|
-
frontmatter: note.frontmatter,
|
|
395
|
+
fm: note.frontmatter,
|
|
329
396
|
content: note.content
|
|
330
|
-
}, null,
|
|
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
|
-
|
|
376
|
-
directories: listing.directories,
|
|
443
|
+
dirs: listing.directories,
|
|
377
444
|
files: listing.files
|
|
378
|
-
}, null,
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
};
|
package/dist/src/search.js
CHANGED
|
@@ -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 -
|
|
49
|
-
const excerptEnd = Math.min(searchableText.length, index + searchQuery.length +
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
p: relativePath,
|
|
70
|
+
t: title,
|
|
71
|
+
ex: excerpt,
|
|
72
|
+
mc: matchCount,
|
|
73
|
+
ln: lineNumber
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
}
|