@mauricio.wolff/mcp-obsidian 0.5.5 → 0.6.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.
package/README.md CHANGED
@@ -6,9 +6,26 @@
6
6
 
7
7
  A universal AI bridge for Obsidian vaults using the Model Context Protocol (MCP) standard. Connect any MCP-compatible AI assistant to your knowledge base - works with Claude, ChatGPT, and future AI tools. This server provides safe read/write access to your notes while preventing YAML frontmatter corruption.
8
8
 
9
- 🌐 **Website:** [mcp-obsidian.org](https://mcp-obsidian.org)
9
+ <div align="center">
10
+
11
+ [https://mcp-obsidian.org](https://mcp-obsidian.org)
12
+
13
+ </div>
14
+
15
+ <div align="center">
16
+
17
+ [![GitHub Stars](https://img.shields.io/github/stars/bitbonsai/mcp-obsidian?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/bitbonsai/mcp-obsidian)
18
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/BitBonsai?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/sponsors/bitbonsai)
19
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/BitBonsai?style=flat&logo=github&logoColor=white&color=9065ea&labelColor=262626)](https://github.com/sponsors/bitbonsai)
20
+ [![Ko-Fi](https://img.shields.io/badge/Ko--fi-Support%20Me-9065ea?style=flat&logo=ko-fi&logoColor=white&labelColor=262626)](https://ko-fi.com/bitbonsai)
21
+ [![Liberapay](https://img.shields.io/badge/Liberapay-Weekly%20Support-9065ea?style=flat&logo=liberapay&logoColor=white&labelColor=262626)](https://liberapay.com/bitbonsai/)
22
+
23
+ </div>
10
24
 
11
- **Universal Compatibility:** Works with any MCP-compatible AI assistant including Claude Desktop, Claude Code, ChatGPT Desktop (Enterprise+), IntelliJ IDEA 2025.1+, Cursor IDE, Windsurf IDE, and future AI platforms that adopt the MCP standard.
25
+
26
+
27
+ ## Universal Compatibility
28
+ Works with any MCP-compatible AI assistant including Claude Desktop, Claude Code, ChatGPT Desktop (Enterprise+), IntelliJ IDEA 2025.1+, Cursor IDE, Windsurf IDE, and future AI platforms that adopt the MCP standard.
12
29
 
13
30
  https://github.com/user-attachments/assets/657ac4c6-1cd2-4cc3-829f-fd095a32f71c
14
31
 
@@ -242,6 +259,14 @@ Edit `.claude.json` in your project or add to the projects section:
242
259
  claude mcp add obsidian --scope user npx @mauricio.wolff/mcp-obsidian /path/to/your/vault
243
260
  ```
244
261
 
262
+ #### Goose Desktop
263
+
264
+ On Goose Desktop settings, click **Add custom extension**, and on the command field add:
265
+
266
+ ```bash
267
+ npx @mauricio.wolff/mcp-obsidian@latest /path/to/your/vault
268
+ ```
269
+
245
270
  #### Other MCP-Compatible Clients (2025)
246
271
 
247
272
  **Confirmed MCP Support:**
package/dist/server.js CHANGED
@@ -18,7 +18,7 @@ const fileSystem = new FileSystemService(vaultPath, pathFilter, frontmatterHandl
18
18
  const searchService = new SearchService(vaultPath, pathFilter);
19
19
  const server = new Server({
20
20
  name: "mcp-obsidian",
21
- version: "0.5.4"
21
+ version: "0.6.0"
22
22
  }, {
23
23
  capabilities: {
24
24
  tools: {},
@@ -69,6 +69,33 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
69
69
  required: ["path", "content"]
70
70
  }
71
71
  },
72
+ {
73
+ name: "patch_note",
74
+ description: "Efficiently update part of a note by replacing a specific string. This is more efficient than rewriting the entire note for small changes.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ path: {
79
+ type: "string",
80
+ description: "Path to the note relative to vault root"
81
+ },
82
+ oldString: {
83
+ type: "string",
84
+ description: "The exact string to replace. Must match exactly including whitespace and line breaks."
85
+ },
86
+ newString: {
87
+ type: "string",
88
+ description: "The new string to insert in place of oldString"
89
+ },
90
+ replaceAll: {
91
+ type: "boolean",
92
+ description: "If true, replace all occurrences. If false (default), the operation will fail if multiple matches are found to prevent unintended replacements.",
93
+ default: false
94
+ }
95
+ },
96
+ required: ["path", "oldString", "newString"]
97
+ }
98
+ },
72
99
  {
73
100
  name: "list_directory",
74
101
  description: "List files and directories in the vault",
@@ -321,6 +348,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
321
348
  ]
322
349
  };
323
350
  }
351
+ case "patch_note": {
352
+ const result = await fileSystem.patchNote({
353
+ path: trimmedArgs.path,
354
+ oldString: trimmedArgs.oldString,
355
+ newString: trimmedArgs.newString,
356
+ replaceAll: trimmedArgs.replaceAll
357
+ });
358
+ return {
359
+ content: [
360
+ {
361
+ type: "text",
362
+ text: JSON.stringify(result, null, 2)
363
+ }
364
+ ],
365
+ isError: !result.success
366
+ };
367
+ }
324
368
  case "list_directory": {
325
369
  const listing = await fileSystem.listDirectory(trimmedArgs.path || '');
326
370
  return {
@@ -129,6 +129,69 @@ export class FileSystemService {
129
129
  throw new Error(`Failed to write file: ${path} - ${error instanceof Error ? error.message : 'Unknown error'}`);
130
130
  }
131
131
  }
132
+ async patchNote(params) {
133
+ const { path, oldString, newString, replaceAll = false } = params;
134
+ if (!this.pathFilter.isAllowed(path)) {
135
+ return {
136
+ success: false,
137
+ path,
138
+ message: `Access denied: ${path}`
139
+ };
140
+ }
141
+ // Validate that oldString and newString are different
142
+ if (oldString === newString) {
143
+ return {
144
+ success: false,
145
+ path,
146
+ message: 'oldString and newString must be different'
147
+ };
148
+ }
149
+ try {
150
+ // Read the existing note
151
+ const note = await this.readNote(path);
152
+ // Get the full content with frontmatter
153
+ const fullContent = note.originalContent;
154
+ // Count occurrences of oldString
155
+ const occurrences = fullContent.split(oldString).length - 1;
156
+ if (occurrences === 0) {
157
+ return {
158
+ success: false,
159
+ path,
160
+ message: `String not found in note: "${oldString.substring(0, 50)}${oldString.length > 50 ? '...' : ''}"`,
161
+ matchCount: 0
162
+ };
163
+ }
164
+ // If not replaceAll and multiple occurrences exist, fail
165
+ if (!replaceAll && occurrences > 1) {
166
+ return {
167
+ success: false,
168
+ path,
169
+ message: `Found ${occurrences} occurrences of the string. Use replaceAll=true to replace all occurrences, or provide a more specific string to match exactly one occurrence.`,
170
+ matchCount: occurrences
171
+ };
172
+ }
173
+ // Perform the replacement
174
+ const updatedContent = replaceAll
175
+ ? fullContent.split(oldString).join(newString)
176
+ : fullContent.replace(oldString, newString);
177
+ // Write the updated content
178
+ const fullPath = this.resolvePath(path);
179
+ await writeFile(fullPath, updatedContent, 'utf-8');
180
+ return {
181
+ success: true,
182
+ path,
183
+ message: `Successfully replaced ${replaceAll ? occurrences : 1} occurrence${occurrences > 1 ? 's' : ''}`,
184
+ matchCount: occurrences
185
+ };
186
+ }
187
+ catch (error) {
188
+ return {
189
+ success: false,
190
+ path,
191
+ message: `Failed to patch note: ${error instanceof Error ? error.message : 'Unknown error'}`
192
+ };
193
+ }
194
+ }
132
195
  async listDirectory(path = '') {
133
196
  // Normalize path: treat '.' as root directory
134
197
  const normalizedPath = path === '.' ? '' : path;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauricio.wolff/mcp-obsidian",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant",
5
5
  "author": "bitbonsai",
6
6
  "license": "MIT",