Sync and MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Sync and MCPsearch my published notes for anything about MCP server setup"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Sync and MCP — server
English | Русский
The server for the Sync and MCP Obsidian plugin. It stores end-to-end encrypted vault blobs (sync), receives the plain-text notes published as "AI Available", serves them over MCP (streamable HTTP) and builds search over them (FTS5 + embeddings). The HTTP contract is in docs/API.md, the architecture in docs/ARCHITECTURE.md (both in Russian).
Get the code:
git clone https://github.com/nejroseld/sync-and-mcp-backend.git
cd sync-and-mcp-backendTechnical names come from the project's working name, obsi-mcp: the Python package obsi_server,
the obsi-server command, the obsi-mcp.service unit, the /opt/obsi-mcp and /var/lib/obsi-mcp
directories and the OBSI_* variables. They are kept so that existing servers update without a migration.
Privacy model
Synced files arrive encrypted on the device with rclone crypt. The server never sees the encryption password, file names or contents.
Notes a user allows for AI are published in plain text: the server indexes only the current version of a published note (
.md) and hands it to MCP clients. Attachments, other AI Available files, and the encrypted sync store are not indexed. Unpublishing deletes the chunks before that text is sent for embeddings. AI access is off by default in the plugin.MCP clients never write to a vault directly. A suggested edit is queued, and the user's Obsidian client applies it only if the AI rules allow it and the note has not changed.
Related MCP server: ohmyself
Multiple users
Every user registers a separate account and creates their own vaults. The administrator issues a one-time invitation; for the second and further users run the same command with another name:
obsi-server --data-dir ./data admin create-invite --name alice
obsi-server --data-dir ./data admin create-invite --name bob
obsi-server --data-dir ./data admin list-usersThe code is shown once. A person needs two values, the server address and the code, so forward them in one message:
Sync and MCP invitation for alice
Server: https://203-0-113-10.sslip.io
Code: inv_...
In Obsidian: Sync and MCP → "I have an invitation", paste this whole message.manage-vps.sh ("Users and invitations" menu item), local.sh and install-vps.sh (for the first
invitation) print this message ready to forward. The plugin takes the first http(s) address and the
inv_... code from the pasted text; line labels do not matter. The user then picks a username and
password, creates a server vault and sets a separate encryption password. Next time they choose
"Sign in to my account". A user sees only their own vaults and the ones the administrator explicitly
shared. In the plugin, the administrator can create invitations and list accounts on the "Server" tab.
After signing in, an account session can call /api/v1/tokens to issue device or MCP tokens with
explicit grants on the user's own vaults. Delegated tokens get no implicit owner access, cannot
create vaults and cannot manage tokens. See docs/API.md for the full contract.
On a fresh install local.sh and install-vps.sh create an admin token and the first invitation.
On an existing server the data and old tokens are kept; create invitations for new users with the
command above (on a VPS use --data-dir /var/lib/obsi-mcp and the service user). Shared vaults and
tokens with grants keep working.
Dependencies: Python 3.11+, fastapi, uvicorn, httpx. For S3 install pip install -e '.[s3]'.
Everything else is the standard library (sqlite3 with FTS5). numpy is optional: when installed,
cosine similarity is computed faster.
Installing on an Ubuntu VPS
From your computer (recommended)
You need a VPS with Ubuntu 22.04 or later and the login details from your hosting provider. Your
computer needs ssh and tar (macOS and Linux have them; on Windows run from WSL or Git Bash).
From the repository directory:
./manage-vps.sh # or with the address: ./manage-vps.sh root@203.0.113.10The first run has four steps:
Server. Paste the address in any form:
203.0.113.10,ubuntu@host:2222,ssh root@host -p 2222. The default user isroot, the default port22.Connection. The SSH password is entered once: the script keeps one shared connection for the whole session. On the first connection to a new server the script shows its SSH key fingerprints and waits for you to confirm that they match the hosting panel or console (
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub); no password is sent without confirmation. If the user is notroot, thesudopassword is asked once (kept only in the script's memory). The script then creates the key~/.ssh/obsi_mcp_ed25519(mode0600; with a running ssh-agent you can set a passphrase) and adds it to the server, so later runs need no password. If the server accepts keys only, the script asks for the path to the key from your provider.HTTPS address. Press Enter to get a free address like
203-0-113-10.sslip.io(a DNS service that returns the IP from the name); your own domain with an A record pointing to the server works too. The script checks DNS, installs Caddy and obtains a Let's Encrypt certificate (scripts/setup-https.sh; with an activeufwit opens ports 80 and 443). Answering "no" leaves the server without HTTPS: it is then reachable only through the SSH tunnel from the menu.Installation. The code is uploaded to the VPS and installed with
install-vps.shwithout questions (S3 is not configured). At the end the script checks/api/v1/healthand shows the first invitation as a ready message with the server address and code; the admin token is shown only after a separate confirmation. Forward the invitation or paste it into Sync and MCP yourself: "I have an invitation".
Later runs open a menu. Enter runs the default item (server update). The menu also has connection
details, status and logs, domain and HTTPS, SSH tunnel, S3, users and invitations, disabling password
login and switching to another server. "Users and invitations" lists registered users (date, number
of vaults and tokens) and unused invitations, then asks for a name and prints a message to forward;
you can create several invitations in a row, an empty name returns to the menu. Configured S3 is
kept on update. Connection settings are stored in ~/.config/obsi-mcp/vps.conf with mode 0600;
passwords are never written there. The log of the last error is saved to
~/.config/obsi-mcp/last-error.log. NO_COLOR=1 disables colours and animation.
The interface language comes from --lang ru|en (remembered in vps.conf), the OBSI_LANG
variable or the system locale; the default is English. Strings live in scripts/i18n/<lang>.sh.
To add a language, copy en.sh to <code>.sh and translate the values; missing keys are shown in
English.
"Key-only server login" disables password and keyboard-interactive SSH authentication for all VPS users; enable it only if everyone else also has key login set up. After the change the script checks a new SSH session. If login cannot be confirmed, the previous SSH settings are restored after two minutes.
Directly on the VPS
From this repository's directory:
sudo ./install-vps.sh
# another local port: sudo env OBSI_PORT=9000 ./install-vps.shThe script installs Python 3.11+ and creates /opt/obsi-mcp/venv from scratch. Dependencies are
installed only from requirements.lock (or requirements-s3.lock when S3 is configured) with hash
checking, so a repeated run produces the same set of packages. The application code is copied to
/opt/obsi-mcp/src. The script creates a dedicated system user and the obsi-mcp.service unit
(PrivateTmp, MemoryHigh=768M, MemoryMax=1G, TasksMax=128; change the memory limits with
OBSI_MEMORY_HIGH and OBSI_MEMORY_MAX). Data is stored in /var/lib/obsi-mcp.
The first installation creates an admin token and the first invitation; read them with
sudo cat /var/lib/obsi-mcp/TOKENS.txt. The invitation is printed on screen only when the output
goes to a terminal. It is valid for 7 days. The invitation message there contains the placeholder
<your server address>: replace it with the public HTTPS address before forwarding. A repeated run
updates the application and the service and keeps the data and previously issued tokens. S3
settings and optional access keys are kept in /etc/obsi-mcp/s3.env with mode 0600; without this
file only local storage is used.
Check: curl http://127.0.0.1:8765/api/v1/health; status: sudo systemctl status obsi-mcp;
logs: sudo journalctl -u obsi-mcp -f. Without a token, /api/v1/health from an external address
answers only {"ok": true}; the version and modules are visible from localhost or with any valid
token. The service listens on 127.0.0.1 only. For access from other devices put a reverse proxy
with HTTPS in front of it on the VPS and use its public URL in the plugin and the MCP client (/mcp).
Quickest start: ./local.sh
./local.shThe first run creates .venv, installs dependencies, creates an admin token and the first
invitation and writes them to data/TOKENS.txt; the invitation is also printed in the terminal as a
ready message. Dependencies are installed from requirements.lock with hash checking. The data
directory and its contents are accessible only to the owner (0700 for directories, 0600 for
files), including on a rerun with old data. If an old TOKENS.txt used to be readable by other local
users, revoke every token stored in it and issue new ones: changing permissions does not revoke
secrets. The script then starts the server on http://127.0.0.1:8766. Later runs just start the
server. Override: OBSI_PORT=9000 OBSI_HOST=0.0.0.0 OBSI_DATA_DIR=~/obsi-data ./local.sh.
For local S3 set OBSI_S3_BUCKET (or s3_bucket in the OBSI_CONFIG file) and, if needed,
OBSI_S3_REGION, OBSI_S3_ENDPOINT, OBSI_S3_PREFIX, OBSI_S3_THRESHOLD_MB and the standard AWS
credential variables before running ./local.sh.
Running on your machine (step by step)
Scenario: the server and Obsidian on the same computer. You need Python 3.11+ (python3 --version).
1. Get the code:
git clone https://github.com/nejroseld/sync-and-mcp-backend.git ~/sync-and-mcp-backend
cd ~/sync-and-mcp-backendWhen moving a working server from another machine, copy its directory together with data/: this
keeps the existing vaults and tokens (see data/TOKENS.txt), and step 4 can be skipped.
2. Install dependencies into a venv:
python3 -m venv .venv
. .venv/bin/activate
pip install -e . # fastapi, uvicorn, httpx; optional numpy: pip install -e '.[fast]'3. Start the server (keep the terminal open, stop with Ctrl+C):
obsi-server --data-dir ./data serve --port 8766
# check from another terminal:
curl http://127.0.0.1:8766/api/v1/healthBy default the server listens on 127.0.0.1 only, which is enough for Obsidian on the same machine.
For a phone on the local network use --host 0.0.0.0 and open the port in the firewall
(sudo firewall-cmd --add-port=8766/tcp).
4. Create an admin token and an invitation (in another terminal with the same --data-dir; the
server can keep running):
. .venv/bin/activate
obsi-server --data-dir ./data admin create-token --kind admin --name root
obsi-server --data-dir ./data admin create-invite --name aliceTokens are shown once, so save them.
5. The plugin in Obsidian: install Sync and MCP
with BRAT (nejroseld/sync-and-mcp) or copy main.js, manifest.json, styles.css from a plugin
release into <vault>/.obsidian/plugins/sync-and-mcp/ and enable it in Settings → Community plugins.
In the plugin's wizard choose "I have an invitation" and paste the address http://127.0.0.1:8766
with the invitation code (or the whole message), then pick a username and password, create a vault
and enter a separate encryption password.
6. MCP in Claude Code:
claude mcp add --transport http obsi http://127.0.0.1:8766/mcp --header "Authorization: Bearer <mcp-token>"Debugging: curl -v http://127.0.0.1:8766/api/v1/health shows that the server is alive;
curl -H "Authorization: Bearer <device-token>" http://127.0.0.1:8766/api/v1/me shows that the token
is valid. connection refused means the server is not running or uses another port; 401 means a
wrong or revoked token.
Running (short version)
python3 -m obsi_server --data-dir ./data serve --host 0.0.0.0 --port 8765
# or: pip install -e . && obsi-server --data-dir ./data serveAll commands (serve and admin ...) must use the same data directory (--data-dir or
OBSI_DATA_DIR; the default is ./obsi-data relative to the current directory). When started
directly, the server requires a private data directory (chmod 700 DIR); local.sh fixes the
permissions of an old directory automatically.
Configuration comes from environment variables (or a TOML file at the path in OBSI_CONFIG, section
[server]):
Variable | Default | Meaning |
|
| SQLite database and blobs ( |
|
| listen address |
|
| enabled modules ( |
|
| limit for encrypted file uploads (the body is written to a temporary file in |
|
| limit for one AI Available file (kept in memory and in SQLite) |
|
| vault quota: ciphertext + AI Available + pending MCP edits; |
|
| concurrent uploads larger than 1 MiB; the rest wait in a queue |
|
| request limit per authorized token per minute; raise it for especially heavy sync |
|
| background embedding thread |
|
| explicit comma-separated origin list; |
|
|
|
|
| session lifetime after login and idle lifetime; at most 10 sessions live per user |
|
| invitation lifetime |
|
|
|
| unset | bucket for attachments above the threshold; requires boto3 credentials |
|
| offload threshold for files other than |
| unset / unset / | S3 parameters |
|
| server-side encryption of S3 objects: |
TOML example: [server]\ndata_dir="/srv/obsi"\nfeatures=["ai","mcp"]\nmax_body_mb=500.
Login requests are rate-limited by IP and account name; registration by IP. MCP has separate limits
for calls and search, reindexing and AI cleanup have their own limits. When a limit is exceeded the
server answers 429 with Retry-After. Counters are stored in SQLite and apply across several
processes sharing one database.
The server speaks plain HTTP; for internet access put it behind a reverse proxy with TLS. S3 setup and file behaviour are described in docs/S3.md.
To give someone access, the administrator creates a one-time invitation in the plugin or with
POST /api/v1/admin/invites. The user enters the code, username and password in the plugin's
connection wizard and then creates their own vault. Old device tokens and grants keep working;
details are in docs/API.md.
Quick start
python3 -m obsi_server --data-dir ./data admin create-token --kind admin --name root
python3 -m obsi_server --data-dir ./data admin create-invite --name alice
python3 -m obsi_server --data-dir ./data admin list-usersFor a shared folder or an MCP client you can create a vault and a token with grants separately
through the CLI or the admin REST API (/api/v1/admin/...).
Other commands: list-invites, revoke-invite INVITE_ID, list-vaults, list-tokens,
revoke TOKEN_ID, grant TOKEN_ID --vault V --ops a,b, rag-enable V, rag-disable V, reindex V,
set-embedding --base-url https://api.example.com/v1 --model text-embedding-3-small --api-key-file ~/emb.key
(key file with mode 0600; --api-key-prompt asks for the key without echo, --clear-api-key
removes it). Parameters that are not passed stay unchanged. The key is not accepted as a
command-line argument: it would be visible in the process list and shell history. The embedding
address must be public https: the server checks it when saving and on every connection, does not
follow redirects and ignores HTTP_PROXY. For a local Ollama (http://localhost:11434/v1) start the
server with OBSI_EMBEDDING_ALLOW_PRIVATE=1. Changing model or base-url resets the vectors and
the index is rebuilt in the background (a running server picks up CLI changes within ~10 seconds).
Only text of the current published notes (.md) from vaults with RAG enabled is sent to the embedding endpoint. A chunk that is no longer that note is deleted and not sent. Without configured embeddings (or with RAG disabled for a vault) search works as full-text search and says
so in the note field.
Connecting MCP clients
Endpoint: http://HOST:8765/mcp, authorization Authorization: Bearer <mcp-token>.
An assistant needs only the list,search,read operations; grant write only if it should suggest
edits (they are still applied by the plugin, not by the server). Note text in MCP responses is
marked as untrusted data: the model must not follow instructions found in notes.
claude mcp add --transport http obsi http://HOST:8765/mcp --header "Authorization: Bearer obsi_..."Claude Desktop (via claude_desktop_config.json and the mcp-remote bridge):
{ "mcpServers": { "obsi": { "command": "npx",
"args": ["mcp-remote", "http://HOST:8765/mcp", "--header", "Authorization: Bearer obsi_..."] } } }Or add the server as a custom connector in the client settings if it supports the Authorization header.
Tests
python3 -m unittest discover -s tests -t tests -vSmoke test (run manually)
export OBSI_DATA_DIR=/tmp/obsi-smoke
python3 -m obsi_server serve --port 18765 &
curl -s http://127.0.0.1:18765/api/v1/health
# {"ok":true,"version":"0.1.0","features":{"ai":true,"mcp":true,"rag":true}} (from localhost)
curl -s -X POST http://127.0.0.1:18765/mcp -H "Authorization: Bearer $MCP" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
# {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"sync-and-mcp",...}}}
curl -s -X POST http://127.0.0.1:18765/mcp -H "Authorization: Bearer $MCP" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# list_vaults, list_notes, search, read_note, write_note, get_change_statusAlso verified: a PUT to ai/files/... followed by search through tools/call finds the note.
Structure
config, db (SQLite WAL, migrations by user_version), auth, vaults, storage, ai_store,
changes, rag, mcp, app (REST + assembly), cli. Modules are connected by hooks: ai_store
notifies rag about PUT/DELETE/clear (removal from the index is synchronous), vaults calls purge
when a vault is deleted. Chunks and FTS are written synchronously on publish; embeddings are filled
in by a background worker (batches of 32, 60 s timeout, retry with backoff on error, the server does
not crash).
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Google Keep-style notes app with an MCP server for AI agents to read/write notes.
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Related MCP Servers
- FlicenseNot gradedqualityAmaintenanceSelf-hosted MCP server that provides remote AI clients with read and write access to Obsidian vaults over HTTPS without needing the Obsidian desktop app running.2-
- AlicenseNot gradedqualityBmaintenanceExposes a personal markdown-based second brain (Obsidian-style) as an MCP server, enabling agents to search, read, and write notes with privacy controls.MIT
- FlicenseAqualityCmaintenanceExposes Obsidian notes as a semantic search and RAG knowledge base over MCP, enabling AI assistants to index, retrieve, and analyze personal notes via natural language.71-
- FlicenseNot gradedqualityAmaintenanceEnables AI clients to read, write, search, and manage files in an Obsidian vault via MCP over SSE, running standalone without the Obsidian desktop app.1-