UNPKG

obsidian-mcp-server

Version:

Model Context Protocol (MCP) server designed for LLMs to interact with Obsidian vaults. Provides secure, token-aware tools for seamless knowledge base management through a standardized interface.

238 lines 9.74 kB
import { BaseToolHandler } from "../base.js"; import { PropertyManager } from "./manager.js"; import { createLogger, ErrorCategoryType } from "../../utils/logging.js"; // Create a logger for property tools const logger = createLogger('PropertyTools'); /** * Helper function to safely convert any error to an object */ function errorToObject(error) { if (error instanceof Error) { return { name: error.name, message: error.message, stack: error.stack, errorCategory: ErrorCategoryType.CATEGORY_SYSTEM }; } else if (typeof error === 'object' && error !== null) { return error; } else { return { error: String(error), errorCategory: ErrorCategoryType.CATEGORY_UNKNOWN }; } } /** * Tool names for property operations */ export const PROPERTY_TOOL_NAMES = { GET_PROPERTIES: "obsidian_get_properties", UPDATE_PROPERTIES: "obsidian_update_properties" }; /** * Tool handler for getting properties from a note */ export class GetPropertiesToolHandler extends BaseToolHandler { propertyManager; constructor(client) { super(PROPERTY_TOOL_NAMES.GET_PROPERTIES, client); this.propertyManager = new PropertyManager(client); } getToolDescription() { return { name: this.name, description: "Get properties (title, tags, status, etc.) from an Obsidian note's YAML frontmatter. Returns all available properties including custom fields.", examples: [ { description: "Get properties from a note", args: { filepath: "path/to/note.md" } }, { description: "Get properties from a documentation file", args: { filepath: "docs/architecture.md" } } ], inputSchema: { type: "object", properties: { filepath: { type: "string", description: "Path to the note file (relative to vault root)", format: "path" } }, required: ["filepath"] } }; } async runTool(args) { logger.startTimer(`get_properties_${args.filepath}`); try { logger.debug(`Getting properties from: ${args.filepath}`); const result = await this.propertyManager.getProperties(args.filepath); const elapsedMs = logger.endTimer(`get_properties_${args.filepath}`); logger.logOperationResult(true, 'get_properties', elapsedMs, { filepath: args.filepath, propertyCount: Object.keys(result.properties || {}).length }); return this.createResponse(result); } catch (error) { const elapsedMs = logger.endTimer(`get_properties_${args.filepath}`); logger.logOperationResult(false, 'get_properties', elapsedMs, { filepath: args.filepath }); logger.error(`Error getting properties from ${args.filepath}:`, errorToObject(error)); return this.handleError(error); } } } /** * Tool handler for updating properties in a note */ export class UpdatePropertiesToolHandler extends BaseToolHandler { propertyManager; constructor(client) { super(PROPERTY_TOOL_NAMES.UPDATE_PROPERTIES, client); this.propertyManager = new PropertyManager(client); } getToolDescription() { return { name: this.name, description: "Update properties in an Obsidian note's YAML frontmatter. Intelligently merges arrays (tags, type, status), handles custom fields, and automatically manages timestamps. Valid property types:\n- type: Any string value\n- status: ['draft', 'in-progress', 'review', 'complete']\n- tags: Array of strings starting with '#'\n- Other fields: title, author, version, platform, repository (URI), dependencies, sources, urls (URI), papers, custom (object)", examples: [ { description: "Update basic metadata", args: { filepath: "path/to/note.md", properties: { title: "Architecture Overview", author: "Development Team", type: ["architecture", "specification"] } } }, { description: "Update tags and status with replace", args: { filepath: "docs/feature.md", properties: { tags: ["#feature", "#in-development", "#high-priority"], status: ["in-progress"] }, replace: true } }, { description: "Add custom fields", args: { filepath: "projects/project-x.md", properties: { custom: { priority: "high", reviewedBy: ["Alice", "Bob"], dueDate: "2025-03-01" } } } } ], inputSchema: { type: "object", properties: { filepath: { type: "string", description: "Path to the note file (relative to vault root)", format: "path" }, properties: { type: "object", description: "Properties to update", properties: { title: { type: "string" }, author: { type: "string" }, type: { type: "array", items: { type: "string" } }, tags: { type: "array", items: { type: "string", pattern: "^#" } }, status: { type: "array", items: { type: "string", enum: ["draft", "in-progress", "review", "complete"] } }, version: { type: "string" }, platform: { type: "string" }, repository: { type: "string", format: "uri" }, dependencies: { type: "array", items: { type: "string" } }, sources: { type: "array", items: { type: "string" } }, urls: { type: "array", items: { type: "string", format: "uri" } }, papers: { type: "array", items: { type: "string" } }, custom: { type: "object", additionalProperties: true } }, additionalProperties: false }, replace: { type: "boolean", description: "If true, arrays will be replaced instead of merged" } }, required: ["filepath", "properties"] } }; } async runTool(args) { logger.startTimer(`update_properties_${args.filepath}`); try { logger.debug(`Updating properties for: ${args.filepath}`, { filepath: args.filepath, propertyCount: Object.keys(args.properties || {}).length, replace: args.replace || false }); const result = await this.propertyManager.updateProperties(args.filepath, args.properties, args.replace); const elapsedMs = logger.endTimer(`update_properties_${args.filepath}`); logger.logOperationResult(true, 'update_properties', elapsedMs, { filepath: args.filepath, propertyCount: Object.keys(args.properties || {}).length, success: result.success }); return this.createResponse(result); } catch (error) { const elapsedMs = logger.endTimer(`update_properties_${args.filepath}`); logger.logOperationResult(false, 'update_properties', elapsedMs, { filepath: args.filepath }); logger.error(`Error updating properties for ${args.filepath}:`, errorToObject(error)); return this.handleError(error); } } } //# sourceMappingURL=tools.js.map