slate
Enables AI agents to read and write to an iPad whiteboard via MCP, including structured elements, handwritten text, and image rendering.
README
Slate
Una whiteboard sull'iPad che gli agenti AI possono leggere e scrivere via MCP.
Disegni e scrivi a mano con la Pencil, aggiungi note e riquadri. Dall'altra parte un agente (Claude Code o qualsiasi client MCP) legge la board come struttura — elementi, testo, posizioni, frecce — e come immagine, così capisce anche quello che è stato scritto a mano. E può rispondere scrivendo sulla board.
Tutto gira sul tuo Mac. Nessun servizio esterno, nessun dato che esce dalla rete.
Prima di adottarlo: il codice di Slate è MIT, ma il canvas è l'SDK di tldraw, che non è open source e limita l'uso gratuito agli ambienti di sviluppo. Per un deployment di produzione serve una licenza tldraw. Dettagli nella sezione Licenza — leggila prima di costruirci sopra.
Come è fatto
iPad Safari (PWA tldraw) ─┐
├─ WebSocket sync ─► board-server (Node 22+)
Mac browser (stessa PWA) ─┘ • @tldraw/sync-core
• SQLite (un file per board)
• render PNG headless + OCR
▲
Claude Code / altri agent ── stdio MCP ──► slate-mcp ────┘
Lo stato vive sul server, non sull'iPad: la board resta leggibile anche a tablet spento.
| Cartella | Cosa fa |
|---|---|
apps/board-server |
Sync tldraw, persistenza, API, render, OCR |
apps/board-web |
La PWA installabile sull'iPad |
apps/slate-mcp |
Il server MCP che parla agli agenti |
tools/ocr |
Binario Swift che usa Apple Vision per la scrittura a mano |
Prerequisiti
- Node ≥ 22.12 — richiesto da tldraw 5. Il
.node-versionpunta a 26. Se la tua shell è su Node 20 (default per altri progetti), usa/opt/homebrew/bin/nodeofnm use 26. - Google Chrome — usato in headless per il render. Nessun browser da scaricare.
- Xcode command line tools — solo per compilare il binario OCR.
Setup
npm install
npm run build
# OCR della scrittura a mano (opzionale ma consigliato)
cd tools/ocr && swiftc -O -o slate-ocr main.swift && cd -
Avvio
npm run dev:server # in sviluppo
Oppure come servizio, così parte da solo al login:
./scripts/install-service.sh
All'avvio il server stampa i link di pairing:
[slate] pair a device: http://192.168.1.x:4501/?token=… ← LAN
[slate] pair a device: http://100.x.x.x:4501/?token=… ← Tailscale
Collegare l'iPad
- Apri uno dei link di pairing su Safari. Il token viene salvato e tolto dall'URL.
- Condividi → Aggiungi a Home. Si apre a schermo intero, senza barre.
- Fuori casa serve Tailscale sull'iPad (usa il link
100.x). In LAN non serve.
Il firewall di macOS blocca tutto
Se l'iPad non si collega ma da Mac funziona, è quasi certamente il firewall:
autorizza il binario node reale (non il symlink).
NODE_REAL=$(readlink -f /opt/homebrew/bin/node)
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add "$NODE_REAL"
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp "$NODE_REAL"
Va rifatto dopo ogni aggiornamento di Node: Homebrew installa in un path
versionato (/opt/homebrew/Cellar/node/26.4.0/bin/node) e l'autorizzazione è
legata a quel path esatto.
Collegare l'agente
claude mcp add slate -- /opt/homebrew/bin/node ~/GitHub/mcp/slate/apps/slate-mcp/dist/index.js
L'MCP parla al server via loopback, che è sempre autorizzato: nessun token da
configurare finché server e agente stanno sulla stessa macchina. Se li separi,
passa SLATE_SERVER e SLATE_TOKEN.
Tool disponibili
| Tool | Cosa fa |
|---|---|
list_boards |
Elenca le board, dalla più recente |
read_board |
Struttura: elementi, testo, posizioni, gruppi, frecce, + OCR della scrittura |
render_board |
Restituisce la board come immagine PNG |
search_board |
Cerca testo, incluso quello scritto a mano |
create_board |
Crea una board vuota |
add_note |
Aggiunge una sticky note (posizionata da sola per non sovrapporsi) |
add_image |
Carica un'immagine da file e la posiziona sulla board (PNG, JPEG, GIF, WebP) |
update_note |
Cambia il testo di un elemento mantenendo stile e posizione |
delete_shapes |
Elimina elementi |
Eliminare una board non è esposto come tool: si fa via
DELETE /api/boards/:id. Un agente può cancellare elementi, non board intere.
Quanto è affidabile l'OCR
Poco, ed è per questo che render_board esiste. Su scrittura a mano reale Apple
Vision prende circa 3 parole su 4: abbastanza per cercare ("in che board avevo
scritto FLUSSO?"), non abbastanza per fidarsi del testo riconosciuto.
Per capire davvero cosa c'è su una board disegnata, l'agente deve guardare
l'immagine. read_board lo dice esplicitamente nel proprio output.
Misurato su una board di prova (scripts/ocr-scale-test.mjs confronta le varianti):
| Variante | Risultato |
|---|---|
| scale 1.5, corretto | CIAO QUESTO E ON / TEST / ALUSSO 1 / FLUSSO? |
| scale 2, corretto (default) | CIAO QUESTO E ON / TEST / FLUSSO 1 / Autor |
| scale 3, corretto | CIAO QUEDO E ON / TEST / FLUSSO 1 / {LUSSO? |
qualsiasi scala, --raw |
sempre uguale o peggio |
Due cose controintuitive emerse dai dati:
- Alzare la risoluzione non migliora linearmente. A scale 3 la scrittura viene
letta peggio che a 2 in alcuni punti: i tratti spessi iniziano a fondersi.
Regola con
SLATE_OCR_SCALE, non dare per scontato che più alto sia meglio. - La correzione linguistica conviene tenerla accesa, anche se spinge verso
parole di dizionario (è lei a trasformare
FLUSSO2inAutor). Disattivarla con--rawpeggiora tutto il resto. Se la tua board è fatta solo di sigle, riprova la misura: il compromesso può ribaltarsi.
Sicurezza
Un solo modello di accesso per tutte le superfici remote — API, sync e upload.
Loopback è sempre autorizzato (è così che entra l'MCP); tutto il resto richiede
il token, che viene generato al primo avvio e salvato in ~/.slate/token (0600).
Non esiste una modalità "senza token": lasciare aperto il sync proteggendo solo l'API sarebbe inutile, perché chi raggiunge il socket di sync ha già accesso completo in lettura e scrittura a ogni board.
Le immagini sotto /media/ si leggono senza token: tldraw le carica con <img src>, che non può portare un header, e mettere il token nell'URL lo farebbe
finire dentro i documenti esportati. I nomi contengono un UUID, quindi non sono
indovinabili.
Per restringere l'ascolto alla sola tailnet:
SLATE_HOST=100.x.x.x npm run dev:server # il tuo indirizzo Tailscale
Variabili d'ambiente
| Variabile | Default | Note |
|---|---|---|
SLATE_HOST |
0.0.0.0 |
Metti l'IP Tailscale per non esporre in LAN |
SLATE_PORT |
4501 |
|
SLATE_DATA_DIR |
~/.slate |
Board, asset, token |
SLATE_TOKEN |
generato | Sovrascrive quello salvato |
SLATE_CHROME_PATH |
Chrome in /Applications | Per il render |
SLATE_OCR_BIN |
tools/ocr/slate-ocr |
|
SLATE_OCR_SCALE |
2 |
Risoluzione del render passato a Vision |
SLATE_SERVER |
http://127.0.0.1:4501 |
Lato MCP |
Trappole già incontrate
Cose che costano un pomeriggio se le scopri da solo.
fontSizeAdjustment: 0rende il testo invisibile. La nota si salva, si rilegge correttamente dai dati, e sull'iPad appare vuota. Il valore giusto ènull("ricalcola"). Se scrivi record a mano, non copiare uno0da un esempio vecchio./assetsè di Vite. Le immagini delle board stanno sotto/mediaperché la build della PWA emette i suoi bundle in/assets: una rotta sovrapposta se li mangia e l'app resta senza JavaScript, fallendo in modo silenzioso.- Il testo è ProseMirror, non stringa.
props.richTextvuole un documento; una stringa viene rifiutata dallo schema. - I tratti sono path base64. tldraw 5 non espone un encoder, quindi non si può fabbricare un tratto a mano libera da script — solo disegnandolo. Per lo stesso motivo la dimensione di un tratto non è calcolabile dal record: la sanno solo il renderer e l'endpoint OCR, che la misurano in un editor vivo.
- Non elencare i tratti uno per uno. Una pagina scritta a mano sono decine o
centinaia di
draw: elencarli produce un muro di righe identiche che seppellisce tutto il resto.read_boardli conta e mostra il testo riconosciuto. - Le note non crescono da sole. Una sticky è un riquadro fisso 200×200 e il
testo che eccede viene tagliato nel render, pur restando integro nei dati:
l'API lo rilegge intero mentre sull'iPad la nota appare mozzata.
add_notestimagrowYdalla lunghezza del testo; il client ricalcola l'altezza esatta alla prima modifica. - Il posizionamento automatico ha bisogno delle misure vere. Siccome i tratti
dichiarano dimensione zero, su una board disegnata il calcolo del bordo destro
cade dove inizia il primo tratto e la nota finisce sopra il disegno. Per
questo
add_noteinterroga/api/boards/:id/boundsquando trova inchiostro. - I record scritti vengono validati. Il server rifiuta con 400 quello che non passa lo schema tldraw, invece di far crashare il canvas sull'iPad più tardi.
- La licenza tldraw non è open source. Vedi la sezione qui sotto: incide su cosa puoi farci, non solo sul watermark.
Licenza
Il codice di Slate è MIT (vedi LICENSE): usalo, modificalo, ridistribuiscilo
come vuoi.
Ma il canvas no. Slate è costruito sull'SDK di tldraw, che non è open source e ha una licenza propria. Vale la pena leggerla prima di adottare Slate, perché è più restrittiva di quanto il watermark lasci intuire:
- L'uso gratuito è limitato ai Development Environment. La licenza definisce Production come «any production deployment of the Software that operates on servers, cloud platforms, web applications, or where the software is used to provide functionality to end users».
- Per un deployment di produzione serve una licenza commerciale o un trial.
- La redistribuzione è ammessa solo come componente di un'altra applicazione, mai standalone, e con copia verbatim della licenza tldraw.
In pratica: un'istanza personale self-hosted sul proprio Mac sta in una zona grigia che solo tldraw può chiarire; qualsiasi cosa somigli a un servizio per altri utenti quasi certamente richiede una licenza. Se il vincolo non ti va bene, la strada è sostituire il canvas con un'alternativa realmente libera — Excalidraw è MIT — tenendo il resto di Slate, che dal canvas dipende solo attraverso il layer di sync e il renderer.
Questa è lettura del testo della licenza, non consulenza legale. Per un uso serio
chiedi a tldraw (sales@tldraw.com) o a un avvocato. Il quadro completo delle
dipendenze e delle loro licenze è in NOTICE.md.
Sviluppo
npm run dev:server # server con reload
npm run dev:web # PWA su :4500, proxy verso il server
node scripts/seed-demo.mjs demo # riempie una board di prova
node scripts/test-mcp.mjs demo # esercita l'MCP su stdio
node scripts/make-icons.mjs # rigenera le icone della PWA
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。