OpalServe

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

StateMeaningWhat to do
matchServer identical on both sidesNothing
driftSame name, different configCompare fields, update one side
missingRegistered with the team but not on this clientopalserve sync
local-onlyPresent locally but not on the team serverEither 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 drift confirms 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 drift should print all match.