Skip to content

MCP SDK authenticateClient requires client_secret even when token_endpoint_auth_method is "none" #25

Description

@xingyanke2011-oss

Environment

  • DevSpace: v1.0.2
  • Node: v24.15.0
  • OS: macOS arm64
  • Tunnel: Cloudflare Tunnel (Named Tunnel)
  • MCP SDK: @modelcontextprotocol/sdk@1.13.4

Problem Description

After a fresh reinstall of DevSpace 1.0.2, OAuth clients database is empty and ChatGPT reconnection fails at the token exchange step with:

{"error":"invalid_client","error_description":"Client secret is required"}

This happens despite the client being registered with token_endpoint_auth_method: "none" (public client, as required by ChatGPT web connector).

Root Cause Analysis

The issue is in MCP SDK v1.13.4 (@modelcontextprotocol/sdk/dist/*/server/auth/middleware/clientAuth.js).

When the client is registered with token_endpoint_auth_method: "none", the authenticateClient middleware still checks for client_secret in the token request body:

// clientAuth.js ~line 19
if (client.client_secret) {
  // Validates client_secret even when token_endpoint_auth_method === "none"
}

This causes the token exchange to fail for public clients (like ChatGPT's web-based OAuth connector) that legitimately do not have a client secret.

Steps to Reproduce

  1. Fresh install DevSpace 1.0.2: npm install -g @waishnav/devspace
  2. Configure with cloudflared tunnel: publicBaseUrl: "https://devspace.trendxai.io"
  3. Add logging: { trustProxy: true } to ~/.devspace/config.json (fixes the separate express-rate-limit issue)
  4. Restart devspace
  5. In ChatGPT, add MCP server: https://devspace.trendxai.io/mcp
  6. ChatGPT registers a new OAuth client (via /register) with token_endpoint_auth_method: "none"
  7. User approves with Owner Password → authorization code is returned
  8. Token exchange fails with {"error":"invalid_client","error_description":"Client secret is required"}

Verification

Direct curl reproduction:

# Register client (returns token_endpoint_auth_method: "none")
CLIENT=$(curl -s -X POST https://devspace.trendxai.io/register \
  -H "Content-Type: application/json" \
  -d '{"redirect_uris": ["https://chatgpt.com/connector/oauth/test"], "client_name": "ChatGPT"}')

CLIENT_ID=$(echo $CLIENT | jq -r '.client_id')

# Authorize (returns code)
CODE=$(curl -s -X POST "https://devspace.trendxai.io/authorize?client_id=$CLIENT_ID&..." \
  -d "owner_token=YOUR_OWNER_TOKEN" \
  -i | grep -o 'code=[^&]*' | cut -d'=' -f2)

# Token exchange (FAILS - requires client_secret even though method is "none")
curl -s -X POST https://devspace.trendxai.io/token \
  -d "grant_type=authorization_code" \
  -d "code=$CODE" \
  -d "client_id=$CLIENT_ID" \
  -d "code_verifier=abc123"
# → {"error":"invalid_client","error_description":"Client secret is required"}

Related Issue

This is related to #5 (express-rate-limit with reverse proxy), which was fixed by setting DEVSPACE_TRUST_PROXY=true. But after fixing the trust proxy issue, this new token exchange bug blocks the connection entirely.

Expected Behavior

When token_endpoint_auth_method is "none", the token endpoint should NOT require a client_secret parameter during the authorization_code grant exchange. This is the standard OAuth 2.0 behavior for public clients (PKCE-only).

Suggested Fix

In clientAuth.js, the authenticateClient middleware should check client.token_endpoint_auth_method before requiring client_secret:

// Only validate client_secret if token_endpoint_auth_method requires it
if (client.client_secret && client.token_endpoint_auth_method !== "none") {
  // Validate client_secret
}

Alternatively, this may need to be fixed upstream in the MCP SDK (@modelcontextprotocol/typescript-sdk), and DevSpace should upgrade to the fixed version.

Additional Context

  • This was working previously (before the reinstall). The issue only appears after the OAuth clients database is cleared and ChatGPT re-registers.
  • The same invalid_client error also manifests as Invalid client_id in some cases when the client registration data is malformed.
  • The clientRegistrationHandler in the SDK also has a related bug: it checks clientMetadata.token_endpoint_auth_method to decide whether to generate client_secret, but the condition if (!clientMetadata.token_endpoint_auth_method || clientMetadata.token_endpoint_auth_method === "client_secret_post") will generate a secret even when the method is explicitly set to "none" (if the condition is mis-evaluated).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions