From 9cf49813a680aba6d2d41190bd62cec6b8c0434f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 00:04:06 +0000 Subject: [PATCH 01/11] fix: server.json audit corrections + OCI ownership label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full audit of server.json against the 2025-12-11 MCP server schema, the actual env vars the code reads, the published Docker image, and GitHub repo metadata. Eight fixes: Critical (the existing entry would not work as published): 1. Dockerfile: add `LABEL io.modelcontextprotocol.server.name=...` — required for `mcp-publisher publish` ownership verification against the OCI image manifest. 2. packages[0].identifier: ghcr.io/aliasunder/vault-cortex → ghcr.io/aliasunder/vault-mcp. The published image is vault-mcp (see .github/workflows/deploy.yml + all docker-compose files); the vault-cortex image does not exist. 3. repository.id: "aliasunder/vault-cortex" → "1226067541". Schema requires the numeric GitHub repo ID (from `gh api repos/aliasunder/vault-cortex --jq '.id'`), not the slug. High-impact gaps (consumers couldn't auto-configure): 4. packages[0].transport.headers: declare `Authorization: Bearer {MCP_AUTH_TOKEN}` so clients know what to send on the wire to /mcp. Previously the server required bearer auth but no header declaration existed. 5. packages[0].runtimeHint + runtimeArguments: declare `docker` plus `-p {PORT}:8000` and `-v {HOST_VAULT_PATH}:/vault` so clients can construct a valid `docker run` from server.json alone. Env-var inaccuracies: 6. PUBLIC_URL: keep isRequired: false but add `default: http://localhost:8000`. Server crashes on startup without PUBLIC_URL (see src/vault-mcp/server.ts:36 — `.required()`); the default lets the local quickstart work without manual override. 7. Add eight missing env vars the server actually reads — verified by grepping `env.get(...)` / `env.X` across `src/`: PORT, HOST, INDEX_DB_PATH, LOG_DIR, LOG_RETENTION_DAYS, PROTECTED_PATHS, ORPHAN_EXCLUDE_FOLDERS, SERVICE_DOCUMENTATION_URL. All optional with defaults sourced from the code where they exist. 8. VAULT_PATH description clarified to distinguish the container path (/vault, declared here) from the host path (declared via the -v runtime argument's HOST_VAULT_PATH variable). Out of scope: icons (waits for ^icon-400), remotes (no public hosted service), URL templating against PORT in transport.url (done — already templated as {PORT}). Verification: jq schema-shape check passes (all required fields present); description 94 chars (under 100 max); identifier matches the actual published image; repository.id matches GitHub API; all 14 declared env vars exist in the source. 496/496 tests pass, lint + tsc clean. After merge, cut v0.15.4 to publish the labeled image — only then can `mcp-publisher publish` succeed against the MCP Registry. --- Dockerfile | 3 ++ server.json | 118 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f2e9be..cd20bba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,9 @@ WORKDIR /app # PUID so both containers can read/write the shared /vault volume. RUN apk add --no-cache tini libstdc++ ENV NODE_ENV=production PORT=8000 HOST=0.0.0.0 +# OCI ownership marker for the MCP Registry — must match `name` in +# server.json. mcp-publisher reads this label off the image manifest. +LABEL io.modelcontextprotocol.server.name="io.github.aliasunder/vault-cortex" COPY --from=deps /app/node_modules ./node_modules COPY --from=build /app/dist/src ./dist/src COPY package.json ./ diff --git a/server.json b/server.json index bcbf08c..e181b9e 100644 --- a/server.json +++ b/server.json @@ -8,53 +8,137 @@ "repository": { "url": "https://github.com/aliasunder/vault-cortex", "source": "github", - "id": "aliasunder/vault-cortex" + "id": "1226067541" }, "packages": [ { "registryType": "oci", - "identifier": "ghcr.io/aliasunder/vault-cortex", + "identifier": "ghcr.io/aliasunder/vault-mcp", "version": "0.15.3", + "runtimeHint": "docker", + "runtimeArguments": [ + { + "type": "named", + "name": "-p", + "description": "Publish the container's port 8000 on the host.", + "value": "{PORT}:8000", + "isRequired": true, + "variables": { + "PORT": { + "description": "Host port to expose vault-mcp on.", + "default": "8000", + "format": "number" + } + } + }, + { + "type": "named", + "name": "-v", + "description": "Bind-mount your Obsidian vault into the container at /vault.", + "value": "{HOST_VAULT_PATH}:/vault", + "isRequired": true, + "variables": { + "HOST_VAULT_PATH": { + "description": "Absolute path to your Obsidian vault on the host machine.", + "isRequired": true, + "format": "filepath" + } + } + } + ], "transport": { "type": "streamable-http", - "url": "http://localhost:8000/mcp" + "url": "http://localhost:{PORT}/mcp", + "headers": [ + { + "name": "Authorization", + "description": "Bearer token used by the MCP client. Must match the MCP_AUTH_TOKEN env var passed to the container.", + "value": "Bearer {MCP_AUTH_TOKEN}", + "isRequired": true, + "isSecret": true, + "variables": { + "MCP_AUTH_TOKEN": { + "description": "Bearer token for MCP client authentication. Generate with: openssl rand -hex 32", + "isRequired": true, + "isSecret": true + } + } + } + ] }, "environmentVariables": [ { "name": "MCP_AUTH_TOKEN", - "description": "Bearer token for MCP client authentication. Generate with: openssl rand -hex 32", + "description": "Bearer token for MCP client authentication. Must match the Authorization header sent by clients. Generate with: openssl rand -hex 32", "isRequired": true, "isSecret": true }, { "name": "VAULT_PATH", - "description": "Absolute path to your Obsidian vault (mounted into the container)", - "isRequired": true, + "description": "Container path to the mounted vault. The host path is bind-mounted here via the -v runtime argument.", + "default": "/vault", "format": "filepath" }, { "name": "PUBLIC_URL", - "description": "Public URL for OAuth discovery metadata. Required for remote deployments with OAuth.", - "isRequired": false + "description": "Public URL clients use to reach this server. Used as the OAuth issuer URL in discovery metadata. Override when exposing the server outside localhost or on a non-default port.", + "default": "http://localhost:8000" + }, + { + "name": "PORT", + "description": "Port the server listens on inside the container.", + "default": "8000", + "format": "number" + }, + { + "name": "HOST", + "description": "Interface the server binds to inside the container.", + "default": "0.0.0.0" }, { "name": "MEMORY_DIR", - "description": "Vault folder for structured memory files", - "default": "About Me", - "isRequired": false + "description": "Vault folder for structured memory files (About Me-style notes).", + "default": "About Me" }, { "name": "TZ", - "description": "IANA timezone for timestamps and daily note resolution", - "default": "UTC", - "isRequired": false + "description": "IANA timezone for timestamps and daily note resolution.", + "default": "UTC" }, { "name": "LOG_LEVEL", - "description": "Logging verbosity", + "description": "Logging verbosity.", "default": "info", - "choices": ["debug", "info", "warn", "error"], - "isRequired": false + "choices": ["debug", "info", "warn", "error"] + }, + { + "name": "LOG_DIR", + "description": "Directory for persistent log files. When set, vault-mcp writes date-stamped .log files here in addition to stdout.", + "format": "filepath" + }, + { + "name": "LOG_RETENTION_DAYS", + "description": "Days to retain persistent log files before cleanup.", + "default": "30", + "format": "number" + }, + { + "name": "INDEX_DB_PATH", + "description": "SQLite FTS5 index DB path. Defaults to /data/index.db inside the container.", + "format": "filepath" + }, + { + "name": "PROTECTED_PATHS", + "description": "Comma-separated vault folder names blocked from vault_delete_note. Default: MEMORY_DIR and \"Daily Notes\"." + }, + { + "name": "ORPHAN_EXCLUDE_FOLDERS", + "description": "Comma-separated vault folder names excluded from vault_find_orphans. Default: \"Daily Notes\", \"Templates\", MEMORY_DIR." + }, + { + "name": "SERVICE_DOCUMENTATION_URL", + "description": "Override the OAuth service documentation URL exposed via discovery metadata.", + "default": "https://github.com/aliasunder/vault-cortex" } ] } From dd73bd67b6c794160dd78b3607bdfe052d3f528d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 00:15:26 +0000 Subject: [PATCH 02/11] fix: drop "Remote" from server.json description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The word was ambiguous and misleading for the registry entry: - Transport sense ("HTTP-based MCP server, not stdio") — true, but redundant: OAuth-protected already implies HTTP. - Deployment sense ("hosted somewhere far away") — false for the package as published. The package describes a `docker run` on the consumer's own machine; no public hosted URL is offered. A registry consumer reading "Remote MCP server" reasonably expects a hosted URL they could plug into Claude Desktop. We don't provide one (the personal Lightsail deploy is not a public multi-tenant service). Dropping "Remote" makes the description match what the package actually delivers. 87 chars (was 94, still under the 100-char schema max). --- server.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.json b/server.json index e181b9e..55e06f1 100644 --- a/server.json +++ b/server.json @@ -2,7 +2,7 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.aliasunder/vault-cortex", "title": "vault-cortex", - "description": "Remote MCP server for Obsidian vaults — search, memory, link graph, 23 tools, OAuth-protected.", + "description": "MCP server for Obsidian vaults — search, memory, link graph, 23 tools, OAuth-protected.", "version": "0.15.3", "websiteUrl": "https://github.com/aliasunder/vault-cortex", "repository": { From 918ab94239ef68d1a6d98212679f882353efb571 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 00:16:18 +0000 Subject: [PATCH 03/11] docs: add upstream-fix hint to adopter compose init-config-perms comment Mirrors the comment in the repo root's docker-compose.yml so adopters self-hosting from deploy/remote/ also see the upstream tracker link and know the workaround is temporary. --- deploy/remote/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/remote/docker-compose.yml b/deploy/remote/docker-compose.yml index 015dd91..ca3973c 100644 --- a/deploy/remote/docker-compose.yml +++ b/deploy/remote/docker-compose.yml @@ -16,6 +16,7 @@ services: # root. Docker named volumes inherit this root ownership, so the obsidian # user (UID 1000) can't write sync state. This init container chowns the # volume before obsidian-sync starts. + # Remove when upstream fixes: github.com/Belphemur/obsidian-headless-sync-docker init-config-perms: image: alpine:latest command: sh -c "chown -R ${PUID:-1000}:${PGID:-1000} /config" From ee9793157142b9e8e0b0e3c89ff8c3a84f468447 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 00:52:55 +0000 Subject: [PATCH 04/11] fix: bake VAULT_PATH=/vault into Dockerfile, hide from server.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VAULT_PATH was overloaded — appearing as both the user-facing host path in runtimeArguments (as HOST_VAULT_PATH) and the container env var in environmentVariables (default "/vault"). Two knobs for what users should see as one concept. Aligned with the README + deploy/local/.env.example convention: users only see VAULT_PATH meaning "where your vault lives on this machine." - Dockerfile: bake VAULT_PATH=/vault into the runtime ENV, alongside PORT/HOST/NODE_ENV. Container always has it; no need to expose as a configurable knob. - server.json: drop VAULT_PATH from environmentVariables (now a Dockerfile internal). Rename HOST_VAULT_PATH → VAULT_PATH in runtimeArguments so the only VAULT_PATH a user sees means the same thing it means in their .env. - Compose files: untouched. Their explicit `VAULT_PATH: /vault` env lines are now redundant no-ops but harmless. --- Dockerfile | 2 +- server.json | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index cd20bba..ab3a7a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ WORKDIR /app # node:24-alpine ships a `node` user at UID 1000 — matches obsidian-sync's # PUID so both containers can read/write the shared /vault volume. RUN apk add --no-cache tini libstdc++ -ENV NODE_ENV=production PORT=8000 HOST=0.0.0.0 +ENV NODE_ENV=production PORT=8000 HOST=0.0.0.0 VAULT_PATH=/vault # OCI ownership marker for the MCP Registry — must match `name` in # server.json. mcp-publisher reads this label off the image manifest. LABEL io.modelcontextprotocol.server.name="io.github.aliasunder/vault-cortex" diff --git a/server.json b/server.json index 55e06f1..cdfdc16 100644 --- a/server.json +++ b/server.json @@ -35,10 +35,10 @@ "type": "named", "name": "-v", "description": "Bind-mount your Obsidian vault into the container at /vault.", - "value": "{HOST_VAULT_PATH}:/vault", + "value": "{VAULT_PATH}:/vault", "isRequired": true, "variables": { - "HOST_VAULT_PATH": { + "VAULT_PATH": { "description": "Absolute path to your Obsidian vault on the host machine.", "isRequired": true, "format": "filepath" @@ -73,12 +73,6 @@ "isRequired": true, "isSecret": true }, - { - "name": "VAULT_PATH", - "description": "Container path to the mounted vault. The host path is bind-mounted here via the -v runtime argument.", - "default": "/vault", - "format": "filepath" - }, { "name": "PUBLIC_URL", "description": "Public URL clients use to reach this server. Used as the OAuth issuer URL in discovery metadata. Override when exposing the server outside localhost or on a non-default port.", From 921497f63fb3a08acc40c4131d60e1469d59e7ab Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 01:11:19 +0000 Subject: [PATCH 05/11] fix: document INDEX_DB_PATH + LOG_DIR defaults in server.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both vars are hardcoded in all compose files but had no default in server.json, so a `docker run` constructed from server.json alone would diverge from the compose user experience: - INDEX_DB_PATH: compose sets /data/index.db; without it the code falls back to /data/search.db (different filename). Documented default /data/index.db to match. - LOG_DIR: compose sets /data/logs to enable persistent file logging; without it the file sink is off entirely (stdout only). Documented default /data/logs so registry-spawned containers log to disk like compose deployments do. Noted that an empty string disables it. Traced all 13 env vars against the compose files + src/: every optional var with a static default now declares it. PROTECTED_PATHS and ORPHAN_EXCLUDE_FOLDERS intentionally have no `default` field — their defaults derive from MEMORY_DIR at runtime and can't be a static string, so they stay documented in the description. --- server.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server.json b/server.json index cdfdc16..fc5aa9c 100644 --- a/server.json +++ b/server.json @@ -107,7 +107,8 @@ }, { "name": "LOG_DIR", - "description": "Directory for persistent log files. When set, vault-mcp writes date-stamped .log files here in addition to stdout.", + "description": "Directory for persistent log files. vault-mcp writes date-stamped .log files here in addition to stdout. Set to an empty string to disable file logging.", + "default": "/data/logs", "format": "filepath" }, { @@ -118,7 +119,8 @@ }, { "name": "INDEX_DB_PATH", - "description": "SQLite FTS5 index DB path. Defaults to /data/index.db inside the container.", + "description": "SQLite FTS5 index DB path inside the container. Its parent directory also holds the OAuth token DB.", + "default": "/data/index.db", "format": "filepath" }, { From 130ea0cdabf651d277bb1efb0608fb351a1a2489 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 01:17:51 +0000 Subject: [PATCH 06/11] docs: document smart defaults inline in all compose files PROTECTED_PATHS / ORPHAN_EXCLUDE_FOLDERS / SERVICE_DOCUMENTATION_URL have runtime-derived smart defaults (config.ts) that previously were only discoverable via the README. Stated them inline in all four compose files so operators editing them see the actual defaults: - PROTECTED_PATHS = ", Daily Notes" - ORPHAN_EXCLUDE_FOLDERS = "Daily Notes, Templates, " - SERVICE_DOCUMENTATION_URL = https://github.com/aliasunder/vault-cortex Root pair (docker-compose.yml, docker-compose.local.yml) keep the active empty-default form; deploy pair (deploy/local, deploy/remote) keep the commented-out form. Comments adapted to each. --- deploy/local/docker-compose.yml | 6 +++++- deploy/remote/docker-compose.yml | 6 +++++- docker-compose.local.yml | 5 +++++ docker-compose.yml | 5 +++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/deploy/local/docker-compose.yml b/deploy/local/docker-compose.yml index 9dd3c34..8dbf53e 100644 --- a/deploy/local/docker-compose.yml +++ b/deploy/local/docker-compose.yml @@ -28,7 +28,11 @@ services: LOG_DIR: /data/logs LOG_RETENTION_DAYS: ${LOG_RETENTION_DAYS:-30} TZ: ${TZ:-UTC} - # Uncomment to override smart defaults (see Configuration in the main README): + # Optional overrides. When unset, the server applies smart defaults + # ( below is the resolved MEMORY_DIR value, default "About Me"): + # PROTECTED_PATHS default: ", Daily Notes" (blocked from vault_delete_note) + # ORPHAN_EXCLUDE_FOLDERS default: "Daily Notes, Templates, " (excluded from vault_find_orphans) + # SERVICE_DOCUMENTATION_URL default: https://github.com/aliasunder/vault-cortex # PROTECTED_PATHS: ${PROTECTED_PATHS:-} # ORPHAN_EXCLUDE_FOLDERS: ${ORPHAN_EXCLUDE_FOLDERS:-} # SERVICE_DOCUMENTATION_URL: ${SERVICE_DOCUMENTATION_URL:-} diff --git a/deploy/remote/docker-compose.yml b/deploy/remote/docker-compose.yml index ca3973c..89e33dc 100644 --- a/deploy/remote/docker-compose.yml +++ b/deploy/remote/docker-compose.yml @@ -73,7 +73,11 @@ services: LOG_DIR: /data/logs LOG_RETENTION_DAYS: ${LOG_RETENTION_DAYS:-30} TZ: ${TZ:-UTC} - # Uncomment to override smart defaults (see Configuration in the main README): + # Optional overrides. When unset, the server applies smart defaults + # ( below is the resolved MEMORY_DIR value, default "About Me"): + # PROTECTED_PATHS default: ", Daily Notes" (blocked from vault_delete_note) + # ORPHAN_EXCLUDE_FOLDERS default: "Daily Notes, Templates, " (excluded from vault_find_orphans) + # SERVICE_DOCUMENTATION_URL default: https://github.com/aliasunder/vault-cortex # PROTECTED_PATHS: ${PROTECTED_PATHS:-} # ORPHAN_EXCLUDE_FOLDERS: ${ORPHAN_EXCLUDE_FOLDERS:-} # SERVICE_DOCUMENTATION_URL: ${SERVICE_DOCUMENTATION_URL:-} diff --git a/docker-compose.local.yml b/docker-compose.local.yml index c8e12b7..82e7648 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -22,6 +22,11 @@ services: LOG_LEVEL: debug MEMORY_DIR: ${MEMORY_DIR:-About Me} TZ: ${TZ:-UTC} + # Left empty = the server applies smart defaults + # ( below is the resolved MEMORY_DIR value, default "About Me"): + # PROTECTED_PATHS default: ", Daily Notes" + # ORPHAN_EXCLUDE_FOLDERS default: "Daily Notes, Templates, " + # SERVICE_DOCUMENTATION_URL default: https://github.com/aliasunder/vault-cortex PROTECTED_PATHS: ${PROTECTED_PATHS:-} ORPHAN_EXCLUDE_FOLDERS: ${ORPHAN_EXCLUDE_FOLDERS:-} SERVICE_DOCUMENTATION_URL: ${SERVICE_DOCUMENTATION_URL:-} diff --git a/docker-compose.yml b/docker-compose.yml index 7e4b29e..1fc74d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,6 +74,11 @@ services: LOG_RETENTION_DAYS: ${LOG_RETENTION_DAYS:-30} TZ: ${TZ:-UTC} MEMORY_DIR: ${MEMORY_DIR:-About Me} + # Left empty = the server applies smart defaults + # ( below is the resolved MEMORY_DIR value, default "About Me"): + # PROTECTED_PATHS default: ", Daily Notes" + # ORPHAN_EXCLUDE_FOLDERS default: "Daily Notes, Templates, " + # SERVICE_DOCUMENTATION_URL default: https://github.com/aliasunder/vault-cortex PROTECTED_PATHS: ${PROTECTED_PATHS:-} ORPHAN_EXCLUDE_FOLDERS: ${ORPHAN_EXCLUDE_FOLDERS:-} SERVICE_DOCUMENTATION_URL: ${SERVICE_DOCUMENTATION_URL:-} From 03c40fd0ff78e3b964a24b7db4095d286e50a227 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 01:31:02 +0000 Subject: [PATCH 07/11] fix: remove PORT and HOST from server.json environmentVariables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both were exposed as user-tunable container env vars while ALSO being used as the host side of the port mapping / bind — the same host-vs- container overload as the earlier VAULT_PATH fix. PORT appeared in three places: environmentVariables (container listen port), runtimeArguments `-p {PORT}:8000` (host port), and transport.url (host port). Overriding it desynced them — e.g. PORT=9000 makes the server listen on 9000 inside the container while `-p 9000:8000` still maps host 9000 to container 8000 → connection refused. HOST had the same latent break (127.0.0.1 would make the server unreachable through the port mapping). The container always listens on 0.0.0.0:8000 — already baked into the Dockerfile ENV. Removing both from environmentVariables leaves PORT only as the host-side variable in `-p {PORT}:8000` + the transport URL, so overriding it now works correctly. Every remaining env var is a value the server actually reads with no runtime-mechanics conflict. --- server.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/server.json b/server.json index fc5aa9c..61578e1 100644 --- a/server.json +++ b/server.json @@ -78,17 +78,6 @@ "description": "Public URL clients use to reach this server. Used as the OAuth issuer URL in discovery metadata. Override when exposing the server outside localhost or on a non-default port.", "default": "http://localhost:8000" }, - { - "name": "PORT", - "description": "Port the server listens on inside the container.", - "default": "8000", - "format": "number" - }, - { - "name": "HOST", - "description": "Interface the server binds to inside the container.", - "default": "0.0.0.0" - }, { "name": "MEMORY_DIR", "description": "Vault folder for structured memory files (About Me-style notes).", From e37cccf1e78bd06ddadf440c1170ee0a867dbdfd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 01:31:03 +0000 Subject: [PATCH 08/11] fix: make LOG_DIR user-overridable in deploy quickstarts + document it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LOG_DIR was hardcoded (LOG_DIR: /data/logs) in the deploy/local and deploy/remote compose files, so users couldn't override it even though server.json advertises it as optional with that default. Switched both to ${LOG_DIR:-/data/logs} and documented it in the matching .env.example files (set empty to disable file logging). Root docker-compose.yml left as-is — its .env.example already documents LOG_DIR as intentionally fixed for the Lightsail deploy. --- deploy/local/.env.example | 4 ++++ deploy/local/docker-compose.yml | 2 +- deploy/remote/.env.example | 4 ++++ deploy/remote/docker-compose.yml | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/deploy/local/.env.example b/deploy/local/.env.example index 8095f9c..a3c21a7 100644 --- a/deploy/local/.env.example +++ b/deploy/local/.env.example @@ -25,5 +25,9 @@ VAULT_PATH= # Log verbosity: debug | info | warn | error (default: info). # LOG_LEVEL=info +# Directory for persistent log files inside the container (default: /data/logs). +# Set to empty to disable file logging (logs still go to stdout either way). +# LOG_DIR=/data/logs + # Days to retain persistent log files before cleanup (default: 30). # LOG_RETENTION_DAYS=30 diff --git a/deploy/local/docker-compose.yml b/deploy/local/docker-compose.yml index 8dbf53e..c7e6cbf 100644 --- a/deploy/local/docker-compose.yml +++ b/deploy/local/docker-compose.yml @@ -25,7 +25,7 @@ services: PUBLIC_URL: ${PUBLIC_URL:-http://localhost:8000} MEMORY_DIR: ${MEMORY_DIR:-About Me} LOG_LEVEL: ${LOG_LEVEL:-info} - LOG_DIR: /data/logs + LOG_DIR: ${LOG_DIR:-/data/logs} LOG_RETENTION_DAYS: ${LOG_RETENTION_DAYS:-30} TZ: ${TZ:-UTC} # Optional overrides. When unset, the server applies smart defaults diff --git a/deploy/remote/.env.example b/deploy/remote/.env.example index 5d1df55..1e5aaae 100644 --- a/deploy/remote/.env.example +++ b/deploy/remote/.env.example @@ -37,6 +37,10 @@ VAULT_NAME= # Log verbosity: debug | info | warn | error (default: info). # LOG_LEVEL=info +# Directory for persistent log files inside the container (default: /data/logs). +# Set to empty to disable file logging (logs still go to stdout either way). +# LOG_DIR=/data/logs + # Days to retain persistent log files before cleanup (default: 30). # LOG_RETENTION_DAYS=30 diff --git a/deploy/remote/docker-compose.yml b/deploy/remote/docker-compose.yml index 89e33dc..3a36caa 100644 --- a/deploy/remote/docker-compose.yml +++ b/deploy/remote/docker-compose.yml @@ -70,7 +70,7 @@ services: PUBLIC_URL: "${PUBLIC_URL:?Set PUBLIC_URL to your server's public URL}" MEMORY_DIR: ${MEMORY_DIR:-About Me} LOG_LEVEL: ${LOG_LEVEL:-info} - LOG_DIR: /data/logs + LOG_DIR: ${LOG_DIR:-/data/logs} LOG_RETENTION_DAYS: ${LOG_RETENTION_DAYS:-30} TZ: ${TZ:-UTC} # Optional overrides. When unset, the server applies smart defaults From 7dbad6fd5f3dcdec87eee3f1322149e32afb2d64 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 01:32:39 +0000 Subject: [PATCH 09/11] fix: make file logging opt-in for the local quickstart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deploy/local defaulted LOG_DIR to /data/logs, turning on persistent file logging for every local user. A local user just trying vault-cortex out doesn't necessarily want log files accumulating — stdout is enough. Switched deploy/local to ${LOG_DIR:-} (empty default = file logging off; set a path in .env to opt in). deploy/remote keeps the /data/logs default since an unattended server benefits from persistent logs. Updated the deploy/local .env.example wording to match (opt-in, not default-on). --- deploy/local/.env.example | 5 +++-- deploy/local/docker-compose.yml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/deploy/local/.env.example b/deploy/local/.env.example index a3c21a7..dcbd274 100644 --- a/deploy/local/.env.example +++ b/deploy/local/.env.example @@ -25,8 +25,9 @@ VAULT_PATH= # Log verbosity: debug | info | warn | error (default: info). # LOG_LEVEL=info -# Directory for persistent log files inside the container (default: /data/logs). -# Set to empty to disable file logging (logs still go to stdout either way). +# Directory for persistent log files inside the container. +# Unset by default — logs go to stdout only. Set a path to also write +# date-stamped log files there (the /data volume persists them). # LOG_DIR=/data/logs # Days to retain persistent log files before cleanup (default: 30). diff --git a/deploy/local/docker-compose.yml b/deploy/local/docker-compose.yml index c7e6cbf..569a6e8 100644 --- a/deploy/local/docker-compose.yml +++ b/deploy/local/docker-compose.yml @@ -25,7 +25,7 @@ services: PUBLIC_URL: ${PUBLIC_URL:-http://localhost:8000} MEMORY_DIR: ${MEMORY_DIR:-About Me} LOG_LEVEL: ${LOG_LEVEL:-info} - LOG_DIR: ${LOG_DIR:-/data/logs} + LOG_DIR: ${LOG_DIR:-} LOG_RETENTION_DAYS: ${LOG_RETENTION_DAYS:-30} TZ: ${TZ:-UTC} # Optional overrides. When unset, the server applies smart defaults From 581bc4cae6d2d1d3479c4ddb7ac3464ebd230c51 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 01:39:06 +0000 Subject: [PATCH 10/11] fix: server.json /data persistence + opt-in logging for deploy/local parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The registry path spawns containers purely from server.json's runtimeArguments — the compose files aren't involved — so a registry-spawned container previously had no /data volume: OAuth tokens, search index, and logs were all ephemeral, lost on restart. Brought server.json to parity with the deploy/local compose experience: - Add `-v vault-cortex-data:/data` named volume so OAuth sessions, the search index, and any logs persist across restarts (matches deploy/local's mcp_data:/data). - Make the vault mount explicitly :rw (matches deploy/local). - Drop LOG_DIR's default so file logging is opt-in (matches the deploy/local ${LOG_DIR:-} change — local users don't get log files unless they set LOG_DIR=/data/logs). --- server.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/server.json b/server.json index 61578e1..549fa49 100644 --- a/server.json +++ b/server.json @@ -35,7 +35,7 @@ "type": "named", "name": "-v", "description": "Bind-mount your Obsidian vault into the container at /vault.", - "value": "{VAULT_PATH}:/vault", + "value": "{VAULT_PATH}:/vault:rw", "isRequired": true, "variables": { "VAULT_PATH": { @@ -44,6 +44,12 @@ "format": "filepath" } } + }, + { + "type": "named", + "name": "-v", + "description": "Named volume for persistent state under /data — search index, OAuth token DB, and any log files. Keeps OAuth sessions alive across container restarts.", + "value": "vault-cortex-data:/data" } ], "transport": { @@ -96,8 +102,7 @@ }, { "name": "LOG_DIR", - "description": "Directory for persistent log files. vault-mcp writes date-stamped .log files here in addition to stdout. Set to an empty string to disable file logging.", - "default": "/data/logs", + "description": "Directory for persistent log files. Unset by default — logs go to stdout only. Set to /data/logs to also write date-stamped .log files to the persistent volume.", "format": "filepath" }, { From f2d858de7afeabae7aae75b6beedd3b128c09a30 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 01:44:52 +0000 Subject: [PATCH 11/11] fix: bake INDEX_DB_PATH into Dockerfile, remove from server.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit INDEX_DB_PATH is hardcoded (INDEX_DB_PATH: /data/index.db) in all four compose files — never user-overridable — so it's a container-internal implementation detail, not a config knob. Same category as VAULT_PATH, PORT, and HOST. Baked INDEX_DB_PATH=/data/index.db into the Dockerfile ENV (keeps the registry-spawned container on the same filename as the compose flow, rather than the code's /data/search.db fallback) and removed it from server.json environmentVariables. The remaining 10 declared env vars are all genuinely user-tunable. --- Dockerfile | 2 +- server.json | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index ab3a7a9..e2ce85e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ WORKDIR /app # node:24-alpine ships a `node` user at UID 1000 — matches obsidian-sync's # PUID so both containers can read/write the shared /vault volume. RUN apk add --no-cache tini libstdc++ -ENV NODE_ENV=production PORT=8000 HOST=0.0.0.0 VAULT_PATH=/vault +ENV NODE_ENV=production PORT=8000 HOST=0.0.0.0 VAULT_PATH=/vault INDEX_DB_PATH=/data/index.db # OCI ownership marker for the MCP Registry — must match `name` in # server.json. mcp-publisher reads this label off the image manifest. LABEL io.modelcontextprotocol.server.name="io.github.aliasunder/vault-cortex" diff --git a/server.json b/server.json index 549fa49..a6b0863 100644 --- a/server.json +++ b/server.json @@ -111,12 +111,6 @@ "default": "30", "format": "number" }, - { - "name": "INDEX_DB_PATH", - "description": "SQLite FTS5 index DB path inside the container. Its parent directory also holds the OAuth token DB.", - "default": "/data/index.db", - "format": "filepath" - }, { "name": "PROTECTED_PATHS", "description": "Comma-separated vault folder names blocked from vault_delete_note. Default: MEMORY_DIR and \"Daily Notes\"."