MCP import credentials
What import writes
import reads each tool's own form in env, headers, url, and args back as ${NAME}, including a whole-value $NAME on Gemini and Crush and %NAME% on Gemini. A ${NAME} in a field the tool never expands is plain text, so the spec gets $${NAME}, as in Warp, Zed, and Codex args. Import also replaces every literal env and headers value with a reference, because it cannot tell a token from a plain setting and a spec is meant to be committed. A plain setting such as NODE_ENV: production becomes NODE_ENV: ${NODE_ENV}, a variable you must now set. Put a plain setting back by hand when it is not a secret.
- An
envvalue reads the variable its key names. - A header reads
<SERVER>_<HEADER>in upper case, with any character other than a letter or digit as_, and keeps aBearerprefix outside the reference. - When two different values would share a name, each one reads
<SERVER>_<KEY>instead, then_2,_3. Codex forwards a variable only under its key's own name, so a renamedenvvalue such asAPI_KEY: ${GH_API_KEY}is left out of.codex/config.tomlwith a note. Rename the variable to match the key when Codex needs that server. - A
${NAME:-default}loses its default, since a default is a value too. - A value with text around its references, such as
postgres://u:pw@${HOST}/dborBearer sk-1 ${EXTRA}, is replaced whole, since that text may be the secret. So is a value with any other${...}, such as a VS Code${input:id}prompt. - A Crush
$(command)value becomes a reference. - When a
urlor argument is exactly one URL, only its credentials become references: auser:password@password, a username that is a token by the prefixes below (https://ghp_...@github.com), and the value of a query or fragment parameter with a credential name, such asapi_key,access_token, orclientSecret(nottoken_type,key_id, orsortKey). A parameter named exactlyauth,sig,signature,code,session,jwt, orbearercounts too, butcountry_codedoes not. Each reads<SERVER>_<NAME>, sopostgresql://admin:pw@db/appin serverpgbecomespostgresql://admin:${PG_PASSWORD}@db/app. The rest of the URL stays as written, including a leading reference such as${API_BASE}/mcp?token=.... The output names the field and variable, never the URL. - One URL means no spaces, quotes, or
$${, and it starts with a scheme or a${NAME}. Import never rewrites any other value, such as ash -ccommand or--url=.... - In
args, the value of a long flag with a credential name becomes a reference named after the flag, as--api-key valueor--api-key=value. In servergh,--api-key Xbecomes--api-key ${GH_API_KEY}. A flag such as--token-file,--key-id,--sort-key, or--no-tokenis not a credential. These values stay as written: a$NAME, a file path (starting with/,~,./,../, or a drive such asC:\), a file name ending in.json,.txt,.pem,.key,.env,.yaml,.yml,.toml,.crt,.p12, or.db, a value starting with@, a number, a boolean word, and lower-case words joined by dashes such asstreamable-http. After a flag whose name ends inkey,apikey,authentication, orauthorization, a value needs at least 8 characters, so--require-api-key runstays as written. - An argument that is a whole token by its prefix becomes
${<SERVER>_TOKEN}, then_2,_3. The prefixes areghp_,gho_,ghu_,ghs_,ghr_,github_pat_,glpat-,xoxb-,xoxp-,xoxa-,xapp-,sk_live_,sk_test_,rk_live_,sk-proj-,sk-ant-,npm_,AIza,sk-, andAKIA, each with a plausible length. A JWT (eyJ...with three dot-separated parts) counts too. - Import leaves the server out and names the server and field when a value it does not rewrite still holds a literal credential. A credential here is any of these:
- a URL password or credential parameter (a quoted value such as
token='...'counts, a quoted'${NAME}'does not); - a token by its prefix inside a longer value;
- a
Bearerwith a literal token that has a digit, one of-._~+/=, or at least 20 characters; - a line
Name: valueor a JSON pair"name": "value"with a credential key and a literal value, as in a headers valueX-Api-Key: ...or{"apiKey": "..."}; - a
--headeror-Hargument such asAuthorization: ...,Cookie: ..., orX-Api-Key: ...with a literal value; - a
user:password@hostwithout a scheme, as inroot:pw@db:3306; - a
NAME=valueor--name valuewith a credential name inside a longer value, as insh -c "API_TOKEN=... exec srv"orsh -c "npx srv --token ..."; - an all-digit password before
#, or before/with no host, as inredis://:1234#5@host.
- a URL password or credential parameter (a quoted value such as
- Import never rewrites
command,cwd, ContinuerequestOptionskeys other thanheaders, or anx-<target>block. A credential in any of them, or a key there with a credential name and a literal value, leaves the server out. - Import does not find these credentials, so check such servers by hand:
- a short flag such as
-p secretor-psecret, since-pmay be a port or profile, or a single-dash flag such as-token secret; - a credential value that starts with
-in its own argument after a flag; - a value shorter than 8 characters in its own argument after a flag whose name ends in
key,apikey,authentication, orauthorization; - a
Bearertoken shorter than 20 characters with only letters; - a token in a URL path, such as a Slack webhook
https://hooks.slack.com/services/..., also underimport --global; - a URL username that is a token without one of the prefixes above;
- a token format not listed above.
- a short flag such as
- A query or fragment parameter named exactly
authorcodealways becomes a reference, even without a secret.
Import prints each replacement and the variable to set. import --global cannot change a server, so it leaves out each server with a literal credential and names the server and field, never the value. That is any credential above, and an env or header value with a credential name or that looks like a credential. A plain setting such as NODE_ENV: production, a path, a number, or Bearer ${TOKEN} still imports as written, marked !literal. A value under a credential name that does not look like a credential stays unmarked, so lint asks about it. Add a left-out server by hand under local/mcps, or write it with a ${NAME} reference.
Credential names
A name is a credential name when its last word is token, secret, password, passwd, pwd, pass, accesstoken, authtoken, credential, credentials, cookie, bearer, apikey, authentication, or authorization. Words split at punctuation and camelCase, so clientSecret and API_TOKEN count and token_type does not. A one-word name ending in password, passwd, secret, or token counts too, as PGPASSWORD and MYTOKEN do.
A last word key counts unless the word before it is sort, cache, public, partition, primary, foreign, idempotency, routing, object, row, hash, map, lookup, group, dedup, shard, index, unique, composite, natural, surrogate, ssh, gpg, pgp, s3, or id. So api_key, OPENAI_KEY, and --openai-key count, and sort_key, cache_key, --public-key, and --ssh-key do not.
In an env or header key, an x-<target> or requestOptions key, and a Name: value line or JSON pair, a last word cred or auth counts too, as in MY_CRED and X_AUTH. A flag such as --auth oauth does not.