obsidian-mcp-server 1.2.2 → 1.2.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 +76 -6
- package/build/obsidian.js +185 -16
- package/build/properties.js +12 -11
- package/build/propertyTools.js +11 -22
- package/build/propertyTypes.js +28 -14
- package/build/resources.js +69 -0
- package/build/server.js +36 -12
- package/build/tools.js +424 -48
- package/build/types.js +20 -3
- package/package.json +3 -3
- package/src/obsidian.ts +244 -21
- package/src/properties.ts +19 -15
- package/src/propertyTools.ts +13 -22
- package/src/propertyTypes.ts +36 -14
- package/src/resources.ts +76 -0
- package/src/server.ts +39 -14
- package/src/tools.ts +460 -49
- package/src/types.ts +87 -3
package/src/tools.ts
CHANGED
|
@@ -9,8 +9,17 @@ import {
|
|
|
9
9
|
ComplexSearchArgs,
|
|
10
10
|
FileContentsArgs,
|
|
11
11
|
ListFilesArgs,
|
|
12
|
-
|
|
12
|
+
GetTagsArgs,
|
|
13
|
+
TagResponse,
|
|
14
|
+
ObsidianError,
|
|
15
|
+
JsonLogicQuery,
|
|
16
|
+
SearchResult,
|
|
17
|
+
SimpleSearchResult,
|
|
18
|
+
SearchMatch,
|
|
19
|
+
ObsidianFile,
|
|
20
|
+
PeriodType
|
|
13
21
|
} from "./types.js";
|
|
22
|
+
import { PropertyManager } from "./properties.js";
|
|
14
23
|
|
|
15
24
|
const TOOL_NAMES = {
|
|
16
25
|
LIST_FILES_IN_VAULT: "obsidian_list_files_in_vault",
|
|
@@ -19,7 +28,8 @@ const TOOL_NAMES = {
|
|
|
19
28
|
FIND_IN_FILE: "obsidian_find_in_file",
|
|
20
29
|
APPEND_CONTENT: "obsidian_append_content",
|
|
21
30
|
PATCH_CONTENT: "obsidian_patch_content",
|
|
22
|
-
COMPLEX_SEARCH: "obsidian_complex_search"
|
|
31
|
+
COMPLEX_SEARCH: "obsidian_complex_search",
|
|
32
|
+
GET_TAGS: "obsidian_get_tags"
|
|
23
33
|
} as const;
|
|
24
34
|
|
|
25
35
|
// Load token limits from environment or use defaults
|
|
@@ -302,7 +312,7 @@ export class FindInFileToolHandler extends BaseToolHandler<SearchArgs> {
|
|
|
302
312
|
getToolDescription(): Tool {
|
|
303
313
|
return {
|
|
304
314
|
name: this.name,
|
|
305
|
-
description: "Full-text search across all files in the vault. Returns matching files with surrounding context for each match. Useful for finding specific content, references, or patterns across notes.",
|
|
315
|
+
description: "Full-text search across all files in the vault. Returns matching files with surrounding context for each match. For results with more than 5 matching files, returns only file names and match counts to prevent overwhelming responses. Useful for finding specific content, references, or patterns across notes.",
|
|
306
316
|
examples: [
|
|
307
317
|
{
|
|
308
318
|
description: "Search for a specific term",
|
|
@@ -316,17 +326,39 @@ export class FindInFileToolHandler extends BaseToolHandler<SearchArgs> {
|
|
|
316
326
|
args: {
|
|
317
327
|
query: "#todo"
|
|
318
328
|
},
|
|
319
|
-
response:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
response: {
|
|
330
|
+
"message": "Found 1 file with matches:",
|
|
331
|
+
"results": [
|
|
332
|
+
{
|
|
333
|
+
"filename": "Projects/AI.md",
|
|
334
|
+
"matches": [
|
|
335
|
+
{
|
|
336
|
+
"context": "Research needed:\n#todo Implement transformer architecture\nDeadline: Next week",
|
|
337
|
+
"match": { "start": 15, "end": 45 }
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
description: "Example response with many matches (file-only format)",
|
|
346
|
+
args: {
|
|
347
|
+
query: "API"
|
|
348
|
+
},
|
|
349
|
+
response: {
|
|
350
|
+
"message": "Found 92 files with matches. Showing file names only:",
|
|
351
|
+
"results": [
|
|
352
|
+
{
|
|
353
|
+
"filename": "Developer/Documentation/API.md",
|
|
354
|
+
"matchCount": 43
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"filename": "Projects/API_Design.md",
|
|
358
|
+
"matchCount": 34
|
|
359
|
+
}
|
|
360
|
+
]
|
|
361
|
+
}
|
|
330
362
|
}
|
|
331
363
|
],
|
|
332
364
|
inputSchema: {
|
|
@@ -349,10 +381,36 @@ export class FindInFileToolHandler extends BaseToolHandler<SearchArgs> {
|
|
|
349
381
|
|
|
350
382
|
async runTool(args: SearchArgs): Promise<Array<TextContent>> {
|
|
351
383
|
try {
|
|
352
|
-
const results = await this.client.search(args.query, args.contextLength);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
384
|
+
const results = await this.client.search(args.query, args.contextLength ?? 100) as SimpleSearchResult[];
|
|
385
|
+
|
|
386
|
+
// If more than 5 results, only return filenames
|
|
387
|
+
if (results.length > 5) {
|
|
388
|
+
const fileOnlyResults = results.map(result => ({
|
|
389
|
+
filename: result.filename,
|
|
390
|
+
matchCount: result.matches.length
|
|
391
|
+
}));
|
|
392
|
+
return this.createResponse({
|
|
393
|
+
message: `Found ${results.length} files with matches. Showing file names only:`,
|
|
394
|
+
results: fileOnlyResults
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Otherwise return full context as before
|
|
399
|
+
const formattedResults = results.map(result => ({
|
|
400
|
+
filename: result.filename,
|
|
401
|
+
matches: result.matches.map(match => ({
|
|
402
|
+
context: match.context,
|
|
403
|
+
match: {
|
|
404
|
+
text: match.context.substring(match.match.start, match.match.end),
|
|
405
|
+
position: {
|
|
406
|
+
start: match.match.start,
|
|
407
|
+
end: match.match.end
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
})),
|
|
411
|
+
score: result.score
|
|
412
|
+
}));
|
|
413
|
+
return this.createResponse(formattedResults);
|
|
356
414
|
} catch (error) {
|
|
357
415
|
return this.handleError(error);
|
|
358
416
|
}
|
|
@@ -466,46 +524,21 @@ export class ComplexSearchToolHandler extends BaseToolHandler<ComplexSearchArgs>
|
|
|
466
524
|
getToolDescription(): Tool {
|
|
467
525
|
return {
|
|
468
526
|
name: this.name,
|
|
469
|
-
description: "
|
|
527
|
+
description: "File path pattern matching using JsonLogic queries. Supported operations:\n- glob: Pattern matching for paths (e.g., \"*.md\")\n- Variable access: {\"var\": \"path\"}\n\nNote: For full-text content search, date-based searches, or other advanced queries, use obsidian_find_in_file instead.",
|
|
470
528
|
examples: [
|
|
471
529
|
{
|
|
472
|
-
description: "Find markdown files in
|
|
530
|
+
description: "Find markdown files in Projects folder",
|
|
473
531
|
args: {
|
|
474
532
|
query: {
|
|
475
|
-
"
|
|
476
|
-
{"glob": ["Projects/*.md", {"var": "path"}]},
|
|
477
|
-
{"contains": [{"var": "content"}, "#active"]}
|
|
478
|
-
]
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
description: "Find recently modified documentation",
|
|
484
|
-
args: {
|
|
485
|
-
query: {
|
|
486
|
-
"and": [
|
|
487
|
-
{"glob": ["docs/*.md", {"var": "path"}]},
|
|
488
|
-
{">=": [
|
|
489
|
-
{"var": "mtime"},
|
|
490
|
-
{"date": "-7 days"}
|
|
491
|
-
]},
|
|
492
|
-
{"!=": [{"var": "size"}, 0]}
|
|
493
|
-
]
|
|
533
|
+
"glob": ["Projects/*.md", {"var": "path"}]
|
|
494
534
|
}
|
|
495
535
|
}
|
|
496
536
|
},
|
|
497
537
|
{
|
|
498
|
-
description: "Find files
|
|
538
|
+
description: "Find files in a specific subfolder",
|
|
499
539
|
args: {
|
|
500
540
|
query: {
|
|
501
|
-
"
|
|
502
|
-
{"or": [
|
|
503
|
-
{"glob": ["*.md", {"var": "path"}]},
|
|
504
|
-
{"glob": ["*.txt", {"var": "path"}]}
|
|
505
|
-
]},
|
|
506
|
-
{"contains": [{"var": "content"}, "TODO"]},
|
|
507
|
-
{"<": [{"var": "size"}, 10000]}
|
|
508
|
-
]
|
|
541
|
+
"glob": ["**/Test/*.md", {"var": "path"}]
|
|
509
542
|
}
|
|
510
543
|
}
|
|
511
544
|
}
|
|
@@ -525,10 +558,388 @@ export class ComplexSearchToolHandler extends BaseToolHandler<ComplexSearchArgs>
|
|
|
525
558
|
|
|
526
559
|
async runTool(args: ComplexSearchArgs): Promise<Array<TextContent>> {
|
|
527
560
|
try {
|
|
561
|
+
// Perform search
|
|
528
562
|
const results = await this.client.searchJson(args.query);
|
|
529
|
-
|
|
563
|
+
console.debug('Search results:', results);
|
|
564
|
+
|
|
565
|
+
// Format response based on result type
|
|
566
|
+
const formattedResults = results.map(result => {
|
|
567
|
+
if ('matches' in result) {
|
|
568
|
+
// SimpleSearchResult
|
|
569
|
+
return {
|
|
570
|
+
filename: result.filename,
|
|
571
|
+
matches: result.matches,
|
|
572
|
+
score: result.score
|
|
573
|
+
};
|
|
574
|
+
} else {
|
|
575
|
+
// SearchResult
|
|
576
|
+
return {
|
|
577
|
+
filename: result.filename,
|
|
578
|
+
result: result.result
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
return this.createResponse(formattedResults);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
console.error('Complex search error:', error);
|
|
586
|
+
return this.handleError(error);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export class GetTagsToolHandler extends BaseToolHandler<GetTagsArgs> {
|
|
592
|
+
private static readonly TAG_PATTERN = /#[a-zA-Z0-9_-]+/g;
|
|
593
|
+
private propertyManager: PropertyManager;
|
|
594
|
+
|
|
595
|
+
constructor(client: ObsidianClient) {
|
|
596
|
+
super(TOOL_NAMES.GET_TAGS, client);
|
|
597
|
+
this.propertyManager = new PropertyManager(client);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
getToolDescription(): Tool {
|
|
601
|
+
return {
|
|
602
|
+
name: this.name,
|
|
603
|
+
description: "Get all tags used across the Obsidian vault with their usage counts. Optionally filter tags within a specific folder.",
|
|
604
|
+
examples: [
|
|
605
|
+
{
|
|
606
|
+
description: "Get all tags in vault",
|
|
607
|
+
args: {}
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
description: "Get tags in Projects folder",
|
|
611
|
+
args: {
|
|
612
|
+
path: "Projects"
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
description: "Example response",
|
|
617
|
+
args: {},
|
|
618
|
+
response: {
|
|
619
|
+
"tags": [
|
|
620
|
+
{
|
|
621
|
+
"name": "#project",
|
|
622
|
+
"count": 15,
|
|
623
|
+
"files": [
|
|
624
|
+
"Projects/ProjectA.md",
|
|
625
|
+
"Projects/ProjectB.md"
|
|
626
|
+
]
|
|
627
|
+
}
|
|
628
|
+
],
|
|
629
|
+
"metadata": {
|
|
630
|
+
"totalOccurrences": 45,
|
|
631
|
+
"uniqueTags": 12,
|
|
632
|
+
"scannedFiles": 30
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
],
|
|
637
|
+
inputSchema: {
|
|
638
|
+
type: "object",
|
|
639
|
+
properties: {
|
|
640
|
+
path: {
|
|
641
|
+
type: "string",
|
|
642
|
+
description: "Optional path to limit tag search to specific folder",
|
|
643
|
+
format: "path"
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
private async processFiles(files: ObsidianFile[], basePath: string, tagMap: Map<string, Set<string>>): Promise<number> {
|
|
651
|
+
let scannedFiles = 0;
|
|
652
|
+
|
|
653
|
+
for (const file of files) {
|
|
654
|
+
const fullPath = basePath ? `${basePath}/${file.path}` : file.path;
|
|
655
|
+
|
|
656
|
+
if (file.type === "folder" && file.children) {
|
|
657
|
+
// Recursively process subdirectories
|
|
658
|
+
scannedFiles += await this.processFiles(file.children, fullPath, tagMap);
|
|
659
|
+
} else if (file.type === "file" && file.path.endsWith('.md')) {
|
|
660
|
+
// Process markdown files
|
|
661
|
+
scannedFiles++;
|
|
662
|
+
const content = await this.client.getFileContents(fullPath);
|
|
663
|
+
|
|
664
|
+
// Extract tags from frontmatter
|
|
665
|
+
const properties = this.propertyManager.parseProperties(content);
|
|
666
|
+
if (properties.tags) {
|
|
667
|
+
properties.tags.forEach((tag: string) => {
|
|
668
|
+
if (!tagMap.has(tag)) {
|
|
669
|
+
tagMap.set(tag, new Set());
|
|
670
|
+
}
|
|
671
|
+
tagMap.get(tag)!.add(fullPath);
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Extract inline tags using regex
|
|
676
|
+
const inlineTags = content.match(GetTagsToolHandler.TAG_PATTERN) || [];
|
|
677
|
+
inlineTags.forEach(tag => {
|
|
678
|
+
if (!tagMap.has(tag)) {
|
|
679
|
+
tagMap.set(tag, new Set());
|
|
680
|
+
}
|
|
681
|
+
tagMap.get(tag)!.add(fullPath);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return scannedFiles;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async runTool(args: GetTagsArgs): Promise<Array<TextContent>> {
|
|
690
|
+
try {
|
|
691
|
+
const tagMap = new Map<string, Set<string>>();
|
|
692
|
+
const basePath = args.path || '';
|
|
693
|
+
|
|
694
|
+
// Get files from vault or specific directory
|
|
695
|
+
const files = args.path
|
|
696
|
+
? await this.client.listFilesInDir(args.path)
|
|
697
|
+
: await this.client.listFilesInVault();
|
|
698
|
+
|
|
699
|
+
// Process files recursively
|
|
700
|
+
const scannedFiles = await this.processFiles(files, basePath, tagMap);
|
|
701
|
+
|
|
702
|
+
// Calculate total occurrences
|
|
703
|
+
const totalOccurrences = Array.from(tagMap.values())
|
|
704
|
+
.reduce((sum, files) => sum + files.size, 0);
|
|
705
|
+
|
|
706
|
+
const response: TagResponse = {
|
|
707
|
+
tags: Array.from(tagMap.entries())
|
|
708
|
+
.map(([name, files]) => ({
|
|
709
|
+
name,
|
|
710
|
+
count: files.size,
|
|
711
|
+
files: Array.from(files).sort()
|
|
712
|
+
}))
|
|
713
|
+
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)),
|
|
714
|
+
metadata: {
|
|
715
|
+
totalOccurrences,
|
|
716
|
+
uniqueTags: tagMap.size,
|
|
717
|
+
scannedFiles
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
return this.createResponse(response);
|
|
530
722
|
} catch (error) {
|
|
531
723
|
return this.handleError(error);
|
|
532
724
|
}
|
|
533
725
|
}
|
|
534
726
|
}
|
|
727
|
+
|
|
728
|
+
// Export all handlers
|
|
729
|
+
export class ListCommandsToolHandler extends BaseToolHandler<Record<string, never>> {
|
|
730
|
+
constructor(client: ObsidianClient) {
|
|
731
|
+
super("obsidian_list_commands", client);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
getToolDescription(): Tool {
|
|
735
|
+
return {
|
|
736
|
+
name: this.name,
|
|
737
|
+
description: "Get a list of available commands that can be executed in Obsidian.",
|
|
738
|
+
examples: [
|
|
739
|
+
{
|
|
740
|
+
description: "List all available commands",
|
|
741
|
+
args: {}
|
|
742
|
+
}
|
|
743
|
+
],
|
|
744
|
+
inputSchema: {
|
|
745
|
+
type: "object",
|
|
746
|
+
properties: {},
|
|
747
|
+
required: []
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
async runTool(): Promise<Array<TextContent>> {
|
|
753
|
+
try {
|
|
754
|
+
const commands = await this.client.listCommands();
|
|
755
|
+
return this.createResponse(commands);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
return this.handleError(error);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export class ExecuteCommandToolHandler extends BaseToolHandler<{commandId: string}> {
|
|
763
|
+
constructor(client: ObsidianClient) {
|
|
764
|
+
super("obsidian_execute_command", client);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
getToolDescription(): Tool {
|
|
768
|
+
return {
|
|
769
|
+
name: this.name,
|
|
770
|
+
description: "Execute a specific command in Obsidian by its ID.",
|
|
771
|
+
examples: [
|
|
772
|
+
{
|
|
773
|
+
description: "Execute the graph view command",
|
|
774
|
+
args: {
|
|
775
|
+
commandId: "graph:open"
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
],
|
|
779
|
+
inputSchema: {
|
|
780
|
+
type: "object",
|
|
781
|
+
properties: {
|
|
782
|
+
commandId: {
|
|
783
|
+
type: "string",
|
|
784
|
+
description: "The ID of the command to execute"
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
required: ["commandId"]
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
async runTool(args: {commandId: string}): Promise<Array<TextContent>> {
|
|
793
|
+
try {
|
|
794
|
+
await this.client.executeCommand(args.commandId);
|
|
795
|
+
return this.createResponse({ message: `Successfully executed command: ${args.commandId}` });
|
|
796
|
+
} catch (error) {
|
|
797
|
+
return this.handleError(error);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
export class OpenFileToolHandler extends BaseToolHandler<{filepath: string; newLeaf?: boolean}> {
|
|
803
|
+
constructor(client: ObsidianClient) {
|
|
804
|
+
super("obsidian_open_file", client);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
getToolDescription(): Tool {
|
|
808
|
+
return {
|
|
809
|
+
name: this.name,
|
|
810
|
+
description: "Open a specific file in Obsidian, optionally in a new leaf.",
|
|
811
|
+
examples: [
|
|
812
|
+
{
|
|
813
|
+
description: "Open a file in the current leaf",
|
|
814
|
+
args: {
|
|
815
|
+
filepath: "Projects/research.md"
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
description: "Open a file in a new leaf",
|
|
820
|
+
args: {
|
|
821
|
+
filepath: "Projects/research.md",
|
|
822
|
+
newLeaf: true
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
],
|
|
826
|
+
inputSchema: {
|
|
827
|
+
type: "object",
|
|
828
|
+
properties: {
|
|
829
|
+
filepath: {
|
|
830
|
+
type: "string",
|
|
831
|
+
description: "Path to the file to open (relative to vault root)",
|
|
832
|
+
format: "path"
|
|
833
|
+
},
|
|
834
|
+
newLeaf: {
|
|
835
|
+
type: "boolean",
|
|
836
|
+
description: "Whether to open the file in a new leaf",
|
|
837
|
+
default: false
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
required: ["filepath"]
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
async runTool(args: {filepath: string; newLeaf?: boolean}): Promise<Array<TextContent>> {
|
|
846
|
+
try {
|
|
847
|
+
await this.client.openFile(args.filepath, args.newLeaf);
|
|
848
|
+
return this.createResponse({
|
|
849
|
+
message: `Successfully opened ${args.filepath}${args.newLeaf ? ' in new leaf' : ''}`
|
|
850
|
+
});
|
|
851
|
+
} catch (error) {
|
|
852
|
+
return this.handleError(error);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export class GetActiveFileToolHandler extends BaseToolHandler<Record<string, never>> {
|
|
858
|
+
constructor(client: ObsidianClient) {
|
|
859
|
+
super("obsidian_get_active_file", client);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
getToolDescription(): Tool {
|
|
863
|
+
return {
|
|
864
|
+
name: this.name,
|
|
865
|
+
description: "Get the content and metadata of the currently active file in Obsidian.",
|
|
866
|
+
examples: [
|
|
867
|
+
{
|
|
868
|
+
description: "Get active file content",
|
|
869
|
+
args: {}
|
|
870
|
+
}
|
|
871
|
+
],
|
|
872
|
+
inputSchema: {
|
|
873
|
+
type: "object",
|
|
874
|
+
properties: {},
|
|
875
|
+
required: []
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async runTool(): Promise<Array<TextContent>> {
|
|
881
|
+
try {
|
|
882
|
+
const activeFile = await this.client.getActiveFile();
|
|
883
|
+
return this.createResponse(activeFile);
|
|
884
|
+
} catch (error) {
|
|
885
|
+
return this.handleError(error);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
export class GetPeriodicNoteToolHandler extends BaseToolHandler<{period: PeriodType["type"]}> {
|
|
891
|
+
constructor(client: ObsidianClient) {
|
|
892
|
+
super("obsidian_get_periodic_note", client);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
getToolDescription(): Tool {
|
|
896
|
+
return {
|
|
897
|
+
name: this.name,
|
|
898
|
+
description: "Get the content and metadata of a periodic note (daily, weekly, monthly, quarterly, or yearly).",
|
|
899
|
+
examples: [
|
|
900
|
+
{
|
|
901
|
+
description: "Get today's daily note",
|
|
902
|
+
args: {
|
|
903
|
+
period: "daily"
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
],
|
|
907
|
+
inputSchema: {
|
|
908
|
+
type: "object",
|
|
909
|
+
properties: {
|
|
910
|
+
period: {
|
|
911
|
+
type: "string",
|
|
912
|
+
enum: ["daily", "weekly", "monthly", "quarterly", "yearly"],
|
|
913
|
+
description: "The type of periodic note to retrieve"
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
required: ["period"]
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
async runTool(args: {period: PeriodType["type"]}): Promise<Array<TextContent>> {
|
|
922
|
+
try {
|
|
923
|
+
const note = await this.client.getPeriodicNote(args.period);
|
|
924
|
+
return this.createResponse(note);
|
|
925
|
+
} catch (error) {
|
|
926
|
+
return this.handleError(error);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export const handlers = [
|
|
932
|
+
ListFilesInVaultToolHandler,
|
|
933
|
+
ListFilesInDirToolHandler,
|
|
934
|
+
GetFileContentsToolHandler,
|
|
935
|
+
FindInFileToolHandler,
|
|
936
|
+
AppendContentToolHandler,
|
|
937
|
+
PatchContentToolHandler,
|
|
938
|
+
ComplexSearchToolHandler,
|
|
939
|
+
GetTagsToolHandler,
|
|
940
|
+
ListCommandsToolHandler,
|
|
941
|
+
ExecuteCommandToolHandler,
|
|
942
|
+
OpenFileToolHandler,
|
|
943
|
+
GetActiveFileToolHandler,
|
|
944
|
+
GetPeriodicNoteToolHandler
|
|
945
|
+
];
|
package/src/types.ts
CHANGED
|
@@ -20,12 +20,43 @@ export const DEFAULT_OBSIDIAN_CONFIG: ObsidianServerConfig = {
|
|
|
20
20
|
port: 27124
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
|
+
export interface NoteJson {
|
|
24
|
+
content: string;
|
|
25
|
+
frontmatter: Record<string, unknown>;
|
|
26
|
+
path: string;
|
|
27
|
+
stat: {
|
|
28
|
+
ctime: number;
|
|
29
|
+
mtime: number;
|
|
30
|
+
size: number;
|
|
31
|
+
};
|
|
32
|
+
tags: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
export interface ObsidianFile {
|
|
24
36
|
path: string;
|
|
25
37
|
type: "file" | "folder";
|
|
26
38
|
children?: ObsidianFile[];
|
|
27
39
|
}
|
|
28
40
|
|
|
41
|
+
export interface ObsidianCommand {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ObsidianStatus {
|
|
47
|
+
authenticated: boolean;
|
|
48
|
+
ok: string;
|
|
49
|
+
service: string;
|
|
50
|
+
versions: {
|
|
51
|
+
obsidian: string;
|
|
52
|
+
self: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PeriodType {
|
|
57
|
+
type: "daily" | "weekly" | "monthly" | "quarterly" | "yearly";
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
export interface SearchMatch {
|
|
30
61
|
context: string;
|
|
31
62
|
match: {
|
|
@@ -35,11 +66,18 @@ export interface SearchMatch {
|
|
|
35
66
|
}
|
|
36
67
|
|
|
37
68
|
export interface SearchResult {
|
|
69
|
+
filename: string;
|
|
70
|
+
result: unknown;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface SimpleSearchResult {
|
|
38
74
|
filename: string;
|
|
39
75
|
score: number;
|
|
40
76
|
matches: SearchMatch[];
|
|
41
77
|
}
|
|
42
78
|
|
|
79
|
+
export type SearchResponse = SearchResult | SimpleSearchResult;
|
|
80
|
+
|
|
43
81
|
export interface ToolHandler<T = Record<string, unknown>> {
|
|
44
82
|
name: string;
|
|
45
83
|
getToolDescription(): Tool;
|
|
@@ -62,7 +100,7 @@ export interface SearchArgs {
|
|
|
62
100
|
}
|
|
63
101
|
|
|
64
102
|
export interface JsonLogicQuery {
|
|
65
|
-
[operator: string]: unknown
|
|
103
|
+
[operator: string]: unknown;
|
|
66
104
|
}
|
|
67
105
|
|
|
68
106
|
export interface ComplexSearchArgs {
|
|
@@ -77,6 +115,27 @@ export interface ListFilesArgs {
|
|
|
77
115
|
dirpath: string;
|
|
78
116
|
}
|
|
79
117
|
|
|
118
|
+
export interface GetTagsArgs {
|
|
119
|
+
path?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface TagInfo {
|
|
123
|
+
name: string;
|
|
124
|
+
count: number;
|
|
125
|
+
files: string[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface TagMetadata {
|
|
129
|
+
totalOccurrences: number;
|
|
130
|
+
uniqueTags: number;
|
|
131
|
+
scannedFiles: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface TagResponse {
|
|
135
|
+
tags: TagInfo[];
|
|
136
|
+
metadata: TagMetadata;
|
|
137
|
+
}
|
|
138
|
+
|
|
80
139
|
export interface RateLimitConfig {
|
|
81
140
|
windowMs: number;
|
|
82
141
|
maxRequests: number;
|
|
@@ -87,13 +146,38 @@ export const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig = {
|
|
|
87
146
|
maxRequests: 200
|
|
88
147
|
} as const;
|
|
89
148
|
|
|
90
|
-
export
|
|
149
|
+
export interface ApiError {
|
|
150
|
+
errorCode: number; // 5-digit error code
|
|
151
|
+
message: string; // Message describing the error
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class ObsidianError extends Error implements ApiError {
|
|
155
|
+
public readonly errorCode: number;
|
|
156
|
+
|
|
91
157
|
constructor(
|
|
92
158
|
message: string,
|
|
93
|
-
|
|
159
|
+
errorCode: number = 50000, // Default server error code
|
|
94
160
|
public readonly details?: unknown
|
|
95
161
|
) {
|
|
96
162
|
super(message);
|
|
97
163
|
this.name = "ObsidianError";
|
|
164
|
+
|
|
165
|
+
// Ensure 5-digit error code
|
|
166
|
+
if (errorCode < 10000 || errorCode > 99999) {
|
|
167
|
+
// Convert HTTP status codes to 5-digit codes
|
|
168
|
+
// 4xx -> 4xxxx
|
|
169
|
+
// 5xx -> 5xxxx
|
|
170
|
+
this.errorCode = errorCode < 1000 ? errorCode * 100 : 50000;
|
|
171
|
+
} else {
|
|
172
|
+
this.errorCode = errorCode;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Convert to API error format
|
|
177
|
+
toApiError(): ApiError {
|
|
178
|
+
return {
|
|
179
|
+
errorCode: this.errorCode,
|
|
180
|
+
message: this.message
|
|
181
|
+
};
|
|
98
182
|
}
|
|
99
183
|
}
|