obsidian-mcp-server 1.2.6 → 1.4.0

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.
Files changed (79) hide show
  1. package/README.md +54 -10
  2. package/build/index.js +12 -3
  3. package/build/mcp/handlers.js +138 -0
  4. package/build/mcp/index.js +7 -0
  5. package/build/mcp/server.js +131 -0
  6. package/build/mcp/types.js +7 -0
  7. package/build/{obsidian.js → obsidian/client.js} +156 -104
  8. package/build/obsidian/errors.js +75 -0
  9. package/build/obsidian/index.js +7 -0
  10. package/build/obsidian/types.js +12 -0
  11. package/build/resources/index.js +15 -0
  12. package/build/{resources.js → resources/tags.js} +35 -14
  13. package/build/resources/types.js +5 -0
  14. package/build/tools/base.js +78 -0
  15. package/build/tools/files/content.js +171 -0
  16. package/build/tools/files/index.js +22 -0
  17. package/build/tools/files/list.js +133 -0
  18. package/build/tools/index.js +31 -0
  19. package/build/tools/properties/index.js +19 -0
  20. package/build/{properties.js → tools/properties/manager.js} +50 -13
  21. package/build/{propertyTools.js → tools/properties/tools.js} +22 -6
  22. package/build/{propertyTypes.js → tools/properties/types.js} +19 -7
  23. package/build/tools/search/complex.js +203 -0
  24. package/build/tools/search/index.js +20 -0
  25. package/build/tools/search/simple.js +127 -0
  26. package/build/utils/errors.js +59 -0
  27. package/build/utils/index.js +9 -0
  28. package/build/utils/logging.js +119 -0
  29. package/build/utils/rate-limiting.js +94 -0
  30. package/build/utils/tokenization.js +62 -0
  31. package/build/utils/validation.js +88 -0
  32. package/examples/README.md +48 -0
  33. package/examples/append-content.md +63 -0
  34. package/examples/complex-search.md +117 -0
  35. package/examples/find-in-file.md +94 -0
  36. package/examples/get-file-contents.md +72 -0
  37. package/examples/get-properties.md +89 -0
  38. package/examples/list-files-in-dir.md +55 -0
  39. package/examples/list-files-in-vault.md +53 -0
  40. package/examples/patch-content.md +60 -0
  41. package/examples/update-properties.md +126 -0
  42. package/mcp-client-config.example.json +23 -0
  43. package/package.json +4 -3
  44. package/src/index.ts +13 -3
  45. package/src/mcp/handlers.ts +183 -0
  46. package/src/mcp/index.ts +6 -0
  47. package/src/mcp/server.ts +162 -0
  48. package/src/mcp/types.ts +46 -0
  49. package/src/{obsidian.ts → obsidian/client.ts} +174 -134
  50. package/src/obsidian/errors.ts +105 -0
  51. package/src/obsidian/index.ts +6 -0
  52. package/src/obsidian/types.ts +124 -0
  53. package/src/resources/index.ts +17 -0
  54. package/src/{resources.ts → resources/tags.ts} +42 -16
  55. package/src/resources/types.ts +30 -0
  56. package/src/tools/base.ts +112 -0
  57. package/src/tools/files/content.ts +206 -0
  58. package/src/tools/files/index.ts +31 -0
  59. package/src/tools/files/list.ts +150 -0
  60. package/src/tools/index.ts +38 -0
  61. package/src/tools/properties/index.ts +21 -0
  62. package/src/{properties.ts → tools/properties/manager.ts} +54 -13
  63. package/src/{propertyTools.ts → tools/properties/tools.ts} +33 -7
  64. package/src/{propertyTypes.ts → tools/properties/types.ts} +31 -7
  65. package/src/tools/search/complex.ts +231 -0
  66. package/src/tools/search/index.ts +22 -0
  67. package/src/tools/search/simple.ts +147 -0
  68. package/src/utils/errors.ts +69 -0
  69. package/src/utils/index.ts +8 -0
  70. package/src/utils/logging.ts +146 -0
  71. package/src/utils/rate-limiting.ts +114 -0
  72. package/src/utils/tokenization.ts +71 -0
  73. package/src/utils/validation.ts +95 -0
  74. package/build/server.js +0 -238
  75. package/build/tools.js +0 -863
  76. package/build/types.js +0 -37
  77. package/src/server.ts +0 -308
  78. package/src/tools.ts +0 -946
  79. package/src/types.ts +0 -184
