Skip to main content
Glama
sureshsankaran

Obsidian Tools MCP Server

read_note

Retrieve content from a specific note in your Obsidian vault by providing its file path.

Instructions

Read the content of a single note

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYesPath to the note relative to vault root

Implementation Reference

  • The handler function for the 'read_note' tool. It resolves the full path to the note, checks if the file exists, and reads its contents using fs.readFile.
    async function handleReadNote(args: { path: string }): Promise<string> {
      const fullPath = resolvePath(args.path);
    
      if (!(await fileExists(fullPath))) {
        throw new Error(`Note not found at ${args.path}`);
      }
    
      return await fs.readFile(fullPath, "utf-8");
    }
  • The schema definition for the 'read_note' tool in the tools array, used for tool listing and validation. Defines input as an object with a required 'path' string.
    {
      name: "read_note",
      description: "Read the content of a single note",
      inputSchema: {
        type: "object",
        properties: {
          path: {
            type: "string",
            description: "Path to the note relative to vault root",
          },
        },
        required: ["path"],
      },
    },
  • src/index.ts:893-895 (registration)
    The tool is registered in the main switch statement dispatcher for handling tool calls in the MCP server request handler.
    case "read_note":
      result = await handleReadNote(args as { path: string });
      break;
  • Helper functions resolvePath and fileExists used by the read_note handler to normalize paths and check file existence.
    function resolvePath(notePath: string): string {
      // Ensure .md extension
      const normalizedPath = notePath.endsWith(".md") ? notePath : `${notePath}.md`;
      return path.join(VAULT_PATH, normalizedPath);
    }
    
    async function ensureDir(filePath: string): Promise<void> {
      const dir = path.dirname(filePath);
      await fs.mkdir(dir, { recursive: true });
    }
    
    async function fileExists(filePath: string): Promise<boolean> {
      try {
        await fs.access(filePath);
        return true;
      } catch {
        return false;
      }
    }
    
    async function getAllNotes(dir: string = VAULT_PATH): Promise<string[]> {
      const notes: string[] = [];
      const entries = await fs.readdir(dir, { withFileTypes: true });
    
      for (const entry of entries) {
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory() && !entry.name.startsWith(".")) {
          notes.push(...(await getAllNotes(fullPath)));
        } else if (entry.isFile() && entry.name.endsWith(".md")) {
          notes.push(path.relative(VAULT_PATH, fullPath));
        }
      }
    
      return notes;
    }
    
    function parseFrontmatter(content: string): {
      frontmatter: Record<string, unknown> | null;
      body: string;
      raw: string;
    } {
      const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
      if (match) {
        try {
          // Simple YAML parsing for common cases
          const yaml: Record<string, unknown> = {};
          const lines = match[1].split("\n");
          for (const line of lines) {
            const colonIndex = line.indexOf(":");
            if (colonIndex > 0) {
              const key = line.slice(0, colonIndex).trim();
              let value: unknown = line.slice(colonIndex + 1).trim();
              // Handle arrays
              if (value === "") {
                value = [];
              } else if (
                typeof value === "string" &&
                value.startsWith("[") &&
                value.endsWith("]")
              ) {
                value = value
                  .slice(1, -1)
                  .split(",")
                  .map((v) => v.trim());
              }
              yaml[key] = value;
            }
          }
          return { frontmatter: yaml, body: match[2], raw: match[1] };
        } catch {
          return { frontmatter: null, body: content, raw: "" };
        }
      }
      return { frontmatter: null, body: content, raw: "" };
    }
    
    function serializeFrontmatter(obj: Record<string, unknown>): string {
      const lines: string[] = [];
      for (const [key, value] of Object.entries(obj)) {
        if (value === null || value === undefined) continue;
        if (Array.isArray(value)) {
          lines.push(`${key}: [${value.join(", ")}]`);
        } else {
          lines.push(`${key}: ${value}`);
        }
      }
      return `---\n${lines.join("\n")}\n---\n`;
    }
    
    // Tool handlers
    async function handleCreateNote(args: {
      path: string;
      content: string;
      overwrite?: boolean;
    }): Promise<string> {
      const fullPath = resolvePath(args.path);
    
      if (!args.overwrite && (await fileExists(fullPath))) {
        throw new Error(
          `Note already exists at ${args.path}. Use overwrite: true to replace.`
        );
      }
    
      await ensureDir(fullPath);
      await fs.writeFile(fullPath, args.content, "utf-8");
      return `Successfully created note at ${args.path}`;
    }
    
    async function handleDeleteNote(args: { path: string }): Promise<string> {
      const fullPath = resolvePath(args.path);
    
      if (!(await fileExists(fullPath))) {
        throw new Error(`Note not found at ${args.path}`);
      }
    
      await fs.unlink(fullPath);
      return `Successfully deleted note at ${args.path}`;
    }
    
    async function handleUpdateNote(args: {
      path: string;
      content: string;
    }): Promise<string> {
      const fullPath = resolvePath(args.path);
    
      if (!(await fileExists(fullPath))) {
        throw new Error(`Note not found at ${args.path}`);
      }
    
      await fs.writeFile(fullPath, args.content, "utf-8");
      return `Successfully updated note at ${args.path}`;
    }
    
    async function handleAppendToNote(args: {
      path: string;
      content: string;
      separator?: string;
    }): Promise<string> {
      const fullPath = resolvePath(args.path);
      const separator = args.separator ?? "\n\n";
    
      if (!(await fileExists(fullPath))) {
        throw new Error(`Note not found at ${args.path}`);
      }
    
      const existingContent = await fs.readFile(fullPath, "utf-8");
      const newContent = existingContent + separator + args.content;
      await fs.writeFile(fullPath, newContent, "utf-8");
      return `Successfully appended content to ${args.path}`;
    }
    
    async function handlePrependToNote(args: {
      path: string;
      content: string;
      separator?: string;
    }): Promise<string> {
      const fullPath = resolvePath(args.path);
      const separator = args.separator ?? "\n\n";
    
      if (!(await fileExists(fullPath))) {
        throw new Error(`Note not found at ${args.path}`);
      }
    
      const existingContent = await fs.readFile(fullPath, "utf-8");
      const { frontmatter, body, raw } = parseFrontmatter(existingContent);
    
      let newContent: string;
      if (frontmatter) {
        newContent = `---\n${raw}\n---\n${args.content}${separator}${body}`;
      } else {
        newContent = args.content + separator + existingContent;
      }
    
      await fs.writeFile(fullPath, newContent, "utf-8");
      return `Successfully prepended content to ${args.path}`;
    }
    
    async function handleRenameNote(args: {
      oldPath: string;
      newPath: string;
    }): Promise<string> {
      const oldFullPath = resolvePath(args.oldPath);
      const newFullPath = resolvePath(args.newPath);
    
      if (!(await fileExists(oldFullPath))) {
        throw new Error(`Note not found at ${args.oldPath}`);
      }
    
      if (await fileExists(newFullPath)) {
        throw new Error(`Note already exists at ${args.newPath}`);
      }
    
      await ensureDir(newFullPath);
      await fs.rename(oldFullPath, newFullPath);
      return `Successfully moved note from ${args.oldPath} to ${args.newPath}`;
    }
    
    async function handleReadNote(args: { path: string }): Promise<string> {
      const fullPath = resolvePath(args.path);
    
      if (!(await fileExists(fullPath))) {
        throw new Error(`Note not found at ${args.path}`);
      }
    
      return await fs.readFile(fullPath, "utf-8");
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sureshsankaran/obsidian-tools-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server