@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 +27 -2
- package/dist/server.js +45 -1
- package/dist/src/filesystem.js +63 -0
- package/package.json +1 -1
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
|
-
|
|
9
|
+
<div align="center">
|
|
10
|
+
|
|
11
|
+
[https://mcp-obsidian.org](https://mcp-obsidian.org)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div align="center">
|
|
16
|
+
|
|
17
|
+
[](https://github.com/bitbonsai/mcp-obsidian)
|
|
18
|
+
[](https://github.com/sponsors/bitbonsai)
|
|
19
|
+
[](https://github.com/sponsors/bitbonsai)
|
|
20
|
+
[](https://ko-fi.com/bitbonsai)
|
|
21
|
+
[](https://liberapay.com/bitbonsai/)
|
|
22
|
+
|
|
23
|
+
</div>
|
|
10
24
|
|
|
11
|
-
|
|
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.
|
|
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 {
|
package/dist/src/filesystem.js
CHANGED
|
@@ -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;
|