Skip to main content
Glama
maxkuminov

Obsidian MCP (pgvector + Ollama, self-hosted)

by maxkuminov

read_note

Retrieve note content and metadata from an Obsidian vault by path, with optional section or offset for partial reads. Returns structured fields for direct editing or analysis.

Instructions

Read a note from the Obsidian vault by its relative path.

Returns a structured result, not a rendered document. Metadata and note content sit in separate fields, so there is no envelope to parse and no textual procedure to get wrong — read the fields:

  • content — the selected note text. Whole-note reads: the body with a valid YAML frontmatter block stripped, which is exactly what edit_note(path, content) full replacement accepts. Section reads: the section's body only, which is exactly what edit_note(path, content, section=...) accepts. Pass it straight back; do not add, strip or split anything.

  • heading — section reads only: the matched heading line, with no line terminator. It is not part of content, and a section write must not be sent it — the heading line is never rewritten.

  • path, title, tags — metadata as data.

  • frontmatter_yaml — the frontmatter block's YAML source, fence lines excluded, LF-normalized (a CRLF or lone-CR block comes back with LF terminators — the same declared residual content carries, because this tool normalises and the write tools work on raw bytes; edit_note still reattaches the original block byte-identically). This is the authoritative copy. frontmatter is a best-effort JSON view of the same block for convenience and may be absent — dates, non-string keys, recursive aliases and unpaired-surrogate escapes have no faithful JSON form, and metadata_omissions then says which and why. To change frontmatter use set_frontmatter, or edit the raw block with edit_note(find=...); never write back a round trip of the JSON view.

  • truncated, offset, next_offset, total_chars — truncation as data. outline (whole-note reads that were truncated) lists every section with its #N ordinal so you can fetch the one you want directly, and notice carries the guidance in prose.

  • metadata_omissions — any metadata field this response had to drop, and why. Nothing is ever signalled by a marker inside a field.

  • content_hash — this note's file digest, sha256:<64 lowercase hex>, and the token the write tools accept as expected_hash to bind a write to the bytes you actually read. Three things about it: it is the whole file's hash in every mode — a section read and a truncated read return the same value a whole-note read of the unchanged file returns, so a section write guarded with it is refused when anything in the file changed (that is the trade, and it is why the argument is optional: bind when you reasoned about what you read, omit when you are appending to a log); it comes from the same read that built this response, never a second one; and it is not a hash of content — a note with frontmatter, or with CRLF terminators, has a digest the returned text cannot reproduce, so never compute it yourself, hand this value back verbatim. read_file on the same path is the byte-exact route to the file itself, frontmatter_yaml here being authoritative but LF-normalized.

  • error — set when the read failed (missing note, bad offset/limit, unknown section). It is a normal result, not a transport error, and the content-bearing fields are absent beside it.

Budgets are per field, not per response: content is bounded by the server's response cap, the outline by its own equal budget, and the metadata fields by a third — so a truncated whole-note read can carry several capped components. Read the one section you need with section= rather than paging a large note.

Round trips. A whole-note content is byte-exact input for edit_note(path, content) only when the read is complete and unwindowed (offset=0 and truncated false); a truncated read must be paged to the end first, or full replacement replaces the whole body with the fragment. A section content is byte-exact input for edit_note(section=...) under the same completeness condition. Byte-identity holds for notes whose body newlines are LF: terminators inside the selected content come back as LF, because this tool normalises and the write tools rewrite raw bytes.

