obsidian-mcp-server 1.5.4 → 1.5.6

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 (47) hide show
  1. package/Dockerfile +3 -2
  2. package/README.md +2 -2
  3. package/build/index.js +1 -1
  4. package/build/mcp/handlers.js +3 -6
  5. package/build/mcp/index.js +1 -1
  6. package/build/mcp/server.js +5 -5
  7. package/build/obsidian/client.js +5 -8
  8. package/build/obsidian/index.js +1 -1
  9. package/build/resources/index.js +1 -1
  10. package/build/resources/tags.js +36 -8
  11. package/build/tools/base.js +2 -2
  12. package/build/tools/files/content.js +10 -10
  13. package/build/tools/files/index.js +4 -4
  14. package/build/tools/files/list.js +3 -3
  15. package/build/tools/index.js +2 -2
  16. package/build/tools/properties/index.js +1 -1
  17. package/build/tools/properties/manager.js +160 -59
  18. package/build/tools/properties/tools.js +8 -7
  19. package/build/tools/properties/types.js +1 -0
  20. package/build/tools/search/complex.js +35 -19
  21. package/build/tools/search/index.js +2 -2
  22. package/build/tools/search/simple.js +6 -6
  23. package/docs/tree.md +5 -1
  24. package/package.json +3 -3
  25. package/src/index.ts +2 -1
  26. package/src/mcp/handlers.ts +12 -15
  27. package/src/mcp/index.ts +3 -2
  28. package/src/mcp/server.ts +13 -13
  29. package/src/mcp/types.ts +0 -2
  30. package/src/obsidian/client.ts +17 -17
  31. package/src/obsidian/errors.ts +1 -1
  32. package/src/obsidian/index.ts +2 -1
  33. package/src/resources/index.ts +1 -1
  34. package/src/resources/tags.ts +42 -11
  35. package/src/tools/base.ts +3 -3
  36. package/src/tools/files/content.ts +13 -13
  37. package/src/tools/files/index.ts +11 -11
  38. package/src/tools/files/list.ts +4 -4
  39. package/src/tools/index.ts +3 -3
  40. package/src/tools/properties/index.ts +1 -1
  41. package/src/tools/properties/manager.ts +188 -60
  42. package/src/tools/properties/tools.ts +10 -9
  43. package/src/tools/properties/types.ts +1 -0
  44. package/src/tools/search/complex.ts +54 -38
  45. package/src/tools/search/index.ts +2 -2
  46. package/src/tools/search/simple.ts +7 -7
  47. package/src/utils/index.ts +1 -1
package/Dockerfile CHANGED
@@ -24,7 +24,8 @@ RUN npm prune --production
24
24
  FROM node:22-slim
25
25
 
26
26
  ENV NODE_ENV=production \
27
- PATH="/home/service-user/.local/bin:${PATH}"
27
+ PATH="/home/service-user/.local/bin:${PATH}" \
28
+ OBSIDIAN_API_KEY="abc"
28
29
 
29
30
  # Install mcp-proxy globally for runtime use
30
31
  # Combine update, install, and clean in one layer
@@ -59,4 +60,4 @@ USER service-user
59
60
 
60
61
  # Define the command to run the application
61
62
  # CMD ["mcp-proxy", "node", "build/index.js"] # Keep original for reference
