You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Once MCPHub boots in Database mode (DB_URL set), mcp_settings.json is read once to seed Postgres and is never consulted again. Subsequent file edits silently have no effect, and dashboard / API edits never get written back to the file. There's also no on-disk indicator of whether the current state diverges from the file. This produces a few related operator failure modes that took us a while to triangulate during initial setup of a claude.ai custom-connector deployment.
Observable symptoms
File edits don't apply. Operator edits mcp_settings.json (e.g. to add "args": [] to a stdio server that needs it), restarts the container, sees no change. The only way out is docker compose down -v to wipe the DB and re-seed, which also wipes admin / OAuth / token state.
File and DB diverge. Dashboard edits diverge the DB from the file. There's no in-product cue telling the operator the file they're staring at is no longer authoritative.
Origin is invisible. Once in the DB, a server has no origin / managedBy / source field. Whether it came from the file, the dashboard, or the API is only inferable from owner = NULL heuristically — which conflicts with the permission model rework in Social-login users default to isAdmin=false with no documented path to admin #817 if we redefine NULL there.
Possible directions
Each row works on its own; they can also be combined:
A. Source of truth
Option
Behaviour
A1. mcp_settings.json is purely an init seed (current behaviour, documented).
Startup logs a warning when mcp_settings.json mtime is newer than the DB's migration timestamp. No automatic re-import.
A2. Reconcile on startup.
mcp_settings.json is re-read every boot and upserted into the DB. UI edits get overwritten unless the server is marked managedBy: 'ui'.
A3. Two-way sync.
UI edits write back to the file; file edits are detected via fs watcher and applied to the DB. Highest complexity.
A4. Rename file post-migration.
Move mcp_settings.json → mcp_settings.imported.json (or move it inside the container's data volume) on successful migration. Physically prevents the "I edited the file and nothing happened" failure.
A5. Explicit re-import CLI.
DB-only by default, mcphub config import (or a dashboard button) explicitly re-applies the file.
A6. Grafana-style allowUiUpdates.
Per-server managedBy: 'file' | 'ui'. File-managed servers are read-only in the UI; UI-managed servers are immutable from the file.
B. Origin tracking
Option
Behaviour
B1. origin enum column
'file' | 'dashboard' | 'api' (or similar). Read-only metadata, useful for filtering and audit.
B2. source string
Free-text like "mcp_settings.json:toggl" for full traceability.
B3. managedBy column (composes with A6)
'file' | 'ui'. Drives whether the server is editable in the UI.
B4. No-op, lean on owner = NULL as a heuristic
Status quo. Breaks if NULL semantics change for permission reasons (#817).
B5. Separate server_labels table
Flexible key/value tags. Heaviest but most future-proof.
The permission rework in #817 (visibility column) sits in the same migration. Bundling B1 or B3 with that migration is much cheaper than landing them separately. They're also semantically adjacent: a 'file' origin server is naturally a 'public' visibility server in most single-user / small-team deployments.
Recommendation
Lowest-cost combination that closes the operator-pain reports: A1 (warning on stale file) + B1 (origin column). Lands in one small migration, doesn't change existing behaviour, makes the divergence diagnosable.
A4 (rename mcp_settings.json after a successful migration) is also worth considering alongside — it's effectively free to implement and physically prevents the "I edited the file and nothing happened" failure mode that A1's warning only flags after the fact. Combining A1 + A4 + B1 covers detection, prevention, and origin tracking together.
A6 + B3 is the right long-term answer if MCPHub wants to support GitOps-style declarative configuration.
Happy to open a PR for the minimal combination once #817's direction is settled (so the schema migration only happens once).
Summary
Once MCPHub boots in Database mode (
DB_URLset),mcp_settings.jsonis read once to seed Postgres and is never consulted again. Subsequent file edits silently have no effect, and dashboard / API edits never get written back to the file. There's also no on-disk indicator of whether the current state diverges from the file. This produces a few related operator failure modes that took us a while to triangulate during initial setup of aclaude.aicustom-connector deployment.Observable symptoms
mcp_settings.json(e.g. to add"args": []to a stdio server that needs it), restarts the container, sees no change. The only way out isdocker compose down -vto wipe the DB and re-seed, which also wipes admin / OAuth / token state.origin/managedBy/sourcefield. Whether it came from the file, the dashboard, or the API is only inferable fromowner = NULLheuristically — which conflicts with the permission model rework in Social-login users default to isAdmin=false with no documented path to admin #817 if we redefine NULL there.Possible directions
Each row works on its own; they can also be combined:
A. Source of truth
mcp_settings.jsonis purely an init seed (current behaviour, documented).mcp_settings.jsonmtime is newer than the DB's migration timestamp. No automatic re-import.mcp_settings.jsonis re-read every boot and upserted into the DB. UI edits get overwritten unless the server is markedmanagedBy: 'ui'.mcp_settings.json→mcp_settings.imported.json(or move it inside the container's data volume) on successful migration. Physically prevents the "I edited the file and nothing happened" failure.mcphub config import(or a dashboard button) explicitly re-applies the file.allowUiUpdates.managedBy: 'file' | 'ui'. File-managed servers are read-only in the UI; UI-managed servers are immutable from the file.B. Origin tracking
originenum column'file' | 'dashboard' | 'api'(or similar). Read-only metadata, useful for filtering and audit.sourcestring"mcp_settings.json:toggl"for full traceability.managedBycolumn (composes with A6)'file' | 'ui'. Drives whether the server is editable in the UI.owner = NULLas a heuristicserver_labelstableRelation to #817
The permission rework in #817 (
visibilitycolumn) sits in the same migration. Bundling B1 or B3 with that migration is much cheaper than landing them separately. They're also semantically adjacent: a'file'origin server is naturally a'public'visibility server in most single-user / small-team deployments.Recommendation
Lowest-cost combination that closes the operator-pain reports: A1 (warning on stale file) + B1 (
origincolumn). Lands in one small migration, doesn't change existing behaviour, makes the divergence diagnosable.A4 (rename
mcp_settings.jsonafter a successful migration) is also worth considering alongside — it's effectively free to implement and physically prevents the "I edited the file and nothing happened" failure mode that A1's warning only flags after the fact. Combining A1 + A4 + B1 covers detection, prevention, and origin tracking together.A6 + B3 is the right long-term answer if MCPHub wants to support GitOps-style declarative configuration.
Happy to open a PR for the minimal combination once #817's direction is settled (so the schema migration only happens once).
🤖 Generated with Claude Code