@@ -1,5 +1,15 @@
1
+ /**
2
+ * Property manager for Obsidian notes
3
+ */
1
4
  import { parse, stringify } from 'yaml';
2
- import { ObsidianPropertiesSchema, PropertyUpdateSchema } from './propertyTypes.js';
5
+ import { EOL } from 'os';
6
+ import { createLogger } from '../../utils/logging.js';
7
+ import { ObsidianPropertiesSchema, PropertyUpdateSchema } from './types.js';
8
+ // Create a logger for property operations
9
+ const logger = createLogger('PropertyManager');
10
+ /**
11
+ * Manages YAML frontmatter properties in Obsidian notes
12
+ */
3
13
  export class PropertyManager {
4
14
  client;
5
15
  constructor(client) {
@@ -7,48 +17,59 @@ export class PropertyManager {
7
17
  }
8
18
  /**
9
19
  * Parse YAML frontmatter from note content
20
+ * @param content The note content
21
+ * @returns Extracted properties
10
22
  */
11
23
  parseProperties(content) {
12
24
  try {
13
- // Extract frontmatter between --- markers
14
- const match = content.match(/^---\n([\s\S]*?)\n---/);
25
+ // Extract frontmatter between --- markers (handles both \n and \r\n)
26
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
15
27
  if (!match) {
28
+ logger.debug('No frontmatter found in content');
16
29
  return {};
17
30
  }
18
31
  const frontmatter = match[1];
19
32
  const properties = parse(frontmatter);
33
+ // Handle tags - don't add # prefix in frontmatter
34
+ if (properties.tags && Array.isArray(properties.tags)) {
35
+ properties.tags = properties.tags.map((tag) => tag.startsWith('#') ? tag.substring(1) : tag);
36
+ }
20
37
  // Validate against schema
21
38
  const result = ObsidianPropertiesSchema.safeParse(properties);
22
39
  if (!result.success) {
23
- console.warn('Property validation warnings:', result.error);
24
- // Return partial valid properties rather than throwing
40
+ logger.warn('Property validation warnings:', result.error);
41
+ // Return the properties with fixed tags
25
42
  return properties;
26
43
  }
27
44
  return result.data;
28
45
  }
29
46
  catch (error) {
30
- console.error('Error parsing properties:', error);
47
+ logger.error('Error parsing properties:', error);
31
48
  return {};
32
49
  }
33
50
  }
34
51
  /**
35
52
  * Generate YAML frontmatter from properties
53
+ * @param properties The properties to convert to YAML
54
+ * @returns YAML frontmatter string
36
55
  */
37
56
  generateProperties(properties) {
38
57
  try {
39
58
  // Remove undefined values
40
59
  const cleanProperties = Object.fromEntries(Object.entries(properties).filter(([_, v]) => v !== undefined));
41
- // Generate YAML
60
+ // Generate YAML with platform-specific line endings
42
61
  const yaml = stringify(cleanProperties);
43
- return `---\n${yaml}---\n`;
62
+ return `---${EOL}${yaml}---${EOL}`;
44
63
  }
45
64
  catch (error) {
46
- console.error('Error generating properties:', error);
65
+ logger.error('Error generating properties:', error);
47
66
  throw error;
48
67
  }
49
68
  }
50
69
  /**
51
- * Validate property values
70
+ * Validate property values against schema
71
+ * @param properties The properties to validate
72
+ * @returns Validation result
52
73
  */
53
74
  validateProperties(properties) {
54
75
  const result = PropertyUpdateSchema.safeParse(properties);
@@ -62,6 +83,10 @@ export class PropertyManager {
62
83
  }
63
84
  /**
64
85
  * Merge new properties with existing ones
86
+ * @param existing The existing properties
87
+ * @param updates The new properties to merge
88
+ * @param replace Whether to replace arrays instead of merging them
89
+ * @returns The merged properties
65
90
  */
66
91
  mergeProperties(existing, updates, replace = false) {
67
92
  const merged = { ...existing };
@@ -94,9 +119,12 @@ export class PropertyManager {
94
119
  }
95
120
  /**
96
121
  * Get properties from a note
122
+ * @param filepath Path to the note
123
+ * @returns The properties from the note
97
124
  */
98
125
  async getProperties(filepath) {
99
126
  try {
127
+ logger.debug(`Getting properties from file: ${filepath}`);
100
128
  const content = await this.client.getFileContents(filepath);
101
129
  const properties = this.parseProperties(content);
102
130
  return {
@@ -106,6 +134,7 @@ export class PropertyManager {
106
134
  };
107
135
  }
108
136
  catch (error) {
137
+ logger.error(`Failed to get properties from ${filepath}:`, error);
109
138
  return {
110
139
  success: false,
111
140
  message: `Failed to get properties: ${error instanceof Error ? error.message : String(error)}`,
@@ -115,12 +144,17 @@ export class PropertyManager {
115
144
  }
116
145
  /**
117
146
  * Update properties of a note
147
+ * @param filepath Path to the note
148
+ * @param newProperties The new properties to apply
149
+ * @param replace Whether to replace arrays instead of merging them
150
+ * @returns The result of the update operation
118
151
  */
119
152
  async updateProperties(filepath, newProperties, replace = false) {
120
153
  try {
121
154
  // Validate new properties
122
155
  const validation = this.validateProperties(newProperties);
123
156
  if (!validation.valid) {
157
+ logger.warn(`Invalid properties for ${filepath}:`, validation.errors);
124
158
  return {
125
159
  success: false,
126
160
  message: 'Invalid properties',
@@ -128,17 +162,19 @@ export class PropertyManager {
128
162
  };
129
163
  }
130
164
  // Get existing content and properties
165
+ logger.debug(`Updating properties for file: ${filepath}`);
131
166
  const content = await this.client.getFileContents(filepath);
132
167
  const existingProperties = this.parseProperties(content);
133
168
  // Merge properties
134
169
  const mergedProperties = this.mergeProperties(existingProperties, newProperties, replace);
135
170
  // Generate new frontmatter
136
171
  const newFrontmatter = this.generateProperties(mergedProperties);
137
- // Replace existing frontmatter or prepend to file
138
- const newContent = content.replace(/^---[\s\S]*?---\n/, '') || '';
172
+ // Replace existing frontmatter or prepend to file (handles both \n and \r\n)
173
+ const newContent = content.replace(/^---[\s\S]*?---\r?\n/, '') || '';
139
174
  const updatedContent = newFrontmatter + newContent;
140
175
  // Update file
141
176
  await this.client.updateContent(filepath, updatedContent);
177
+ logger.debug(`Successfully updated properties for ${filepath}`);
142
178
  return {
143
179
  success: true,
144
180
  message: 'Properties updated successfully',
@@ -146,6 +182,7 @@ export class PropertyManager {
146
182
  };
147
183
  }
148
184
  catch (error) {
185
+ logger.error(`Failed to update properties for ${filepath}:`, error);
149
186
  return {
150
187
  success: false,
151
188
  message: `Failed to update properties: ${error instanceof Error ? error.message : String(error)}`,
@@ -154,4 +191,4 @@ export class PropertyManager {
154
191
  }
155
192
  }
156
193
  }
157
- //# sourceMappingURL=properties.js.map
194
+ //# sourceMappingURL=manager.js.map
@@ -1,13 +1,22 @@
1
- import { BaseToolHandler } from "./tools.js";
2
- import { PropertyManager } from "./properties.js";
3
- const TOOL_NAMES = {
1
+ import { BaseToolHandler } from "../base.js";
2
+ import { PropertyManager } from "./manager.js";
3
+ import { createLogger } from "../../utils/logging.js";
4
+ // Create a logger for property tools
5
+ const logger = createLogger('PropertyTools');
6
+ /**
7
+ * Tool names for property operations
8
+ */
9
+ export const PROPERTY_TOOL_NAMES = {
4
10
  GET_PROPERTIES: "obsidian_get_properties",
5
11
  UPDATE_PROPERTIES: "obsidian_update_properties"
6
12
  };
13
+ /**
14
+ * Tool handler for getting properties from a note
15
+ */
7
16
  export class GetPropertiesToolHandler extends BaseToolHandler {
8
17
  propertyManager;
9
18
  constructor(client) {
10
- super(TOOL_NAMES.GET_PROPERTIES, client);
19
+ super(PROPERTY_TOOL_NAMES.GET_PROPERTIES, client);
11
20
  this.propertyManager = new PropertyManager(client);
12
21
  }
13
22
  getToolDescription() {
@@ -43,18 +52,23 @@ export class GetPropertiesToolHandler extends BaseToolHandler {
43
52
  }
44
53
  async runTool(args) {
45
54
  try {
55
+ logger.debug(`Getting properties from: ${args.filepath}`);
46
56
  const result = await this.propertyManager.getProperties(args.filepath);
47
57
  return this.createResponse(result);
48
58
  }
49
59
  catch (error) {
60
+ logger.error(`Error getting properties from ${args.filepath}:`, error);
50
61
  return this.handleError(error);
51
62
  }
52
63
  }
53
64
  }
65
+ /**
66
+ * Tool handler for updating properties in a note
67
+ */
54
68
  export class UpdatePropertiesToolHandler extends BaseToolHandler {
55
69
  propertyManager;
56
70
  constructor(client) {
57
- super(TOOL_NAMES.UPDATE_PROPERTIES, client);
71
+ super(PROPERTY_TOOL_NAMES.UPDATE_PROPERTIES, client);
58
72
  this.propertyManager = new PropertyManager(client);
59
73
  }
60
74
  getToolDescription() {
@@ -164,12 +178,14 @@ export class UpdatePropertiesToolHandler extends BaseToolHandler {
164
178
  }
165
179
  async runTool(args) {
166
180
  try {
181
+ logger.debug(`Updating properties for: ${args.filepath}`);
167
182
  const result = await this.propertyManager.updateProperties(args.filepath, args.properties, args.replace);
168
183
  return this.createResponse(result);
169
184
  }
170
185
  catch (error) {
186
+ logger.error(`Error updating properties for ${args.filepath}:`, error);
171
187
  return this.handleError(error);
172
188
  }
173
189
  }
174
190
  }
175
- //# sourceMappingURL=propertyTools.js.map
191
+ //# sourceMappingURL=tools.js.map
@@ -1,14 +1,24 @@
1
+ /**
2
+ * Property types and schemas for Obsidian notes
3
+ */
1
4
  import { z } from "zod";
2
- // Define validation schemas
3
- // Allow any string for type to be more flexible
5
+ /**
6
+ * Define validation schemas
7
+ * Allow any string for type to be more flexible
8
+ */
4
9
  export const PropertyType = z.string();
10
+ /**
11
+ * Valid status values for notes
12
+ */
5
13
  export const StatusEnum = z.enum([
6
14
  "draft",
7
15
  "in-progress",
8
16
  "review",
9
17
  "complete"
10
18
  ]);
11
- // Schema for reading properties (includes timestamps)
19
+ /**
20
+ * Schema for reading properties (includes timestamps)
21
+ */
12
22
  export const ObsidianPropertiesSchema = z.object({
13
23
  // Basic Metadata
14
24
  // Note: Timestamps are managed automatically
@@ -18,7 +28,7 @@ export const ObsidianPropertiesSchema = z.object({
18
28
  // Classification
19
29
  type: z.array(PropertyType).optional(),
20
30
  // Organization
21
- tags: z.array(z.string().startsWith("#")).optional(),
31
+ tags: z.array(z.string()).optional(),
22
32
  // Technical Metadata
23
33
  status: z.array(StatusEnum).optional(),
24
34
  version: z.string().optional(),
@@ -32,7 +42,9 @@ export const ObsidianPropertiesSchema = z.object({
32
42
  // Custom Fields
33
43
  custom: z.record(z.unknown()).optional()
34
44
  });
35
- // Schema for validating property updates (excludes timestamps)
45
+ /**
46
+ * Schema for validating property updates (excludes timestamps)
47
+ */
36
48
  export const PropertyUpdateSchema = z.object({
37
49
  // Basic Metadata
38
50
  title: z.string().optional(),
@@ -40,7 +52,7 @@ export const PropertyUpdateSchema = z.object({
40
52
  // Classification
41
53
  type: z.array(PropertyType).optional(),
42
54
  // Organization
43
- tags: z.array(z.string().startsWith("#")).optional(),
55
+ tags: z.array(z.string()).optional(),
44
56
  // Technical Metadata
45
57
  status: z.array(StatusEnum).optional(),
46
58
  version: z.string().optional(),
@@ -54,4 +66,4 @@ export const PropertyUpdateSchema = z.object({
54
66
  // Custom Fields
55
67
  custom: z.record(z.unknown()).optional()
56
68
  });
57
- //# sourceMappingURL=propertyTypes.js.map
69
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,203 @@
1
+ import { PropertyManager } from "../properties/manager.js";
2
+ import { BaseToolHandler } from "../base.js";
3
+ import { createLogger } from "../../utils/logging.js";
4
+ // Create a logger for complex search operations
5
+ const logger = createLogger('ComplexSearchTools');
6
+ /**
7
+ * Tool name for complex search
8
+ */
9
+ export const COMPLEX_SEARCH_TOOL_NAME = "obsidian_complex_search";
10
+ /**
11
+ * Tool handler for complex JsonLogic searches
12
+ */
13
+ export class ComplexSearchToolHandler extends BaseToolHandler {
14
+ constructor(client) {
15
+ super(COMPLEX_SEARCH_TOOL_NAME, client);
16
+ }
17
+ getToolDescription() {
18
+ return {
19
+ name: this.name,
20
+ 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.",
21
+ examples: [
22
+ {
23
+ description: "Find markdown files in Projects folder",
24
+ args: {
25
+ query: {
26
+ "glob": ["Projects/*.md", { "var": "path" }]
27
+ }
28
+ }
29
+ },
30
+ {
31
+ description: "Find files in a specific subfolder",
32
+ args: {
33
+ query: {
34
+ "glob": ["**/Test/*.md", { "var": "path" }]
35
+ }
36
+ }
37
+ }
38
+ ],
39
+ inputSchema: {
40
+ type: "object",
41
+ properties: {
42
+ query: {
43
+ type: "object",
44
+ description: "JsonLogic query object. Example: {\"glob\": [\"*.md\", {\"var\": \"path\"}]} matches all markdown files"
45
+ }
46
+ },
47
+ required: ["query"]
48
+ }
49
+ };
50
+ }
51
+ async runTool(args) {
52
+ try {
53
+ logger.debug(`Executing complex search with query: ${JSON.stringify(args.query)}`);
54
+ // Perform search
55
+ const results = await this.client.searchJson(args.query);
56
+ // Format response based on result type
57
+ const formattedResults = results.map(result => {
58
+ if ('matches' in result) {
59
+ // SimpleSearchResult
60
+ return {
61
+ filename: result.filename,
62
+ matches: result.matches,
63
+ score: result.score
64
+ };
65
+ }
66
+ else {
67
+ // SearchResult
68
+ return {
69
+ filename: result.filename,
70
+ result: result.result
71
+ };
72
+ }
73
+ });
74
+ logger.debug(`Complex search found ${results.length} results`);
75
+ return this.createResponse({
76
+ message: `Found ${results.length} result(s)`,
77
+ results: formattedResults
78
+ });
79
+ }
80
+ catch (error) {
81
+ logger.error(`Complex search error: ${error}`);
82
+ return this.handleError(error);
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * Tool handler for getting all tags used in the vault
88
+ */
89
+ export class GetTagsToolHandler extends BaseToolHandler {
90
+ propertyManager;
91
+ constructor(client) {
92
+ super("obsidian_get_tags", client);
93
+ this.propertyManager = new PropertyManager(client);
94
+ }
95
+ getToolDescription() {
96
+ return {
97
+ name: this.name,
98
+ description: "Get all tags used across the Obsidian vault with their usage counts. Optionally filter tags within a specific folder.",
99
+ examples: [
100
+ {
101
+ description: "Get all tags in vault",
102
+ args: {}
103
+ },
104
+ {
105
+ description: "Get tags in Projects folder",
106
+ args: {
107
+ path: "Projects"
108
+ }
109
+ },
110
+ {
111
+ description: "Example response",
112
+ args: {},
113
+ response: {
114
+ "tags": [
115
+ {
116
+ "name": "project",
117
+ "count": 15,
118
+ "files": [
119
+ "Projects/ProjectA.md",
120
+ "Projects/ProjectB.md"
121
+ ]
122
+ }
123
+ ],
124
+ "metadata": {
125
+ "totalOccurrences": 45,
126
+ "uniqueTags": 12,
127
+ "scannedFiles": 30
128
+ }
129
+ }
130
+ }
131
+ ],
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: {
135
+ path: {
136
+ type: "string",
137
+ description: "Optional path to limit tag search to specific folder",
138
+ format: "path"
139
+ }
140
+ }
141
+ }
142
+ };
143
+ }
144
+ async runTool(args) {
145
+ try {
146
+ logger.debug(`Getting tags${args.path ? ` in path: ${args.path}` : ' in whole vault'}`);
147
+ const tagMap = new Map();
148
+ // Use searchJson to find files with .md extension
149
+ const query = args.path
150
+ ? { "glob": [`${args.path}/**/*.md`.replace(/\\/g, '/'), { "var": "path" }] }
151
+ : { "glob": ["**/*.md", { "var": "path" }] };
152
+ const results = await this.client.searchJson(query);
153
+ let scannedFiles = 0;
154
+ // Process each file to extract tags from frontmatter
155
+ for (const result of results) {
156
+ if (!('filename' in result))
157
+ continue;
158
+ try {
159
+ const content = await this.client.getFileContents(result.filename);
160
+ scannedFiles++;
161
+ // Use PropertyManager to properly parse frontmatter
162
+ const properties = this.propertyManager.parseProperties(content);
163
+ // Process tags if they exist
164
+ if (properties.tags && Array.isArray(properties.tags)) {
165
+ // Process each tag
166
+ for (const tag of properties.tags) {
167
+ const cleanTag = tag.replace(/^#/, ''); // Remove leading # if present
168
+ if (!tagMap.has(cleanTag)) {
169
+ tagMap.set(cleanTag, new Set());
170
+ }
171
+ tagMap.get(cleanTag).add(result.filename);
172
+ }
173
+ }
174
+ }
175
+ catch (error) {
176
+ logger.error(`Failed to process file ${result.filename}:`, error);
177
+ }
178
+ }
179
+ // Calculate total occurrences
180
+ const totalOccurrences = Array.from(tagMap.values())
181
+ .reduce((sum, files) => sum + files.size, 0);
182
+ return this.createResponse({
183
+ tags: Array.from(tagMap.entries())
184
+ .map(([name, files]) => ({
185
+ name,
186
+ count: files.size,
187
+ files: Array.from(files).sort()
188
+ }))
189
+ .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)),
190
+ metadata: {
191
+ totalOccurrences,
192
+ uniqueTags: tagMap.size,
193
+ scannedFiles,
194
+ lastUpdate: Date.now()
195
+ }
196
+ });
197
+ }
198
+ catch (error) {
199
+ return this.handleError(error);
200
+ }
201
+ }
202
+ }
203
+ //# sourceMappingURL=complex.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Search tools exports
3
+ */
4
+ export * from './simple.js';
5
+ export * from './complex.js';
6
+ import { FindInFileToolHandler } from './simple.js';
7
+ import { ComplexSearchToolHandler, GetTagsToolHandler } from './complex.js';
8
+ /**
9
+ * Create all search-related tool handlers
10
+ * @param client The ObsidianClient instance
11
+ * @returns Array of search tool handlers
12
+ */
13
+ export function createSearchToolHandlers(client) {
14
+ return [
15
+ new FindInFileToolHandler(client),
16
+ new ComplexSearchToolHandler(client),
17
+ new GetTagsToolHandler(client)
18
+ ];
19
+ }
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,127 @@
1
+ import { BaseToolHandler } from "../base.js";
2
+ import { createLogger } from "../../utils/logging.js";
3
+ // Create a logger for search operations
4
+ const logger = createLogger('SimpleSearchTools');
5
+ /**
6
+ * Tool name for simple search
7
+ */
8
+ export const SIMPLE_SEARCH_TOOL_NAME = "obsidian_find_in_file";
9
+ /**
10
+ * Tool handler for simple text search across files
11
+ */
12
+ export class FindInFileToolHandler extends BaseToolHandler {
13
+ constructor(client) {
14
+ super(SIMPLE_SEARCH_TOOL_NAME, client);
15
+ }
16
+ getToolDescription() {
17
+ return {
18
+ name: this.name,
19
+ 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.",
20
+ examples: [
21
+ {
22
+ description: "Search for a specific term",
23
+ args: {
24
+ query: "neural networks",
25
+ contextLength: 20
26
+ }
27
+ },
28
+ {
29
+ description: "Search with default context",
30
+ args: {
31
+ query: "#todo"
32
+ },
33
+ response: {
34
+ "message": "Found 1 file with matches:",
35
+ "results": [
36
+ {
37
+ "filename": "Projects/AI.md",
38
+ "matches": [
39
+ {
40
+ "context": "Research needed:\n#todo Implement transformer architecture\nDeadline: Next week",
41
+ "match": { "start": 15, "end": 45 }
42
+ }
43
+ ]
44
+ }
45
+ ]
46
+ }
47
+ },
48
+ {
49
+ description: "Example response with many matches (file-only format)",
50
+ args: {
51
+ query: "API"
52
+ },
53
+ response: {
54
+ "message": "Found 92 files with matches. Showing file names only:",
55
+ "results": [
56
+ {
57
+ "filename": "Developer/Documentation/API.md",
58
+ "matchCount": 43
59
+ },
60
+ {
61
+ "filename": "Projects/API_Design.md",
62
+ "matchCount": 34
63
+ }
64
+ ]
65
+ }
66
+ }
67
+ ],
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ query: {
72
+ type: "string",
73
+ description: "Text pattern to search for. Can include tags, keywords, or phrases."
74
+ },
75
+ contextLength: {
76
+ type: "integer",
77
+ description: "Number of characters to include before and after each match for context (default: 10)",
78
+ default: 10
79
+ }
80
+ },
81
+ required: ["query"]
82
+ }
83
+ };
84
+ }
85
+ async runTool(args) {
86
+ try {
87
+ logger.debug(`Searching for "${args.query}" with context length: ${args.contextLength || 100}`);
88
+ const results = await this.client.search(args.query, args.contextLength ?? 100);
89
+ // If more than 5 results, only return filenames and match counts to prevent overwhelming responses
90
+ if (results.length > 5) {
91
+ const fileOnlyResults = results.map(result => ({
92
+ filename: result.filename,
93
+ matchCount: result.matches.length
94
+ }));
95
+ logger.debug(`Found ${results.length} files with matches, returning file-only format`);
96
+ return this.createResponse({
97
+ message: `Found ${results.length} files with matches. Showing file names only:`,
98
+ results: fileOnlyResults
99
+ });
100
+ }
101
+ // Otherwise return full context as before
102
+ const formattedResults = results.map(result => ({
103
+ filename: result.filename,
104
+ matches: result.matches.map(match => ({
105
+ context: match.context,
106
+ match: {
107
+ text: match.context.substring(match.match.start, match.match.end),
108
+ position: {
109
+ start: match.match.start,
110
+ end: match.match.end
111
+ }
112
+ }
113
+ })),
114
+ score: result.score
115
+ }));
116
+ logger.debug(`Found ${results.length} files with matches, returning with context`);
117
+ return this.createResponse({
118
+ message: `Found ${results.length} file(s) with matches:`,
119
+ results: formattedResults
120
+ });
121
+ }
122
+ catch (error) {
123
+ return this.handleError(error);
124
+ }
125
+ }
126
+ }
127
+ //# sourceMappingURL=simple.js.map