@bitbonsai/mcpvault 0.8.2
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 +994 -0
- package/dist/server.js +690 -0
- package/dist/src/filesystem.js +763 -0
- package/dist/src/filesystem.test.js +946 -0
- package/dist/src/frontmatter.js +129 -0
- package/dist/src/frontmatter.test.js +118 -0
- package/dist/src/integration.test.js +168 -0
- package/dist/src/pathfilter.js +94 -0
- package/dist/src/pathfilter.test.js +264 -0
- package/dist/src/search.js +182 -0
- package/dist/src/search.test.js +212 -0
- package/dist/src/types.js +1 -0
- package/dist/src/uri.js +22 -0
- package/dist/src/uri.test.js +46 -0
- package/package.json +69 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { FileSystemService } from "./src/filesystem.js";
|
|
6
|
+
import { FrontmatterHandler, parseFrontmatter } from "./src/frontmatter.js";
|
|
7
|
+
import { PathFilter } from "./src/pathfilter.js";
|
|
8
|
+
import { SearchService } from "./src/search.js";
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { dirname, join, resolve } 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 cliArgs = process.argv.slice(2);
|
|
19
|
+
const firstArg = cliArgs[0];
|
|
20
|
+
if (firstArg === "--version" || firstArg === "-v") {
|
|
21
|
+
console.log(VERSION);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
if (firstArg === "--help" || firstArg === "-h") {
|
|
25
|
+
console.log(`
|
|
26
|
+
mcpvault v${VERSION}
|
|
27
|
+
|
|
28
|
+
Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
npx @bitbonsai/mcpvault [vault-path]
|
|
32
|
+
|
|
33
|
+
Arguments:
|
|
34
|
+
[vault-path] Optional path to your Obsidian vault directory
|
|
35
|
+
Defaults to current working directory when omitted
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--version, -v Show version number
|
|
39
|
+
--help, -h Show this help message
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
npx @bitbonsai/mcpvault
|
|
43
|
+
npx @bitbonsai/mcpvault ~/Documents/MyVault
|
|
44
|
+
npx @bitbonsai/mcpvault ./Vault
|
|
45
|
+
npx @bitbonsai/mcpvault /path/to/obsidian/vault
|
|
46
|
+
npx @bitbonsai/mcpvault "/path/with spaces/Obsidian Vault"
|
|
47
|
+
`);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
// Join trailing args to support vault paths with spaces.
|
|
51
|
+
// When omitted, default to current working directory.
|
|
52
|
+
const vaultPathArg = cliArgs.join(' ').trim();
|
|
53
|
+
const vaultPath = resolve(vaultPathArg || process.cwd());
|
|
54
|
+
// Initialize services
|
|
55
|
+
const pathFilter = new PathFilter();
|
|
56
|
+
const frontmatterHandler = new FrontmatterHandler();
|
|
57
|
+
const fileSystem = new FileSystemService(vaultPath, pathFilter, frontmatterHandler);
|
|
58
|
+
const searchService = new SearchService(vaultPath, pathFilter);
|
|
59
|
+
const server = new Server({
|
|
60
|
+
name: "mcpvault",
|
|
61
|
+
version: VERSION
|
|
62
|
+
}, {
|
|
63
|
+
capabilities: {
|
|
64
|
+
tools: {},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
68
|
+
return {
|
|
69
|
+
tools: [
|
|
70
|
+
{
|
|
71
|
+
name: "read_note",
|
|
72
|
+
description: "Read a note from the Obsidian vault",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
path: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "Path to the note relative to vault root"
|
|
79
|
+
},
|
|
80
|
+
prettyPrint: {
|
|
81
|
+
type: "boolean",
|
|
82
|
+
description: "Format JSON response with indentation (default: false)",
|
|
83
|
+
default: false
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
required: ["path"]
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "write_note",
|
|
91
|
+
description: "Write a note to the Obsidian vault",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
path: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Path to the note relative to vault root"
|
|
98
|
+
},
|
|
99
|
+
content: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Content of the note"
|
|
102
|
+
},
|
|
103
|
+
frontmatter: {
|
|
104
|
+
type: "object",
|
|
105
|
+
description: "Frontmatter object (optional)"
|
|
106
|
+
},
|
|
107
|
+
mode: {
|
|
108
|
+
type: "string",
|
|
109
|
+
enum: ["overwrite", "append", "prepend"],
|
|
110
|
+
description: "Write mode: 'overwrite' (default), 'append', or 'prepend'",
|
|
111
|
+
default: "overwrite"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
required: ["path", "content"]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "patch_note",
|
|
119
|
+
description: "Efficiently update part of a note by replacing a specific string. This is more efficient than rewriting the entire note for small changes.",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: {
|
|
123
|
+
path: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "Path to the note relative to vault root"
|
|
126
|
+
},
|
|
127
|
+
oldString: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "The exact string to replace. Must match exactly including whitespace and line breaks."
|
|
130
|
+
},
|
|
131
|
+
newString: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "The new string to insert in place of oldString"
|
|
134
|
+
},
|
|
135
|
+
replaceAll: {
|
|
136
|
+
type: "boolean",
|
|
137
|
+
description: "If true, replace all occurrences. If false (default), the operation will fail if multiple matches are found to prevent unintended replacements.",
|
|
138
|
+
default: false
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
required: ["path", "oldString", "newString"]
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "list_directory",
|
|
146
|
+
description: "List files and directories in the vault (includes non-note filenames, while read/write tools remain note-only)",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
path: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "Path relative to vault root (default: '/')",
|
|
153
|
+
default: "/"
|
|
154
|
+
},
|
|
155
|
+
prettyPrint: {
|
|
156
|
+
type: "boolean",
|
|
157
|
+
description: "Format JSON response with indentation (default: false)",
|
|
158
|
+
default: false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "delete_note",
|
|
165
|
+
description: "Delete a note from the Obsidian vault (requires confirmation)",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
path: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Path to the note relative to vault root"
|
|
172
|
+
},
|
|
173
|
+
confirmPath: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Confirmation: must exactly match the path parameter to proceed with deletion"
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
required: ["path", "confirmPath"]
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "search_notes",
|
|
183
|
+
description: "Search for notes in the vault by content or frontmatter",
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
query: {
|
|
188
|
+
type: "string",
|
|
189
|
+
description: "Search query text"
|
|
190
|
+
},
|
|
191
|
+
limit: {
|
|
192
|
+
type: "number",
|
|
193
|
+
description: "Maximum number of results (default: 5, max: 20)",
|
|
194
|
+
default: 5
|
|
195
|
+
},
|
|
196
|
+
searchContent: {
|
|
197
|
+
type: "boolean",
|
|
198
|
+
description: "Search in note content (default: true)",
|
|
199
|
+
default: true
|
|
200
|
+
},
|
|
201
|
+
searchFrontmatter: {
|
|
202
|
+
type: "boolean",
|
|
203
|
+
description: "Search in frontmatter (default: false)",
|
|
204
|
+
default: false
|
|
205
|
+
},
|
|
206
|
+
caseSensitive: {
|
|
207
|
+
type: "boolean",
|
|
208
|
+
description: "Case sensitive search (default: false)",
|
|
209
|
+
default: false
|
|
210
|
+
},
|
|
211
|
+
prettyPrint: {
|
|
212
|
+
type: "boolean",
|
|
213
|
+
description: "Format JSON response with indentation (default: false)",
|
|
214
|
+
default: false
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
required: ["query"]
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "move_note",
|
|
222
|
+
description: "Move or rename a note in the vault",
|
|
223
|
+
inputSchema: {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
oldPath: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "Current path of the note"
|
|
229
|
+
},
|
|
230
|
+
newPath: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "New path for the note"
|
|
233
|
+
},
|
|
234
|
+
overwrite: {
|
|
235
|
+
type: "boolean",
|
|
236
|
+
description: "Allow overwriting existing file (default: false)",
|
|
237
|
+
default: false
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
required: ["oldPath", "newPath"]
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "move_file",
|
|
245
|
+
description: "Move or rename any file in the vault (binary-safe, file-only, requires confirmation)",
|
|
246
|
+
inputSchema: {
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: {
|
|
249
|
+
oldPath: {
|
|
250
|
+
type: "string",
|
|
251
|
+
description: "Current path of the file"
|
|
252
|
+
},
|
|
253
|
+
newPath: {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "New path for the file"
|
|
256
|
+
},
|
|
257
|
+
confirmOldPath: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "Confirmation: must exactly match oldPath"
|
|
260
|
+
},
|
|
261
|
+
confirmNewPath: {
|
|
262
|
+
type: "string",
|
|
263
|
+
description: "Confirmation: must exactly match newPath"
|
|
264
|
+
},
|
|
265
|
+
overwrite: {
|
|
266
|
+
type: "boolean",
|
|
267
|
+
description: "Allow overwriting existing file (default: false)",
|
|
268
|
+
default: false
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
required: ["oldPath", "newPath", "confirmOldPath", "confirmNewPath"]
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "read_multiple_notes",
|
|
276
|
+
description: "Read multiple notes in a batch (max 10 files)",
|
|
277
|
+
inputSchema: {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: {
|
|
280
|
+
paths: {
|
|
281
|
+
type: "array",
|
|
282
|
+
items: { type: "string" },
|
|
283
|
+
description: "Array of note paths to read",
|
|
284
|
+
maxItems: 10
|
|
285
|
+
},
|
|
286
|
+
includeContent: {
|
|
287
|
+
type: "boolean",
|
|
288
|
+
description: "Include note content (default: true)",
|
|
289
|
+
default: true
|
|
290
|
+
},
|
|
291
|
+
includeFrontmatter: {
|
|
292
|
+
type: "boolean",
|
|
293
|
+
description: "Include frontmatter (default: true)",
|
|
294
|
+
default: true
|
|
295
|
+
},
|
|
296
|
+
prettyPrint: {
|
|
297
|
+
type: "boolean",
|
|
298
|
+
description: "Format JSON response with indentation (default: false)",
|
|
299
|
+
default: false
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
required: ["paths"]
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "update_frontmatter",
|
|
307
|
+
description: "Update frontmatter of a note without changing content",
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
path: {
|
|
312
|
+
type: "string",
|
|
313
|
+
description: "Path to the note"
|
|
314
|
+
},
|
|
315
|
+
frontmatter: {
|
|
316
|
+
type: "object",
|
|
317
|
+
description: "Frontmatter object to update"
|
|
318
|
+
},
|
|
319
|
+
merge: {
|
|
320
|
+
type: "boolean",
|
|
321
|
+
description: "Merge with existing frontmatter (default: true)",
|
|
322
|
+
default: true
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
required: ["path", "frontmatter"]
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "get_notes_info",
|
|
330
|
+
description: "Get metadata for notes without reading full content",
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: "object",
|
|
333
|
+
properties: {
|
|
334
|
+
paths: {
|
|
335
|
+
type: "array",
|
|
336
|
+
items: { type: "string" },
|
|
337
|
+
description: "Array of note paths to get info for"
|
|
338
|
+
},
|
|
339
|
+
prettyPrint: {
|
|
340
|
+
type: "boolean",
|
|
341
|
+
description: "Format JSON response with indentation (default: false)",
|
|
342
|
+
default: false
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
required: ["paths"]
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "get_frontmatter",
|
|
350
|
+
description: "Extract frontmatter from a note without reading the content",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
type: "object",
|
|
353
|
+
properties: {
|
|
354
|
+
path: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description: "Path to the note relative to vault root"
|
|
357
|
+
},
|
|
358
|
+
prettyPrint: {
|
|
359
|
+
type: "boolean",
|
|
360
|
+
description: "Format JSON response with indentation (default: false)",
|
|
361
|
+
default: false
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
required: ["path"]
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: "manage_tags",
|
|
369
|
+
description: "Add, remove, or list tags in a note",
|
|
370
|
+
inputSchema: {
|
|
371
|
+
type: "object",
|
|
372
|
+
properties: {
|
|
373
|
+
path: {
|
|
374
|
+
type: "string",
|
|
375
|
+
description: "Path to the note relative to vault root"
|
|
376
|
+
},
|
|
377
|
+
operation: {
|
|
378
|
+
type: "string",
|
|
379
|
+
enum: ["add", "remove", "list"],
|
|
380
|
+
description: "Operation to perform: 'add', 'remove', or 'list'"
|
|
381
|
+
},
|
|
382
|
+
tags: {
|
|
383
|
+
type: "array",
|
|
384
|
+
items: { type: "string" },
|
|
385
|
+
description: "Array of tags (required for 'add' and 'remove' operations)"
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
required: ["path", "operation"]
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: "get_vault_stats",
|
|
393
|
+
description: "Get vault statistics including total notes, folders, size, and recently modified files. Useful for understanding vault scope before batch operations.",
|
|
394
|
+
inputSchema: {
|
|
395
|
+
type: "object",
|
|
396
|
+
properties: {
|
|
397
|
+
recentCount: {
|
|
398
|
+
type: "number",
|
|
399
|
+
description: "Number of recently modified files to return (default: 5, max: 20)",
|
|
400
|
+
default: 5
|
|
401
|
+
},
|
|
402
|
+
prettyPrint: {
|
|
403
|
+
type: "boolean",
|
|
404
|
+
description: "Format JSON response with indentation (default: false)",
|
|
405
|
+
default: false
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
]
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
// Helper function to trim path arguments
|
|
414
|
+
function trimPaths(args) {
|
|
415
|
+
const trimmed = { ...args };
|
|
416
|
+
// Trim single path properties
|
|
417
|
+
if (trimmed.path && typeof trimmed.path === 'string') {
|
|
418
|
+
trimmed.path = trimmed.path.trim();
|
|
419
|
+
}
|
|
420
|
+
if (trimmed.oldPath && typeof trimmed.oldPath === 'string') {
|
|
421
|
+
trimmed.oldPath = trimmed.oldPath.trim();
|
|
422
|
+
}
|
|
423
|
+
if (trimmed.newPath && typeof trimmed.newPath === 'string') {
|
|
424
|
+
trimmed.newPath = trimmed.newPath.trim();
|
|
425
|
+
}
|
|
426
|
+
if (trimmed.confirmPath && typeof trimmed.confirmPath === 'string') {
|
|
427
|
+
trimmed.confirmPath = trimmed.confirmPath.trim();
|
|
428
|
+
}
|
|
429
|
+
if (trimmed.confirmOldPath && typeof trimmed.confirmOldPath === 'string') {
|
|
430
|
+
trimmed.confirmOldPath = trimmed.confirmOldPath.trim();
|
|
431
|
+
}
|
|
432
|
+
if (trimmed.confirmNewPath && typeof trimmed.confirmNewPath === 'string') {
|
|
433
|
+
trimmed.confirmNewPath = trimmed.confirmNewPath.trim();
|
|
434
|
+
}
|
|
435
|
+
// Trim path arrays
|
|
436
|
+
if (trimmed.paths && Array.isArray(trimmed.paths)) {
|
|
437
|
+
trimmed.paths = trimmed.paths.map((p) => typeof p === 'string' ? p.trim() : p);
|
|
438
|
+
}
|
|
439
|
+
return trimmed;
|
|
440
|
+
}
|
|
441
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
442
|
+
const { name, arguments: args } = request.params;
|
|
443
|
+
const trimmedArgs = trimPaths(args);
|
|
444
|
+
try {
|
|
445
|
+
switch (name) {
|
|
446
|
+
case "read_note": {
|
|
447
|
+
const note = await fileSystem.readNote(trimmedArgs.path);
|
|
448
|
+
const indent = trimmedArgs.prettyPrint ? 2 : undefined;
|
|
449
|
+
return {
|
|
450
|
+
content: [
|
|
451
|
+
{
|
|
452
|
+
type: "text",
|
|
453
|
+
text: JSON.stringify({
|
|
454
|
+
fm: note.frontmatter,
|
|
455
|
+
content: note.content
|
|
456
|
+
}, null, indent)
|
|
457
|
+
}
|
|
458
|
+
]
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
case "write_note": {
|
|
462
|
+
const fm = parseFrontmatter(trimmedArgs.frontmatter);
|
|
463
|
+
await fileSystem.writeNote({
|
|
464
|
+
path: trimmedArgs.path,
|
|
465
|
+
content: trimmedArgs.content,
|
|
466
|
+
...(fm !== undefined && { frontmatter: fm }),
|
|
467
|
+
mode: trimmedArgs.mode || 'overwrite'
|
|
468
|
+
});
|
|
469
|
+
return {
|
|
470
|
+
content: [
|
|
471
|
+
{
|
|
472
|
+
type: "text",
|
|
473
|
+
text: `Successfully wrote note: ${trimmedArgs.path} (mode: ${trimmedArgs.mode || 'overwrite'})`
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
case "patch_note": {
|
|
479
|
+
const result = await fileSystem.patchNote({
|
|
480
|
+
path: trimmedArgs.path,
|
|
481
|
+
oldString: trimmedArgs.oldString,
|
|
482
|
+
newString: trimmedArgs.newString,
|
|
483
|
+
replaceAll: trimmedArgs.replaceAll
|
|
484
|
+
});
|
|
485
|
+
return {
|
|
486
|
+
content: [
|
|
487
|
+
{
|
|
488
|
+
type: "text",
|
|
489
|
+
text: JSON.stringify(result, null, 2)
|
|
490
|
+
}
|
|
491
|
+
],
|
|
492
|
+
isError: !result.success
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
case "list_directory": {
|
|
496
|
+
const listing = await fileSystem.listDirectory(trimmedArgs.path || '');
|
|
497
|
+
const indent = trimmedArgs.prettyPrint ? 2 : undefined;
|
|
498
|
+
return {
|
|
499
|
+
content: [
|
|
500
|
+
{
|
|
501
|
+
type: "text",
|
|
502
|
+
text: JSON.stringify({
|
|
503
|
+
dirs: listing.directories,
|
|
504
|
+
files: listing.files
|
|
505
|
+
}, null, indent)
|
|
506
|
+
}
|
|
507
|
+
]
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
case "delete_note": {
|
|
511
|
+
const result = await fileSystem.deleteNote({
|
|
512
|
+
path: trimmedArgs.path,
|
|
513
|
+
confirmPath: trimmedArgs.confirmPath
|
|
514
|
+
});
|
|
515
|
+
return {
|
|
516
|
+
content: [
|
|
517
|
+
{
|
|
518
|
+
type: "text",
|
|
519
|
+
text: JSON.stringify(result, null, 2)
|
|
520
|
+
}
|
|
521
|
+
],
|
|
522
|
+
isError: !result.success
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
case "search_notes": {
|
|
526
|
+
const results = await searchService.search({
|
|
527
|
+
query: trimmedArgs.query,
|
|
528
|
+
limit: trimmedArgs.limit,
|
|
529
|
+
searchContent: trimmedArgs.searchContent,
|
|
530
|
+
searchFrontmatter: trimmedArgs.searchFrontmatter,
|
|
531
|
+
caseSensitive: trimmedArgs.caseSensitive
|
|
532
|
+
});
|
|
533
|
+
const indent = trimmedArgs.prettyPrint ? 2 : undefined;
|
|
534
|
+
return {
|
|
535
|
+
content: [
|
|
536
|
+
{
|
|
537
|
+
type: "text",
|
|
538
|
+
text: JSON.stringify(results, null, indent)
|
|
539
|
+
}
|
|
540
|
+
]
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
case "move_note": {
|
|
544
|
+
const result = await fileSystem.moveNote({
|
|
545
|
+
oldPath: trimmedArgs.oldPath,
|
|
546
|
+
newPath: trimmedArgs.newPath,
|
|
547
|
+
overwrite: trimmedArgs.overwrite
|
|
548
|
+
});
|
|
549
|
+
return {
|
|
550
|
+
content: [
|
|
551
|
+
{
|
|
552
|
+
type: "text",
|
|
553
|
+
text: JSON.stringify(result, null, 2)
|
|
554
|
+
}
|
|
555
|
+
],
|
|
556
|
+
isError: !result.success
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
case "move_file": {
|
|
560
|
+
const result = await fileSystem.moveFile({
|
|
561
|
+
oldPath: trimmedArgs.oldPath,
|
|
562
|
+
newPath: trimmedArgs.newPath,
|
|
563
|
+
confirmOldPath: trimmedArgs.confirmOldPath,
|
|
564
|
+
confirmNewPath: trimmedArgs.confirmNewPath,
|
|
565
|
+
overwrite: trimmedArgs.overwrite
|
|
566
|
+
});
|
|
567
|
+
return {
|
|
568
|
+
content: [
|
|
569
|
+
{
|
|
570
|
+
type: "text",
|
|
571
|
+
text: JSON.stringify(result, null, 2)
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
isError: !result.success
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
case "read_multiple_notes": {
|
|
578
|
+
const result = await fileSystem.readMultipleNotes({
|
|
579
|
+
paths: trimmedArgs.paths,
|
|
580
|
+
includeContent: trimmedArgs.includeContent,
|
|
581
|
+
includeFrontmatter: trimmedArgs.includeFrontmatter
|
|
582
|
+
});
|
|
583
|
+
const indent = trimmedArgs.prettyPrint ? 2 : undefined;
|
|
584
|
+
return {
|
|
585
|
+
content: [
|
|
586
|
+
{
|
|
587
|
+
type: "text",
|
|
588
|
+
text: JSON.stringify({
|
|
589
|
+
ok: result.successful,
|
|
590
|
+
err: result.failed
|
|
591
|
+
}, null, indent)
|
|
592
|
+
}
|
|
593
|
+
]
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
case "update_frontmatter": {
|
|
597
|
+
const fm = parseFrontmatter(trimmedArgs.frontmatter);
|
|
598
|
+
if (!fm) {
|
|
599
|
+
throw new Error('frontmatter is required');
|
|
600
|
+
}
|
|
601
|
+
await fileSystem.updateFrontmatter({
|
|
602
|
+
path: trimmedArgs.path,
|
|
603
|
+
frontmatter: fm,
|
|
604
|
+
merge: trimmedArgs.merge
|
|
605
|
+
});
|
|
606
|
+
return {
|
|
607
|
+
content: [
|
|
608
|
+
{
|
|
609
|
+
type: "text",
|
|
610
|
+
text: `Successfully updated frontmatter for: ${trimmedArgs.path}`
|
|
611
|
+
}
|
|
612
|
+
]
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
case "get_notes_info": {
|
|
616
|
+
const result = await fileSystem.getNotesInfo(trimmedArgs.paths);
|
|
617
|
+
const indent = trimmedArgs.prettyPrint ? 2 : undefined;
|
|
618
|
+
return {
|
|
619
|
+
content: [
|
|
620
|
+
{
|
|
621
|
+
type: "text",
|
|
622
|
+
text: JSON.stringify(result, null, indent)
|
|
623
|
+
}
|
|
624
|
+
]
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
case "get_frontmatter": {
|
|
628
|
+
const note = await fileSystem.readNote(trimmedArgs.path);
|
|
629
|
+
const indent = trimmedArgs.prettyPrint ? 2 : undefined;
|
|
630
|
+
return {
|
|
631
|
+
content: [
|
|
632
|
+
{
|
|
633
|
+
type: "text",
|
|
634
|
+
text: JSON.stringify(note.frontmatter, null, indent)
|
|
635
|
+
}
|
|
636
|
+
]
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
case "manage_tags": {
|
|
640
|
+
const result = await fileSystem.manageTags({
|
|
641
|
+
path: trimmedArgs.path,
|
|
642
|
+
operation: trimmedArgs.operation,
|
|
643
|
+
tags: trimmedArgs.tags
|
|
644
|
+
});
|
|
645
|
+
return {
|
|
646
|
+
content: [
|
|
647
|
+
{
|
|
648
|
+
type: "text",
|
|
649
|
+
text: JSON.stringify(result, null, 2)
|
|
650
|
+
}
|
|
651
|
+
],
|
|
652
|
+
isError: !result.success
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
case "get_vault_stats": {
|
|
656
|
+
const recentCount = Math.min(trimmedArgs.recentCount || 5, 20);
|
|
657
|
+
const stats = await fileSystem.getVaultStats(recentCount);
|
|
658
|
+
const indent = trimmedArgs.prettyPrint ? 2 : undefined;
|
|
659
|
+
return {
|
|
660
|
+
content: [
|
|
661
|
+
{
|
|
662
|
+
type: "text",
|
|
663
|
+
text: JSON.stringify({
|
|
664
|
+
notes: stats.totalNotes,
|
|
665
|
+
folders: stats.totalFolders,
|
|
666
|
+
size: stats.totalSize,
|
|
667
|
+
recent: stats.recentlyModified
|
|
668
|
+
}, null, indent)
|
|
669
|
+
}
|
|
670
|
+
]
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
default:
|
|
674
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
return {
|
|
679
|
+
content: [
|
|
680
|
+
{
|
|
681
|
+
type: "text",
|
|
682
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
683
|
+
}
|
|
684
|
+
],
|
|
685
|
+
isError: true
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
const transport = new StdioServerTransport();
|
|
690
|
+
await server.connect(transport);
|