### Development Setup Commands Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/README.md Commands to set up the development environment for obsidian-mcp. This includes cloning the repository, installing Node.js dependencies, and building the project. ```bash # Clone the repository git clone https://github.com/StevenStavrakis/obsidian-mcp cd obsidian-mcp # Install dependencies npm install # Build npm run build ``` -------------------------------- ### Perform File System Operations in TypeScript Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md Illustrates safe file system operations in TypeScript. The 'Bad' example shows a direct write, while the 'Good' example emphasizes ensuring parent directories exist before writing files, preventing common errors. ```typescript // ❌ Bad // await fs.writeFile(path, content); // ✅ Good // await ensureDirectory(path.dirname(fullPath)); // await fs.writeFile(fullPath, content, 'utf8'); ``` -------------------------------- ### Format Responses in TypeScript Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md Shows preferred methods for formatting tool responses in TypeScript. The 'Bad' example uses raw JSON stringification, while the 'Good' example suggests using a dedicated utility for consistent and readable output. ```typescript // ❌ Bad // return createToolResponse(JSON.stringify(result)); // ✅ Good // return createToolResponse(formatOperationResult(result)); ``` -------------------------------- ### Implement Core Tool Functionality Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Provides an example of a private async function that encapsulates the tool's core logic. It outlines parameter handling, return types, and error management, including custom error types like `McpError` and file system error handling. ```typescript async function performOperation( vaultPath: string, param1: string, param2: number, optionalParam?: string ): Promise { try { // Implement core functionality // Use utility functions for common operations // Handle errors appropriately return { success: true, message: "Operation completed successfully", // Include relevant details }; } catch (error) { if (error instanceof McpError) { throw error; } throw handleFsError(error, 'operation name'); } } ``` -------------------------------- ### Test Schema Conversion with createSchemaHandler Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Demonstrates the correct usage of `createSchemaHandler` for testing Zod schema conversion to JSON Schema. This example uses a discriminated union for different operations. ```typescript const schema = z.discriminatedUnion('operation', [ z.object({ operation: z.literal('read'), path: z.string() }), z.object({ operation: z.literal('write'), path: z.string(), content: z.string() }) ]); // Verify schema handler creation succeeds const schemaHandler = createSchemaHandler(schema); ``` -------------------------------- ### Handle Errors in TypeScript Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md Illustrates proper error handling in TypeScript. The 'Good' example shows how to catch specific errors, re-throw known error types, and wrap unexpected file system errors with context. ```typescript // ❌ Bad // catch (error) { // return createToolResponse(`Error: ${error}`); // } // ✅ Good // catch (error) { // if (error instanceof McpError) { // throw error; // } // throw handleFsError(error, 'operation name'); // } ``` -------------------------------- ### Validate Inputs in TypeScript Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md Demonstrates input validation best practices in TypeScript. The 'Bad' example uses a direct type assertion, while the 'Good' example shows using a schema handler for safe and validated input parsing. ```typescript // ❌ Bad // const input = args as { path: string }; // ✅ Good // const validated = schemaHandler.parse(args); ``` -------------------------------- ### Create Bad Search Tool in TypeScript Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md Demonstrates a problematic implementation of a search tool. This example features an anti-pattern of recursive file searching without limits and poor response formatting, illustrating potential issues in tool development. ```typescript import * as fs from 'fs/promises'; import * as path from 'path'; // Assuming Tool, createToolResponse, and McpError are defined elsewhere // interface Tool { name: string; description: string; inputSchema: any; handler: (args: any) => Promise; } // declare function createToolResponse(content: string): any; // declare class McpError extends Error { constructor(message?: string); } // declare function handleFsError(error: any, operation: string): McpError; // declare const schemaHandler: { parse: (args: any) => any }; // declare function validateVaultPath(vaultPath: string, fullPath: string): void; // declare function formatOperationResult(result: any): string; // declare function ensureDirectory(dir: string): Promise; export function createBadSearchTool(vaultPath: string) { return { name: "search", description: "Searches files", inputSchema: { jsonSchema: { type: "object", properties: { query: { type: "string" } } }, parse: (input: any) => input }, handler: async (args) => { // Bad: Recursive search without limits async function searchDir(dir: string): Promise { const results: string[] = []; const files = await fs.readdir(dir); for (const file of files) { const fullPath = path.join(dir, file); const stat = await fs.stat(fullPath); if (stat.isDirectory()) { results.push(...await searchDir(fullPath)); } else { const content = await fs.readFile(fullPath, 'utf8'); if (content.includes(args.query)) { results.push(fullPath); } } } return results; } try { const matches = await searchDir(vaultPath); // Poor response formatting return { type: "success", data: `Found matches in:\n${matches.join('\n')}` }; } catch (error) { return { type: "error", data: `Search failed: ${error}` }; } } }; } ``` -------------------------------- ### Create Tool Directory Structure Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Illustrates the initial step of creating a dedicated directory for a new tool within the project's source structure. This sets up the basic file organization. ```bash src/tools/your-tool-name/ └── index.ts ``` -------------------------------- ### Smithery CLI Installation Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/README.md Command to install the obsidian-mcp server for Claude Desktop automatically using the Smithery CLI. This is an alternative installation method. ```bash npx -y @smithery/cli install obsidian-mcp --client claude ``` -------------------------------- ### Define Zod Input Schema for Tool Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Demonstrates how to define a Zod schema for validating tool inputs. It includes examples of string and number validation, optional parameters, custom error messages, and parameter descriptions, utilizing `.strict()` for schema enforcement. ```typescript const schema = z.object({ param1: z.string() .min(1, "Parameter cannot be empty") .describe("Description of what this parameter does"), param2: z.number() .min(0) .describe("Description of numeric constraints"), optionalParam: z.string() .optional() .describe("Optional parameters should have clear descriptions too") }).strict(); const schemaHandler = createSchemaHandler(schema); ``` -------------------------------- ### Ensure Schema Validation in Zod Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Highlights the importance of including validation and descriptions for schema properties. This example shows a Zod object missing essential validation for a 'path' field. ```typescript const schema = z.object({ path: z.string() // Missing validation and description }); ``` -------------------------------- ### Implement Bad Write File Tool (TypeScript) Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md An anti-pattern example of a file writing tool. It suffers from vague descriptions, inadequate input schema handling with no real validation, direct file system operations without proper error handling, and poor response formatting. This implementation is prone to errors and security vulnerabilities. ```typescript // Anti-pattern example export function createBadWriteFileTool(vaultPath: string): Tool { return { name: "write-file", description: "Writes a file", // Too vague inputSchema: { // Missing proper schema handler jsonSchema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } } }, parse: (input: any) => input // No validation! }, handler: async (args) => { try { // Missing path validation const filePath = path.join(vaultPath, args.path); // Direct fs operations without proper error handling await fs.writeFile(filePath, args.content); // Poor response formatting return createToolResponse("File written"); } catch (error) { // Bad error handling return createToolResponse(`Error: ${error}`); } } }; } ``` -------------------------------- ### Perform Path Operations in TypeScript Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md Highlights safe path manipulation in TypeScript. The 'Bad' example shows a potentially unsafe path join, whereas the 'Good' example includes validation to ensure operations stay within the intended vault path. ```typescript // ❌ Bad // const fullPath = path.join(vaultPath, args.path); // ✅ Good // const fullPath = path.join(vaultPath, validated.path); // validateVaultPath(vaultPath, fullPath); ``` -------------------------------- ### Implement Search Files Tool (TypeScript) Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md A good implementation for a tool that searches for text within vault files. It uses Zod for defining a flexible input schema, allowing for search queries, case sensitivity options, and path scoping. It includes robust error handling and clear response formatting for search results. ```typescript const schema = z.object({ query: z.string() .min(1, "Search query cannot be empty") .describe("Text to search for"), caseSensitive: z.boolean() .optional() .describe("Whether to perform case-sensitive search"), path: z.string() .optional() .describe("Optional subfolder to limit search scope") }).strict(); const schemaHandler = createSchemaHandler(schema); async function searchFiles( vaultPath: string, query: string, options: SearchOptions ): Promise { try { const searchPath = options.path ? path.join(vaultPath, options.path) : vaultPath; validateVaultPath(vaultPath, searchPath); // Implementation details... return { success: true, message: "Search completed", results: matches, totalMatches: totalCount, matchedFiles: fileCount }; } catch (error) { throw handleFsError(error, 'search files'); } } export function createSearchTool(vaultPath: string): Tool { if (!vaultPath) { throw new Error("Vault path is required"); } return { name: "search-files", description: "Search for text in vault files", inputSchema: schemaHandler, handler: async (args) => { const validated = schemaHandler.parse(args); const result = await searchFiles(vaultPath, validated.query, { caseSensitive: validated.caseSensitive, path: validated.path }); return createToolResponse(formatSearchResult(result)); } }; } ``` -------------------------------- ### Use Standardized Responses Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Demonstrates an anti-pattern in response formatting where raw strings are returned, making the output vague. It's recommended to use utility functions like `createToolResponse` for standardized results. ```typescript return createToolResponse("Done"); // Too vague ``` -------------------------------- ### Avoid Duplicating Utility Functions Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Highlights an anti-pattern in code organization where existing utility functions are reimplemented. Always leverage existing utilities to maintain consistency and reduce redundancy. ```typescript function isValidPath(path: string) { // Don't reimplement existing utilities } ``` -------------------------------- ### Implement Write File Tool (TypeScript) Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/tool-examples.md A good implementation of a tool to write content to a file within a vault. It utilizes Zod for robust input validation, custom utility functions for path validation and error handling, and follows a clear structure for tool definition and execution. It ensures proper directory creation and uses UTF-8 encoding for file writing. ```typescript import { z } from "zod"; import { Tool, FileOperationResult } from "../../types.js"; import { validateVaultPath } from "../../utils/path.js"; import { handleFsError } from "../../utils/errors.js"; import { createToolResponse, formatFileResult } from "../../utils/responses.js"; import { createSchemaHandler } from "../../utils/schema.js"; const schema = z.object({ path: z.string() .min(1, "Path cannot be empty") .refine(path => !path.includes('..'), "Path cannot contain '..'") .describe("Path to the file relative to vault root"), content: z.string() .min(1, "Content cannot be empty") .describe("File content to write") }).strict(); const schemaHandler = createSchemaHandler(schema); async function writeFile( vaultPath: string, filePath: string, content: string ): Promise { const fullPath = path.join(vaultPath, filePath); validateVaultPath(vaultPath, fullPath); try { await ensureDirectory(path.dirname(fullPath)); await fs.writeFile(fullPath, content, 'utf8'); return { success: true, message: "File written successfully", path: fullPath, operation: 'create' }; } catch (error) { throw handleFsError(error, 'write file'); } } export function createWriteFileTool(vaultPath: string): Tool { if (!vaultPath) { throw new Error("Vault path is required"); } return { name: "write-file", description: "Write content to a file in the vault", inputSchema: schemaHandler, handler: async (args) => { const validated = schemaHandler.parse(args); const result = await writeFile(vaultPath, validated.path, validated.content); return createToolResponse(formatFileResult(result)); } }; } ``` -------------------------------- ### Create Tool Factory Function Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Shows how to export a factory function that constructs and returns the tool's interface. This includes setting the tool's name, description, input schema handler, and the main execution handler, with integrated validation and error handling. ```typescript export function createYourTool(vaultPath: string): Tool { if (!vaultPath) { throw new Error("Vault path is required"); } return { name: "your-tool-name", description: `Clear description of what the tool does. Examples: - Basic usage: { "param1": "value", "param2": 42 } - With options: { "param1": "value", "param2": 42, "optionalParam": "extra" }`, inputSchema: schemaHandler, handler: async (args) => { try { const validated = schemaHandler.parse(args); const result = await performOperation( vaultPath, validated.param1, validated.param2, validated.optionalParam ); return createToolResponse(formatOperationResult(result)); } catch (error) { if (error instanceof z.ZodError) { throw new McpError( ErrorCode.InvalidRequest, `Invalid arguments: ${error.errors.map(e => e.message).join(", ")}` ); } throw error; } } }; } ``` -------------------------------- ### Handle Validation Errors in Handlers Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Illustrates an anti-pattern where input validation is skipped before performing an operation. Ensure all parameters are validated to prevent unexpected behavior and errors. ```typescript handler: async (args) => { const result = await performOperation(args.param); // Missing validation } ``` -------------------------------- ### Adhere to Proper Response Types Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Shows an anti-pattern in response formatting where a simple object with a message is returned instead of a structured response object. Always use proper response types for clarity and consistency. ```typescript return { message: "Success" // Missing proper response structure }; ``` -------------------------------- ### Avoid Complex Refinements in Zod Schemas Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Demonstrates a Zod schema pattern to avoid, specifically using `superRefine` with access to parent context, which can be unreliable. This practice is discouraged for maintainability and predictability. ```typescript schema.superRefine((val, ctx) => { const parent = ctx.parent; // Unreliable }); ``` -------------------------------- ### Avoid Mixing Concerns in Handlers Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Illustrates an anti-pattern in code organization where core logic is placed directly within the handler function. It's better to split complex logic into smaller, reusable functions. ```typescript handler: async (args) => { // Don't put core logic here const files = await fs.readdir(path); // ... more direct implementation } ``` -------------------------------- ### Avoid Non-JSON Schema Compatible Zod Features Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Shows an anti-pattern in schema design where complex Zod refinements that do not translate well to JSON Schema are used. Prefer features with clear JSON Schema equivalents. ```typescript const schema = z.object({ data: z.any().superRefine((val, ctx) => { // Complex custom validation that won't translate }) }); ``` -------------------------------- ### Conditional Validation with Discriminated Unions Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Demonstrates best practices for handling conditional validation requirements using Zod's discriminated unions. This approach is preferred over complex refinements for better JSON Schema compatibility and clarity. ```typescript // ✅ DO: Use discriminated unions for different operation types const deleteSchema = z.object({ operation: z.literal('delete'), target: z.string(), content: z.undefined() }).strict(); const editSchema = z.object({ operation: z.enum(['update', 'append']), target: z.string(), content: z.string().min(1) }).strict(); const schema = z.discriminatedUnion('operation', [ deleteSchema, editSchema ]); // ❌ DON'T: Use complex refinements that don't translate well to JSON Schema const schema = z.object({ operation: z.enum(['delete', 'update', 'append']), target: z.string(), content: z.string().optional() }).superRefine((data, ctx) => { if (data.operation === 'delete') { if (data.content !== undefined) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Content not allowed for delete" }); } } else if (!data.content) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Content required for non-delete" }); } }); ``` -------------------------------- ### Avoid Throwing Raw Errors in Catch Blocks Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Shows an anti-pattern in error handling where raw errors are re-thrown without any processing or conversion. It's better to convert errors to a standardized format like McpErrors. ```typescript catch (error) { throw error; } ``` -------------------------------- ### Separate Validation Concerns in Zod Schemas Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Illustrates a Zod schema anti-pattern where validation logic for different operations is mixed within a single `superRefine` block. It's recommended to keep validation concerns separate and focused. ```typescript const schema = z.object({ operation: z.enum(['delete', 'update']), content: z.string().superRefine((val, ctx) => { // Don't put operation-specific logic here }) }); ``` -------------------------------- ### Validate File Paths in Zod Schemas Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/docs/creating-tools.md Demonstrates a Zod schema anti-pattern where a file path is defined as a simple string without specific path validation or description. It's crucial to add validation for fields like file paths. ```typescript const schema = z.object({ path: z.string().describe("File path") // Missing path validation }); ``` -------------------------------- ### Manual Claude Desktop Configuration Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/README.md Configuration for manually adding the obsidian-mcp server to Claude Desktop. This JSON snippet specifies the command to execute and its arguments, including paths to Obsidian vaults. ```json { "mcpServers": { "obsidian": { "command": "npx", "args": ["-y", "obsidian-mcp", "/path/to/your/vault", "/path/to/your/vault2"] } } } ``` ```json "Users/username/Documents/MyVault" ``` ```json "C:\\Users\\username\\Documents\\MyVault" ``` -------------------------------- ### Obsidian MCP Server Tools Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/README.md Lists the available tools provided by the obsidian-mcp server for interacting with an Obsidian vault. Each tool performs a specific action on notes or tags. ```APIDOC read-note - Read the contents of a note create-note - Create a new note edit-note - Edit an existing note delete-note - Delete a note move-note - Move a note to a different location create-directory - Create a new directory search-vault - Search notes in the vault add-tags - Add tags to a note remove-tags - Remove tags from a note rename-tag - Rename a tag across all notes manage-tags - List and organize tags list-available-vaults - List all available vaults (helps with multi-vault setups) ``` -------------------------------- ### Development Claude Desktop Configuration Source: https://github.com/stevenstavrakis/obsidian-mcp/blob/main/README.md Configuration for adding the locally built obsidian-mcp server to Claude Desktop during development. This JSON snippet points to the build output of the project. ```json { "mcpServers": { "obsidian": { "command": "node", "args": ["/build/main.js", "/path/to/your/vault", "/path/to/your/vault2"] } } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.