Query Base
query_baseExecute filters from a .base file to retrieve matching note paths, optionally overlaying a named view's filters and sorting.
Instructions
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.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Vault-relative path to the .base file. | |
| view | No | Optional view name (or view type) to apply on top of the base-level filters. | |
| limit | No | Maximum number of matching notes to return (1-1000, default: 100). | |
| includeFrontmatter | No | If true, include each row's frontmatter in the output. |
Implementation Reference
- src/lib/bases.ts:321-361 (handler)Core implementation of the query_base tool logic. Applies Base document filters (and optionally view-level filters) to an array of pre-built rows, sorts by view order, and returns matching rows plus warnings.
export function queryBase( rows: readonly BaseRow[], base: BaseDocument, viewName?: string, ): QueryResult { const ctx: EvaluationContext = { warnings: [] }; const baseFilter = flattenFilter(base.filters as BaseFilter | BaseFilter[] | undefined); let viewFilter: BaseFilter | undefined; let order: string[] | undefined; if (viewName && Array.isArray(base.views)) { const view = base.views.find((v) => v.name === viewName || v.type === viewName); if (!view) { ctx.warnings.push(`View not found: "${viewName}"; using base-level filters only.`); } else { viewFilter = flattenFilter(view.filters as BaseFilter | BaseFilter[] | undefined); order = Array.isArray(view.order) ? view.order : undefined; } } const matches = rows.filter((row) => evaluateFilter(row, baseFilter, ctx) && evaluateFilter(row, viewFilter, ctx), ); if (order && order.length > 0) { matches.sort((a, b) => { for (const key of order!) { const va = readProperty(a, key); const vb = readProperty(b, key); if (va === vb) continue; if (va === undefined || va === null) return 1; if (vb === undefined || vb === null) return -1; if (typeof va === "number" && typeof vb === "number") return va - vb; return String(va).localeCompare(String(vb)); } return 0; }); } return { rows: matches, warnings: ctx.warnings }; } - src/tools/bases.ts:97-171 (registration)Registration of the 'query_base' tool on the MCP server, including its input schema (path, view, limit, includeFrontmatter) and the async handler that reads the base file, parses it, loads all notes, builds rows, and calls the queryBase library function.
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/tools/bases.ts:108-130 (schema)Input schema for query_base: path (required string), view (optional string), limit (optional number 1-1000, default 100), includeFrontmatter (optional boolean, default 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."), }, - src/lib/bases.ts:89-96 (helper)Helper that builds a BaseRow from a note's path and content, extracting frontmatter and tags for use by query_base.
export function buildRow(path: string, content: string): BaseRow { const { data } = parseFrontmatter(content); return { path, frontmatter: data, tags: extractTags(content), }; } - src/lib/bases.ts:108-112 (helper)Helper that normalizes a filter (arrays become 'and' combinators) for use by the query_base handler.
function flattenFilter(filter: BaseFilter | BaseFilter[] | undefined): BaseFilter | undefined { if (filter === undefined) return undefined; if (Array.isArray(filter)) return { and: filter }; return filter; }