Local-first Apple Notes backup control room for macOS with a read-only MCP surface.
Apple Notes Snapshot MCP Server
Apple Notes Snapshot is a local-first Apple Notes backup control room for macOS that exposes a read-only Model Context Protocol (MCP) surface. It keeps an upstream notes-exporter engine and adds path-aware exports, launchd scheduling, visible health checks, and a calmer recovery path when the local backup loop drifts.
🛠️ Key Features
Local-first Apple Notes backup control room for macOS
Read-only MCP surface
Uses upstream notes-exporter engine
Path-aware exports
launchd scheduling
Visible health checks
Recovery path for backup loop drift
🚀 Use Cases
Schedule and manage Apple Notes exports on macOS
Monitor backup health via visible checks
Handle drift in local backup loops using a calmer recovery path
⚡ Developer Benefits
Integrates with an existing notes-exporter engine
Provides a stable, read-only MCP interface
Supports macOS automation via launchd
⚠️ Limitations
Designed for backup/export control rather than recovery of deleted notes (see sister repo notes-recover)
Apple Notes Snapshot is the backup control room for Apple Notes on
macOS. Keep the upstream notes-exporter engine, then add path-aware exports,
launchd scheduling, visible health checks, and a calmer recovery path when
the local backup loop drifts.
🤝 Sister repo: looking to recover deleted notes after the fact, not back them up beforehand? See notes-recover — the forensic-grade reader for Apple Notes' NoteStore.sqlite (recover deleted notes, browse the raw store, ship audit-ready bundles). notebackup covers the before; notes-recover covers the after. Together they cover the full Apple Notes lifecycle on macOS.
After the control room already makes sense, the repo also ships extra builder
and diagnostics surfaces so humans and coding agents can inspect the same
backup state without turning the workflow into a hosted service.
Keep the path obvious
Review or override the export destination before the first snapshot writes.
Turn one-off exports into a local loop
Use launchd to schedule repeatable snapshots instead of babysitting manual exports.
See health before you guess
Check freshness, failure reasons, logs, and recovery clues when the loop drifts.
Read the next safe move first
The Web console now surfaces an operator focus deck above the raw transcript so you can see the next move, the reason, and the reading order before diving into action output.
Category: Apple Notes backup control room.
AI/agent hook: AI-assisted diagnostics plus optional coding-agent access once the operator workflow is already clear.
Result: a calmer, more reviewable backup workflow on your own Mac.
Best fit: people who already rely on Apple Notes and want a calmer,
reviewable backup routine on their own Mac.
Not the goal: cloud sync, team collaboration, or two-way write-back into
Apple Notes.
At A Glance
If you only want the shortest truthful filter before reading deeper, use this
table first:
the proof page after one healthy local loop exists
Second ring only
AI Diagnose, Local Web API, and MCP come after the operator path already makes sense
What it must never be reduced to
a hosted Notes service, cloud sync product, or generic AI dashboard
What you can prove in one local pass
The destination is obvious: you can review or override the snapshot path before anything writes.
The loop is real: one manual run plus launchd turns a one-off export into a repeatable local rhythm.
The control room is inspectable: status, verify, doctor, logs, and proof pages all stay on the same local facts.
Integration surfaces are optional: AI Diagnose, the Local Web API, and MCP all stay behind the operator story instead of replacing it.
If you want the shortest public evidence trail after that first pass, open the
proof page.
It collects the repo-owned gates, the GitHub-controlled release and Pages
evidence, and the current access boundary in one place.
Start with Run -> Install -> Verify
Treat the first healthy loop like a three-stop checklist, not like a dashboard
to decode all at once.
Run
Use ./notesctl run --no-status once so macOS can surface permissions and
the local ledger can record a first successful baseline.
Install
Use ./notesctl install --minutes 30 --load after that first run so the
workflow becomes a repeatable launchd loop instead of a manual chore.
Verify
Use ./notesctl verify first, then ./notesctl doctor only if warnings or
empty state remain.
After that verified loop exists, the Web console, proof page, AI Diagnose,
Local Web API, and MCP surfaces become much easier to read because they are all
describing a baseline you already proved.
Integration and maintainer routes after the operator path
The public product front door is still the local backup control room. The lanes
below are for builders after the first healthy loop exists; they do not replace
Run -> Install -> Verify.
Public product front door: backup control room
the main story is still the local path review, the first snapshot run, the
launchd loop, and the health check surface on your own Mac
Second ring builder protocol lane: pure MCP
the repo's main machine-facing surface is the local stdio MCP flow behind
./notesctl mcp and the root server.json
Start here if you want the shortest honest path from manual export to a
repeatable local snapshot loop. The first-successful-run promise is 3 steps
and about 3 minutes, not a zero-click install.
Review config/notes_snapshot.env and keep or change the default export path.
Run one snapshot so macOS can show Apple Notes / AppleScript permission prompts.
Install the scheduler, then verify health.
bash
# review config/notes_snapshot.env first
./notesctl run --no-status
./notesctl install --minutes 30 --load
./notesctl verify
./notesctl doctor
Fail fast:
If ./notesctl verify says FAIL: no last_success record; run ./notesctl run,
you have not completed the first manual export yet.
If macOS permissions block the first run, use ./notesctl permissions and the
public troubleshooting guide.
The public
proof page
shows the repo-side gates, live-surface checks, and trust boundary in one
place.
Maintainer-grade verification lives later in
Proof and verification; it is not required for the
first successful snapshot.
Choose the right lane
Keep the operator lane and the maintainer lane separate.
Operator lane
./notesctl run --no-status
./notesctl install --minutes 30 --load
./notesctl verify
./notesctl doctor
./notesctl status --full
optional after the local state exists: ./notesctl ai-diagnose, ./notesctl web, and ./notesctl mcp
Maintainer lane
./notesctl clean-cache --dry-run
./notesctl clean-cache
./notesctl rebuild-dev-env
./notesctl update-vendor
./notesctl setup / ./notesctl self-heal
If your goal is a healthy backup loop, stay in the operator lane first. The
maintainer lane is for repo upkeep and contributor verification after the local
workflow already makes sense.
Why this exists
The upstream notes-exporter
project is great when you want to export Apple Notes right now. This repository
exists for the moment when "run it once" turns into "keep it healthy every
day."
Apple Notes Snapshot wraps the upstream exporter with:
one supported human entrypoint: notesctl
scheduled execution via launchd
lock protection to avoid overlapping runs
structured state and metrics files for health checks
log rotation and log-health summaries
an optional local Web console for status and safe actions
What you get over upstream notes-exporter
Think of upstream as the engine and this repository as the control room
around it. The wrapper tells you where snapshots will land,
and makes the schedule and health surface easier to inspect when something
breaks.
Need
Upstream notes-exporter
Apple Notes Snapshot
Export notes once
Yes
Yes
Schedule recurring exports
Manual setup
Built-in launchd flow
Check freshness and last success
Limited
status, verify, and Web health UI
See structured run metadata
Limited
State files, metrics, and summaries
Rotate logs and inspect failures
Manual
Wrapper-owned log handling
Use a local Web console
No
Optional, token-aware local control plane
Use upstream directly if you only need a one-time export. Use this repository
when you want a repeatable local backup loop with visible scheduler state,
clear snapshot paths, and easier recovery when something fails.
What the Web console shows
The local Web console is optional, and it makes the most sense after you have
already completed the first run -> install -> verify pass. Use it to watch
the loop you just proved, not as a replacement for that first proof.
It surfaces:
snapshot health, last success, launchd state, and failure reason
an operator focus deck that says what to do next and which panel to open first
doctor warnings and dependency readiness
recent metrics and trigger sources
log-health summaries
access policy and quick actions
Optional AI and agent-facing surfaces
These are additive surfaces around the same local control room. They do not
replace notesctl or the deterministic runtime checks.
AI Diagnose
Use ./notesctl ai-diagnose when you want an advisory explanation of the
current local state. It reads status, doctor, log-health, and recent-run
summaries, routes model calls through a local Switchyard runtime when AI is
enabled, and still works as a deterministic fallback when no AI provider is
configured. Read the public
AI Diagnose guide.
MCP Provider
Use ./notesctl mcp when you want a stdio-first, read-only-first MCP surface
for agents. It exposes local backup diagnostics and resources without turning
the Web console into a fake MCP API. Read the public
MCP guide.
Local Web API
Use ./notesctl web when you want the token-gated local browser control room
plus a small JSON API backed by the same repo-owned command surface. It is a
local operator API, not a public OpenAPI or hosted integration surface. Read
the public
Local Web API guide.
How AI and agents fit
Use this mental model if you care about Codex, Claude Code, MCP, or other
host-local integration ecosystems.
Natural fit:
Codex- and Claude Code-style local workflows when the host can launch stdio MCP servers
MCP-aware coding agents that need the same local backup facts as a human operator
AI Diagnose = operator next-step assistant
It explains local status, doctor, log-health, and recent-run evidence.
It is not a generic chat overlay and it does not become the system truth.
MCP Provider = read-only agent substrate
It exposes the same local backup state to MCP-aware hosts.
It does not turn the project into a hosted agent platform or write-capable
remote control plane.
Local Web API = token-gated browser/API surface
It serves the local Web console plus JSON endpoints like status,
doctor, recent-runs, and access.
It is meant for browser or local HTTP workflows on your own machine, not as a
public OpenAPI promise.
notesctl + state.json + aggregate summaries + token-gated Web API = current substrate
This repository does not ship a public OpenAPI, generated client, or SDK
today.
The truthful integration entry points are the CLI contract, the web/API
surface, and the read-only MCP surface.
Integration-facing docs have their own shelves now, so this README does not need to carry
every host-specific setup detail:
Open the public
For Agents guide
for the truthful builder overview and proof legend.
Open the
Local Web API guide
if your workflow is browser- or local-HTTP-shaped instead of stdio MCP.
Typical workflows
1. Review the export path, then prove the first run
bash
./notesctl run --no-status
./notesctl install --minutes 30 --load
This is the primary getting-started path. Everything else in the docs site is a
supporting surface around that flow.
2. Check whether backups are still healthy
bash
./notesctl status --full
./notesctl verify
./notesctl doctor
3. Open the local console when you want a control room
bash
export NOTES_SNAPSHOT_WEB_TOKEN="<long-random-token>"
./notesctl web
4. Refresh the vendored upstream when you maintain the wrapper
bash
./notesctl update-vendor --ref <tag|branch|sha>
Who this is for
People who already rely on Apple Notes and want local, repeatable snapshots
macOS users who prefer launchd over cloud schedulers
Anyone who wants backup health, logs, and operational visibility around Apple
Notes exports
Maintainers who want a reviewable wrapper instead of ad hoc shell snippets
Who this is not for
Teams looking for a shared cloud notes product
Anyone expecting two-way sync back into Apple Notes
Cross-platform note automation workflows
Hosted SaaS or desktop app buyers
Proof and verification
This repository ships with repo-owned verification commands instead of asking
you to trust screenshots alone. These are maintainer / contributor gates, not
required for the first successful snapshot.
If you want the shorter public-facing evidence page first, open the
proof page.
It keeps the repo-side gates, GitHub-controlled delivery facts, and the
current access boundary in one place. The ladder below remains the
maintainer-grade verification contract.
Default local maintainer lane:
bash
./notesctl rebuild-dev-env
./.runtime-cache/dev/venv/bin/python -m pre_commit run --all-files
PYTHON_BIN=./.runtime-cache/dev/venv/bin/python scripts/checks/ci_gate.sh
Maintainer verification expects a local Python 3.11+ toolchain. ./notesctl rebuild-dev-env
recreates .runtime-cache/dev/venv from scratch so the documented verification
commands stay aligned with the current checkout path and interpreter.
GitHub Actions for this repo run on GitHub-hosted runners; the local ladder
below is maintainer verification, not a self-hosted runner requirement.
Five-layer CI contract:
Layer
Canonical home
What belongs here
pre-commit
local hook
gitleaks, docs-link-root hygiene, legacy-path scan, and public-surface-sensitive scan
pre-push
local hook
scripts/checks/ci_gate.sh for repo-local deterministic checks: docs/root hygiene, vendor tree hygiene, unit tests, and wrapper smoke
Nightly Deterministic Audit reruns the repo-owned ladder on GitHub-hosted runners without making pre-push heavier
manual
real machine / owner session
`notesctl run
GitHub-only governance gates:
Dependency Review runs on pull requests because it needs GitHub's base/head
dependency diff.
CodeQL, Secret Scan, GitHub Alert Gate, Actionlint, Zizmor, and
Trivy stay hosted-first; local reruns are optional maintainer repro steps,
not part of the default hook path.
Latest release tags should point at the current canonical closeout commit; do
not treat an older tag as proof of current repo-side closure.
Runtime and cache contract
Think of the runtime layout as two rooms with one job each:
repo-local rebuildables live under .runtime-cache/
repo-owned machine-level residue lives under the current machine cache root
managed by notesctl
.runtime-cache/cache/apple-notes-snapshot -> repo-local runtime cache/state support
.runtime-cache/temp -> scratch
.runtime-cache/logs -> repo-local logs
.runtime-cache/pytest -> pytest cache
.runtime-cache/coverage -> coverage data
.runtime-cache/pycache -> Python bytecode cache
.runtime-cache/browser-proof -> generated proof screenshots that can be rebuilt from the current docs surface
.runtime-cache/phase1 -> historical hard-cut rollback artifacts, not current runtime truth
.runtime-cache/phase1-history-rebuild -> historical rebuild rollback artifacts, not current runtime truth
.runtime-cache/mcp-registry-lane/out -> release-ready MCP registry artifacts that can be rebuilt on demand
Current external repo-owned contract:
launchd records for current and stale labels
runtime residue for launched repo-owned services
repo-scoped runtime copies used by the launchd wrapper
the persistent isolated browser root for this repo
disposable browser temp residue
the vendor-runtime current pointer/cache
Older machine-level Application Support and cache roots are migration inputs
only. New repo-owned runtime/cache writes should stay inside the current
repo-managed machine cache root.
Local cleanup and runtime audit
This is a maintainer-only cleanup lane. It is useful when you are rebuilding
verification tooling or reclaiming repo-owned runtime residue, not when you are
trying to complete the first successful snapshot.
./notesctl clean-cache --dry-run
previews repo-local rebuildable/disposable cleanup under .runtime-cache/
reports repo-local support surfaces plus external repo-owned cache/runtime roots
./notesctl clean-runtime --dry-run
previews cleanup for stale non-current residue under the repo-managed machine cache root
./notesctl clean-runtime
removes stale external repo-owned residue while protecting current launchd labels
./notesctl browser-bootstrap
copies the current apple-notes-snapshot source profile out of the default Chrome root into the repo-owned isolated root
./notesctl browser-open
launches or attaches to the single repo-owned Chrome instance for this repo
./notesctl rebuild-dev-env
restores the canonical path-aware maintainer environment after cleanup
The repo may still clean legacy .pytest_cache, .coverage, or scattered
__pycache__ directories if they are already present, but those are migration
backstops. The current contract routes repo-owned disposable artifacts into
.runtime-cache/*, including historical rollback folders and proof captures
that are safe to regenerate when you no longer need them.
Automatic janitor hooks run on repo-owned entrypoints that create or reuse
machine-level residue, including run, web, install, ensure,
rebuild-dev-env, and runtime-audit. The default policy is intentionally
strict:
external repo-owned budget: 2 GB
general external TTL: 72 hours
browser clone TTL: 24 hours
current/protected launchd labels are never deleted by TTL alone
browser/chrome-user-data/ is permanent state and excluded from TTL/cap cleanup
This repository does not have a repo-owned Docker cleanup lane today.
The automatic janitor does not touch Docker, system temp roots, or shared tool
caches from Cursor, Codex, Claude, Serena, Homebrew, pip, nodeenv, uv, or
other machine-wide tooling.
Browser automation contract
Browser automation in this repository now uses an isolated Chrome root +
single repo-owned instance + CDP attach contract.
The old default Chrome user-data root is now only a one-time read source for
./notesctl browser-bootstrap. It is no longer the long-term runtime root for
this repo.
Treat browser-bootstrap as a one-time migration, not as a routine sync step.
If you later add or refresh logins inside the isolated root, do not rerun
browser-bootstrap unless you intentionally want to replace the isolated root
from the default Chrome root again.
Use these commands in order:
./notesctl browser-bootstrap
one-time copy from the default Chrome root into the isolated repo-owned root
./notesctl browser-open
launch the single repo-owned Chrome instance if it is not running yet
otherwise return attach info instead of second-launching
./notesctl browser-contract --json
print the canonical attach-first contract, including the CDP endpoint for Playwright/CDP clients
If 127.0.0.1:9337 is already occupied on your machine, the repo should fail
fast instead of silently attaching to the wrong thing. In that case, use a
deliberate env override such as NOTES_SNAPSHOT_CHROME_CDP_PORT=9347 before
starting or attaching to the repo-owned instance.
This repo does not silently fall back to bundled Chromium, and it does
not keep launching new browser instances against the same user-data dir.
Human manual use and automation are expected to attach to the same repo-owned
Chrome instance.
CI contract
The repository keeps five verification layers, and they are not interchangeable:
Layer
Default trigger
Canonical home
Contract
pre-commit
every local commit attempt
local hook
quick hygiene only
pre-push
every local push attempt
local hook
deterministic repo-local quick gate only
hosted
pull request / push / workflow dispatch
GitHub-hosted runners
GitHub-state-aware security and policy gates
nightly
scheduled GitHub run
Nightly Deterministic Audit reruns the repo-owned ladder on GitHub-hosted runners
manual
deliberate human/operator action
real machine / owner session
live browser, desktop, provider, and external control-plane proof
This open-source repository does not rely on a local self-hosted runner
lane. Local verification exists so maintainers can reproduce the repo-owned
gates before or after a pull request, not because CI is expected to run on the
developer's Mac.
High-value local checks:
bash
./notesctl status --full
./notesctl verify
./notesctl doctor
The repo-owned quick gate now covers docs/root hygiene, vendor tree hygiene,
unit tests, and wrapper-level JSON/help smoke checks. GitHub alert state moved
fully into the hosted lane so the default local pre-push path stays lighter and
more deterministic. The 90%+ coverage bar still applies to repo-owned Python
surfaces under scripts/ops; the shell wrapper surface is guarded by smoke
checks rather than pretending it shares that coverage metric.
Verification ladder and test pyramid
Think of the verification contract like a small testing pyramid instead of one
giant "just run everything" blob.
Unit tests
fastest checks for repo-owned Python logic and deterministic report shaping
the real operator path on a real Mac:
run -> verify -> status -> doctor -> web
Read the pyramid as:
unit tests catch logic regressions fastest
wrapper smoke protects the shell contract
E2E checks prove the repo-owned pieces still cooperate
manual-local validation proves the actual machine is healthy
Security and privacy
This project is intentionally user-controlled on your own machine:
your Apple Notes content stays in your own Apple Notes account and export
destination
the optional Web console is local by default and token-aware when enabled
the repository documents private vulnerability reporting in SECURITY.md
secrets, runtime caches, generated launchd files, and logs are not part of
the tracked source surface
Read SECURITY.md before reporting sensitive issues.
FAQ
Is this a replacement for the upstream exporter?
No. It is a wrapper that makes the upstream exporter safer and easier to operate
as an ongoing local workflow.
Does it sync changes back into Apple Notes?
No. This repository is for snapshotting and exporting, not two-way sync.
Do I need the Web console?
No. notesctl is the supported human entrypoint. The Web console is optional.
How do I open the interactive menu?
Run ./notesctl with no arguments. The interactive menu lives on the canonical
command surface now.
Do I need GitHub Pages to use the tool?
No. Pages is only for public documentation and search visibility. The actual
workflow stays local on your Mac.
What if I move the checkout to another path?
Reinstall the scheduler so the path-aware launchd surface points at the current
checkout. If you also use the repo-owned verification environment or the
optional Web console, rebuild that environment too:
If you need a support-routing summary first, read SUPPORT.md or the
public support page.
Contributing
Read CONTRIBUTING.md before changing wrapper logic, docs, or
public-facing copy. The project favors small, reviewable changes with explicit
verification notes.
License
This repository is released under the MIT License.