62
- CMD ["mcp-proxy", "node", "build/index.js"]
63
+ CMD ["node", "build/index.js"]
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Obsidian MCP Server
2
2
 
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.8.3-blue.svg)](https://www.typescriptlang.org/)
4
- [![Model Context Protocol](https://img.shields.io/badge/MCP-1.10.1-green.svg)](https://modelcontextprotocol.io/)
5
- [![Version](https://img.shields.io/badge/Version-1.5.4-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
+ [![Model Context Protocol](https://img.shields.io/badge/MCP-1.10.2-green.svg)](https://modelcontextprotocol.io/)
5
+ [![Version](https://img.shields.io/badge/Version-1.5.6-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6
6
  [![Status](https://img.shields.io/badge/Status-Stable-green.svg)]()
7
7
  [![GitHub](https://img.shields.io/github/stars/cyanheads/obsidian-mcp-server?style=social)](https://github.com/cyanheads/obsidian-mcp-server)
8
8
 
package/build/index.js CHANGED
@@ -14,7 +14,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
14
14
  // Export the run function and other important modules
15
15
  export { run } from "./mcp/server.js";
16
16
  export * from "./obsidian/index.js";
17
- export * from "./tools/index.js";
18
17
  export * from "./resources/index.js";
18
+ export * from "./tools/index.js";
19
19
  export * from "./utils/index.js";
20
20
  //# sourceMappingURL=index.js.map
@@ -1,11 +1,8 @@
1
- /**
2
- * MCP server request handlers
3
- */
4
- import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
1
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
2
  import { ObsidianError } from "../utils/errors.js";
6
- import { validateToolArguments } from "../utils/validation.js";
7
- import { rateLimiter } from "../utils/rate-limiting.js";
8
3
  import { createLogger, ErrorCategoryType } from "../utils/logging.js";
4
+ import { rateLimiter } from "../utils/rate-limiting.js";
5
+ import { validateToolArguments } from "../utils/validation.js";
9
6
  import { DEFAULT_TIMEOUT_CONFIG, McpErrorCode } from "./types.js";
10
7
  // Create a logger for request handlers
11
8
  const logger = createLogger('McpHandlers');
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * MCP module exports
3
3
  */
4
- export * from './types.js';
5
4
  export * from './handlers.js';
6
5
  export * from './server.js';
6
+ export * from './types.js';
7
7
  //# sourceMappingURL=index.js.map
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * MCP server implementation
3
3
  */
4
- import { config } from "dotenv";
5
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { config } from "dotenv";
7
+ import { ObsidianClient } from "../obsidian/client.js";
8
+ import { createTagResource } from "../resources/index.js";
9
+ import { createToolHandlerMap, createToolHandlers } from "../tools/index.js";
7
10
  import { ObsidianError } from "../utils/errors.js"; // Import ObsidianError
8
11
  import { createLogger, ErrorCategoryType } from "../utils/logging.js";
9
12
  import { rateLimiter } from "../utils/rate-limiting.js";
10
- import { createTagResource } from "../resources/index.js";
11
- import { createToolHandlers, createToolHandlerMap } from "../tools/index.js";
12
- import { ObsidianClient } from "../obsidian/client.js";
13
- import { setupToolListingHandler, setupToolCallingHandler, setupResourceListingHandler, setupResourceReadingHandler } from "./handlers.js";
13
+ import { setupResourceListingHandler, setupResourceReadingHandler, setupToolCallingHandler, setupToolListingHandler } from "./handlers.js";
14
14
  import { McpErrorCode } from "./types.js";
15
15
  // Create a logger for the server
16
16
  const logger = createLogger('McpServer');
@@ -1,16 +1,13 @@
1
- /**
2
- * Obsidian REST API client implementation
3
- */
4
1
  import axios from "axios";
5
- import { Agent } from "node:https";
6
2
  import { readFileSync } from "fs";
7
- import { fileURLToPath } from 'url';
3
+ import { Agent } from "node:https";
8
4
  import { dirname, join } from "path";
9
- import { createLogger } from '../utils/logging.js';
5
+ import { fileURLToPath } from 'url';
10
6
  import { ObsidianError } from '../utils/errors.js';
11
- import { validateFilePath, sanitizeHeader } from '../utils/validation.js';
12
- import { DEFAULT_OBSIDIAN_CONFIG } from './types.js';
7
+ import { createLogger } from '../utils/logging.js';
8
+ import { sanitizeHeader, validateFilePath } from '../utils/validation.js';
13
9
  import { createMissingAPIKeyMessage, handleAxiosError } from './errors.js';
10
+ import { DEFAULT_OBSIDIAN_CONFIG } from './types.js';
14
11
  // Logger for the ObsidianClient
15
12
  const logger = createLogger('ObsidianClient');
16
13
  // Get package version for user agent
@@ -2,6 +2,6 @@
2
2
  * Exports for the Obsidian module
3
3
  */
4
4
  export * from './client.js';
5
- export * from './types.js';
6
5
  export * from './errors.js';
6
+ export * from './types.js';
7
7
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Resources module exports
3
3
  */
4
- export * from './types.js';
5
4
  export * from './tags.js';
5
+ export * from './types.js';
6
6
  import { TagResource } from './tags.js';
7
7
  /**
8
8
  * Create and return the tag resource
@@ -1,6 +1,6 @@
1
1
  import pLimit from 'p-limit'; // Import p-limit
2
- import { PropertyManager } from "../tools/properties/manager.js";
3
2
  import { sep } from "path";
3
+ import { PropertyManager } from "../tools/properties/manager.js";
4
4
  import { createLogger, ErrorCategoryType } from "../utils/logging.js";
5
5
  // Create a logger for tag resources
6
6
  const logger = createLogger('TagResource');
@@ -77,8 +77,13 @@ export class TagResource {
77
77
  // This call is now rate-limited
78
78
  const content = await this.client.getFileContents(filename);
79
79
  // Only extract tags from frontmatter YAML
80
- const properties = this.propertyManager.parseProperties(content);
81
- return { filename, tags: properties.tags || [] };
80
+ const parseResult = this.propertyManager.parseProperties(content);
81
+ // Handle potential parsing errors before accessing properties
82
+ if (parseResult.error) {
83
+ logger.warn(`Skipping tags for ${filename} due to parsing error: ${parseResult.error.message}`);
84
+ return { filename, tags: [] }; // Return empty tags on error
85
+ }
86
+ return { filename, tags: parseResult.properties.tags || [] };
82
87
  }
83
88
  catch (error) {
84
89
  logger.error(`Failed to process file ${filename}:`, errorToObject(error));
@@ -175,9 +180,11 @@ export class TagResource {
175
180
  }
176
181
  }
177
182
  /**
178
- * Get the content for the resource
183
+ * Get the content for the resource, optionally filtering by path.
184
+ * Note: The path filtering logic assumes the tool handler passes the path correctly.
179
185
  */
180
- async getContent() {
186
+ // TODO: Verify how the path argument is passed from the tool handler
187
+ async getContent(filterPath) {
181
188
  logger.startTimer('get_tags_content');
182
189
  try {
183
190
  if (!this.isInitialized) {
@@ -187,15 +194,36 @@ export class TagResource {
187
194
  else {
188
195
  await this.updateCacheIfNeeded();
189
196
  }
197
+ let filteredTagEntries = [];
198
+ // Filter tags based on the provided path
199
+ if (filterPath) {
200
+ // Normalize path to ensure it ends with a separator for accurate startsWith check
201
+ const normalizedFilterPath = filterPath.endsWith(sep) ? filterPath : filterPath + sep;
202
+ logger.debug(`Filtering tags for path: ${normalizedFilterPath}`);
203
+ filteredTagEntries = Array.from(this.tagCache.entries())
204
+ .map(([name, files]) => {
205
+ // Filter files within each tag entry
206
+ const relevantFiles = Array.from(files).filter(file => file.startsWith(normalizedFilterPath));
207
+ return { name, files: new Set(relevantFiles) };
208
+ })
209
+ .filter(tagEntry => tagEntry.files.size > 0); // Keep only tags that exist in the filtered path
210
+ logger.debug(`Found ${filteredTagEntries.length} tags after filtering.`);
211
+ }
212
+ else {
213
+ // If no path provided, use all tags from the cache
214
+ filteredTagEntries = Array.from(this.tagCache.entries()).map(([name, files]) => ({ name, files }));
215
+ }
190
216
  const response = {
191
- tags: Array.from(this.tagCache.entries())
192
- .map(([name, files]) => ({
217
+ tags: filteredTagEntries
218
+ .map(({ name, files }) => ({
193
219
  name,
194
220
  count: files.size,
195
221
  files: Array.from(files).sort()
196
222
  }))
197
- .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)),
223
+ .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)), // Sort filtered results
198
224
  metadata: {
225
+ // Note: Metadata currently reflects the entire vault cache, even when filtered.
226
+ // Adjust calculation here if path-specific metadata is desired.
199
227
  totalOccurrences: Array.from(this.tagCache.values())
200
228
  .reduce((sum, files) => sum + files.size, 0),
201
229
  uniqueTags: this.tagCache.size,
@@ -1,7 +1,7 @@
1
+ import { McpErrorCode } from "../mcp/types.js";
1
2
  import { ObsidianError } from "../utils/errors.js";
2
- import { tokenCounter } from "../utils/tokenization.js";
3
3
  import { createLogger, ErrorCategoryType } from "../utils/logging.js";
4
- import { McpErrorCode } from "../mcp/types.js";
4
+ import { tokenCounter } from "../utils/tokenization.js";
5
5
  // Create a logger for tool operations
6
6
  const logger = createLogger('Tools');
7
7
  /**
@@ -1,5 +1,5 @@
1
- import { BaseToolHandler } from "../base.js";
2
1
  import { createLogger } from "../../utils/logging.js";
2
+ import { BaseToolHandler } from "../base.js";
3
3
  // Create a logger for file content operations
4
4
  const logger = createLogger('FileContentTools');
5
5
  /**
@@ -8,7 +8,7 @@ const logger = createLogger('FileContentTools');
8
8
  export const FILE_CONTENT_TOOL_NAMES = {
9
9
  GET_FILE_CONTENTS: "obsidian_get_file_contents",
10
10
  APPEND_CONTENT: "obsidian_append_content",
11
- PATCH_CONTENT: "obsidian_patch_content"
11
+ UPDATE_CONTENT: "obsidian_update_content" // Renamed from PATCH_CONTENT
12
12
  };
13
13
  /**
14
14
  * Tool handler for getting file contents
@@ -20,7 +20,7 @@ export class GetFileContentsToolHandler extends BaseToolHandler {
20
20
  getToolDescription() {
21
21
  return {
22
22
  name: this.name,
23
- description: "Return the content of a single file in your vault. Supports markdown files, text files, and other readable formats. Returns the raw content including any YAML frontmatter.",
23
+ description: "Retrieves the full content of a specified file within your Obsidian vault. Supports various readable file formats.",
24
24
  examples: [
25
25
  {
26
26
  description: "Get content of a markdown note",
@@ -69,7 +69,7 @@ export class AppendContentToolHandler extends BaseToolHandler {
69
69
  getToolDescription() {
70
70
  return {
71
71
  name: this.name,
72
- description: "Append content to a new or existing file in the vault.",
72
+ description: "Appends the provided content to the end of a specified file in the vault. If the file does not exist, it will be created.",
73
73
  examples: [
74
74
  {
75
75
  description: "Append a new task",
@@ -120,17 +120,17 @@ export class AppendContentToolHandler extends BaseToolHandler {
120
120
  /**
121
121
  * Tool handler for updating file content
122
122
  */
123
- export class PatchContentToolHandler extends BaseToolHandler {
123
+ export class UpdateContentToolHandler extends BaseToolHandler {
124
124
  constructor(client) {
125
- super(FILE_CONTENT_TOOL_NAMES.PATCH_CONTENT, client);
125
+ super(FILE_CONTENT_TOOL_NAMES.UPDATE_CONTENT, client); // Renamed from PATCH_CONTENT
126
126
  }
127
127
  getToolDescription() {
128
128
  return {
129
- name: this.name,
130
- description: "Update the entire content of an existing note or create a new one.",
129
+ name: this.name, // Will be obsidian_update_content
130
+ description: "Overwrites the entire content of a specified file in the vault with the provided content. If the file does not exist, it will be created.",
131
131
  examples: [
132
132
  {
133
- description: "Update a note's content",
133
+ description: "Overwrite a note's content",
134
134
  args: {
135
135
  filepath: "project.md",
136
136
  content: "# Project Notes\n\nThis will replace the entire content of the note."
@@ -147,7 +147,7 @@ export class PatchContentToolHandler extends BaseToolHandler {
147
147
  },
148
148
  content: {
149
149
  type: "string",
150
- description: "New content for the note (replaces existing content)"
150
+ description: "The new, complete content for the file (overwrites existing content)."
151
151
  }
152
152
  },
153
153
  required: ["filepath", "content"]
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * File operation tools exports
3
3
  */
4
- export * from './list.js';
5
4
  export * from './content.js';
6
- import { ListFilesInVaultToolHandler, ListFilesInDirToolHandler } from './list.js';
7
- import { GetFileContentsToolHandler, AppendContentToolHandler, PatchContentToolHandler } from './content.js';
5
+ export * from './list.js';
6
+ import { AppendContentToolHandler, GetFileContentsToolHandler, UpdateContentToolHandler } from './content.js';
7
+ import { ListFilesInDirToolHandler, ListFilesInVaultToolHandler } from './list.js';
8
8
  /**
9
9
  * Create all file-related tool handlers
10
10
  * @param client The ObsidianClient instance
@@ -16,7 +16,7 @@ export function createFileToolHandlers(client) {
16
16
  new ListFilesInDirToolHandler(client),
17
17
  new GetFileContentsToolHandler(client),
18
18
  new AppendContentToolHandler(client),
19
- new PatchContentToolHandler(client)
19
+ new UpdateContentToolHandler(client)
20
20
  ];
21
21
  }
22
22
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
- import { BaseToolHandler } from "../base.js";
2
1
  import { createLogger } from "../../utils/logging.js";
2
+ import { BaseToolHandler } from "../base.js";
3
3
  // Create a logger for file operations
4
4
  const logger = createLogger('FileTools');
5
5
  /**
@@ -19,7 +19,7 @@ export class ListFilesInVaultToolHandler extends BaseToolHandler {
19
19
  getToolDescription() {
20
20
  return {
21
21
  name: this.name,
22
- description: "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.",
22
+ description: "Lists all files and directories within the root of your Obsidian vault. Returns a hierarchical structure detailing files, folders, and their types.",
23
23
  examples: [
24
24
  {
25
25
  description: "List all files in vault",
@@ -74,7 +74,7 @@ export class ListFilesInDirToolHandler extends BaseToolHandler {
74
74
  getToolDescription() {
75
75
  return {
76
76
  name: this.name,
77
- description: "Lists all files and directories that exist in a specific Obsidian directory. Returns a hierarchical structure showing files, folders, and their relationships. Useful for exploring vault organization and finding specific files.",
77
+ description: "Lists files and directories within a specific folder in your Obsidian vault. Returns a hierarchical structure. Note: Empty directories may not be included in the results. Useful for exploring vault organization.",
78
78
  examples: [
79
79
  {
80
80
  description: "List files in Documents folder",
@@ -1,11 +1,11 @@
1
1
  import { createFileToolHandlers } from './files/index.js';
2
- import { createSearchToolHandlers } from './search/index.js';
3
2
  import { createPropertyToolHandlers } from './properties/index.js';
3
+ import { createSearchToolHandlers } from './search/index.js';
4
4
  // Export the base handler and all submodules
5
5
  export * from './base.js';
6
6
  export * from './files/index.js';
7
- export * from './search/index.js';
8
7
  export * from './properties/index.js';
8
+ export * from './search/index.js';
9
9
  /**
10
10
  * Create all tool handlers
11
11
  * @param client The ObsidianClient instance
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Properties module exports
3
3
  */
4
- export * from './types.js';
5
4
  export * from './manager.js';
6
5
  export * from './tools.js';
6
+ export * from './types.js';
7
7
  import { GetPropertiesToolHandler, UpdatePropertiesToolHandler } from './tools.js';
8
8
  /**
9
9
  * Create all property-related tool handlers