folio
Integrates with Git history to expose every version Git holds, merged with Folio saves, with block-by-block comparisons and daily or weekly digests.
Allows opening SQLite data files as tables and trees, with column summaries and filtering, and embedding tables, charts, or values from SQLite data into documents; supports SQL queries across data.
Folio
A live view of a folder of markdown and data. Point Folio at a folder and read it in your browser: every page rendered with GitHub-flavoured markdown, math, highlighted code and diagrams, every page current the moment you save it, and the folder known as a whole - what you have not read yet, which figures no longer match their data, what links where and what is broken.
Install and run
npx @jakubro/folio ~/notesOpen the address it prints: http://localhost:8640/, or the next free port when that one is taken. Folio needs Node.js 22.18 or later; npm install --global @jakubro/folio keeps it installed as folio.
In Claude Code, two commands install Folio's MCP server and its agent skill as a plugin:
claude plugin marketplace add jakubro/folio
claude plugin install folio@folioThe plugin's tools read the Folio serving the folder Claude Code is opened in; its Folio setting, in /config, names another by its address, such as localhost:8641, or its socket.
Related MCP server: basic-memory
What Folio does
Folio keeps a model of the whole folder:
What is new to you. Folio remembers what you have read. Reopen a page and every block added, changed or removed since is marked - a table by its rows, a list by its items, code by its lines - the old wording a click away, and the tree points at every document that moved.
Data files and data in documents. CSV, JSON, Parquet, SQLite and other data files open as tables and trees, with each column's spread under its header and, for any filter, what sets its records apart from the rest. A document embeds a table, a chart or a single value from them, or a SQL query across them, and each one redraws when its data changes.
Tables that sort and filter. Tables written in a page, embedded from data or opened from a data file sort by several columns and filter by text, whole values, patterns or several values at once, the filters joined by and or or. None of it touches the file, and the view lives in the page's address.
Figures checked against their data. Tie a number in your prose to the data it quotes, and Folio flags it the moment the data disagrees.
Folder conventions and health. Declare kinds of documents and rules as queries in a
FOLIO.md. One health view - andfolio checkin CI - lists every broken link, missing include, failing query and broken rule.History. Every version git holds, merged with the saves Folio saw, compared block by block, and digests of any day or week.
Agents and editors. An MCP server, a JSON API and a language server give them the same model you read, and an agent skill teaches agents to write documents that use it.
The folder stays plain markdown: every page still reads on GitHub, and Folio writes into it only what a FOLIO.md asks for.
Documentation
Full documentation is published at jakubro.github.io/folio.
Guide - every view Folio adds, with pictures
Reference - every option, address, key and rule, and what Folio reads and writes
Architecture - how Folio works inside, and why
License
Copyright (C) 2026 Jakub Roman. Distributed under the GNU GPL v3.
Available Tools
16 toolsfolio_changesList what changed since the reader read itARead-onlyIdempotent
List the documents the reader has read before whose blocks changed since they last read them, newest change first - what they have to catch up on; with path, only that document or those under that folder. Use it for what changed for the reader; for every change in the folder over a period, read or not, use folio_digest, and for the marks on one page folio_reads with its path. It reveals which documents the reader has read. While the reader pauses sharing in Folio, it answers only {paused: true, message}. Answers {documents: [{path, blocks: how many changed, at: ISO time of the latest change}], version: the same while the list is}. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | A document, relative to the served folder or absolute under it, such as notes/plan.md, or a folder, such as notes/; omit for the whole folder |
folio_digestSummarize what changed in a periodARead-onlyIdempotent
Summarize what changed in the served folder over a period: every document created, changed or removed, with the versions it started and ended on, and every embedded value that moved, before and after. Use it for what changed this week or since a date across the whole folder, read or not; for what changed of what the reader read use folio_changes, and for one document's versions folio_history. Answers {from, to, documents: [{path, status: created, changed or removed, from, to: version ids folio_history takes}], values: [{document, reference, before, after, at}]}, times ISO 8601. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | The period's end, an ISO 8601 date or time as from is; default now | |
| from | No | The period's start, an ISO 8601 date or time such as 2026-11-02 or 2026-11-02T09:00:00+01:00 - a date alone is midnight UTC, a time without an offset UTC; default a day before to |
folio_documentRead a document's blocksARead-onlyIdempotent
Read one document as Folio's model holds it: its blocks with their ids, the blocks its page shows once includes are expanded, its declared kind and fields, and the data files it reads. Call it for the block ids that folio_provenance and folio_history take and that a link to a block names - an id is 8 hex characters Folio derives, never invented - and to see what a page is made of; read the file itself for its full text. Answers {path, kind: markdown or mermaid, blocks: [{id, kind, lines: [first, last] from 1, text: its first 100 characters}], page: [{id, kind, source: the file the block comes from, lines, at: its lines in the expanded page, version}], conventions: {declarations, kind, declaredIn, fields}, data: [paths]}. A document Folio has not read yet is an error: ask again shortly. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | The document's path, relative to the served folder or absolute under it, such as notes/plan.md |
folio_documentsList the folder's documentsARead-onlyIdempotent
List every markdown and diagram document under a folder of the served folder, with the kind a FOLIO.md declaration gives it. Use it to see what exists or which documents are of a kind; for documents matching a condition - a title, a field's value, a link - query folio.documents or kind. with folio_query, and for one document's blocks use folio_document. Answers {ready, documents: [{path, kind}]}: kind is null where no declaration places the document; ready false means Folio is still reading the folder and the list is incomplete, so ask again shortly. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| folder | No | A folder, relative to the served folder or absolute under it, such as notes/; omit for the whole folder |
folio_embedResolve a reference a document writesARead-onlyIdempotent
Resolve one data reference as a document writes it in {table: ...}, {value: ...}, a figure or a chart, from that document's folder, and return what it shows there. Use it to check a reference before writing it into a document, or to read the value a page shows; for a question of your own over the data use folio_query. Answers {version, grid: {columns, rows, total, partial} for a table, value: for a reference naming one value, error: why the reference resolves to nothing}. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | The document whose folder the reference resolves from, relative to the served folder or absolute under it, such as reports/weekly.md | |
| reference | Yes | The reference as written: a data file relative to the document (data/daily.csv) or absolute under the served folder, a part of it after # (data/daily.csv#0.rain_mm, archive.sqlite#readings), a .sql file, or #name for a query fenced in the document (#totals, #totals#0.rain_mm) |
folio_healthList everything wrong in the folderARead-onlyIdempotent
List every problem in the served folder, or with path only in that document or under that folder: links to files, headings or blocks not there, includes that find nothing and include loops, figures their data contradicts, diagrams, charts and queries that fail, data that cannot be read, and declarations, fields, rules and saved views a document breaks. Use it after editing documents, given the path of what you edited, to answer which figures disagree with their data, or to find what to fix; for one block's figures in detail use folio_provenance, and for what needs the reader, ranked, folio_needs. It reveals nothing of the reader's reading, and waits while health is being checked. Answers {ready, problems: [{kind: figure, link, include, loop, diagram, chart, query, data, declaration, field, rule or view, path, line, block, severity: error or warning - absent for a problem that is neither, what, anchor: the element to land on, opens: the document showing it when another, occurrence: which of a rule's alike rows}], version: the same while the problems are}; ready false means Folio is still reading the folder, so ask again shortly. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | A document, relative to the served folder or absolute under it, such as notes/plan.md, or a folder, such as notes/; omit for the whole folder |
folio_historyRead a document's historyARead-onlyIdempotent
Read a document's versions, newest first, merged from its git commits and every save Folio saw - a document since deleted included. With version, that version's full text; with block, each text the block has had; with reference, each result that embed has shown. Use it to answer how a passage or a value changed and when; for everything that changed in the folder over a period use folio_digest. Give at most one of version, block and reference. Answers {path, versions: [{id, at: ISO time, seen: a list of saved, committed and file, by: the commit's author, removed}]}, {path, version, text: null when unknown}, {path, block, versions: [{version, at: ISO time, text: absent where the block was not there}]} or {path, reference, results: [{at: ISO time, value or table, error}]}. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | The document's path, relative to the served folder or absolute under it, such as notes/plan.md, also one since deleted | |
| block | No | A block's id as folio_document lists it, for each text the block has had | |
| version | No | A version's id from this tool's versions, for that version's text | |
| reference | No | An embed's reference as the document writes it, such as #totals#0.n, for each result it has shown |
folio_kindsRead the declared kinds and rulesARead-onlyIdempotent
Read what the FOLIO.md declarations applying at a path declare: the kinds of documents with the fields each carries, the rules - queries whose every row is a violation - and the health entries, layered from the served folder down as Folio applies them. Use it before writing a document of a kind, to learn its fields and the rules it must keep; for one document's own kind and field values use folio_document, and for the rules broken now folio_health. Answers {declarations: [FOLIO.md paths, outermost first], kinds: [{name, declaredIn, where, fields}], rules: [{name, declaredIn, query, message, severity, line and block: where it is declared}], health: [{declaredIn, where, skip, error}]}, every list empty where nothing is declared. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | A document, relative to the served folder or absolute under it, such as notes/plan.md, or a folder ending in /, such as notes/; omit for the served folder itself |
folio_needsList what needs the readerARead-onlyIdempotent
List what needs the reader's attention, ranked: every error in the folder, then problems in documents they have read, rows of the folder's declared attention queries, and documents they read that changed since; with path, only what lies in that document or under that folder. Use it to triage what to look at first; for every problem whoever reads it use folio_health, and for only the documents changed since read folio_changes. It reveals what the reader has read. While the reader pauses sharing in Folio, it answers only {paused: true, message}. Answers {ready, needs: [{source: error, problem, attention or changed, path, line, block, what, name: the attention query's, at}], version: the same while the list is}; ready false, with no needs, while Folio still reads the folder. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | A document, relative to the served folder or absolute under it, such as notes/plan.md, or a folder, such as notes/; omit for the whole folder |
folio_presenceSee what the reader is looking atARead-onlyIdempotent
Show what the reader is looking at now: the page open in each browser tab and the block under their pointer with the start of its text, the most recent first. Use it when the reader means what is on their screen - this page, this paragraph; for what they read before use folio_reads. It reveals the reader's screen; a hidden tab shows nothing. While the reader pauses sharing in Folio, it answers only {paused: true, message}. Answers {paused: false, looking: [{page, block: {source, id, text}, at: ISO time}]}. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
folio_provenanceTrace where a block came fromARead-onlyIdempotent
Trace one block of a page to its origin: the file and lines it comes from, when it last changed, the data files and queries behind its embedded tables and values with when each changed, and each figure in it checked against its data. Use it to explain why a number or table reads as it does, or whether a figure still matches its data; take the block's id - and, for a block an include brings, its source - from folio_document's page. For every problem in the folder at once use folio_health. An id not on the page is an error. Answers {source, kind, lines: [first, last], changed: ISO time, included: {section} for an included block, data: [{reference, files: [{path, changed}], query, error}], claims: [{figure, reference, state: matching, contradicted or unchecked, live: the data's value, reason, deliberate: true when left unchecked by choice, as a range is}], conventions}. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | The page's path, relative to the served folder or absolute under it, such as notes/plan.md | |
| block | Yes | The block's id, 8 hex characters, as folio_document lists it | |
| source | No | The file the block's text comes from when an include brings it onto the page, as folio_document's page names it; omit for the page's own blocks |
folio_queryRun a SQL query over the folderARead-onlyIdempotent
Run one read-only DuckDB SELECT over the served folder's data files and its documents as tables, and return the rows. Use it for any question data answers - counts, filters, joins across CSV, JSON, Parquet, SQLite or DuckDB files - and for questions about documents as data: what links to a page (folio.links where target is its path), open tasks, documents of a kind by a field. To read what one reference written in a document shows, use folio_embed. Anything but one SELECT, a path outside the folder and a web address are refused; a query stops after 10 seconds. Answers {columns: [{name, type}], rows, total: the row count, absent when rows were cut, partial: true when rows were cut, files: the data files read}: rows stop at 5,000 or 16 MB, and a long cell is cut at 20,000 characters, fewer when there are more than 10 columns.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | One SELECT. A data file is a table named by its path in single quotes, relative to folder, from the served folder after a slash, or absolute under the served folder: 'data/daily.csv', 'archive.sqlite#readings', or 'queries/q.sql' for a query file's result; * and ** patterns read many files at once. Documents are folio.documents(path, filename: the name without its folder, title: the one h1 outside <details>, kind), folio.headings(path, line, level, text, anchor), folio.links(path, line, url, target, fragment, context), folio.tasks(path, line, done, text), folio.frontmatter(path, key, value), folio.tables(path, line, row, header, cells), and kind.<name>(path and a column per field the kind declares) | |
| folder | No | The folder the query runs from, relative to the served folder or absolute under it, such as reports/; quoted paths resolve against it. Omit for the served folder |
folio_readsRead what the reader has readARead-onlyIdempotent
Read the reader's read record. For a folder, or with no path, every document read under it and when it was last read; for a document, each block with the version the reader read and when, and the marks its page shows. Use it to tell whether the reader has seen a passage or what is new to them on a page; for the list of documents changed since read use folio_changes. It reveals the reader's reading. While the reader pauses sharing in Folio, it answers only {paused: true, message}. Answers {documents: [{path, at: ISO time}]} for a folder, or for a document {blocks: [{id, kind, source, lines, version, read: {version, at: ISO time} or null}], marks: [{source, id, mark: new or changed, before and after: the text as read and as it is, data: embeds whose data changed - a mark from data alone has no before or after, at: ISO time, parts: the rows, items or lines marked in a table, list or code block}], removed: [{source, id, text, after and before: the ids it stood between, at: ISO time}], links: [{source, id, url, state: changed or gone}], upstream: [{path, how, at}], version}. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | A document, relative to the served folder or absolute under it, such as notes/plan.md, or a folder ending in /; omit for every document read |
folio_staleList documents that outlived their useARead-onlyIdempotent
List documents that have probably outlived their use - by default unchanged for a year, never read and linked from nowhere, or what the folder's declared stale query names - each with when its file last changed and why; with path, only that document or those under that folder. Use it to suggest what to archive; Folio only lists them and never moves or deletes anything. For documents the reader should catch up on use folio_changes instead, and for everything wrong in the folder folio_health. It reveals which documents the reader never read. While the reader pauses sharing in Folio, it answers only {paused: true, message}. Answers {ready, documents: [{path, changed: ISO time, why}], version: the same while the list is}; ready false, with no documents, while Folio still reads the folder. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | A document, relative to the served folder or absolute under it, such as notes/plan.md, or a folder, such as notes/; omit for the whole folder |
folio_used_byList the documents a file feedsARead-onlyIdempotent
List the documents that include a file or whose embeds and queries read it - the pages a change to it moves. Use it before editing, moving or deleting a document, data file or query file; it does not list links, so for what links to a document query folio.links with folio_query. Answers {ready, documents: [{source, how: includes or embeds}]}; ready false, with no documents, while Folio still reads the folder. A list cut to fit one answer adds omitted, the entries each list left out, and narrow, how to ask for less.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | The file's path, relative to the served folder or absolute under it: a document, data file or .sql file, such as data/daily.csv |
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
16 tool updates
- First observed
folio_changes - First observed
folio_digest - First observed
folio_document - First observed
folio_documents - First observed
folio_embed - First observed
folio_health - First observed
folio_history - First observed
folio_kinds - First observed
folio_needs - First observed
folio_presence - First observed
folio_provenance - First observed
folio_query - First observed
folio_reads - First observed
folio_related - First observed
folio_stale - First observed
folio_used_by
TDQS
Scored across 16 tools
Most tools are clearly distinct, and descriptions actively disambiguate near-neighbors by naming the alternative ('for what changed of what the reader read use folio_changes, and for one document's versions folio_history'). There are nonetheless two overlapping clusters — folio_changes/folio_digest/folio_history/folio_reads (what changed or was read) and folio_health/folio_needs/folio_stale (what is wrong or needs attention) — plus folio_query vs folio_embed, which an agent must read closely to separate.
Every tool uses the identical folio_ prefix followed by a single lowercase noun, with no camelCase or verb-style outliers anywhere in the set. The pattern is fully predictable, so an agent can reliably guess and remember names.
At 16 tools the surface sits just above the comfortable range, but the domain is genuinely broad (reading state, provenance, embeddings, health, history, digests, impact analysis) and each tool maps to a distinct capability rather than a variant of another. A couple of the analysis tools could plausibly be folded together, but nothing is filler.
The read/analysis lifecycle is well covered: discovery, per-document structure, provenance, data querying, semantic relations, health, change tracking, history, and impact analysis before edits. The notable gap is that there are no mutation tools at all — no create/update/move — so writing is left to external file editing, which the descriptions implicitly assume ('use it after editing documents').
Maintenance
Related MCP Connectors
Foliora MCP for public discovery, account reads, and agent execution of human-approved changes.
Publish your agent's Markdown as shareable web pages with revision provenance and comments.
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Fillo MCP server — provision, scaffold, publish, and query forms from your coding agent.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server to interact with Obsidian via the Local REST API community plugin.2134,523MIT
- AlicenseAqualityBmaintenanceBasic Memory is a knowledge management system that allows you to build a persistent semantic graph from conversations with AI assistants. All knowledge is stored in standard Markdown files on your computer, giving you full control and ownership of your data. Integrates directly with Obsidan.md21219,662 PyPI4,519AGPL 3.0
- FlicenseNot gradedqualityCmaintenanceMCP server that enables full-text search and link navigation over Markdown files as a knowledge graph.-
- AlicenseNot gradedqualityBmaintenanceA local dashboard and Model Context Protocol server for managing a multi-project portfolio whose entire state lives in markdown files.MIT