Skip to content

[FEAT] Support pre-registered OAuth clients for HTTP MCP servers (for Slack and others that reject Dynamic Client Registration) #996

@listell-writer

Description

@listell-writer

Summary

Droid's HTTP MCP OAuth flow relies on RFC 7591 Dynamic Client Registration (DCR). Some hosted MCP servers only accept pre-registered public OAuth clients and refuse DCR, which makes those servers impossible to connect from Droid regardless of interactive vs exec mode. Confirmed on Slack (mcp.slack.com/mcp); likely affects other vendors with tightly scoped OAuth apps.

Environment

  • Droid CLI 0.104.0
  • macOS (darwin 25.4.0)
  • Terminal: iTerm.app and VS Code, same result

Reproduction

  1. droid mcp add slack https://mcp.slack.com/mcp --type http

  2. Start interactive droid. Observe slack in "Authentication required".

  3. /mcp -> slack -> Authenticate.

  4. Browser never opens. TUI loops:

    Authenticating with slack...
    Authentication cancelled for slack.
    Authentication flow did not start for slack.
    

Log evidence (~/.factory/logs/droid-log-single.log)

INFO: [McpService] Server requires OAuth, initiating authentication
       name: slack, url: https://mcp.slack.com/mcp
INFO: OAuth callback server started { port: 54623 }
WARN: [McpHub] Error in MCP server
       name: slack
       cause: Error: Incompatible auth server: does not support dynamic client registration
         at MAD (../../node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js:805:23)

Root cause

mcp.slack.com/mcp implements OAuth 2.0 Protected Resource Metadata (RFC 9728) but its authorization server does not implement RFC 7591 DCR. @modelcontextprotocol/sdk throws Incompatible auth server: does not support dynamic client registration. Droid has no fallback to pre-registered client credentials, so the flow aborts before the browser is opened and the TUI surfaces it as a cancellation.

Proposed behaviour

Allow configuring a pre-registered OAuth client per HTTP server in mcp.json, e.g.:

{
  "mcpServers": {
    "slack": {
      "type": "http",
      "url": "https://mcp.slack.com/mcp",
      "oauth": {
        "clientId": "1601185624273.8899143856786",
        "callbackPort": 3118
      }
    }
  }
}
  • oauth.clientId populated -> pass as clientInformation to the MCP SDK auth provider so the DCR branch is skipped.
  • oauth.callbackPort populated -> bind the OAuth callback listener to that port rather than scanning for a free one, so the server's pre-registered redirect URI (often http://localhost:<fixed_port>/) still matches.

This mirrors how Claude Code configures Slack today. 1601185624273.8899143856786 is the public slackapi/slack-mcp-plugin app id with redirect URI registered to http://localhost:3118/.

Affected servers

Any hosted MCP whose OAuth server lacks DCR. Confirmed: Slack (mcp.slack.com/mcp). Likely others with tightly scoped OAuth apps.

Workarounds

None inside Droid. Users either run a local stdio MCP (for Slack, korotovsky/slack-mcp-server with xoxc/xoxd tokens) or drop that server entirely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    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