MCP Server: OAuth Protected Resource Metadata resource field returns array instead of string

Summary

The GitLab OAuth Protected Resource Metadata discovery endpoint incorrectly returns the resource field as a JSON array of strings instead of a single JSON string. This causes authentication failures in OAuth clients that strictly validate metadata types (e.g., Gemini CLI, Windsurf).

Originally reported in this feedback thread by @iaros1521.

Steps to reproduce

Run the following command to inspect the raw metadata:

curl -s https://gitlab.com/.well-known/oauth-protected-resource/api/v4/mcp | jq

Observe that the resource field is a JSON array.

{
  "resource": [
    "https://gitlab.com/api/v4/mcp"
  ],
  "authorization_servers": [
    "https://gitlab.com"
  ],
  "scopes_supported": [
    "mcp"
  ]
}

Example Project

N/A — this is a server-side endpoint bug affecting all MCP clients using OAuth discovery.

What is the current bug behavior?

The resource field is returned as a JSON array:

{
  "resource": ["https://gitlab.com/api/v4/mcp"],
  "authorization_servers": ["https://gitlab.com"],
  "scopes_supported": ["mcp"]
}

Strict OAuth clients (e.g., Gemini CLI) fail with errors such as:

failed to decode protected resource response: json: cannot unmarshal array into Go struct field OAuthProtectedResource.resource of type string

What is the expected correct behavior?

The resource parameter should be a single string (the resource identifier URI), per the OAuth 2.0 Protected Resource Metadata specification:

{
  "resource": "https://gitlab.com/api/v4/mcp",
  "authorization_servers": ["https://gitlab.com"],
  "scopes_supported": ["mcp"]
}

Note that authorization_servers correctly remains an array.

Relevant logs and/or screenshots

From Windsurf logs (also reported in this thread):

[MCP] Failed to register OAuth client: failed to get server metadata:
failed to decode protected resource response:
json: cannot unmarshal array into Go struct field
OAuthProtectedResource.resource of type string

Output of checks

N/A — this is a GitLab.com SaaS endpoint issue.

Possible fixes

Implementation plan:

  1. Locate the metadata endpoint handler — search for oauth-protected-resource or OauthProtectedResource in lib/api/ or app/controllers/. Find the response builder that constructs the JSON payload for /.well-known/oauth-protected-resource/*.

  2. Fix the resource field serialization — change from array to single string. This is a one-line fix:

    # Before (incorrect)
    resource: [resource_identifier]
    
    # After (correct)
    resource: resource_identifier
  3. Verify authorization_servers remains an arrayauthorization_servers is correctly an array; do not change it.

  4. Add a regression test — add a request spec asserting the resource field is a String, not an Array:

    it 'returns resource as a string' do
      get '/.well-known/oauth-protected-resource/api/v4/mcp'
      expect(json_response['resource']).to be_a(String)
      expect(json_response['resource']).to eq('https://gitlab.com/api/v4/mcp')
    end
  5. Test with affected clients — verify the fix resolves authentication failures in Gemini CLI and Windsurf.

References

Clients

Fix verification summary in #596356 (comment 3726566492)

Known workarounds

  • Codex: Pin to 0.146.1
Edited by Michael Friedrich