MCP servers
The graft, one key inside a file that is yours, secrets never written, and targets derived rather than declared.
Every other artifact overpower installs copies: a file or a directory appears
that was not there before, and git status shows it as new. An MCP server
grafts: one key appears inside a configuration document you already own and
already edit yourself, and git diff shows a change to your file rather than a
new one.
uvx overpower@latest install --mcp cloudflare --runtime claude-code
Because the destination is somebody else's file, yours and not overpower's, three things follow, and all three are guarantees rather than incidental behaviour.
| Guarantee | What it means |
|---|---|
| The plan names the key | the last line before the write reads .mcp.json › mcpServers.cloudflare ← claude-code, the exact key inside the exact document |
| The rest of the document survives byte for byte | comments preserved, unknown root keys preserved, and a server already present is not reformatted |
| A server of the same name is overwritten | without asking and without --force, the same rule a colliding path follows |
Writing the whole file back out through a generic JSON serialiser would reflow it
regardless of whether anything meaningful changed, and git diff would stop
answering what the tool actually did versus what happened to be nearby.
A configuration file that is already broken is refused, never repaired. Editing a file that is not overpower's, on the tool's own initiative, is not something an install is allowed to do.
Where the runtime itself holds a freshly written server back from connecting, the
command says so. In Claude Code, a server written into .mcp.json is born pending
approval and stays inert until you approve it there, so the install prints that
warning naming the file, still at exit 0, because the write succeeded and what
remains is a step that belongs to you.
doctor reads the same fact the other way. A server pending approval is one of
its five findings, and a finding fails: an install that exited 0 followed
by a doctor on the same machine exits 3 over the same server, until you
approve it. In a pipeline that chains the two, approve first or split the steps.
--global writes your personal file instead
--global writes to one personal file per target instead of a repository one,
~/.claude.json for Claude Code, or the VS Code user profile, which is a
different path per operating system. That file is yours in a stronger sense than a
repository file: ~/.claude.json, for instance, also carries your user ID and
onboarding state.
Nothing else in it is touched, and because a graft never replaces a whole file, only adds a key, there is no approval gate either: a server written into your own personal file is one you have already implicitly approved by having written it yourself.
The secret is never written; the address is
A recipe declares a secret as a slot, a name and a role, never a value and never a specific spelling. What lands in the configuration file is the reference the runtime itself expands at connection time, not the secret.
{
"coolify": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@masonator/coolify-mcp@2.12.0"],
"env": {
"COOLIFY_BASE_URL": "https://vps.panlabs.tech",
"COOLIFY_ACCESS_TOKEN": "${COOLIFY_ACCESS_TOKEN}"
}
}
}
Those last two lines are the whole distinction. A slot is what overpower refuses
to write, and everything else in env is what it writes because it can. An address
like a base URL is not a secret, and treating it like one would only leave the
server unable to find what it is supposed to talk to.
Three slot roles exist, env, header and bearer. A bearer slot is rendered
as Authorization: Bearer ${VAR} without the recipe ever having to spell that
string out. No reference carries a default value, because ${VAR:-fallback} syntax
is understood by exactly one runtime, and in every other runtime reading the very
same .mcp.json that whole expression is treated as a literal string.
Targets are derived, never declared
list --mcp prints a targets line for every recipe, but there is no targets
field anywhere in the recipe file itself.
Which pairs of runtime and scope a given recipe can actually serve is computed from its transport and the roles of its slots against a table in code, and printed fresh every time. A declared field would go stale silently the day a runtime gained the capability to receive that server; a derived one cannot, because it is recomputed from the current table on every read.
Each target printed is a pair, a runtime and the scope it reads that server
in. A recipe that no target can serve at all prints none rather than an empty
line, because the empty list and the answer none are different things.