Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| edd445f8ae | |||
| c8abde8a88 | |||
| a0801a82fd | |||
| 82d2409fe3 | |||
| 57b58a0d22 | |||
| 466587d1c5 |
52
CHANGELOG.md
52
CHANGELOG.md
@@ -73,6 +73,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Comprehensive input schema definitions
|
- Comprehensive input schema definitions
|
||||||
- Security audit of parameter handling
|
- Security audit of parameter handling
|
||||||
|
|
||||||
|
## [1.1.3] - 2026-04-17
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Large File Chunking**: Fixed issue #5 where reading large files (e.g. PDFs) caused a "Tool result is too large" error in Claude Desktop
|
||||||
|
- `obsidian_read_note` now returns at most 50,000 characters by default (configurable up to 500,000)
|
||||||
|
- New `max_chars` parameter caps the number of characters returned per call (default: 50000, max: 500000)
|
||||||
|
- New `offset` parameter enables pagination — pass the offset from the truncation message to read the next chunk
|
||||||
|
- When truncated, the response includes a message stating the total size and the exact `offset` to use for the next call
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- **Obsidian Must Be Running**: Clarified in README (issue #4) that the Obsidian application must be open and running before any MCP tools are used
|
||||||
|
- Added prominent callout in Prerequisites section
|
||||||
|
- Expanded Troubleshooting entry with explanation of root cause and fix
|
||||||
|
|
||||||
|
## [1.1.2] - 2026-04-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Ampersand in Filenames**: Fixed issue #2 where files with `&` in their names (e.g., "Research & Development.md") were causing search and file-not-found errors
|
||||||
|
- Single ampersands are now preserved in filenames and paths
|
||||||
|
- Security maintained: Dangerous `&&` command operators are still blocked by injection pattern detection
|
||||||
|
- Also preserves parentheses `()`, brackets `[]`, and braces `{}` which are safe in quoted CLI arguments
|
||||||
|
- Affects all file operations and search tools
|
||||||
|
|
||||||
|
## [1.1.1] - 2026-04-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Quote Escaping**: Fixed critical bug where note content was being truncated when containing double quotes
|
||||||
|
- Content like `"Bot QM"` is now properly escaped and passed to the CLI without truncation
|
||||||
|
- Internal double quotes are escaped as `\"` before being wrapped in parameter quotes
|
||||||
|
- Prevents shell from misinterpreting quote boundaries in parameter values
|
||||||
|
- Affects all tools that pass content: create, append, prepend, search queries, etc.
|
||||||
|
|
||||||
|
## [1.1.0] - 2026-04-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Square Brackets Preservation**: Fixed critical bug where square brackets `[` and `]` were being removed from note content during sanitization
|
||||||
|
- Wikilinks (`[[link]]`) now work correctly when creating or modifying notes
|
||||||
|
- Task checkboxes (`- [ ] Task` and `- [x] Done`) are properly preserved
|
||||||
|
- Array notation and date formats with brackets are no longer corrupted
|
||||||
|
- Security: Square brackets are safe because parameter values are quoted and passed as array arguments to the CLI
|
||||||
|
- All dangerous shell metacharacters (`;`, `|`, `$()`, backticks, etc.) are still properly blocked
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Planned
|
### Planned
|
||||||
@@ -86,9 +128,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Version History
|
## Version History
|
||||||
|
|
||||||
|
- **1.1.3** - Bug fix release: Large file chunking for obsidian_read_note; docs clarification for Obsidian must be running (fixes #4, #5)
|
||||||
|
- **1.1.2** - Bug fix release: Ampersand support in filenames (fixes #2)
|
||||||
|
- **1.1.1** - Bug fix release: Quote escaping in note content
|
||||||
|
- **1.1.0** - Bug fix release: Square brackets preservation in note content
|
||||||
- **1.0.0** - Initial release with 28 MCP tools across 3 user stories
|
- **1.0.0** - Initial release with 28 MCP tools across 3 user stories
|
||||||
- File Operations (8 tools)
|
- File Operations (8 tools)
|
||||||
- Search & Discovery (12 tools)
|
- Search & Discovery (12 tools)
|
||||||
- Task & Property Management (8 tools)
|
- Task & Property Management (8 tools)
|
||||||
|
|
||||||
[1.0.0]: https://github.com/yourusername/obsidian-mcp/releases/tag/v1.0.0
|
[1.1.3]: https://git.mortons.site/Peter.Morton/obsidian-mcp/releases/tag/v1.1.3
|
||||||
|
[1.1.2]: https://git.mortons.site/Peter.Morton/obsidian-mcp/releases/tag/v1.1.2
|
||||||
|
[1.1.1]: https://git.mortons.site/Peter.Morton/obsidian-mcp/releases/tag/v1.1.1
|
||||||
|
[1.1.0]: https://git.mortons.site/Peter.Morton/obsidian-mcp/releases/tag/v1.1.0
|
||||||
|
[1.0.0]: https://git.mortons.site/Peter.Morton/obsidian-mcp/releases/tag/v1.0.0
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ An MCP (Model Context Protocol) Bundle that exposes Obsidian CLI capabilities to
|
|||||||
|
|
||||||
- Node.js >= 18.0.0
|
- Node.js >= 18.0.0
|
||||||
- Obsidian CLI installed and configured
|
- Obsidian CLI installed and configured
|
||||||
- Obsidian application installed
|
- Obsidian application installed and **running**
|
||||||
|
|
||||||
|
> **Important:** The Obsidian application must be open and running before using any MCP tools. The CLI used by this bundle communicates with the running Obsidian instance. If Obsidian is not running, commands will launch the GUI instead of executing — start Obsidian first, then use the MCP tools.
|
||||||
|
|
||||||
### Install via Claude Desktop
|
### Install via Claude Desktop
|
||||||
|
|
||||||
@@ -194,7 +196,9 @@ Contributions are welcome! Please ensure:
|
|||||||
|
|
||||||
### "Obsidian not running" error
|
### "Obsidian not running" error
|
||||||
|
|
||||||
Start the Obsidian application before using MCP tools.
|
The Obsidian application must be running before using any MCP tools. The CLI communicates with the running Obsidian instance — if Obsidian is closed, commands will launch the GUI app instead of executing.
|
||||||
|
|
||||||
|
**Fix:** Open the Obsidian application and wait for it to fully load, then retry the operation.
|
||||||
|
|
||||||
### "Vault not found" error
|
### "Vault not found" error
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": "0.3",
|
"manifest_version": "0.3",
|
||||||
"name": "obsidian-mcp",
|
"name": "obsidian-mcp",
|
||||||
"version": "1.0.0",
|
"version": "1.1.3",
|
||||||
"display_name": "Obsidian CLI Bundle",
|
"display_name": "Obsidian CLI Bundle",
|
||||||
"description": "MCP Bundle for Obsidian CLI - Enable AI assistants to manage Obsidian vaults through conversational interface",
|
"description": "MCP Bundle for Obsidian CLI - Enable AI assistants to manage Obsidian vaults through conversational interface",
|
||||||
"long_description": "This MCP bundle provides a comprehensive set of tools for AI assistants to interact with and manage Obsidian vaults. It includes capabilities for creating, reading, updating, and deleting notes, managing links and tags, handling tasks, and more. With this bundle, AI assistants can seamlessly integrate with Obsidian to help users organize their knowledge and workflows.",
|
"long_description": "This MCP bundle provides a comprehensive set of tools for AI assistants to interact with and manage Obsidian vaults. It includes capabilities for creating, reading, updating, and deleting notes, managing links and tags, handling tasks, and more. With this bundle, AI assistants can seamlessly integrate with Obsidian to help users organize their knowledge and workflows.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-mcp",
|
"name": "obsidian-mcp",
|
||||||
"version": "1.0.0",
|
"version": "1.1.3",
|
||||||
"description": "MCP Bundle for Obsidian CLI - Enable AI assistants to manage Obsidian vaults through Model Context Protocol",
|
"description": "MCP Bundle for Obsidian CLI - Enable AI assistants to manage Obsidian vaults through Model Context Protocol",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro
|
|||||||
// T031: Read note tool
|
// T031: Read note tool
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
'obsidian_read_note',
|
'obsidian_read_note',
|
||||||
'Read the content of a note from the Obsidian vault. Specify either the note name (file) or full path (path).',
|
'Read the content of a note from the Obsidian vault. Specify either the note name (file) or full path (path). For large files (e.g. PDFs), use max_chars and offset to read in chunks and avoid exceeding context limits.',
|
||||||
{
|
{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -142,6 +142,14 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Exact file path (folder/note.md)',
|
description: 'Exact file path (folder/note.md)',
|
||||||
},
|
},
|
||||||
|
max_chars: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum characters to return (default: 50000, max: 500000). Use to avoid output-too-large errors on big files.',
|
||||||
|
},
|
||||||
|
offset: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Character offset to start reading from (default: 0). Use with max_chars to page through large files.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createToolHandler(
|
createToolHandler(
|
||||||
@@ -157,6 +165,14 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Exact file path (folder/note.md)',
|
description: 'Exact file path (folder/note.md)',
|
||||||
},
|
},
|
||||||
|
max_chars: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum characters to return (default: 50000, max: 500000)',
|
||||||
|
},
|
||||||
|
offset: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Character offset to start reading from (default: 0)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (args) => {
|
async (args) => {
|
||||||
@@ -170,11 +186,24 @@ export async function registerFileOperationTools(server: ObsidianMCPServer): Pro
|
|||||||
const result = await executeObsidianCommand('read', cmdArgs);
|
const result = await executeObsidianCommand('read', cmdArgs);
|
||||||
handleCLIResult(result, { operation: 'read_note', identifier: sanitized.file || sanitized.path });
|
handleCLIResult(result, { operation: 'read_note', identifier: sanitized.file || sanitized.path });
|
||||||
|
|
||||||
|
const offset: number = validated.offset ?? 0;
|
||||||
|
const maxChars: number = validated.max_chars ?? 50000;
|
||||||
|
const fullContent = result.stdout;
|
||||||
|
const totalChars = fullContent.length;
|
||||||
|
const chunk = fullContent.slice(offset, offset + maxChars);
|
||||||
|
const isTruncated = offset + maxChars < totalChars;
|
||||||
|
|
||||||
|
let text = chunk;
|
||||||
|
if (isTruncated) {
|
||||||
|
const nextOffset = offset + maxChars;
|
||||||
|
text += `\n\n[Content truncated: showing characters ${offset}–${offset + chunk.length} of ${totalChars} total. To read the next chunk, call obsidian_read_note again with offset=${nextOffset}.]`;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: formatForMCP(result.stdout, 'text'),
|
text,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,12 @@
|
|||||||
export function formatParam(key: string, value: string | number): string {
|
export function formatParam(key: string, value: string | number): string {
|
||||||
// Always quote string values to handle spaces and special characters safely
|
// Always quote string values to handle spaces and special characters safely
|
||||||
// Note: Obsidian CLI docs say: "Quote values with spaces: name="My Note""
|
// Note: Obsidian CLI docs say: "Quote values with spaces: name="My Note""
|
||||||
return `${key}="${value}"`;
|
|
||||||
|
// Escape any double quotes in the value to prevent shell interpretation issues
|
||||||
|
// This prevents truncation when content contains quotes like "Bot QM"
|
||||||
|
const escapedValue = String(value).replace(/"/g, '\\"');
|
||||||
|
|
||||||
|
return `${key}="${escapedValue}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,8 +8,13 @@ import { logger } from '../utils/logger.js';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Characters that should be removed or escaped for security
|
* Characters that should be removed or escaped for security
|
||||||
|
* Note: Brackets [], parentheses (), and braces {} are safe because values are quoted and passed as array args
|
||||||
|
* They're essential for Obsidian markdown (wikilinks [[link]], tasks - [ ] Task, templates {{...}}, etc.)
|
||||||
|
* Note: Single & is safe in quoted args (filenames like "Research & Development.md")
|
||||||
|
* We only block: ; | ` $ < > (command separators, pipes, substitution, redirects)
|
||||||
|
* Command injection patterns (&&, ||, etc.) are handled separately
|
||||||
*/
|
*/
|
||||||
const DANGEROUS_CHARS = /[;&|`$(){}[\]<>]/g;
|
const DANGEROUS_CHARS = /[;|`$<>]/g;
|
||||||
const COMMAND_INJECTION_PATTERNS = [
|
const COMMAND_INJECTION_PATTERNS = [
|
||||||
/\$\(/g, // Command substitution $(...)
|
/\$\(/g, // Command substitution $(...)
|
||||||
/`[^`]*`/g, // Command substitution `...`
|
/`[^`]*`/g, // Command substitution `...`
|
||||||
@@ -67,7 +72,8 @@ export function sanitizePath(path: string): string {
|
|||||||
sanitized = sanitized.replace(/^\/+|\/+$/g, '');
|
sanitized = sanitized.replace(/^\/+|\/+$/g, '');
|
||||||
|
|
||||||
// Remove dangerous characters but allow path separators
|
// Remove dangerous characters but allow path separators
|
||||||
sanitized = sanitized.replace(/[;&|`$(){}[\]<>]/g, '');
|
// Note: Brackets, parentheses, braces, and single & are safe in paths (quoted args)
|
||||||
|
sanitized = sanitized.replace(/[;|`$<>]/g, '');
|
||||||
|
|
||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,11 +104,13 @@ export const createNoteSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Read note parameters
|
// Read note parameters
|
||||||
export const readNoteSchema = z.union([
|
export const readNoteSchema = z.object({
|
||||||
z.object({ file: noteNameSchema }),
|
file: noteNameSchema.optional(),
|
||||||
z.object({ path: filePathSchema }),
|
path: filePathSchema.optional(),
|
||||||
]).refine(
|
offset: z.number().int().nonnegative().optional().default(0),
|
||||||
(data) => ('file' in data && data.file) || ('path' in data && data.path),
|
max_chars: z.number().int().positive().max(500000).optional().default(50000),
|
||||||
|
}).refine(
|
||||||
|
(data) => data.file || data.path,
|
||||||
{ message: 'Either file or path must be provided' }
|
{ message: 'Either file or path must be provided' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user