.inkbook file specification
The .inkbook format is a light, local-first JSON document schema designed to persist drawing notebooks written on Cecilia's Notes iPad application. Because notebooks are stored as plain files, external LLM agents, scripts, and MCP servers can safely read, append, and replace notebooks to integrate note-taking into code-generation or productivity workflows.
machine-readable contracts
Use these resources to programmatically validate document structure.
mcp server integration
To make it easy for AI agents and LLMs to interact with the notes application, we have published the official Model Context Protocol (MCP) server for Cecilia's Notes. External agents can use this MCP server to read, query, and modify notebooks programmatically using standard tools.
Users can install the MCP server globally via npm:
npm install -g cecilias-notes-mcp
Once installed, add it to your local MCP client configuration (such as Claude Desktop or Cursor) to grant your AI assistants direct access to interact with your local notebook library.
Top-Level Schema
An .inkbook file is a single JSON object with version "1" metadata. The tables below map the required fields and constraints.
| Field | Type | Required | Notes & Context |
|---|---|---|---|
$schema |
string (uri) | optional | Must match exactly https://venugopinath.me/cecilias-notes/schemas/inkbook/v1.json |
version |
string | required | Fixed value "1" for this specification version. |
id |
string (uuid) | required | Unique UUID for the notebook. Casing rules apply (see below). Filename format must be <id>.inkbook. |
title |
string | required | The title displayed at the top of the iPad notebook workspace. |
subject |
string | required | A free-form category/folder tag. If the subject does not exist on import, the iPad app automatically creates it. |
created_at |
string (timestamp) | required | ISO-8601 UTC timestamp with second precision (e.g. YYYY-MM-DDTHH:MM:SSZ). |
updated_at |
string (timestamp) | required | ISO-8601 UTC timestamp with second precision (e.g. YYYY-MM-DDTHH:MM:SSZ). |
cover_tone |
string enum | optional | Cover tone visual. Must be one of: parchment, studio-white, ash, coal, midnight, moss, dusk, ink-black. |
page_template |
string enum | optional | Background grid layout. Must be one of: blank, lined, grid, dot-grid, cornell, music. |
page_size |
string enum | optional | Aspect ratio constraints. Must be one of: a4, letter, ipad-canvas. |
agent |
object | optional | Metadata identifying the writing system (see sub-schemas below). |
pages |
array | required | List of page objects containing the note content. |
mcp_action |
string enum | optional | V1.1+ field for optimistic concurrency. One of: create, append, replace. |
base_updated_at |
string (timestamp) | optional | The last updated_at value read prior to editing. Required when mcp_action is set to "append". |
id field using **uppercase** strings to align with iOS native UUID().uuidString behavior. However, readers MUST parse and compare UUID values using **case-insensitive** matching.
Agent Metadata Object
Writers utilize the agent object to leave an audit trail, indicating which client-side wrapper, LLM model, or tool interface modified the file.
{
"written_by": "cecilias-notes-mcp", // e.g., name of the adapter
"model": "gemini-3.5-flash", // optional: model signature
"tool": "append_page", // name of tool action
"tool_version": "1.0.4" // tool version
}
Page Structure
Each entry in the pages[] array holds the following fields:
id: (Required, string) A case-insensitive UUID string.index: (Required, integer) A 0-based index indicating the page's sequence order.created_at: (Optional, string) ISO-8601 date-time string.blocks: (Required, array) A list of content blocks mapping text elements.
Content Block Union (Discriminated)
Content within a page is structured as a list of components called blocks. Each block contains a type string parameter that acts as a discriminator.
The v1 format supports the following block shapes:
| Block Type | Properties | Example JSON Representation |
|---|---|---|
heading |
content (string)level (int: 1, 2, or 3)
|
|
paragraph |
content (string)
|
|
list |
style (string: "bullet" | "numbered")items (array of strings)
|
|
code |
content (string)language (optional, string)
|
|
divider |
None |
|
quote |
content (string)attribution (optional, string)
|
|
callout |
content (string)kind (string: "note" | "warning" | "tip")
|
|
Optimistic Concurrency Contract
Since notebooks sync via files in iCloud Drive, multiple actors (the iPad App, desktop scripts, AI agents) might try to write to a file at the same time. To prevent concurrent edits from overwriting each other and causing data loss, Cecilia's Notes employs an optimistic concurrency contract using the mcp_action and base_updated_at parameters.
A writer performing a read-modify-write action (such as appending a page or changing content blocks) **must** follow this lifecycle:
- **Read**: Load the current
.inkbookfile from storage. - **Stash**: Record its
updated_attimestamp value as your targetbase_updated_at. - **Mutate**: Apply the desired modifications, and generate a new
updated_attimestamp corresponding to the present time. - **Write**: Write back the file, setting
mcp_actionto"append"andbase_updated_atto the stashed timestamp.
When Cecilia's Notes iPad App receives a write request, it applies the following evaluation matrix:
| Situation | Action Applied | Reasoning |
|---|---|---|
No existing notebook is registered with this notebook's id. |
Wholesale Replace | Treated as a clean, initial notebook creation. |
The mcp_action parameter is missing, or set to "create", "replace", or an unrecognized value. |
Wholesale Replace | Force overwrite. Either the notebook is new, or the writer explicitly requested to override history (e.g. "replace"). |
mcp_action is "append" and the notebook's current updated_at equals the incoming base_updated_at. |
Wholesale Replace | **Safe operation**. No concurrent changes occurred since the writer read the file; history is updated cleanly. |
mcp_action is "append" and the notebook's current updated_at does NOT equal the incoming base_updated_at. |
Merge | **Conflict detected**. Concurrent edits occurred. The app retains all existing pages, and only appends new pages from the write payload whose page id does not conflict with existing pages. |
mcp_action is "append", but the base_updated_at value is missing. |
Merge | **Conservative fallback**. Since concurrency state cannot be proven, the app merges to safeguard against page data loss. |
mcp_action: "replace".