Args: path: Vault-relative path to the note (e.g. "Cards/My Note.md") section: Optional ATX heading to read instead of the whole note. Plain text ("Balance Sheet"), a path-style chain ("Parent/Child") when the heading appears under different parents, or a "#N" ordinal ("#7", 1-based document order) — the ordinal is the only form that can address duplicate headings sharing the same parent. The outline returned with a truncated note carries the ordinal for every section. A bare "#N" always selects by position and is never shadowed by a heading whose text happens to be "#N"; use "Parent/#N" to reach such a heading by title. offset: Character offset to start reading from (default 0). Use the next_offset the response reports to continue. limit: Maximum characters of content to return. Only lowers the server cap; it cannot raise it.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYes
limitNo
offsetNo
sectionNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathNo
tagsNo
errorNo
titleNo
noticeNo
offsetNo
contentNo
headingNo
outlineNo
truncatedNo
frontmatterNo
next_offsetNo
total_charsNo
content_hashNo
frontmatter_yamlNo
metadata_coercionsNo
metadata_omissionsNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changedv0.8.2
    • addedInput schema / additionalProperties
      Added value: +false
  2. Changed3 schema fields changedv0.8.1
    • addedOutput schema / $defs / MetadataCoercion
      Added value: +{
      +  "description": "One metadata value this response rendered in a canonicalized form.\n\nServer-authored, every field of it, exactly as `MetadataOmission` is: the\nchannel is out of band precisely so that nothing has to be signalled inside\nthe note-controlled field itself.",
      +  "properties": {
      +    "detail": {
      +      "title": "Detail",
      +      "type": "string"
      +    },
      +    "field": {
      +      "title": "Field",
      +      "type": "string"
      +    },
      +    "reason": {
      +      "title": "Reason",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "field",
      +    "reason",
      +    "detail"
      +  ],
      +  "title": "MetadataCoercion",
      +  "type": "object"
      +}
    • addedOutput schema / properties / content_hash
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Content Hash"
      +}
    • addedOutput schema / properties / metadata_coercions
      Added value: +{
      +  "anyOf": [
      +    {
      +      "items": {
      +        "$ref": "#/$defs/MetadataCoercion"
      +      },
      +      "type": "array"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Metadata Coercions"
      +}
  3. Changed20 schema fields changedv0.8.0
    • addedOutput schema / $defs
      Added value: +{
      +  "MetadataOmission": {
      +    "description": "One metadata field this response could not carry, and why.\n\nServer-authored, every field of it. This is the *only* channel that reports\na dropped field: nothing is ever signalled by writing a marker into the\nnote-controlled field itself.",
      +    "properties": {
      +      "detail": {
      +        "title": "Detail",
      +        "type": "string"
      +      },
      +      "field": {
      +        "title": "Field",
      +        "type": "string"
      +      },
      +      "reason": {
      +        "title": "Reason",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "field",
      +      "reason",
      +      "detail"
      +    ],
      +    "title": "MetadataOmission",
      +    "type": "object"
      +  },
      +  "NoteOutline": {
      +    "description": "The outline, with its degraded states as data rather than as prose.\n\n`truncated` is the explicit marker the requirement asks for: when the\nbudget cannot hold even one entry, `entries` is empty and `truncated` is\ntrue, which is a statement, not a silence. `omitted`, `first_ordinal` and\n`last_ordinal` are present exactly when the listing is incomplete — a\ncomplete listing carries no omission summary, so nothing has to be reserved\nfor one.",
      +    "properties": {
      +      "entries": {
      +        "default": [],
      +        "items": {
      +          "$ref": "#/$defs/OutlineEntry"
      +        },
      +        "title": "Entries",
      +        "type": "array"
      +      },
      +      "first_ordinal": {
      +        "anyOf": [
      +          {
      +            "type": "integer"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null,
      +        "title": "First Ordinal"
      +      },
      +      "last_ordinal": {
      +        "anyOf": [
      +          {
      +            "type": "integer"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null,
      +        "title": "Last Ordinal"
      +      },
      +      "omitted": {
      +        "anyOf": [
      +          {
      +            "type": "integer"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null,
      +        "title": "Omitted"
      +      },
      +      "truncated": {
      +        "default": false,
      +        "title": "Truncated",
      +        "type": "boolean"
      +      }
      +    },
      +    "title": "NoteOutline",
      +    "type": "object"
      +  },
      +  "OutlineEntry": {
      +    "description": "One section of a truncated whole-note read's heading outline.",
      +    "properties": {
      +      "depth": {
      +        "title": "Depth",
      +        "type": "integer"
      +      },
      +      "duplicate": {
      +        "title": "Duplicate",
      +        "type": "boolean"
      +      },
      +      "exceeds_cap": {
      +        "title": "Exceeds Cap",
      +        "type": "boolean"
      +      },
      +      "ordinal": {
      +        "title": "Ordinal",
      +        "type": "integer"
      +      },
      +      "size": {
      +        "title": "Size",
      +        "type": "integer"
      +      },
      +      "text": {
      +        "title": "Text",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "ordinal",
      +      "depth",
      +      "text",
      +      "size",
      +      "exceeds_cap",
      +      "duplicate"
      +    ],
      +    "title": "OutlineEntry",
      +    "type": "object"
      +  }
      +}
    • addedOutput schema / description
      Added value: +"What `read_note` returns.\n\nEvery field is either server-controlled or a note-controlled value sitting\nalone in a field of its own. Nothing here is composed into a frame, so\nnothing note-controlled can change which field another value appears in —\nthat is the whole point (#149).\n\nOn an error result the content-bearing fields are absent: `error` is the\nanswer, and a caller must never find a half-response beside it."
    • addedOutput schema / properties / content
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Content"
      +}
    • addedOutput schema / properties / error
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Error"
      +}
    • addedOutput schema / properties / frontmatter
      Added value: +{
      +  "anyOf": [
      +    {
      +      "additionalProperties": true,
      +      "type": "object"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Frontmatter"
      +}
    • addedOutput schema / properties / frontmatter_yaml
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Frontmatter Yaml"
      +}
    • addedOutput schema / properties / heading
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Heading"
      +}
    • addedOutput schema / properties / metadata_omissions
      Added value: +{
      +  "anyOf": [
      +    {
      +      "items": {
      +        "$ref": "#/$defs/MetadataOmission"
      +      },
      +      "type": "array"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Metadata Omissions"
      +}
    • addedOutput schema / properties / next_offset
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "integer"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Next Offset"
      +}
    • addedOutput schema / properties / notice
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Notice"
      +}
    • addedOutput schema / properties / offset
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "integer"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Offset"
      +}
    • addedOutput schema / properties / outline
      Added value: +{
      +  "anyOf": [
      +    {
      +      "$ref": "#/$defs/NoteOutline"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null
      +}
    • addedOutput schema / properties / path
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Path"
      +}
    • removedOutput schema / properties / result
      Removed value: -{
      -  "title": "Result",
      -  "type": "string"
      -}
    • addedOutput schema / properties / tags
      Added value: +{
      +  "anyOf": [
      +    {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Tags"
      +}
    • addedOutput schema / properties / title
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Title"
      +}
    • addedOutput schema / properties / total_chars
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "integer"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Total Chars"
      +}
    • addedOutput schema / properties / truncated
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "boolean"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Truncated"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "result"
      -]
    • changedOutput schema / title
      Previous value: -"read_noteOutput"New value: +"ReadNoteResult"
  4. Addedv0.7.0
  5. Removedv0.5.4
  6. First observed

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden and exceeds it. It discloses truncation semantics, content_hash is the whole-file hash and not content's, LF normalization behavior, error as a normal result, and round-trip byte-exactness conditions. Nothing is hidden or ambiguous.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but its structure (bolded field labels, separate 'Round trips' section) makes it scannable. Every sentence adds needed detail for a complex tool. Slight redundancy like 'that is the trade, and it is why the argument is optional' could be tighter, but overall it is appropriately sized for the complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of parameter descriptions in the schema and the presence of an output schema, the description covers all parameters, all return fields with their invariants, error conditions, round-trip guarantees, and cross-tool relationships. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% (only types/titles). The description fully compensates: path gets an example, section explains heading formats, ordinals, and duplicate handling, offset clarifies default and next_offset usage, limit explains it only lowers the server cap. Every parameter is semantically enriched.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states exactly what the tool does: 'Read a note from the Obsidian vault by its relative path.' It goes further to say it returns a structured result (not rendered), and distinguishes itself from read_file as the byte-exact route. This is a specific verb+resource with clear differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly directs when to use alternatives: read_file for byte-exact reads, edit_note for writes, set_frontmatter for frontmatter changes. It also advises reading a section directly rather than paging a large note, and explains the trade-off of using content_hash for write binding. All usage context is explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.