MCP tools
folio-mcp exposes nine tools plus one prompt per skill
found under any sheet’s skills/ directory. Tools wrap Python SDK
methods; prompts render packaged operating procedures.
Every tool is a thin wrapper around a Python SDK method. Argument and return shapes follow the SDK; the docstrings below are surfaced to the model so it picks the right tool for the right situation.
Skill prompts use the name <sheet-id>:<skill-name>. Standard
prompts/list returns every skill across every sheet under the
--root; prompts/get renders the markdown body with declared
arguments substituted. See Sheet skills.
get_contract
get_contract(sheet_path: str) -> objectReturn the validated contract for sheet_path. Use this first to
discover the available fields, primary key, and which fields are derived.
Combine with query or list_records for actual data access.
query
query(sheet_path: str, sql: str, params: list = None) -> list[object]Run a SELECT-only DuckDB query against records. Prefer this over
list_records for COUNT, GROUP BY, MIN/MAX, and other
aggregations so you do not pull every row into the agent’s context. Use
? placeholders + params for any user-provided value.
list_records
list_records( sheet_path: str, filter: str = None, fields: list[str] = None, limit: int = 50, cursor: str = None, params: list = None, format: "json" | "toon" = "json",) -> objectList records page by page. Always pass fields to project only the
columns you need. Use filter (a DuckDB WHERE-clause) plus params for
condition values. format='toon' returns a token-efficient TOON string
in records instead of a JSON list.
get_record
get_record(sheet_path: str, id: str, fields: list[str] = None) -> object | nullFetch one record by primary key. Returns null if missing.
upsert_records
upsert_records(sheet_path: str, records: list[object], actor: str = None) -> objectInsert or update records by primary key. actor is required (or set
--actor on the server start). editable_by patterns on the contract
are enforced.
delete_records
delete_records(sheet_path: str, ids: list[str], actor: str = None) -> objectDelete records by primary key id.
materialize
materialize( sheet_path: str, targets: list[str] = None, record_ids: list[str] = None, force: boolean = false, actor: str = None,) -> objectMaterialize derived fields. Returns the §10.6 envelope. Combine with
materialization_status to inspect what is stale before running.
force=true recomputes even when the cache and human_override say
otherwise.
materialization_status
materialization_status(sheet_path: str, targets: list[str] = None) -> objectPer-target counts (ai_count / import_count / human_count / none_count)
and the last run timestamp.
provenance
provenance( sheet_path: str, record_id: str, field: str, history: boolean = false,) -> objectReturn the latest provenance entry, or the full append-only history.
TOON output for list_records
When format="toon", the records field becomes a single
TOON-encoded string:
[3]{id, country}:cust_001, Japancust_002, "United States"cust_003, SwedenThe header [N]{cols}: declares the row count and column names. Rows are
comma-separated values with JSON-encoded strings when needed. This saves
3-5x in tokens compared to JSON for tabular data, which matters when the
agent’s context is precious.
Path safety
Every sheet_path is resolved against the server’s --root. Paths that
escape are rejected with a FolioError:
get_contract("../escape")# → FolioError: sheet_path must live under the MCP rootErrors
Each tool reports failures the same way:
- A
FolioErroris raised by the SDK and FastMCP wraps it in anisErrorresult with the message. materializereturns the §10.6 envelope; per-record failures are onfailures[], not exceptions.
Agents should always check the failures[] field on materialize
results, even when the call itself succeeded.