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 | jqObserve 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 stringOutput of checks
N/A — this is a GitLab.com SaaS endpoint issue.
Possible fixes
Implementation plan:
-
Locate the metadata endpoint handler — search for
oauth-protected-resourceorOauthProtectedResourceinlib/api/orapp/controllers/. Find the response builder that constructs the JSON payload for/.well-known/oauth-protected-resource/*. -
Fix the
resourcefield serialization — change from array to single string. This is a one-line fix:# Before (incorrect) resource: [resource_identifier] # After (correct) resource: resource_identifier -
Verify
authorization_serversremains an array —authorization_serversis correctly an array; do not change it. -
Add a regression test — add a request spec asserting the
resourcefield is aString, not anArray: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 -
Test with affected clients — verify the fix resolves authentication failures in Gemini CLI and Windsurf.
References
- Original feedback note by @iaros1521
- Windsurf error log showing the same root cause
- Parent feedback issue: #561564
- Spec / RFC limitations: !250085 (comment 3707686593)
Clients
- Gemini CLI (now: Antigravity CLI) - #561564 (comment 3181494577)
- Windsurf - #561564 (comment 2903719757) (internal)
- Devin - #596356 (comment 3679754513) (internal)
- MCP Gateway (new Okta agent gateway) - #561564 (comment 3704313202)
- Codex >= 0.147.0 (using rpmc_client 3.0.0 - Rust MCP SDK) - #596356 (comment 3688729188) (internal)
- Mistral Vibe (Python MCP SDK) - #596356 (comment 3704618615)
Fix verification summary in #596356 (comment 3726566492)
Known workarounds
- Codex: Pin to 0.146.1