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/build/server.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { config } from "dotenv";
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { ObsidianClient } from "./obsidian.js";
|
|
6
6
|
import { ObsidianError, DEFAULT_RATE_LIMIT_CONFIG } from "./types.js";
|
|
7
|
-
import {
|
|
7
|
+
import { TagResource } from "./resources.js";
|
|
8
|
+
import { ListFilesInVaultToolHandler, ListFilesInDirToolHandler, GetFileContentsToolHandler, FindInFileToolHandler, AppendContentToolHandler, PatchContentToolHandler, ComplexSearchToolHandler, GetTagsToolHandler } from "./tools.js";
|
|
8
9
|
import { GetPropertiesToolHandler, UpdatePropertiesToolHandler } from "./propertyTools.js";
|
|
9
10
|
// Load environment variables
|
|
10
11
|
config();
|
|
@@ -60,9 +61,12 @@ const handlers = [
|
|
|
60
61
|
new PatchContentToolHandler(client),
|
|
61
62
|
new ComplexSearchToolHandler(client),
|
|
62
63
|
new GetPropertiesToolHandler(client),
|
|
63
|
-
new UpdatePropertiesToolHandler(client)
|
|
64
|
+
new UpdatePropertiesToolHandler(client),
|
|
65
|
+
new GetTagsToolHandler(client)
|
|
64
66
|
];
|
|
65
67
|
handlers.forEach(handler => toolHandlers.set(handler.name, handler));
|
|
68
|
+
// Initialize resources
|
|
69
|
+
const tagResource = new TagResource(client);
|
|
66
70
|
// Create MCP server
|
|
67
71
|
const server = new Server({
|
|
68
72
|
name: "obsidian-mcp-server",
|
|
@@ -70,10 +74,26 @@ const server = new Server({
|
|
|
70
74
|
}, {
|
|
71
75
|
capabilities: {
|
|
72
76
|
tools: {},
|
|
73
|
-
resources: {
|
|
77
|
+
resources: {
|
|
78
|
+
[tagResource.getResourceDescription().uri]: tagResource
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// Set up resource handlers
|
|
83
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
84
|
+
return {
|
|
85
|
+
resources: [tagResource.getResourceDescription()]
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
89
|
+
if (request.params.uri === tagResource.getResourceDescription().uri) {
|
|
90
|
+
return {
|
|
91
|
+
contents: await tagResource.getContent()
|
|
92
|
+
};
|
|
74
93
|
}
|
|
94
|
+
throw new ObsidianError(`Resource not found: ${request.params.uri}`, 40400); // 40400 = Not found
|
|
75
95
|
});
|
|
76
|
-
// Set up
|
|
96
|
+
// Set up tool handlers
|
|
77
97
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
78
98
|
const tools = [];
|
|
79
99
|
for (const handler of toolHandlers.values()) {
|
|
@@ -141,17 +161,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
141
161
|
const { name, arguments: args } = request.params;
|
|
142
162
|
const handler = toolHandlers.get(name);
|
|
143
163
|
if (!handler) {
|
|
144
|
-
throw new ObsidianError(`Unknown tool: ${name}`,
|
|
164
|
+
throw new ObsidianError(`Unknown tool: ${name}`, 40400); // 40400 = Not found
|
|
145
165
|
}
|
|
146
166
|
// Check rate limit
|
|
147
167
|
if (!checkRateLimit(name)) {
|
|
148
|
-
throw new ObsidianError(`Rate limit exceeded for tool: ${name}. Please try again later.`,
|
|
168
|
+
throw new ObsidianError(`Rate limit exceeded for tool: ${name}. Please try again later.`, 42900 // 42900 = Rate limit exceeded
|
|
169
|
+
);
|
|
149
170
|
}
|
|
150
171
|
// Add timeout handling
|
|
151
172
|
const timeoutMs = parseInt(process.env.TOOL_TIMEOUT_MS ?? '60000'); // 60 second default timeout
|
|
152
173
|
const timeoutPromise = new Promise((_, reject) => {
|
|
153
174
|
setTimeout(() => {
|
|
154
|
-
reject(new ObsidianError(`Tool execution timed out after ${timeoutMs}ms`,
|
|
175
|
+
reject(new ObsidianError(`Tool execution timed out after ${timeoutMs}ms`, 40800)); // 40800 = Request timeout
|
|
155
176
|
}, timeoutMs);
|
|
156
177
|
});
|
|
157
178
|
try {
|
|
@@ -159,7 +180,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
159
180
|
const toolDescription = handler.getToolDescription();
|
|
160
181
|
const validationResult = validateToolArguments(args, toolDescription.inputSchema);
|
|
161
182
|
if (!validationResult.valid) {
|
|
162
|
-
throw new ObsidianError(`Invalid tool arguments: ${validationResult.errors.join(', ')}`,
|
|
183
|
+
throw new ObsidianError(`Invalid tool arguments: ${validationResult.errors.join(', ')}`, 40000 // 40000 = Bad request
|
|
184
|
+
);
|
|
163
185
|
}
|
|
164
186
|
// Race between tool execution and timeout
|
|
165
187
|
const content = await Promise.race([
|
|
@@ -171,7 +193,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
171
193
|
catch (error) {
|
|
172
194
|
if (error instanceof ObsidianError) {
|
|
173
195
|
// Check if the operation actually succeeded despite the error
|
|
174
|
-
if (error.
|
|
196
|
+
if (error.errorCode === 20400) { // 20400 = Success with no content
|
|
175
197
|
return {
|
|
176
198
|
content: [{
|
|
177
199
|
type: "text",
|
|
@@ -190,9 +212,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
190
212
|
args
|
|
191
213
|
});
|
|
192
214
|
if (error instanceof Error) {
|
|
193
|
-
throw new ObsidianError(`Tool '${name}' execution failed: ${error.message}`,
|
|
215
|
+
throw new ObsidianError(`Tool '${name}' execution failed: ${error.message}`, 50000, // 50000 = Internal server error
|
|
216
|
+
{ originalError: error.stack });
|
|
194
217
|
}
|
|
195
|
-
throw new ObsidianError("Tool execution failed with unknown error",
|
|
218
|
+
throw new ObsidianError("Tool execution failed with unknown error", 50000, // 50000 = Internal server error
|
|
219
|
+
{ error });
|
|
196
220
|
}
|
|
197
221
|
});
|
|
198
222
|
// Error handler
|
package/build/tools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { encoding_for_model } from "tiktoken";
|
|
2
2
|
import { ObsidianError } from "./types.js";
|
|
3
|
+
import { PropertyManager } from "./properties.js";
|
|
3
4
|
const TOOL_NAMES = {
|
|
4
5
|
LIST_FILES_IN_VAULT: "obsidian_list_files_in_vault",
|
|
5
6
|
LIST_FILES_IN_DIR: "obsidian_list_files_in_dir",
|
|
@@ -7,7 +8,8 @@ const TOOL_NAMES = {
|
|
|
7
8
|
FIND_IN_FILE: "obsidian_find_in_file",
|
|
8
9
|
APPEND_CONTENT: "obsidian_append_content",
|
|
9
10
|
PATCH_CONTENT: "obsidian_patch_content",
|
|
10
|
-
COMPLEX_SEARCH: "obsidian_complex_search"
|
|
11
|
+
COMPLEX_SEARCH: "obsidian_complex_search",
|
|
12
|
+
GET_TAGS: "obsidian_get_tags"
|
|
11
13
|
};
|
|
12
14
|
// Load token limits from environment or use defaults
|
|
13
15
|
const MAX_TOKENS = parseInt(process.env.MAX_TOKENS ?? '20000');
|
|
@@ -259,7 +261,7 @@ export class FindInFileToolHandler extends BaseToolHandler {
|
|
|
259
261
|
getToolDescription() {
|
|
260
262
|
return {
|
|
261
263
|
name: this.name,
|
|
262
|
-
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.",
|
|
264
|
+
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.",
|
|
263
265
|
examples: [
|
|
264
266
|
{
|
|
265
267
|
description: "Search for a specific term",
|
|
@@ -273,17 +275,39 @@ export class FindInFileToolHandler extends BaseToolHandler {
|
|
|
273
275
|
args: {
|
|
274
276
|
query: "#todo"
|
|
275
277
|
},
|
|
276
|
-
response:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
278
|
+
response: {
|
|
279
|
+
"message": "Found 1 file with matches:",
|
|
280
|
+
"results": [
|
|
281
|
+
{
|
|
282
|
+
"filename": "Projects/AI.md",
|
|
283
|
+
"matches": [
|
|
284
|
+
{
|
|
285
|
+
"context": "Research needed:\n#todo Implement transformer architecture\nDeadline: Next week",
|
|
286
|
+
"match": { "start": 15, "end": 45 }
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
description: "Example response with many matches (file-only format)",
|
|
295
|
+
args: {
|
|
296
|
+
query: "API"
|
|
297
|
+
},
|
|
298
|
+
response: {
|
|
299
|
+
"message": "Found 92 files with matches. Showing file names only:",
|
|
300
|
+
"results": [
|
|
301
|
+
{
|
|
302
|
+
"filename": "Developer/Documentation/API.md",
|
|
303
|
+
"matchCount": 43
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"filename": "Projects/API_Design.md",
|
|
307
|
+
"matchCount": 34
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}
|
|
287
311
|
}
|
|
288
312
|
],
|
|
289
313
|
inputSchema: {
|
|
@@ -305,10 +329,34 @@ export class FindInFileToolHandler extends BaseToolHandler {
|
|
|
305
329
|
}
|
|
306
330
|
async runTool(args) {
|
|
307
331
|
try {
|
|
308
|
-
const results = await this.client.search(args.query, args.contextLength);
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
332
|
+
const results = await this.client.search(args.query, args.contextLength ?? 100);
|
|
333
|
+
// If more than 5 results, only return filenames
|
|
334
|
+
if (results.length > 5) {
|
|
335
|
+
const fileOnlyResults = results.map(result => ({
|
|
336
|
+
filename: result.filename,
|
|
337
|
+
matchCount: result.matches.length
|
|
338
|
+
}));
|
|
339
|
+
return this.createResponse({
|
|
340
|
+
message: `Found ${results.length} files with matches. Showing file names only:`,
|
|
341
|
+
results: fileOnlyResults
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// Otherwise return full context as before
|
|
345
|
+
const formattedResults = results.map(result => ({
|
|
346
|
+
filename: result.filename,
|
|
347
|
+
matches: result.matches.map(match => ({
|
|
348
|
+
context: match.context,
|
|
349
|
+
match: {
|
|
350
|
+
text: match.context.substring(match.match.start, match.match.end),
|
|
351
|
+
position: {
|
|
352
|
+
start: match.match.start,
|
|
353
|
+
end: match.match.end
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
})),
|
|
357
|
+
score: result.score
|
|
358
|
+
}));
|
|
359
|
+
return this.createResponse(formattedResults);
|
|
312
360
|
}
|
|
313
361
|
catch (error) {
|
|
314
362
|
return this.handleError(error);
|
|
@@ -417,46 +465,21 @@ export class ComplexSearchToolHandler extends BaseToolHandler {
|
|
|
417
465
|
getToolDescription() {
|
|
418
466
|
return {
|
|
419
467
|
name: this.name,
|
|
420
|
-
description: "
|
|
468
|
+
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.",
|
|
421
469
|
examples: [
|
|
422
470
|
{
|
|
423
|
-
description: "Find markdown files in
|
|
471
|
+
description: "Find markdown files in Projects folder",
|
|
424
472
|
args: {
|
|
425
473
|
query: {
|
|
426
|
-
"
|
|
427
|
-
{ "glob": ["Projects/*.md", { "var": "path" }] },
|
|
428
|
-
{ "contains": [{ "var": "content" }, "#active"] }
|
|
429
|
-
]
|
|
474
|
+
"glob": ["Projects/*.md", { "var": "path" }]
|
|
430
475
|
}
|
|
431
476
|
}
|
|
432
477
|
},
|
|
433
478
|
{
|
|
434
|
-
description: "Find
|
|
479
|
+
description: "Find files in a specific subfolder",
|
|
435
480
|
args: {
|
|
436
481
|
query: {
|
|
437
|
-
"
|
|
438
|
-
{ "glob": ["docs/*.md", { "var": "path" }] },
|
|
439
|
-
{ ">=": [
|
|
440
|
-
{ "var": "mtime" },
|
|
441
|
-
{ "date": "-7 days" }
|
|
442
|
-
] },
|
|
443
|
-
{ "!=": [{ "var": "size" }, 0] }
|
|
444
|
-
]
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
description: "Find files by multiple criteria",
|
|
450
|
-
args: {
|
|
451
|
-
query: {
|
|
452
|
-
"and": [
|
|
453
|
-
{ "or": [
|
|
454
|
-
{ "glob": ["*.md", { "var": "path" }] },
|
|
455
|
-
{ "glob": ["*.txt", { "var": "path" }] }
|
|
456
|
-
] },
|
|
457
|
-
{ "contains": [{ "var": "content" }, "TODO"] },
|
|
458
|
-
{ "<": [{ "var": "size" }, 10000] }
|
|
459
|
-
]
|
|
482
|
+
"glob": ["**/Test/*.md", { "var": "path" }]
|
|
460
483
|
}
|
|
461
484
|
}
|
|
462
485
|
}
|
|
@@ -475,12 +498,365 @@ export class ComplexSearchToolHandler extends BaseToolHandler {
|
|
|
475
498
|
}
|
|
476
499
|
async runTool(args) {
|
|
477
500
|
try {
|
|
501
|
+
// Perform search
|
|
478
502
|
const results = await this.client.searchJson(args.query);
|
|
479
|
-
|
|
503
|
+
console.debug('Search results:', results);
|
|
504
|
+
// Format response based on result type
|
|
505
|
+
const formattedResults = results.map(result => {
|
|
506
|
+
if ('matches' in result) {
|
|
507
|
+
// SimpleSearchResult
|
|
508
|
+
return {
|
|
509
|
+
filename: result.filename,
|
|
510
|
+
matches: result.matches,
|
|
511
|
+
score: result.score
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
// SearchResult
|
|
516
|
+
return {
|
|
517
|
+
filename: result.filename,
|
|
518
|
+
result: result.result
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
return this.createResponse(formattedResults);
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
console.error('Complex search error:', error);
|
|
526
|
+
return this.handleError(error);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
export class GetTagsToolHandler extends BaseToolHandler {
|
|
531
|
+
static TAG_PATTERN = /#[a-zA-Z0-9_-]+/g;
|
|
532
|
+
propertyManager;
|
|
533
|
+
constructor(client) {
|
|
534
|
+
super(TOOL_NAMES.GET_TAGS, client);
|
|
535
|
+
this.propertyManager = new PropertyManager(client);
|
|
536
|
+
}
|
|
537
|
+
getToolDescription() {
|
|
538
|
+
return {
|
|
539
|
+
name: this.name,
|
|
540
|
+
description: "Get all tags used across the Obsidian vault with their usage counts. Optionally filter tags within a specific folder.",
|
|
541
|
+
examples: [
|
|
542
|
+
{
|
|
543
|
+
description: "Get all tags in vault",
|
|
544
|
+
args: {}
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
description: "Get tags in Projects folder",
|
|
548
|
+
args: {
|
|
549
|
+
path: "Projects"
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
description: "Example response",
|
|
554
|
+
args: {},
|
|
555
|
+
response: {
|
|
556
|
+
"tags": [
|
|
557
|
+
{
|
|
558
|
+
"name": "#project",
|
|
559
|
+
"count": 15,
|
|
560
|
+
"files": [
|
|
561
|
+
"Projects/ProjectA.md",
|
|
562
|
+
"Projects/ProjectB.md"
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
],
|
|
566
|
+
"metadata": {
|
|
567
|
+
"totalOccurrences": 45,
|
|
568
|
+
"uniqueTags": 12,
|
|
569
|
+
"scannedFiles": 30
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
inputSchema: {
|
|
575
|
+
type: "object",
|
|
576
|
+
properties: {
|
|
577
|
+
path: {
|
|
578
|
+
type: "string",
|
|
579
|
+
description: "Optional path to limit tag search to specific folder",
|
|
580
|
+
format: "path"
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
async processFiles(files, basePath, tagMap) {
|
|
587
|
+
let scannedFiles = 0;
|
|
588
|
+
for (const file of files) {
|
|
589
|
+
const fullPath = basePath ? `${basePath}/${file.path}` : file.path;
|
|
590
|
+
if (file.type === "folder" && file.children) {
|
|
591
|
+
// Recursively process subdirectories
|
|
592
|
+
scannedFiles += await this.processFiles(file.children, fullPath, tagMap);
|
|
593
|
+
}
|
|
594
|
+
else if (file.type === "file" && file.path.endsWith('.md')) {
|
|
595
|
+
// Process markdown files
|
|
596
|
+
scannedFiles++;
|
|
597
|
+
const content = await this.client.getFileContents(fullPath);
|
|
598
|
+
// Extract tags from frontmatter
|
|
599
|
+
const properties = this.propertyManager.parseProperties(content);
|
|
600
|
+
if (properties.tags) {
|
|
601
|
+
properties.tags.forEach((tag) => {
|
|
602
|
+
if (!tagMap.has(tag)) {
|
|
603
|
+
tagMap.set(tag, new Set());
|
|
604
|
+
}
|
|
605
|
+
tagMap.get(tag).add(fullPath);
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
// Extract inline tags using regex
|
|
609
|
+
const inlineTags = content.match(GetTagsToolHandler.TAG_PATTERN) || [];
|
|
610
|
+
inlineTags.forEach(tag => {
|
|
611
|
+
if (!tagMap.has(tag)) {
|
|
612
|
+
tagMap.set(tag, new Set());
|
|
613
|
+
}
|
|
614
|
+
tagMap.get(tag).add(fullPath);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return scannedFiles;
|
|
619
|
+
}
|
|
620
|
+
async runTool(args) {
|
|
621
|
+
try {
|
|
622
|
+
const tagMap = new Map();
|
|
623
|
+
const basePath = args.path || '';
|
|
624
|
+
// Get files from vault or specific directory
|
|
625
|
+
const files = args.path
|
|
626
|
+
? await this.client.listFilesInDir(args.path)
|
|
627
|
+
: await this.client.listFilesInVault();
|
|
628
|
+
// Process files recursively
|
|
629
|
+
const scannedFiles = await this.processFiles(files, basePath, tagMap);
|
|
630
|
+
// Calculate total occurrences
|
|
631
|
+
const totalOccurrences = Array.from(tagMap.values())
|
|
632
|
+
.reduce((sum, files) => sum + files.size, 0);
|
|
633
|
+
const response = {
|
|
634
|
+
tags: Array.from(tagMap.entries())
|
|
635
|
+
.map(([name, files]) => ({
|
|
636
|
+
name,
|
|
637
|
+
count: files.size,
|
|
638
|
+
files: Array.from(files).sort()
|
|
639
|
+
}))
|
|
640
|
+
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)),
|
|
641
|
+
metadata: {
|
|
642
|
+
totalOccurrences,
|
|
643
|
+
uniqueTags: tagMap.size,
|
|
644
|
+
scannedFiles
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
return this.createResponse(response);
|
|
648
|
+
}
|
|
649
|
+
catch (error) {
|
|
650
|
+
return this.handleError(error);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Export all handlers
|
|
655
|
+
export class ListCommandsToolHandler extends BaseToolHandler {
|
|
656
|
+
constructor(client) {
|
|
657
|
+
super("obsidian_list_commands", client);
|
|
658
|
+
}
|
|
659
|
+
getToolDescription() {
|
|
660
|
+
return {
|
|
661
|
+
name: this.name,
|
|
662
|
+
description: "Get a list of available commands that can be executed in Obsidian.",
|
|
663
|
+
examples: [
|
|
664
|
+
{
|
|
665
|
+
description: "List all available commands",
|
|
666
|
+
args: {}
|
|
667
|
+
}
|
|
668
|
+
],
|
|
669
|
+
inputSchema: {
|
|
670
|
+
type: "object",
|
|
671
|
+
properties: {},
|
|
672
|
+
required: []
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
async runTool() {
|
|
677
|
+
try {
|
|
678
|
+
const commands = await this.client.listCommands();
|
|
679
|
+
return this.createResponse(commands);
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
return this.handleError(error);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
export class ExecuteCommandToolHandler extends BaseToolHandler {
|
|
687
|
+
constructor(client) {
|
|
688
|
+
super("obsidian_execute_command", client);
|
|
689
|
+
}
|
|
690
|
+
getToolDescription() {
|
|
691
|
+
return {
|
|
692
|
+
name: this.name,
|
|
693
|
+
description: "Execute a specific command in Obsidian by its ID.",
|
|
694
|
+
examples: [
|
|
695
|
+
{
|
|
696
|
+
description: "Execute the graph view command",
|
|
697
|
+
args: {
|
|
698
|
+
commandId: "graph:open"
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
],
|
|
702
|
+
inputSchema: {
|
|
703
|
+
type: "object",
|
|
704
|
+
properties: {
|
|
705
|
+
commandId: {
|
|
706
|
+
type: "string",
|
|
707
|
+
description: "The ID of the command to execute"
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
required: ["commandId"]
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
async runTool(args) {
|
|
715
|
+
try {
|
|
716
|
+
await this.client.executeCommand(args.commandId);
|
|
717
|
+
return this.createResponse({ message: `Successfully executed command: ${args.commandId}` });
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
return this.handleError(error);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
export class OpenFileToolHandler extends BaseToolHandler {
|
|
725
|
+
constructor(client) {
|
|
726
|
+
super("obsidian_open_file", client);
|
|
727
|
+
}
|
|
728
|
+
getToolDescription() {
|
|
729
|
+
return {
|
|
730
|
+
name: this.name,
|
|
731
|
+
description: "Open a specific file in Obsidian, optionally in a new leaf.",
|
|
732
|
+
examples: [
|
|
733
|
+
{
|
|
734
|
+
description: "Open a file in the current leaf",
|
|
735
|
+
args: {
|
|
736
|
+
filepath: "Projects/research.md"
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
description: "Open a file in a new leaf",
|
|
741
|
+
args: {
|
|
742
|
+
filepath: "Projects/research.md",
|
|
743
|
+
newLeaf: true
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
],
|
|
747
|
+
inputSchema: {
|
|
748
|
+
type: "object",
|
|
749
|
+
properties: {
|
|
750
|
+
filepath: {
|
|
751
|
+
type: "string",
|
|
752
|
+
description: "Path to the file to open (relative to vault root)",
|
|
753
|
+
format: "path"
|
|
754
|
+
},
|
|
755
|
+
newLeaf: {
|
|
756
|
+
type: "boolean",
|
|
757
|
+
description: "Whether to open the file in a new leaf",
|
|
758
|
+
default: false
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
required: ["filepath"]
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
async runTool(args) {
|
|
766
|
+
try {
|
|
767
|
+
await this.client.openFile(args.filepath, args.newLeaf);
|
|
768
|
+
return this.createResponse({
|
|
769
|
+
message: `Successfully opened ${args.filepath}${args.newLeaf ? ' in new leaf' : ''}`
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
return this.handleError(error);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
export class GetActiveFileToolHandler extends BaseToolHandler {
|
|
778
|
+
constructor(client) {
|
|
779
|
+
super("obsidian_get_active_file", client);
|
|
780
|
+
}
|
|
781
|
+
getToolDescription() {
|
|
782
|
+
return {
|
|
783
|
+
name: this.name,
|
|
784
|
+
description: "Get the content and metadata of the currently active file in Obsidian.",
|
|
785
|
+
examples: [
|
|
786
|
+
{
|
|
787
|
+
description: "Get active file content",
|
|
788
|
+
args: {}
|
|
789
|
+
}
|
|
790
|
+
],
|
|
791
|
+
inputSchema: {
|
|
792
|
+
type: "object",
|
|
793
|
+
properties: {},
|
|
794
|
+
required: []
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
async runTool() {
|
|
799
|
+
try {
|
|
800
|
+
const activeFile = await this.client.getActiveFile();
|
|
801
|
+
return this.createResponse(activeFile);
|
|
802
|
+
}
|
|
803
|
+
catch (error) {
|
|
804
|
+
return this.handleError(error);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
export class GetPeriodicNoteToolHandler extends BaseToolHandler {
|
|
809
|
+
constructor(client) {
|
|
810
|
+
super("obsidian_get_periodic_note", client);
|
|
811
|
+
}
|
|
812
|
+
getToolDescription() {
|
|
813
|
+
return {
|
|
814
|
+
name: this.name,
|
|
815
|
+
description: "Get the content and metadata of a periodic note (daily, weekly, monthly, quarterly, or yearly).",
|
|
816
|
+
examples: [
|
|
817
|
+
{
|
|
818
|
+
description: "Get today's daily note",
|
|
819
|
+
args: {
|
|
820
|
+
period: "daily"
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
],
|
|
824
|
+
inputSchema: {
|
|
825
|
+
type: "object",
|
|
826
|
+
properties: {
|
|
827
|
+
period: {
|
|
828
|
+
type: "string",
|
|
829
|
+
enum: ["daily", "weekly", "monthly", "quarterly", "yearly"],
|
|
830
|
+
description: "The type of periodic note to retrieve"
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
required: ["period"]
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
async runTool(args) {
|
|
838
|
+
try {
|
|
839
|
+
const note = await this.client.getPeriodicNote(args.period);
|
|
840
|
+
return this.createResponse(note);
|
|
480
841
|
}
|
|
481
842
|
catch (error) {
|
|
482
843
|
return this.handleError(error);
|
|
483
844
|
}
|
|
484
845
|
}
|
|
485
846
|
}
|
|
847
|
+
export const handlers = [
|
|
848
|
+
ListFilesInVaultToolHandler,
|
|
849
|
+
ListFilesInDirToolHandler,
|
|
850
|
+
GetFileContentsToolHandler,
|
|
851
|
+
FindInFileToolHandler,
|
|
852
|
+
AppendContentToolHandler,
|
|
853
|
+
PatchContentToolHandler,
|
|
854
|
+
ComplexSearchToolHandler,
|
|
855
|
+
GetTagsToolHandler,
|
|
856
|
+
ListCommandsToolHandler,
|
|
857
|
+
ExecuteCommandToolHandler,
|
|
858
|
+
OpenFileToolHandler,
|
|
859
|
+
GetActiveFileToolHandler,
|
|
860
|
+
GetPeriodicNoteToolHandler
|
|
861
|
+
];
|
|
486
862
|
//# sourceMappingURL=tools.js.map
|
package/build/types.js
CHANGED
|
@@ -8,13 +8,30 @@ export const DEFAULT_RATE_LIMIT_CONFIG = {
|
|
|
8
8
|
maxRequests: 200
|
|
9
9
|
};
|
|
10
10
|
export class ObsidianError extends Error {
|
|
11
|
-
code;
|
|
12
11
|
details;
|
|
13
|
-
|
|
12
|
+
errorCode;
|
|
13
|
+
constructor(message, errorCode = 50000, // Default server error code
|
|
14
|
+
details) {
|
|
14
15
|
super(message);
|
|
15
|
-
this.code = code;
|
|
16
16
|
this.details = details;
|
|
17
17
|
this.name = "ObsidianError";
|
|
18
|
+
// Ensure 5-digit error code
|
|
19
|
+
if (errorCode < 10000 || errorCode > 99999) {
|
|
20
|
+
// Convert HTTP status codes to 5-digit codes
|
|
21
|
+
// 4xx -> 4xxxx
|
|
22
|
+
// 5xx -> 5xxxx
|
|
23
|
+
this.errorCode = errorCode < 1000 ? errorCode * 100 : 50000;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
this.errorCode = errorCode;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Convert to API error format
|
|
30
|
+
toApiError() {
|
|
31
|
+
return {
|
|
32
|
+
errorCode: this.errorCode,
|
|
33
|
+
message: this.message
|
|
34
|
+
};
|
|
18
35
|
}
|
|
19
36
|
}
|
|
20
37
|
//# sourceMappingURL=types.js.map
|