[FEATURE] mcp oauth config does not replace values from userconfig in a plugin
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Claude Code plugins support user-configurable values (userConfig in plugin.json) that get substituted into .mcp.json via ${user_config.<key>}. Per docs, this substitution only resolves inside command, args, env, url, and headers fields. The oauth block on an http/sse MCP server entry (clientId, callbackPort, scopes, authServerMetadataUrl) is excluded — it's read by the OAuth handshake code before substitution runs. Any ${user_config.} placeholder placed inside oauth. is sent to the server verbatim, unexpanded (confirmed: authorize URL literally contained client_id=%24%7Buser_config.oauth_client_id%7D). Number-typed fields like callbackPort fail validation outright if given a template string, since the raw file is schema-checked before substitution.
Net effect: a plugin author cannot ship an MCP server that uses real OAuth and lets the end user supply their own clientId/callbackPort/scopes per install. Only non-OAuth fields (host, headers, static args) are configurable.
Proposed Solution
Extend ${user_config.*}/env-var expansion to cover the oauth block's fields (clientId, callbackPort, scopes, authServerMetadataUrl, etc.), applying substitution before the OAuth handshake code reads that block — same pass already used for url/headers. For number-typed fields (callbackPort), resolve the substituted string to its native type post-expansion rather than schema-validating the raw template string.
Alternative Solutions
Using hardcoded values in oauth config
Priority
Critical - Blocking my work
Feature Category
MCP server integration
Use Case Example
A plugin exposes an internal company MCP server behind OAuth2/PKCE. The server's OAuth clientId differs per deployment tenant (e.g. one client_id per customer org, not shared publicly), and the callback port needs to be user-chosen to avoid local port conflicts. Today the plugin author must hardcode one fixed clientId/callbackPort for every installer, or drop OAuth entirely in favor of a headersHelper script that manually injects a bearer token — losing the standard OAuth login flow Claude Code otherwise provides out of the box.
Additional Context
Claude Code Version: 2.1.231