Release v1.2.0: Enhanced Authentication & Parent Folder Detection

Phase 1.5 Complete:
- Add automatic API key generation with secure random generation
- Add createParents parameter to create_note tool
- Fix authentication vulnerability (auth enabled without key)
- Add MCP client configuration snippet generator
- Improve UI/UX for authentication management
- Add comprehensive test coverage

Security:
- Fixed critical vulnerability in authentication middleware
- Implement three-layer defense (UI, server start, middleware)
- Cryptographically secure key generation (32 chars)

Features:
- Auto-generate API key when authentication enabled
- Copy/regenerate buttons for API key management
- Recursive parent folder creation for nested paths
- Enhanced error messages with actionable guidance
- Selectable connection information and config snippets

Documentation:
- Updated CHANGELOG.md with v1.2.0 release notes
- Updated ROADMAP.md (Phase 1.5 marked complete)
- Created IMPLEMENTATION_NOTES_AUTH.md
- Created RELEASE_NOTES_v1.2.0.md
This commit is contained in:
2025-10-16 22:11:33 -04:00
parent 7524271eaa
commit d074470d11
15 changed files with 823 additions and 375 deletions

View File

@@ -30,17 +30,21 @@ export class ToolRegistry {
},
{
name: "create_note",
description: "Create a new file in the Obsidian vault. Use this to create a new note or file. The parent folder must already exist - this will NOT auto-create folders. Path must be vault-relative with file extension. Will fail if the file already exists. Use list_notes() to verify the parent folder exists before creating.",
description: "Create a new file in the Obsidian vault. Use this to create a new note or file. By default, parent folders must already exist. Set createParents to true to automatically create missing parent folders. Path must be vault-relative with file extension. Will fail if the file already exists. Use list_notes() to verify the parent folder exists before creating.",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "Vault-relative path for the new file (e.g., 'folder/note.md' or 'projects/2024/report.md'). Must include file extension. Parent folders must exist. Paths are case-sensitive on macOS/Linux. Do not use leading or trailing slashes."
description: "Vault-relative path for the new file (e.g., 'folder/note.md' or 'projects/2024/report.md'). Must include file extension. Paths are case-sensitive on macOS/Linux. Do not use leading or trailing slashes."
},
content: {
type: "string",
description: "The complete content to write to the new file. Can include markdown formatting, frontmatter, etc."
},
createParents: {
type: "boolean",
description: "If true, automatically create missing parent folders. If false (default), returns an error if parent folders don't exist. Default: false"
}
},
required: ["path", "content"]
@@ -122,7 +126,7 @@ export class ToolRegistry {
case "read_note":
return await this.noteTools.readNote(args.path);
case "create_note":
return await this.noteTools.createNote(args.path, args.content);
return await this.noteTools.createNote(args.path, args.content, args.createParents ?? false);
case "update_note":
return await this.noteTools.updateNote(args.path, args.content);
case "delete_note":

View File

@@ -53,7 +53,7 @@ export class NoteTools {
}
}
async createNote(path: string, content: string): Promise<CallToolResult> {
async createNote(path: string, content: string, createParents: boolean = false): Promise<CallToolResult> {
// Validate path
if (!path || path.trim() === '') {
return {
@@ -88,30 +88,72 @@ export class NoteTools {
};
}
// Explicit parent folder detection (before write operation)
const parentPath = PathUtils.getParentPath(normalizedPath);
if (parentPath) {
// Check if parent exists
if (!PathUtils.pathExists(this.app, parentPath)) {
if (createParents) {
// Auto-create parent folders recursively
try {
await this.createParentFolders(parentPath);
} catch (error) {
return {
content: [{ type: "text", text: ErrorMessages.operationFailed('create parent folders', parentPath, (error as Error).message) }],
isError: true
};
}
} else {
// Return clear error before attempting file creation
return {
content: [{ type: "text", text: ErrorMessages.parentFolderNotFound(normalizedPath, parentPath) }],
isError: true
};
}
}
// Check if parent is actually a folder (not a file)
if (PathUtils.fileExists(this.app, parentPath)) {
return {
content: [{ type: "text", text: ErrorMessages.notAFolder(parentPath) }],
isError: true
};
}
}
// Proceed with file creation
try {
const file = await this.app.vault.create(normalizedPath, content);
return {
content: [{ type: "text", text: `Note created successfully: ${file.path}` }]
};
} catch (error) {
const errorMsg = (error as Error).message;
// Check for parent folder not found error
if (errorMsg.includes('parent folder')) {
const parentPath = PathUtils.getParentPath(normalizedPath);
return {
content: [{ type: "text", text: ErrorMessages.parentFolderNotFound(normalizedPath, parentPath) }],
isError: true
};
}
return {
content: [{ type: "text", text: ErrorMessages.operationFailed('create note', normalizedPath, errorMsg) }],
content: [{ type: "text", text: ErrorMessages.operationFailed('create note', normalizedPath, (error as Error).message) }],
isError: true
};
}
}
/**
* Recursively create parent folders
* @private
*/
private async createParentFolders(path: string): Promise<void> {
// Get parent path
const parentPath = PathUtils.getParentPath(path);
// If there's a parent and it doesn't exist, create it first (recursion)
if (parentPath && !PathUtils.pathExists(this.app, parentPath)) {
await this.createParentFolders(parentPath);
}
// Create the current folder if it doesn't exist
if (!PathUtils.pathExists(this.app, path)) {
await this.app.vault.createFolder(path);
}
}
async updateNote(path: string, content: string): Promise<CallToolResult> {
// Validate path
if (!path || path.trim() === '') {