obsidian-mcp-server 1.5.0 → 1.5.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 +3 -3
- package/build/mcp/handlers.js +26 -5
- package/build/mcp/server.js +6 -13
- package/build/resources/tags.js +72 -15
- package/build/tools/properties/manager.js +18 -11
- package/build/utils/idGenerator.js +21 -0
- package/build/utils/tokenization.js +24 -11
- package/build/utils/validation.js +7 -3
- package/package.json +15 -13
- package/repomix.config.json +20 -0
- package/src/mcp/handlers.ts +34 -6
- package/src/mcp/server.ts +14 -17
- package/src/resources/tags.ts +79 -21
- package/src/tools/properties/manager.ts +24 -15
- package/src/utils/idGenerator.ts +22 -0
- package/src/utils/tokenization.ts +26 -12
- package/src/utils/validation.ts +14 -4
- package/examples/README.md +0 -48
- package/examples/append-content.md +0 -63
- package/examples/complex-search.md +0 -117
- package/examples/find-in-file.md +0 -94
- package/examples/get-file-contents.md +0 -72
- package/examples/get-properties.md +0 -89
- package/examples/list-files-in-dir.md +0 -55
- package/examples/list-files-in-vault.md +0 -53
- package/examples/patch-content.md +0 -60
- package/examples/update-properties.md +0 -126
package/src/resources/tags.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Resource, TextContent } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { ObsidianClient } from "../obsidian/client.js";
|
|
6
6
|
import { JsonLogicQuery } from "../obsidian/types.js";
|
|
7
|
+
import pLimit from 'p-limit'; // Import p-limit
|
|
7
8
|
import { PropertyManager } from "../tools/properties/manager.js";
|
|
8
9
|
import { join, sep } from "path";
|
|
9
10
|
import { createLogger, ErrorCategoryType } from "../utils/logging.js";
|
|
@@ -40,6 +41,7 @@ export class TagResource {
|
|
|
40
41
|
private tagCache: Map<string, Set<string>> = new Map();
|
|
41
42
|
private propertyManager: PropertyManager;
|
|
42
43
|
private isInitialized = false;
|
|
44
|
+
private isUpdating = false; // Flag to prevent concurrent updates
|
|
43
45
|
private lastUpdate = 0;
|
|
44
46
|
private updateInterval = 5000; // 5 seconds
|
|
45
47
|
|
|
@@ -77,24 +79,46 @@ export class TagResource {
|
|
|
77
79
|
const results = await this.client.searchJson(query);
|
|
78
80
|
this.tagCache.clear();
|
|
79
81
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
// Create a limiter with a concurrency of 3 (reduced from 10)
|
|
83
|
+
const limit = pLimit(3);
|
|
84
|
+
|
|
85
|
+
// Create promises for processing each file with concurrency limiting
|
|
86
|
+
const processingPromises = results
|
|
87
|
+
.filter(result => 'filename' in result) // Ensure filename exists
|
|
88
|
+
.map((result) => limit(async () => { // Wrap the async function with the limiter
|
|
89
|
+
const filename = result.filename;
|
|
90
|
+
try {
|
|
91
|
+
// This call is now rate-limited
|
|
92
|
+
const content = await this.client.getFileContents(filename);
|
|
93
|
+
// Only extract tags from frontmatter YAML
|
|
94
|
+
const properties = this.propertyManager.parseProperties(content);
|
|
95
|
+
return { filename, tags: properties.tags || [] };
|
|
96
|
+
} catch (error) {
|
|
97
|
+
logger.error(`Failed to process file ${filename}:`, errorToObject(error));
|
|
98
|
+
return { filename, error: true }; // Mark as failed
|
|
99
|
+
}
|
|
100
|
+
})); // Close the limiter wrapper
|
|
101
|
+
|
|
102
|
+
// Execute promises in parallel and wait for all to settle
|
|
103
|
+
const processedResults = await Promise.allSettled(processingPromises);
|
|
104
|
+
|
|
105
|
+
// Populate the cache from settled results
|
|
106
|
+
processedResults.forEach(settledResult => {
|
|
107
|
+
// Check if the promise was fulfilled and didn't encounter a processing error
|
|
108
|
+
if (settledResult.status === 'fulfilled' && !settledResult.value.error) {
|
|
109
|
+
const { filename, tags } = settledResult.value;
|
|
110
|
+
// Ensure tags is an array before iterating
|
|
111
|
+
if (tags && Array.isArray(tags)) {
|
|
112
|
+
tags.forEach((tag: string) => {
|
|
113
|
+
this.addTag(tag, filename);
|
|
92
114
|
});
|
|
93
115
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
} else if (settledResult.status === 'rejected') {
|
|
117
|
+
// Log unexpected rejections from the async map function itself
|
|
118
|
+
logger.error(`Unexpected error during file processing setup:`, errorToObject(settledResult.reason));
|
|
119
|
+
}
|
|
120
|
+
// Errors during getFileContents/parseProperties are already logged within the map function
|
|
121
|
+
});
|
|
98
122
|
|
|
99
123
|
this.isInitialized = true;
|
|
100
124
|
this.lastUpdate = Date.now();
|
|
@@ -125,13 +149,47 @@ export class TagResource {
|
|
|
125
149
|
}
|
|
126
150
|
|
|
127
151
|
/**
|
|
128
|
-
* Update the cache if needed
|
|
152
|
+
* Update the cache if needed, preventing race conditions.
|
|
129
153
|
*/
|
|
130
154
|
private async updateCacheIfNeeded() {
|
|
131
155
|
const now = Date.now();
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
156
|
+
// Check if cache is fresh enough
|
|
157
|
+
if (now - this.lastUpdate <= this.updateInterval) {
|
|
158
|
+
return; // Cache is up-to-date
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check if an update is already in progress
|
|
162
|
+
if (this.isUpdating) {
|
|
163
|
+
logger.debug('Cache update already in progress, skipping redundant update.');
|
|
164
|
+
// Optionally, wait for the ongoing update instead of returning immediately
|
|
165
|
+
// For now, we return to avoid complexity, potentially serving slightly stale data.
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Acquire the update lock
|
|
170
|
+
this.isUpdating = true;
|
|
171
|
+
logger.debug('Acquired cache update lock.');
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
// Double-check the update condition after acquiring the lock
|
|
175
|
+
// to handle cases where another process finished updating
|
|
176
|
+
// while this one was waiting for the lock (though less likely with a simple flag).
|
|
177
|
+
const nowAfterLock = Date.now();
|
|
178
|
+
if (nowAfterLock - this.lastUpdate > this.updateInterval) {
|
|
179
|
+
logger.info('Tag cache needs update, refreshing...');
|
|
180
|
+
await this.initializeCache(); // This method updates this.lastUpdate internally
|
|
181
|
+
} else {
|
|
182
|
+
logger.debug('Cache was updated by another process while waiting for lock.');
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// Log error during update, but don't necessarily block other operations
|
|
186
|
+
logger.error('Error during cache update:', errorToObject(error));
|
|
187
|
+
// Decide if the error should be re-thrown or handled gracefully
|
|
188
|
+
// For now, we log and continue, allowing the lock to be released.
|
|
189
|
+
} finally {
|
|
190
|
+
// Release the update lock
|
|
191
|
+
this.isUpdating = false;
|
|
192
|
+
logger.debug('Released cache update lock.');
|
|
135
193
|
}
|
|
136
194
|
}
|
|
137
195
|
|
|
@@ -191,4 +249,4 @@ export class TagResource {
|
|
|
191
249
|
throw error;
|
|
192
250
|
}
|
|
193
251
|
}
|
|
194
|
-
}
|
|
252
|
+
}
|
|
@@ -37,24 +37,33 @@ export class PropertyManager {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const frontmatter = match[1];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
// Parse YAML first
|
|
41
|
+
const rawProperties = parse(frontmatter);
|
|
42
|
+
|
|
43
|
+
// Validate the raw parsed object against the schema
|
|
44
|
+
const validationResult = ObsidianPropertiesSchema.safeParse(rawProperties);
|
|
45
|
+
|
|
46
|
+
if (!validationResult.success) {
|
|
47
|
+
// Log validation errors and return empty if invalid
|
|
48
|
+
logger.warn('Frontmatter validation failed:', {
|
|
49
|
+
validationError: validationResult.error.flatten()
|
|
50
|
+
});
|
|
51
|
+
return {}; // Return empty object for invalid frontmatter
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
// Use the validated data from now on
|
|
55
|
+
const validatedProperties = validationResult.data;
|
|
56
|
+
|
|
57
|
+
// Handle tags transformation on validated data
|
|
58
|
+
if (validatedProperties.tags && Array.isArray(validatedProperties.tags)) {
|
|
59
|
+
// Create a new array to avoid modifying the validated data directly if needed elsewhere
|
|
60
|
+
validatedProperties.tags = validatedProperties.tags.map((tag: string) =>
|
|
61
|
+
tag.startsWith('#') ? tag.substring(1) : tag
|
|
62
|
+
);
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
// Return the validated (and potentially transformed) properties
|
|
66
|
+
return validatedProperties;
|
|
58
67
|
} catch (error) {
|
|
59
68
|
logger.error('Error parsing properties:', error instanceof Error ? error : { error: String(error) });
|
|
60
69
|
return {};
|
|
@@ -230,4 +239,4 @@ export class PropertyManager {
|
|
|
230
239
|
};
|
|
231
240
|
}
|
|
232
241
|
}
|
|
233
|
-
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a unique, URL-friendly, 6-character alphanumeric ID.
|
|
5
|
+
* Uses nanoid's default alphabet (A-Za-z0-9_-).
|
|
6
|
+
*
|
|
7
|
+
* @returns A 6-character string ID.
|
|
8
|
+
*/
|
|
9
|
+
export function generateShortId(): string {
|
|
10
|
+
return nanoid(6);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generates a unique ID with a specified prefix and length.
|
|
15
|
+
*
|
|
16
|
+
* @param prefix - The prefix for the ID (e.g., 'prj', 'tsk', 'knw').
|
|
17
|
+
* @param length - The desired length of the random part of the ID (default: 10).
|
|
18
|
+
* @returns A prefixed ID string (e.g., 'prj_aBcDeFgHiJ').
|
|
19
|
+
*/
|
|
20
|
+
export function generatePrefixedId(prefix: string, length: number = 10): string {
|
|
21
|
+
return `${prefix}_${nanoid(length)}`;
|
|
22
|
+
}
|
|
@@ -13,22 +13,29 @@ export const TRUNCATION_MESSAGE = "\n\n[Response truncated due to length]";
|
|
|
13
13
|
export class TokenCounter {
|
|
14
14
|
private tokenizer = encoding_for_model("gpt-4"); // This is strictly for token counting, not for LLM inference
|
|
15
15
|
private isShuttingDown = false;
|
|
16
|
+
private cleanupListener: () => void;
|
|
16
17
|
|
|
17
18
|
constructor() {
|
|
18
|
-
//
|
|
19
|
-
|
|
19
|
+
// Define the cleanup logic
|
|
20
|
+
this.cleanupListener = () => {
|
|
20
21
|
if (!this.isShuttingDown) {
|
|
21
22
|
this.isShuttingDown = true;
|
|
22
23
|
if (this.tokenizer) {
|
|
23
24
|
this.tokenizer.free();
|
|
25
|
+
// No need to explicitly set this.tokenizer to null,
|
|
26
|
+
// but ensure it's not used after free()
|
|
24
27
|
}
|
|
28
|
+
// Remove listeners after execution to prevent multiple calls
|
|
29
|
+
// and potential leaks if cleanup is called manually before exit
|
|
30
|
+
this.removeListeners();
|
|
25
31
|
}
|
|
26
32
|
};
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
process.on('
|
|
30
|
-
process.on('
|
|
31
|
-
process.on('
|
|
34
|
+
// Attach listeners
|
|
35
|
+
process.on('exit', this.cleanupListener);
|
|
36
|
+
process.on('SIGINT', this.cleanupListener);
|
|
37
|
+
process.on('SIGTERM', this.cleanupListener);
|
|
38
|
+
process.on('uncaughtException', this.cleanupListener);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
/**
|
|
@@ -57,15 +64,22 @@ export class TokenCounter {
|
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
/**
|
|
60
|
-
* Clean up resources
|
|
67
|
+
* Clean up resources and remove listeners
|
|
61
68
|
*/
|
|
62
69
|
cleanup(): void {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
this.cleanupListener(); // Call the main cleanup logic
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Remove process event listeners
|
|
75
|
+
*/
|
|
76
|
+
private removeListeners(): void {
|
|
77
|
+
process.off('exit', this.cleanupListener);
|
|
78
|
+
process.off('SIGINT', this.cleanupListener);
|
|
79
|
+
process.off('SIGTERM', this.cleanupListener);
|
|
80
|
+
process.off('uncaughtException', this.cleanupListener);
|
|
67
81
|
}
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
// Export a singleton instance
|
|
71
|
-
export const tokenCounter = new TokenCounter();
|
|
85
|
+
export const tokenCounter = new TokenCounter();
|
package/src/utils/validation.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validation utilities for the Obsidian MCP Server
|
|
3
3
|
*/
|
|
4
|
+
import { McpErrorCode } from "../mcp/types.js";
|
|
5
|
+
import { ObsidianError } from "./errors.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Validates a file path to prevent path traversal attacks and other security issues
|
|
@@ -11,12 +13,20 @@ export function validateFilePath(filepath: string): void {
|
|
|
11
13
|
// Prevent path traversal attacks
|
|
12
14
|
const normalizedPath = filepath.replace(/\\/g, '/');
|
|
13
15
|
if (normalizedPath.includes('../') || normalizedPath.includes('..\\')) {
|
|
14
|
-
|
|
16
|
+
// Use ObsidianError for consistency
|
|
17
|
+
throw new ObsidianError(
|
|
18
|
+
'Invalid file path: Path traversal not allowed',
|
|
19
|
+
McpErrorCode.BAD_REQUEST
|
|
20
|
+
);
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
// Additional path validations
|
|
23
|
+
// Additional path validations (check for absolute paths Unix or Windows style)
|
|
18
24
|
if (normalizedPath.startsWith('/') || /^[a-zA-Z]:/.test(normalizedPath)) {
|
|
19
|
-
|
|
25
|
+
// Use ObsidianError for consistency
|
|
26
|
+
throw new ObsidianError(
|
|
27
|
+
'Invalid file path: Absolute paths not allowed',
|
|
28
|
+
McpErrorCode.BAD_REQUEST
|
|
29
|
+
);
|
|
20
30
|
}
|
|
21
31
|
}
|
|
22
32
|
|
|
@@ -92,4 +102,4 @@ export function validateToolArguments(args: unknown, schema: any): { valid: bool
|
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
return { valid: errors.length === 0, errors };
|
|
95
|
-
}
|
|
105
|
+
}
|
package/examples/README.md
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Obsidian MCP Server Tool Examples
|
|
2
|
-
|
|
3
|
-
This directory contains example requests and responses for each tool provided by the obsidian-mcp-server. These examples demonstrate the capabilities and expected output format of each tool.
|
|
4
|
-
|
|
5
|
-
## Tools
|
|
6
|
-
|
|
7
|
-
### 1. [List Files in Vault](list-files-in-vault.md)
|
|
8
|
-
Lists all files and directories in the root directory of your Obsidian vault. Returns a hierarchical structure of files and folders, including metadata like file type.
|
|
9
|
-
|
|
10
|
-
### 2. [List Files in Directory](list-files-in-dir.md)
|
|
11
|
-
Lists all files and directories that exist in a specific Obsidian directory. Returns a hierarchical structure showing files, folders, and their relationships.
|
|
12
|
-
|
|
13
|
-
### 3. [Get File Contents](get-file-contents.md)
|
|
14
|
-
Return the content of a single file in your vault. Supports markdown files, text files, and other readable formats.
|
|
15
|
-
|
|
16
|
-
### 4. [Find in File](find-in-file.md)
|
|
17
|
-
Full-text search across all files in the vault. Returns matching files with surrounding context for each match.
|
|
18
|
-
|
|
19
|
-
### 5. [Append Content](append-content.md)
|
|
20
|
-
Append content to a new or existing file in the vault. Useful for adding new sections, notes, or updates to existing documents.
|
|
21
|
-
|
|
22
|
-
### 6. [Patch Content](patch-content.md)
|
|
23
|
-
Update the entire content of an existing note or create a new one. Provides complete control over file contents.
|
|
24
|
-
|
|
25
|
-
### 7. [Complex Search](complex-search.md)
|
|
26
|
-
File path pattern matching using JsonLogic queries. Supports operations like glob pattern matching and variable access.
|
|
27
|
-
|
|
28
|
-
### 8. [Get Properties](get-properties.md)
|
|
29
|
-
Get properties (title, tags, status, etc.) from an Obsidian note's YAML frontmatter. Returns all available properties including custom fields.
|
|
30
|
-
|
|
31
|
-
### 9. [Update Properties](update-properties.md)
|
|
32
|
-
Update properties in an Obsidian note's YAML frontmatter. Intelligently merges arrays and handles custom fields.
|
|
33
|
-
|
|
34
|
-
## Resources
|
|
35
|
-
|
|
36
|
-
### Direct Resources
|
|
37
|
-
- obsidian://tags - List of all tags used across the Obsidian vault with their usage counts
|
|
38
|
-
|
|
39
|
-
## Production Readiness
|
|
40
|
-
|
|
41
|
-
All tools have been tested and demonstrate:
|
|
42
|
-
- Proper input validation and error handling
|
|
43
|
-
- Comprehensive and well-structured responses
|
|
44
|
-
- Consistent output formatting
|
|
45
|
-
- Practical applicability to real-world Obsidian vault management
|
|
46
|
-
- Deep integration with Obsidian's features and capabilities
|
|
47
|
-
|
|
48
|
-
The examples serve as both documentation and test cases, showing the expected behavior and quality of responses for each tool.
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Append Content Tool Example
|
|
2
|
-
|
|
3
|
-
## Request 1 (Append to Existing Note)
|
|
4
|
-
```json
|
|
5
|
-
{
|
|
6
|
-
"filepath": "Projects/Project Alpha/Notes/Meeting Notes.md",
|
|
7
|
-
"content": "\n\n## Team Meeting - 2024-01-25\n\n### Discussion Points\n- Reviewed project timeline\n- Discussed technical challenges\n- Assigned new tasks\n\n### Action Items\n- [ ] Update documentation\n- [ ] Schedule follow-up meeting\n- [ ] Share progress report"
|
|
8
|
-
}
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Response 1
|
|
12
|
-
```json
|
|
13
|
-
{
|
|
14
|
-
"success": true,
|
|
15
|
-
"message": "Content appended successfully"
|
|
16
|
-
}
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Request 2 (Create New Note)
|
|
20
|
-
```json
|
|
21
|
-
{
|
|
22
|
-
"filepath": "Daily Notes/2024-01-25.md",
|
|
23
|
-
"content": "---\ntitle: Daily Note - January 25, 2024\ntags: [daily-notes]\ndate: 2024-01-25\n---\n\n# Daily Notes\n\n## Tasks\n- [ ] Review project updates\n- [ ] Team meeting at 2 PM\n- [ ] Update documentation\n\n## Notes\n- Started work on new feature\n- Discussed timeline with team\n- Reviewed technical specifications"
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Response 2
|
|
28
|
-
```json
|
|
29
|
-
{
|
|
30
|
-
"success": true,
|
|
31
|
-
"message": "File created and content appended successfully"
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Example Use Cases
|
|
36
|
-
|
|
37
|
-
1. **Meeting Notes**
|
|
38
|
-
- Add new meeting minutes to existing notes
|
|
39
|
-
- Create structured meeting summaries
|
|
40
|
-
- Track action items and decisions
|
|
41
|
-
|
|
42
|
-
2. **Daily Notes**
|
|
43
|
-
- Create daily journal entries
|
|
44
|
-
- Add to existing daily logs
|
|
45
|
-
- Maintain consistent note structure
|
|
46
|
-
|
|
47
|
-
3. **Project Documentation**
|
|
48
|
-
- Add new sections to documentation
|
|
49
|
-
- Update progress logs
|
|
50
|
-
- Append new requirements or specifications
|
|
51
|
-
|
|
52
|
-
## Notes
|
|
53
|
-
- Can append to existing files or create new ones
|
|
54
|
-
- Preserves existing content in the file
|
|
55
|
-
- Maintains proper markdown formatting
|
|
56
|
-
- Supports YAML frontmatter
|
|
57
|
-
- Handles various content types:
|
|
58
|
-
* Meeting notes
|
|
59
|
-
* Daily logs
|
|
60
|
-
* Task lists
|
|
61
|
-
* Documentation updates
|
|
62
|
-
- File path is relative to vault root
|
|
63
|
-
- Creates parent directories if needed
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# Complex Search Tool Example
|
|
2
|
-
|
|
3
|
-
## Request 1 (Simple Glob Pattern)
|
|
4
|
-
```json
|
|
5
|
-
{
|
|
6
|
-
"query": {
|
|
7
|
-
"glob": ["*.md", {"var": "path"}]
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Response 1
|
|
13
|
-
```json
|
|
14
|
-
{
|
|
15
|
-
"matches": [
|
|
16
|
-
"README.md",
|
|
17
|
-
"Projects/Project Alpha/Documentation/Requirements.md",
|
|
18
|
-
"Daily Notes/2024-01-25.md",
|
|
19
|
-
"Templates/Meeting Note.md"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Request 2 (Complex Pattern)
|
|
25
|
-
```json
|
|
26
|
-
{
|
|
27
|
-
"query": {
|
|
28
|
-
"and": [
|
|
29
|
-
{
|
|
30
|
-
"glob": ["Projects/**/*.md", {"var": "path"}]
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"in": [
|
|
34
|
-
"Documentation",
|
|
35
|
-
{"var": "path"}
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Response 2
|
|
44
|
-
```json
|
|
45
|
-
{
|
|
46
|
-
"matches": [
|
|
47
|
-
"Projects/Project Alpha/Documentation/Requirements.md",
|
|
48
|
-
"Projects/Project Alpha/Documentation/Architecture.md",
|
|
49
|
-
"Projects/Project Beta/Documentation/API.md"
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Example Use Cases
|
|
55
|
-
|
|
56
|
-
1. **File Organization**
|
|
57
|
-
- Find files by pattern matching
|
|
58
|
-
- Filter by path components
|
|
59
|
-
- Combine multiple search criteria
|
|
60
|
-
|
|
61
|
-
2. **Content Management**
|
|
62
|
-
- Locate files in specific directories
|
|
63
|
-
- Filter by file extensions
|
|
64
|
-
- Find files matching complex patterns
|
|
65
|
-
|
|
66
|
-
3. **Project Navigation**
|
|
67
|
-
- Search within project directories
|
|
68
|
-
- Find documentation files
|
|
69
|
-
- Filter by file location
|
|
70
|
-
|
|
71
|
-
## Notes
|
|
72
|
-
- Uses JsonLogic for query construction
|
|
73
|
-
- Supports glob pattern matching
|
|
74
|
-
- Can combine multiple conditions
|
|
75
|
-
- Available operations:
|
|
76
|
-
* glob: Pattern matching for paths
|
|
77
|
-
* var: Variable access (path)
|
|
78
|
-
* and/or: Logical operations
|
|
79
|
-
* in: Membership testing
|
|
80
|
-
- Path is relative to vault root
|
|
81
|
-
- Returns matching file paths
|
|
82
|
-
- Useful for complex file filtering
|
|
83
|
-
|
|
84
|
-
## Common Patterns
|
|
85
|
-
|
|
86
|
-
1. **Find All Markdown Files**
|
|
87
|
-
```json
|
|
88
|
-
{
|
|
89
|
-
"glob": ["**/*.md", {"var": "path"}]
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
2. **Files in Specific Directory**
|
|
94
|
-
```json
|
|
95
|
-
{
|
|
96
|
-
"glob": ["Projects/Project Alpha/**/*", {"var": "path"}]
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
3. **Multiple Extensions**
|
|
101
|
-
```json
|
|
102
|
-
{
|
|
103
|
-
"or": [
|
|
104
|
-
{"glob": ["**/*.md", {"var": "path"}]},
|
|
105
|
-
{"glob": ["**/*.txt", {"var": "path"}]}
|
|
106
|
-
]
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
4. **Exclude Pattern**
|
|
111
|
-
```json
|
|
112
|
-
{
|
|
113
|
-
"and": [
|
|
114
|
-
{"glob": ["**/*.md", {"var": "path"}]},
|
|
115
|
-
{"not": {"glob": ["**/Archive/**", {"var": "path"}]}}
|
|
116
|
-
]
|
|
117
|
-
}
|
package/examples/find-in-file.md
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# Find in File Tool Example
|
|
2
|
-
|
|
3
|
-
## Request 1 (Many Results)
|
|
4
|
-
```json
|
|
5
|
-
{
|
|
6
|
-
"query": "tags:",
|
|
7
|
-
"contextLength": 20
|
|
8
|
-
}
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Response 1
|
|
12
|
-
```json
|
|
13
|
-
{
|
|
14
|
-
"message": "Found 34 files with matches. Showing file names only:",
|
|
15
|
-
"results": [
|
|
16
|
-
{
|
|
17
|
-
"filename": "Projects/Project Alpha/Documentation/Requirements.md",
|
|
18
|
-
"matchCount": 1
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"filename": "Projects/Project Beta/Specifications.md",
|
|
22
|
-
"matchCount": 2
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"filename": "Daily Notes/2024-01-25.md",
|
|
26
|
-
"matchCount": 1
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Request 2 (Few Results)
|
|
33
|
-
```json
|
|
34
|
-
{
|
|
35
|
-
"query": "priority: high",
|
|
36
|
-
"contextLength": 50
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Response 2
|
|
41
|
-
```json
|
|
42
|
-
{
|
|
43
|
-
"results": [
|
|
44
|
-
{
|
|
45
|
-
"filename": "Projects/Project Alpha/tasks.md",
|
|
46
|
-
"matches": [
|
|
47
|
-
{
|
|
48
|
-
"line": 15,
|
|
49
|
-
"content": "---\ntitle: Critical Tasks\ntags: [tasks, urgent]\npriority: high\n---\n\n# High Priority Tasks",
|
|
50
|
-
"matchStart": 45,
|
|
51
|
-
"matchEnd": 57
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"filename": "Areas/Work/Deadlines.md",
|
|
57
|
-
"matches": [
|
|
58
|
-
{
|
|
59
|
-
"line": 8,
|
|
60
|
-
"content": "## Q1 Deliverables\n- [ ] Feature launch (priority: high)\n- [ ] Security audit",
|
|
61
|
-
"matchStart": 35,
|
|
62
|
-
"matchEnd": 47
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Example Use Cases
|
|
71
|
-
|
|
72
|
-
1. **Content Search**
|
|
73
|
-
- Find notes with specific tags or properties
|
|
74
|
-
- Search for keywords across the vault
|
|
75
|
-
- Locate specific metadata patterns
|
|
76
|
-
|
|
77
|
-
2. **Task Management**
|
|
78
|
-
- Find high-priority tasks
|
|
79
|
-
- Search for incomplete items
|
|
80
|
-
- Locate notes with specific status
|
|
81
|
-
|
|
82
|
-
3. **Knowledge Discovery**
|
|
83
|
-
- Find related content across notes
|
|
84
|
-
- Search for specific topics or references
|
|
85
|
-
- Identify patterns in note metadata
|
|
86
|
-
|
|
87
|
-
## Notes
|
|
88
|
-
- Returns either file list or detailed matches based on result count
|
|
89
|
-
- Provides line numbers for precise location
|
|
90
|
-
- Shows surrounding context for each match
|
|
91
|
-
- Context length is customizable
|
|
92
|
-
- Supports searching in frontmatter and note content
|
|
93
|
-
- Results include match position information
|
|
94
|
-
- Can search across all vault files
|