[FEATURE] mcp oauth config does not replace values from userconfig in a plugin

Status Open
Reported on v2.1.231
Maintainer reply None cached
Activity 0 comments · opened Aug 24, 2026

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

View original on GitHub ↗