Skip to content

Plugin-sourced HTTP MCP servers with OAuth never trigger authorization prompt #1967

@tmech

Description

@tmech

Summary

HTTP MCP servers defined in marketplace plugins silently fail to authenticate. The OAuth authorization flow (RFC 9728 discovery → browser prompt) never triggers. The same server added manually to ~/.copilot/mcp-config.json works correctly and prompts for authorization.

Reproduction

  1. Create a marketplace plugin with an HTTP MCP server requiring OAuth:

    .github/plugin/marketplace.json:

    {
      "plugins": [{
        "name": "my-plugin",
        "source": "plugins/my-plugin",
        "mcpServers": {
          "my-server": {
            "type": "http",
            "url": "https://my-oauth-protected-mcp-server.example.com",
            "tools": ["*"]
          }
        }
      }]
    }

    The MCP server implements RFC 9728 (/.well-known/oauth-protected-resource) and RFC 8414 (/.well-known/oauth-authorization-server), returning 401 for unauthenticated requests.

  2. Install the plugin via the CLI marketplace.

  3. Start a new session — the plugin's skill loads, but no OAuth prompt appears and no MCP tools are available.

  4. Add the identical server config to ~/.copilot/mcp-config.json — the CLI correctly discovers OAuth metadata and prompts for authorization.

Note: Using an incorrect server URL does produce an error, confirming the plugin MCP config is being read.

Root Cause

In the bundled CLI code (app.js v1.0.4-0), the oj class (MCP host) accepts an onOAuthRequired callback as its 5th constructor parameter. This callback is what triggers the browser-based OAuth flow when a server returns 401.

Workspace MCP host (manual config) — receives all parameters:

new oj(ee, {mcpServers: this.mcpServers ?? {}}, this.excludedTools, this.envValueMode, void 0, this.runtimeSettings)

Plugin MCP host (getOrCreateHost in Qot class) — only passes 2 parameters:

// In Qot.getOrCreateHost():
let o = {mcpServers: n};
let s = new oj(this.logger, o);   // onOAuthRequired is never passed!

Because onOAuthRequired is undefined, the getAuthProvider method returns early:

async getAuthProvider(e, n) {
  if (!this.onOAuthRequired) return;  // ← always exits here for plugin servers
  // ... OAuth flow never reached
}

The 401 is silently swallowed and the server fails to connect without any user-visible error.

Expected Behavior

Plugin-sourced HTTP MCP servers should trigger the same OAuth authorization flow as manually-configured ones. The Qot.getOrCreateHost() method should forward the onOAuthRequired callback to the oj constructor.

Workaround

Add the MCP server manually to ~/.copilot/mcp-config.json alongside the plugin installation:

{
  "mcpServers": {
    "my-server": {
      "type": "http",
      "url": "https://my-oauth-protected-mcp-server.example.com"
    }
  }
}

Authorize it there; the plugin's skills can then use the MCP tools.

Environment

  • CLI Version: 1.0.4-0
  • OS: Windows 11 (ARM64)
  • Plugin format: Marketplace plugin with mcpServers in marketplace.json
  • MCP server transport: HTTP with RFC 9728 OAuth
  • Not affected: stdio MCP servers in plugins (e.g., workiq), manually configured HTTP MCP servers

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:authenticationLogin, OAuth, device auth, token management, and keychain integrationarea:mcpMCP server configuration, discovery, connectivity, OAuth, policy, and registryarea:pluginsPlugin system, marketplace, hooks, skills, extensions, and custom agents

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions