Files
obsidian-mcp-server/src/tools/index.ts
Bill d074470d11 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
2025-10-16 22:11:33 -04:00

154 lines
6.4 KiB
TypeScript

import { App } from 'obsidian';
import { Tool, CallToolResult } from '../types/mcp-types';
import { NoteTools } from './note-tools';
import { VaultTools } from './vault-tools';
export class ToolRegistry {
private noteTools: NoteTools;
private vaultTools: VaultTools;
constructor(app: App) {
this.noteTools = new NoteTools(app);
this.vaultTools = new VaultTools(app);
}
getToolDefinitions(): Tool[] {
return [
{
name: "read_note",
description: "Read the content of a file from the Obsidian vault. Use this to read the contents of a specific note or file. Path must be vault-relative (no leading slash) and include the file extension. Use list_notes() first if you're unsure of the exact path. This only works on files, not folders.",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "Vault-relative path to the file (e.g., 'folder/note.md' or 'daily/2024-10-16.md'). Must include file extension. Paths are case-sensitive on macOS/Linux. Do not use leading or trailing slashes."
}
},
required: ["path"]
}
},
{
name: "create_note",
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. 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"]
}
},
{
name: "update_note",
description: "Update (overwrite) an existing file in the Obsidian vault. Use this to modify the contents of an existing note. This REPLACES the entire file content. The file must already exist. Path must be vault-relative with file extension. Use read_note() first to get current content if you want to make partial changes.",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "Vault-relative path to the existing file (e.g., 'folder/note.md'). Must include file extension. File must exist. Paths are case-sensitive on macOS/Linux. Do not use leading or trailing slashes."
},
content: {
type: "string",
description: "The complete new content that will replace the entire file. To make partial changes, read the file first, modify the content, then update."
}
},
required: ["path", "content"]
}
},
{
name: "delete_note",
description: "Delete a file from the Obsidian vault. Use this to permanently remove a file. This only works on files, NOT folders. The file must exist. Path must be vault-relative with file extension. This operation cannot be undone through the API.",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "Vault-relative path to the file to delete (e.g., 'folder/note.md'). Must be a file, not a folder. Must include file extension. Paths are case-sensitive on macOS/Linux. Do not use leading or trailing slashes."
}
},
required: ["path"]
}
},
{
name: "search_notes",
description: "Search for notes in the Obsidian vault by content or filename. Use this to find notes containing specific text or with specific names. Searches are case-insensitive and match against both file names and file contents. Returns a list of matching file paths.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Text to search for in note names and contents (e.g., 'TODO', 'meeting notes', 'project'). Search is case-insensitive."
}
},
required: ["query"]
}
},
{
name: "get_vault_info",
description: "Get information about the Obsidian vault including vault name, total file count, markdown file count, and root path. Use this to understand the vault structure and get an overview of available content. No parameters required.",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "list_notes",
description: "List markdown files in the vault or in a specific folder. Use this to explore vault structure, verify paths exist, or see what files are available. Call without arguments to list all files in the vault, or provide a folder path to list files in that folder. This is essential for discovering what files exist before reading, updating, or deleting them.",
inputSchema: {
type: "object",
properties: {
folder: {
type: "string",
description: "Optional vault-relative folder path to list files from (e.g., 'projects' or 'daily/2024'). Omit to list all files in vault. Do not use leading or trailing slashes. Paths are case-sensitive on macOS/Linux."
}
}
}
}
];
}
async callTool(name: string, args: any): Promise<CallToolResult> {
try {
switch (name) {
case "read_note":
return await this.noteTools.readNote(args.path);
case "create_note":
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":
return await this.noteTools.deleteNote(args.path);
case "search_notes":
return await this.vaultTools.searchNotes(args.query);
case "get_vault_info":
return await this.vaultTools.getVaultInfo();
case "list_notes":
return await this.vaultTools.listNotes(args.folder);
default:
return {
content: [{ type: "text", text: `Unknown tool: ${name}` }],
isError: true
};
}
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${(error as Error).message}` }],
isError: true
};
}
}
}