Skip to main content
Glama

Read Base

read_base
Read-onlyIdempotent

Parse a Base file to reveal filters, properties, view definitions, and unrecognized fields. Use to discover supported queries before query_base.

Instructions

Return the parsed contents of a Base file: filters, properties, view definitions, and any unrecognized fields. Use to discover what queries a Base supports before calling query_base.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYesVault-relative path to the .base file.

Implementation Reference

  • The actual handler function for the 'read_base' tool. It reads a .base file using readBaseFile(), parses the YAML with parseBaseFile(), and returns a formatted text result with filters, properties, and views.
    async ({ path: basePath }) => {
      try {
        const raw = await readBaseFile(vaultPath, basePath);
        const { doc, warnings } = parseBaseFile(raw);
        const lines: string[] = [`Base: ${basePath}`, ""];
        if (warnings.length > 0) {
          lines.push("Parse warnings:");
          for (const w of warnings) lines.push(`  - ${w}`);
          lines.push("");
        }
        lines.push("Filters:");
        lines.push("  " + JSON.stringify(doc.filters ?? null, null, 2).split("\n").join("\n  "));
        lines.push("");
        if (doc.properties) {
          lines.push(`Properties (${Object.keys(doc.properties).length}):`);
          for (const [key, spec] of Object.entries(doc.properties)) {
            lines.push(`  - ${key}${spec.displayName ? ` (display: ${spec.displayName})` : ""}`);
          }
          lines.push("");
        }
        if (Array.isArray(doc.views) && doc.views.length > 0) {
          lines.push(`Views (${doc.views.length}):`);
          for (const v of doc.views) {
            const nm = v.name ?? "(unnamed)";
            lines.push(`  - ${nm} [type: ${v.type}]`);
          }
        }
        return textResult(lines.join("\n"));
      } catch (err) {
        log.error("read_base failed", { tool: "read_base", err: err as Error });
        return errorResult(`Error reading base: ${sanitizeError(err)}`);
      }
    },
  • The tool registration and input schema for 'read_base'. Registered with server.registerTool("read_base", ...). Input schema requires a single 'path' string parameter describing the vault-relative path to the .base file.
    server.registerTool(
      "read_base",
      {
        title: "Read Base",
        description:
          "Return the parsed contents of a Base file: filters, properties, view definitions, and any unrecognized fields. Use to discover what queries a Base supports before calling query_base.",
        annotations: {
          readOnlyHint: true,
          idempotentHint: true,
          openWorldHint: false,
        },
        inputSchema: {
          path: z
            .string()
            .min(1)
            .describe("Vault-relative path to the .base file."),
        },
      },
  • The registerBaseTools() function registers all base-related tools (including read_base) on the MCP server.
    export function registerBaseTools(server: McpServer, vaultPath: string): void {
      server.registerTool(
        "list_bases",
        {
          title: "List Bases",
          description:
            "Enumerate every Obsidian Bases (`.base`) file in the vault. Bases are YAML-defined database views over notes (filters, properties, table/calendar/kanban views). Returns a sorted list of relative paths plus the total count. Pair with read_base or query_base.",
          annotations: {
            readOnlyHint: true,
            idempotentHint: true,
            openWorldHint: false,
          },
          inputSchema: {},
        },
        async () => {
          try {
            const bases = await listBaseFiles(vaultPath);
            if (bases.length === 0) return textResult("No .base files in this vault.");
            const lines = [`Found ${bases.length} Base file(s):`, "", ...bases];
            return textResult(lines.join("\n"));
          } catch (err) {
            log.error("list_bases failed", { tool: "list_bases", err: err as Error });
            return errorResult(`Error listing bases: ${sanitizeError(err)}`);
          }
        },
      );
    
      server.registerTool(
        "read_base",
        {
          title: "Read Base",
          description:
            "Return the parsed contents of a Base file: filters, properties, view definitions, and any unrecognized fields. Use to discover what queries a Base supports before calling query_base.",
          annotations: {
            readOnlyHint: true,
            idempotentHint: true,
            openWorldHint: false,
          },
          inputSchema: {
            path: z
              .string()
              .min(1)
              .describe("Vault-relative path to the .base file."),
          },
        },
        async ({ path: basePath }) => {
          try {
            const raw = await readBaseFile(vaultPath, basePath);
            const { doc, warnings } = parseBaseFile(raw);
            const lines: string[] = [`Base: ${basePath}`, ""];
            if (warnings.length > 0) {
              lines.push("Parse warnings:");
              for (const w of warnings) lines.push(`  - ${w}`);
              lines.push("");
            }
            lines.push("Filters:");
            lines.push("  " + JSON.stringify(doc.filters ?? null, null, 2).split("\n").join("\n  "));
            lines.push("");
            if (doc.properties) {
              lines.push(`Properties (${Object.keys(doc.properties).length}):`);
              for (const [key, spec] of Object.entries(doc.properties)) {
                lines.push(`  - ${key}${spec.displayName ? ` (display: ${spec.displayName})` : ""}`);
              }
              lines.push("");
            }
            if (Array.isArray(doc.views) && doc.views.length > 0) {
              lines.push(`Views (${doc.views.length}):`);
              for (const v of doc.views) {
                const nm = v.name ?? "(unnamed)";
                lines.push(`  - ${nm} [type: ${v.type}]`);
              }
            }
            return textResult(lines.join("\n"));
          } catch (err) {
            log.error("read_base failed", { tool: "read_base", err: err as Error });
            return errorResult(`Error reading base: ${sanitizeError(err)}`);
          }
        },
      );
    
      server.registerTool(
        "query_base",
        {
          title: "Query Base",
          description:
            "Run a Base file's filters against the vault and return matching note paths. Optionally pick a named view to apply that view's filters and ordering on top of the base-level filters. Supported filter syntax (subset of Obsidian's full DSL): function calls `taggedWith(file, \"tag\")`, `file.hasTag(\"tag\")`, `file.inFolder(\"path\")`; comparisons `key == \"val\"`, `key != x`, `key contains x`, `>=`, `<=`, `>`, `<`; combinators `and:`, `or:`, `not:`. Unsupported clauses are reported as warnings and treated as match-all.",
          annotations: {
            readOnlyHint: true,
            idempotentHint: true,
            openWorldHint: false,
          },
          inputSchema: {
            path: z
              .string()
              .min(1)
              .describe("Vault-relative path to the .base file."),
            view: z
              .string()
              .optional()
              .describe("Optional view name (or view type) to apply on top of the base-level filters."),
            limit: z
              .number()
              .int()
              .min(1)
              .max(1000)
              .optional()
              .default(100)
              .describe("Maximum number of matching notes to return (1-1000, default: 100)."),
            includeFrontmatter: z
              .boolean()
              .optional()
              .default(false)
              .describe("If true, include each row's frontmatter in the output."),
          },
        },
        async ({ path: basePath, view, limit, includeFrontmatter }) => {
          try {
            const raw = await readBaseFile(vaultPath, basePath);
            const { doc, warnings } = parseBaseFile(raw);
            const notes = await listNotes(vaultPath);
            const rows = await mapConcurrent(notes, 16, async (notePath) => {
              const content = await readNote(vaultPath, notePath);
              return buildRow(notePath, content);
            });
            const validRows = rows.filter((r): r is NonNullable<typeof r> => r !== undefined);
            const result = queryBase(validRows, doc, view);
            const allWarnings = [...warnings, ...result.warnings];
            const truncated = result.rows.slice(0, limit);
    
            const lines: string[] = [];
            lines.push(`Base: ${basePath}${view ? ` (view: ${view})` : ""}`);
            lines.push(
              `Matched ${result.rows.length} note(s)${result.rows.length > limit ? ` (showing first ${limit})` : ""}`,
            );
            if (allWarnings.length > 0) {
              lines.push("");
              lines.push("Warnings:");
              for (const w of allWarnings) lines.push(`  - ${w}`);
            }
            lines.push("");
            for (const row of truncated) {
              lines.push(`- ${row.path}`);
              if (includeFrontmatter && Object.keys(row.frontmatter).length > 0) {
                for (const [k, v] of Object.entries(row.frontmatter)) {
                  lines.push(`    ${k}: ${JSON.stringify(v)}`);
                }
              }
            }
            return textResult(lines.join("\n"));
          } catch (err) {
            log.error("query_base failed", { tool: "query_base", err: err as Error });
            return errorResult(`Error querying base: ${sanitizeError(err)}`);
          }
        },
      );
    }
  • The readBaseFile() helper function that resolves the vault path and reads the .base file from disk using fs.readFile().
    export async function readBaseFile(
      vaultPath: string,
      relativePath: string,
    ): Promise<string> {
      const fullPath = await resolveVaultPathSafe(vaultPath, relativePath);
      return fs.readFile(fullPath, "utf-8");
    }
  • The parseBaseFile() helper that parses the YAML content of a .base file into a BaseDocument object, returning any parse warnings.
    export function parseBaseFile(raw: string): ParsedBase {
      const warnings: string[] = [];
      let doc: BaseDocument = {};
      try {
        const parsed = yaml.load(raw);
        if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
          doc = parsed as BaseDocument;
        } else {
          warnings.push("Top-level YAML is not an object; treating as empty Base.");
        }
      } catch (err) {
        warnings.push(`YAML parse error: ${(err as Error).message}`);
      }
      return { doc, warnings };
    }
