Drift Detection
Compare each developer's local registry against the team's central config. Color-coded report in the CLI, on the dashboard, and at POST /api/v1/drift.
In any growing team, MCP server configs drift. Someone adds a server locally, never registers it. Someone else's transport.url falls behind a server move. Three weeks later, AI sessions have inconsistent capabilities and nobody knows why.
opalserve drift is the smallest possible answer to that problem.
Run it
opalserve drift
The CLI compares each entry in your local ~/.opalserve/config.json to the team server's authoritative team_servers table and prints a color-coded report:
✓ match github configs identical
⚠ drift slack transport.url differs
✗ missing postgres on team, not local, opalserve sync
+ local-only debug-mcp on local, not team
The four states
| State | Meaning | What to do |
|---|---|---|
match | Server identical on both sides | Nothing |
drift | Same name, different config | Compare fields, update one side |
missing | Registered with the team but not on this client | opalserve sync |
local-only | Present locally but not on the team server | Either remove it locally or register it with the team via opalserve admin server add |
Drift surfaces the specific fields that differ. If slack shows up as drift, the report names which keys differ, transport.url, env.SLACK_TOKEN, or whatever, so you can decide who is right without diffing files by hand.
On the dashboard
The dashboard at http://localhost:3456/dashboard has a Drift tab that calls the same endpoint and renders the same data with summary stats, total servers checked, breakdown by state, and a refresh button.
The page is a tab inside the Observe group, alongside Overview and Graph. It is read-only; there is no "click to fix" action because in most cases the right fix depends on org policy (run sync? push a local addition to the team?).
The endpoint
Drift detection is also available over HTTP for scripted use cases, Slack bots, dashboards, audit jobs.
curl -X POST http://localhost:3456/api/v1/drift \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "client": "alice@laptop" }'
Response shape:
{
"ok": true,
"data": {
"checkedAt": "2026-05-04T14:32:11.842Z",
"stats": { "match": 4, "drift": 1, "missing": 1, "localOnly": 0 },
"entries": [
{
"name": "slack",
"state": "drift",
"differentFields": ["transport.url"],
"local": { "transport": { "url": "..." } },
"team": { "transport": { "url": "..." } }
}
]
}
}
The endpoint is available in both team-server and team-client modes. In team-client mode it forwards the call to the configured team server.
How it works
The endpoint is implemented in src/server/routes/drift.ts. It loads the local registry via registry.list(), fetches the team registry via the team server's stored team_servers, and computes a deep diff over a stable subset of each server entry, name, transport, env, tags, description.
Volatile fields like lastSeen and error are excluded from the diff so transient connection blips do not show up as drift.
When to run it
- After any team admin change,
opalserve driftconfirms your client picks up the new state. - Inside CI, against a known-good config snapshot.
- As a Slack bot for the team, posted weekly.
- Before onboarding a new developer,
opalserve sync && opalserve driftshould print allmatch.