Skip to content

[FEATURE]: MCP config - prompt for unresolved placeholder values and store the secrets outside the config #58653

Open

Description

@arin8513
  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Config values can only interpolate {env:VAR} from the process environment, and the official
config reference states the sharp edge plainly: when the variable is not set, the reference
"will be replaced with an empty string". A keyed remote server then fails upstream with a
confusing 401 that reads like a bad token while the value never existed at all.

Request: at config parse/adopt time, scan mcp entries for placeholder-style variables; when
one is unresolved, prompt the user ONCE per variable (e.g. "PENPOT_TOKEN - personal access
token for penpot") and store the answers OUTSIDE the plaintext config - the OS keyring, or at
minimum the same protected credential store the OAuth flow already uses.

This is distinct from the MCP input requests (elicitation) shipped in v2.1.4: that covers a
server asking during a call; this is the config asking once, before any server starts.

Two open secret-hygiene reports should close in the same direction:

Why is this needed?

From one week of real use on this machine:

  • a placeholder silently resolved to an empty string and produced a 401 that looked like a bad token;
  • a hand-edited Authorization header cost a debugging round (HTTP 400 Authorization header is badly formatted, visible only at connect time);
  • a keyed vendor's token currently sits in plaintext JSON because there is nowhere else to put it;
  • the same placeholder concept is spelled three different ways across the config formats we now maintain ({env:VAR} for OpenCode, ${env:VAR} in .vscode/mcp.json, ${VAR} in the portable .mcp.json), with no surface anywhere that validates any of them.

Related issues

Additional context

Activity

  1. opencode-agent commented on Oct 24, 2026

    @opencode-agent
    Contributor

    Thanks for splitting this out of #58243 and for the clear write-up!

    To restate: when an mcp entry in the config has a placeholder that can't be resolved, you'd like OpenCode to ask you for the value once per variable, before any server starts. The answer would be stored outside the plaintext config (the OS keyring, or the credential store the OAuth flow already uses) instead of silently becoming an empty string. Today {env:VAR} is only filled from the process environment, and a missing variable becomes an empty string, as the docs say. So this is a feature request rather than a bug. Maintainers will consider it.

    Related issues you may want to follow:

    This can't be done as a plugin today: plugins have no way to ask the user for input before the config is loaded and servers start, and no access to a protected secret store.

  2. Dante-dan commented on Oct 24, 2026

    @Dante-dan
    Contributor

    I'd propose a bounded first version for unresolved MCP {env:VAR} references:

    • Keep process environment values first. Only a missing value enters the credential-reference flow; an explicitly supplied environment value is never overwritten by a stored value.
    • During an explicit interactive config adoption/add/edit action, collect unresolved variable names before starting affected servers and ask once per distinct variable. Cancelling leaves that server disconnected and its prior config intact. No prompt during background reload or headless/server startup: return an actionable missing-secret error naming the variable and server, without making an empty-credential connection attempt.
    • Store an entered value outside the config in a protected credential backend. Keep only a non-secret reference in JSON(C), scoped so unrelated projects cannot silently consume each other's credentials. Prefer an OS credential backend where available; if extending the existing integration credential store as a first phase, make that choice explicit rather than silently falling back to plaintext config. Replacement and removal need explicit lifecycle actions; deleting one reference must not erase a shared secret still in use.
    • Preserve unresolved references as references in config responses. Redact resolved values from API/config output, logs and connection errors, including values echoed back in server errors (mcp: server error text can write config secrets into the local engine log #53051 / api: /api/model, /api/config and /api/provider/{id} return resolved provider credentials #53465). A secret prompt never echoes the entered token.

    The current variable resolver substitutes missing environment values with an empty string. The existing Credential.Service is keyed by integration ID and stores integration credentials in the database; it is not an existing arbitrary MCP-placeholder keyring flow. This proposal therefore needs an explicit MCP credential namespace/reference mapping and a decision on protection/backend guarantees. #44803 is historical reference for keychain work, but it was closed without merging; I would not assume that backend exists in dev.

    Planned acceptance checks: environment-over-store precedence; one prompt for repeated references; no prompt or network attempt on a headless missing secret; cancellation/reload/restart; credential replacement and shared-reference deletion; unavailable-store failure; no literal secret in saved config, API responses or error/log output. Other tools' ${env:VAR} / ${VAR} syntax would require an explicit import conversion rather than guessing during normal OpenCode parsing.

    Would you approve this first-phase design, including the credential backend and reference scope? This is a design proposal based on source inspection; these changes and checks are planned, not implemented or run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions