Git Integration¶
Use this guide to choose and configure the right git mode for your deployment.
Modes¶
- Managed (
GIT_REPO_URL+GIT_TOKEN) The server owns clone, periodic pull, commit, and deferred push. - Unmanaged / commit-only (no
GIT_REPO_URL, existing git repo) The server stages and commits writes, but never pulls or pushes. - No-git (default) The vault is treated as a plain directory with no git operations.
Managed Mode (Recommended For Containerized Deployments)¶
Use managed mode when the server should fully own git synchronization.
MARKDOWN_VAULT_MCP_SOURCE_DIR=/data/vault
MARKDOWN_VAULT_MCP_READ_ONLY=false
MARKDOWN_VAULT_MCP_GIT_REPO_URL=https://github.com/your-org/your-vault.git
MARKDOWN_VAULT_MCP_GIT_USERNAME=x-access-token
MARKDOWN_VAULT_MCP_GIT_TOKEN=github_pat_xxx
MARKDOWN_VAULT_MCP_GIT_PULL_INTERVAL_S=600
MARKDOWN_VAULT_MCP_GIT_PUSH_DELAY_S=30
Behavior:
- If
SOURCE_DIRis empty at startup, the server clonesGIT_REPO_URLinto it. - If
SOURCE_DIRis already a git repo, the server verifiesoriginmatchesGIT_REPO_URL. - Writes are committed and pushed after the configured idle delay.
- Periodic pull uses fast-forward-only updates.
Manual sync: git_sync tool¶
The periodic loops are time-based: pull every
MARKDOWN_VAULT_MCP_GIT_PULL_INTERVAL_S seconds (default 600), push
MARKDOWN_VAULT_MCP_GIT_PUSH_DELAY_S seconds after the last write
(default 30). For workflows where the LLM needs to confirm "your changes
are now on the remote" before telling the user to check another device,
or wants to pull in remote edits right now before continuing the
conversation, call git_sync directly:
git_sync(direction="both")
Use direction="pull" or direction="push" to skip a leg. In
direction="both" mode the push leg only runs when the pull leg
succeeded; otherwise push stays null and the LLM should inspect
pull.reason (and pull.conflict_files) before retrying.
dry_run=true previews what a pull would do (useful for "is there
anything new on origin?") without risking an in-conversation conflict.
The push leg has no safe local "would this be accepted" probe, so a
dry-run push always returns applied=false with
reason="dry_run_unsupported".
Conflict outcome: Syncthing-style sibling resolution
When the pull would otherwise need an interactive merge, the server follows the #232 Syncthing-style flow:
- The pull succeeds (
pull.applied=true,pull.reason="conflicts_resolved_with_siblings"). - HEAD advances to the remote tip — the canonical path now reflects the remote (remote wins).
- The local versions that conflicted are preserved as
<basename>.conflict-mcp-<timestamp>.mdsiblings on the same path; their vault-relative paths are listed inpull.conflict_files. pull.commits_pulledis0on this path because the rebase replays your local commits on top of the remote tip — the counting model only reports linear-history catch-ups.
The LLM (or a downstream agent) is expected to read the listed
sibling(s), reconcile the local content against the remote, and
delete the sibling once merged.
Writes landing during a pull
A write whose deferred git commit has not yet run when a pull starts is
never lost. Before every real (non-dry-run) pull (periodic or git_sync),
the server pauses new writes and drains the deferred-commit queue (a
dry_run preview only fetches and never quiesces), so in the normal case the
just-written file is committed first and the merge runs on a clean tree
(#571). If
that write and the remote touched the same file, it flows through the
Syncthing-style sibling resolution above rather than failing. The drain is
best-effort and time-bounded: if it cannot finish in time, the pull logs a
warning and proceeds anyway; the write is still safely on disk and is
committed on the next opportunity, at worst reverting to the pre-#571
behavior (a non-fast-forward push that the next reconcile resolves).
The full enumeration of pull.reason and push.reason values lives in
the git_sync tool reference.
git_sync is hidden when the deployment isn't in managed git mode (no
MARKDOWN_VAULT_MCP_GIT_REPO_URL set) or when
MARKDOWN_VAULT_MCP_READ_ONLY=true.
Unmanaged / Commit-Only Mode¶
Use unmanaged mode when another process controls pull/push, but you still want MCP writes committed locally.
MARKDOWN_VAULT_MCP_SOURCE_DIR=/data/vault
MARKDOWN_VAULT_MCP_READ_ONLY=false
# No GIT_REPO_URL
# No GIT_TOKEN required
MARKDOWN_VAULT_MCP_GIT_COMMIT_NAME=markdown-vault-mcp
MARKDOWN_VAULT_MCP_GIT_COMMIT_EMAIL=noreply@markdown-vault-mcp
Behavior:
- If
SOURCE_DIRis a git repo, writes are committed locally. - No periodic pull.
- No push.
No-Git Mode¶
Use no-git mode when you only need file persistence.
MARKDOWN_VAULT_MCP_SOURCE_DIR=/data/vault
MARKDOWN_VAULT_MCP_READ_ONLY=false
# No git env vars required
Behavior:
- Files are written to disk.
- No staging, commits, pulls, or pushes.
Provider Username Reference¶
MARKDOWN_VAULT_MCP_GIT_USERNAME controls the HTTPS username prompt:
- GitHub:
x-access-token - GitLab:
oauth2 - Bitbucket: account username
Git LFS¶
If your vault tracks large files (PDFs, images) with Git LFS, the server runs git lfs pull on startup to resolve LFS pointers into actual file content. This is enabled by default.
Set MARKDOWN_VAULT_MCP_GIT_LFS=false to skip the LFS pull. Use this when:
- Your vault does not use Git LFS
git-lfsis not installed in your environment- You want faster startup and don't need LFS-tracked attachments
MARKDOWN_VAULT_MCP_GIT_LFS=false
Legacy Compatibility¶
GIT_TOKEN without GIT_REPO_URL still works for backward compatibility and logs a deprecation warning.