Skip to main content
Glama
Obsidian MCP (pgvector + Ollama, self-hosted)

Obsidian MCP (pgvector + Ollama, self-hosted)

by maxkuminov

Read note

read_note
Read-onlyIdempotent

Read a note from an Obsidian vault by relative path, optionally selecting a heading section or paging content. Returns structured text, metadata, and content hash for safe edits.

Instructions

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

Arguments: path, an optional section (an ATX heading), and offset / limit for paging. Returns a structured result, not a rendered document — read the fields:

  • content — the selected note text. Whole-note reads: the body with a valid YAML frontmatter block stripped, which is byte-exact input for edit_note(path, content) full replacement only when the read is complete and unwindowed (offset=0 and truncated false). Section reads: the section's body only, byte-exact input for edit_note(path, content, section=...) under the same condition. Pass it straight back; do not add, strip or split anything. Page a truncated read to the end before writing it back, or full replacement replaces the whole body with the fragment.

  • heading — section reads only: the matched heading line, with no line terminator. It is not part of content; a section write never rewrites the heading line.

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

  • content_hash — the whole file's digest, sha256:<64 lowercase hex>, in every mode (a section or truncated read returns the value a whole-note read would), and the token the write tools take as expected_hash. It is not a hash of content: never compute it yourself, hand it back verbatim.

  • path, title, tags — metadata as data.

  • frontmatter_yaml — the frontmatter block's YAML source, fence lines excluded, LF-normalized (a CRLF 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 and may be absent; metadata_omissions then says which fields were dropped 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 (truncated whole-note reads) lists every section with its #N ordinal so you can fetch the one you want directly, and notice carries the guidance in prose. Read the one section you need with section= rather than paging a large note; content, the outline and the metadata fields each have their own budget.

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

Byte-identity holds for notes whose body newlines are LF: terminators inside the selected content come back as LF. read_file on the same path is the byte-exact route to the file itself.

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 only form that can address duplicate headings sharing the same parent; the outline of a truncated note carries the ordinal for every section. A bare "#N" always selects by position; use "Parent/#N" to reach a heading titled "#N". offset: Character offset to start reading from (default 0); continue from the reported next_offset. limit: Maximum characters of content to return. Only lowers the server cap; it cannot raise it.

Input Schema

NameRequiredDescriptionDefault
pathYes
limitNo
offsetNo
sectionNo

Output 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.8/5.0
Behavior5/5

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

Annotations already cover safety (readOnly/idempotent/non-destructive), so this is bonus context and it delivers heavily: frontmatter stripping on whole-note reads, LF normalization as a declared residual, `error` returned as a normal result rather than a transport failure, per-field truncation budgets, and content_hash being the whole-file digest in every mode. None of this is derivable from annotations or schema.

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

Conciseness3/5

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

Front-loaded and bullet-organized, but roughly 500 words for a four-parameter read tool, with some repetition ("Pass it straight back; do not add, strip or split anything" restates the byte-exactness point made twice more) and a trailing Args block that re-explains what the body already covered. The density is defensible for the round-trip semantics, but a third of it could be cut without loss.

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?

It never leaves the agent guessing: what is returned in each mode, what is absent when `error` is set, how truncation is signalled, and what token the write tools need. Although an output schema exists and the field-by-field enumeration partly duplicates it, nothing required to call or consume the result 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 description coverage is 0%, so the description carries the full load and does: `section` documents three addressing forms (plain heading, Parent/Child chain, #N ordinal) including the duplicate-heading case, and `offset`/`limit` explain the default, the paging handoff via next_offset, and that limit only lowers the server cap. That is meaning well beyond the bare types in the schema.

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?

Opens with a specific verb+resource+scope: "Read a note from the Obsidian vault by its relative path," and immediately separates itself from the nearest sibling ("read_file on the same path is the byte-exact route to the file itself"). An agent can pick between read_note and read_file without opening either schema.

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?

Explicit routing to alternatives with conditions: section reads instead of paging a large note, set_frontmatter or edit_note(find=...) to change frontmatter, read_file for byte-exact access. It also states negative guidance (never write back a truncated read, never compute content_hash yourself), which is the when-not half of the rule.

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