Read Base
read_baseParse 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
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the .base file. |
Implementation Reference
- src/tools/bases.ts:62-94 (handler)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)}`); } }, - src/tools/bases.ts:44-61 (schema)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."), }, }, - src/tools/bases.ts:17-173 (registration)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)}`); } }, ); } - src/lib/vault.ts:833-839 (helper)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"); } - src/lib/bases.ts:57-71 (helper)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 }; }