Behavior3/5

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

Annotations already declare it as read-only and idempotent. The description adds that it returns parsed contents and mentions what fields are returned, but does not disclose additional behavioral traits like error handling or performance. This is adequate given the annotations.

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

Conciseness5/5

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

Two sentences, front-loaded with the core action, no fluff. Every word adds value, achieving maximum conciseness for a simple tool.

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

Completeness4/5

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

The description covers purpose, usage, and return content. It does not mention error conditions or output format, but for a simple read-only tool with good annotations and schema, this is nearly complete.

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

Parameters3/5

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

Schema coverage is 100% with a single 'path' parameter already well-described as 'Vault-relative path to the .base file.' The description adds no further semantic detail for the parameter, meeting the baseline but not exceeding it.

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 description clearly states the tool returns parsed contents of a Base file, listing specific components (filters, properties, view definitions). It distinguishes itself from sibling 'query_base' by noting its use for discovery before querying, providing a clear purpose.

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

Usage Guidelines4/5

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

The description explicitly says to use this tool 'before calling query_base' to discover what queries a Base supports, providing a clear when-to-use. It does not include explicit when-not-to-use or alternative scenarios, but the guidance is sufficient for typical use.

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

Install Server

Other Tools

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/rps321321/obsidian-mcp-pro'

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