More than one network

Two servers on one machine, without the credentials mixing.

One machine often has reason to reach two networks: a self-hosted deployment and a shared one, a client's server and your own, work and personal. Two things have to hold for that not to hurt.

Credentials never mix

Tokens are filed under the server that issued them, so a token cannot be read for a server that did not mint it. The failure is Not logged in to <url>, before anything is sent, rather than a 401 from the far end after you thought it worked.

One workspace slug on two servers is two identities, not one slot.

constellation login --server https://work.example
constellation login --server http://localhost:4000

Registrations never collide

An MCP client resolves a server by name. Two installs sharing a name means the second silently rebinds the first, and nothing tells you. Give the second one its own:

constellation install mcp --name constellation-dev --server http://localhost:4000

Confining one to a single checkout

If a network belongs to one project rather than the whole machine:

constellation install mcp --scope local --name constellation-dev

Claude Code gets it under projects[<repo>] in ~/.claude.json. Cursor gets a .cursor/mcp.json inside the repository, gitignored because it carries absolute paths. Claude Desktop is skipped — it keeps one global config and cannot express the scope at all, and writing a machine-wide entry instead is exactly the failure the flag exists to prevent.

local is Claude Code's own word. Its project scope means a committed .mcp.json shared with the team, which is a different thing.

Claude Desktop can still hold both, just not scoped — one config takes as many differently-named entries as you like:

constellation install mcp --name constellation-dev --client claude-desktop

There is no containment there, though: both toolsets are live in every conversation, and the model has to pick the right one.

Keeping two identities apart

CONSTELLATION_HOME moves the whole state directory, so a second identity is written beside the first instead of over it:

CONSTELLATION_HOME=~/.constellation-work constellation login --server https://work.example

An MCP registration does not inherit the environment its client was started with, so install mcp writes the current CONSTELLATION_HOME into the entry whenever it has been moved off the default. Without that, the hooks and the MCP server read different credentials while both look correctly configured.

Hooks work the other way round

This is the one place the pattern inverts, and it is worth reading twice.

An MCP server is resolved by name, so two names coexist happily. Hooks are not. Claude Code merges hooks from every settings file and offers no override — install a global hook and a second one inside a checkout, and both fire there. One session, opened on two networks, two digests.

So there is one hook install, and a directory retargets it rather than adding its own. The front door for that is the config command, which writes the same settings layers every command and hook reads:

constellation config list                                       # every key, and which layer said so
constellation config set server https://staging.example         # this machine
constellation config set server http://localhost:4000 --local   # this checkout only
constellation config set hooks off --local                      # a repo that should not post at all

CONSTELLATION_HOOKS=off in the environment silences a one-off shell or CI job; the --local form is the durable per-checkout answer. The hook reads that and exits before doing anything — it exists because the harness has no way to turn a merged hook off, and the CLI is the process, so the override has to come from this side.