API & protocol — overview
The daemon speaks JSON-RPC 2.0 with LSP-style framing on a Unix socket. The plugin opens the socket via SSH local port-forward; you can also connect directly with any tool that can write LSP-framed JSON-RPC over a Unix socket.
Wire format
- Transport: Unix socket (default
~/.obsidian-remote/server.sock) - Framing:
Content-Length: <N>\r\n\r\n<N bytes of UTF-8 JSON>per message — same as the Language Server Protocol. Unknown headers are silently ignored for forward-compat; missing or malformedContent-Lengthcloses the connection. - Max message size: 16 MiB (server-side cap; oversized messages close the connection)
- Encoding: UTF-8 JSON
- Spec: JSON-RPC 2.0
Sections
- Authentication —
auth(token)handshake,server.info - Filesystem —
fs.stat,fs.read*,fs.write*,fs.list,fs.walk,fs.mkdir,fs.remove, etc. - notifications —
fs.watch,fs.unwatch,fs.changed(server-push) - Error codes — full error reference
- Examples — copy-pasteable JSON-RPC envelopes for every common operation
- Protocol evolution & versioning — what’s a breaking change vs capabilities-gated, the strict-equality regime, future-bump triggers
Protocol version
Currently 1. The handshake’s server.info returns a protocolVersion field; clients refuse to proceed if it’s outside the range they support.
interface ServerInfo {
version: string; // semver of the daemon binary, e.g. "0.1.0"
protocolVersion: number; // currently 1
capabilities: string[]; // e.g. ["fs.stat", "fs.watch", "fs.thumbnail"]
vaultRoot: string; // absolute path on remote (informational)
}Adding a method bumps capabilities (clients can feature-detect). Breaking changes to existing methods bump protocolVersion.
Path conventions
All paths are vault-relative, forward slashes only:
notes/today.md— fine/notes/today.md— leading slash rejected withPathOutsideVault (-32015)../escape.md—..rejected withPathOutsideVaultnotes\today.md— backslashes rejected (Windows-style separators are not normalised; the daemon is a Linux/macOS process)""or"/"— vault root
Quick example
Connect, authenticate, list the root:
# Read the token (mode 0600, only your user can read it)
TOKEN=$(cat ~/.obsidian-remote/token)
# LSP-style frame: Content-Length: <N>\r\n\r\n<body>
frame() {
local body="$1"
printf 'Content-Length: %d\r\n\r\n%s' "${#body}" "$body"
}
# Authenticate, then fs.list
{
frame '{"jsonrpc":"2.0","id":1,"method":"auth","params":{"token":"'"$TOKEN"'"}}'
frame '{"jsonrpc":"2.0","id":2,"method":"fs.list","params":{"path":""}}'
} | nc -U ~/.obsidian-remote/server.sock(For real tooling, use a JSON-RPC library that handles header parsing — this is just for spot-checks.)
Stability
Protocol version 1 is frozen for the lifetime of the 1.x line. Method additions are non-breaking (capabilities-gated). Param/result shape changes ship as new methods.
Next: Authentication.