Summary
A remote MCP server that requires OAuth — for example the official Atlassian Rovo server (https://mcp.atlassian.com/v1/mcp, which provides Jira/Confluence/Compass) — cannot be used from ZCode when it is declared in ~/.zcode/cli/config.json. The server fails at connect time with invalid_token / "Missing or invalid access token", and no Authenticate / "Sign in" affordance is offered in Settings → MCP.
The same server URL works in Claude Code, because Claude Code's MCP host attaches an OAuth client provider to HTTP transports automatically and runs the OAuth + PKCE browser flow on the first 401. ZCode already contains that OAuth machinery, but it is not wired into the config-file MCP path.
Environment
- ZCode: 0.15.0
- OS: macOS (Darwin 25.5.0, arm64)
- Config scope: user (
~/.zcode/cli/config.json)
Configuration
{
"mcp": {
"servers": {
"atlassian": {
"type": "http",
"url": "https://mcp.atlassian.com/v1/mcp",
"enabled": true,
"timeoutMs": 60000
}
}
}
}
Repro
- Add the config above to
~/.zcode/cli/config.json.
- Restart ZCode (so the MCP subsystem reconnects).
- Open Settings → MCP.
Actual
- Server
atlassian shows as failed:
Streamable HTTP error: Error POSTing to endpoint: {"error":"invalid_token","error_description":"Missing or invalid access token"}
- The entry shows 0 tools and no Authenticate / Connect / Sign-in button.
- The ZCode log (
~/.zcode/cli/log/zcode-YYYY-MM-DD.jsonl) shows:
{"event":"mcp.server.failed",
"context":{"mcpServerName":"atlassian","transport":"http",
"error":"Streamable HTTP error: Error POSTing to endpoint:
{\"error\":\"invalid_token\",\"error_description\":\"Missing or invalid access token\"}"}}```
- Crucially, the logs contain **no OAuth activity** at all — no `www-authenticate` handling, no authorization-server discovery, no PKCE, no token request. The request is sent with no `Authorization` header.
## Expected
On the first 401 from an OAuth-protected server, ZCode should follow the MCP authorization flow (discover protected-resource metadata from the `WWW-Authenticate` header, run Authorization Code + PKCE, open a browser for login, then retry with a Bearer token) — and expose an "Authenticate" affordance on the server entry, as the in-app strings ("Authenticate with OAuth", "Please authenticate", "Open in browser?") imply it is meant to.
## Root-cause analysis (from inspecting the app bundle)
ZCode bundles a full MCP OAuth implementation: `OAuthClientProvider`, dynamic client registration, PKCE (`code_verifier` / `code_challenge`, S256), `WWW-Authenticate` parsing, token refresh, and the GUI strings referenced above. However, the OAuth branch in the Streamable HTTP transport is gated on an auth provider being present:
```js
if (response.status === 401 && this._authProvider) { /* run OAuth flow */ }
For servers defined via config.json, the transport is constructed without an _authProvider, so the 401 falls straight through to the StreamableHTTPError(401, ...) path and no OAuth flow is ever attempted.
Compounding this, the mcp.servers schema is strict (an unknown key causes the server to be dropped) and exposes no auth / oauth field a user could set to opt in. So there is currently no file-level way to make an OAuth-protected remote server work.
Suggested fix (either or both)
- Preferred: when constructing the Streamable HTTP / SSE transport for a config-file MCP server, attach a default
OAuthClientProvider (the capability already exists in-app), so remote OAuth servers work with no extra config — matching the behavior in Claude Code.
- Alternatively / additionally: add a recognized
auth (or oauth) field to the mcp.servers schema, e.g. {"auth": {"method": "oauth"}}, letting users opt a server into the OAuth provider. This keeps the strict-schema behavior while making OAuth explicitly configurable.
Impact / context
This blocks any remote MCP server that uses OAuth (Atlassian Rovo is the common case) when configured via the standard config file — which is the documented primary path. A workaround (running a local stdio server that authenticates with a static API token) exists but gives a worse experience (API token to manage, Jira-only, no Confluence/Compass) and diverges from the seamless OAuth flow available in peer tools.
I'd like ZCode + GLM to be a drop-in replacement for Claude Code for this class of integration; wiring OAuth into the config-file path would close that gap.
Supporting evidence available
- Full ZCode connection logs (the
mcp.server.connect.started / mcp.server.failed events, with zero OAuth entries).
- The app-bundle strings confirming the OAuth implementation and the
_authProvider gate.
- Side-by-side: identical server URL works in Claude Code (OAuth login completes in-browser on first connect).
Summary
A remote MCP server that requires OAuth — for example the official Atlassian Rovo server (
https://mcp.atlassian.com/v1/mcp, which provides Jira/Confluence/Compass) — cannot be used from ZCode when it is declared in~/.zcode/cli/config.json. The server fails at connect time withinvalid_token / "Missing or invalid access token", and no Authenticate / "Sign in" affordance is offered in Settings → MCP.The same server URL works in Claude Code, because Claude Code's MCP host attaches an OAuth client provider to HTTP transports automatically and runs the OAuth + PKCE browser flow on the first 401. ZCode already contains that OAuth machinery, but it is not wired into the config-file MCP path.
Environment
~/.zcode/cli/config.json)Configuration
{ "mcp": { "servers": { "atlassian": { "type": "http", "url": "https://mcp.atlassian.com/v1/mcp", "enabled": true, "timeoutMs": 60000 } } } }Repro
~/.zcode/cli/config.json.Actual
atlassianshows as failed:Streamable HTTP error: Error POSTing to endpoint: {"error":"invalid_token","error_description":"Missing or invalid access token"}~/.zcode/cli/log/zcode-YYYY-MM-DD.jsonl) shows:{"event":"mcp.server.failed", "context":{"mcpServerName":"atlassian","transport":"http", "error":"Streamable HTTP error: Error POSTing to endpoint: {\"error\":\"invalid_token\",\"error_description\":\"Missing or invalid access token\"}"}}``` - Crucially, the logs contain **no OAuth activity** at all — no `www-authenticate` handling, no authorization-server discovery, no PKCE, no token request. The request is sent with no `Authorization` header. ## Expected On the first 401 from an OAuth-protected server, ZCode should follow the MCP authorization flow (discover protected-resource metadata from the `WWW-Authenticate` header, run Authorization Code + PKCE, open a browser for login, then retry with a Bearer token) — and expose an "Authenticate" affordance on the server entry, as the in-app strings ("Authenticate with OAuth", "Please authenticate", "Open in browser?") imply it is meant to. ## Root-cause analysis (from inspecting the app bundle) ZCode bundles a full MCP OAuth implementation: `OAuthClientProvider`, dynamic client registration, PKCE (`code_verifier` / `code_challenge`, S256), `WWW-Authenticate` parsing, token refresh, and the GUI strings referenced above. However, the OAuth branch in the Streamable HTTP transport is gated on an auth provider being present: ```js if (response.status === 401 && this._authProvider) { /* run OAuth flow */ }For servers defined via
config.json, the transport is constructed without an_authProvider, so the 401 falls straight through to theStreamableHTTPError(401, ...)path and no OAuth flow is ever attempted.Compounding this, the
mcp.serversschema is strict (an unknown key causes the server to be dropped) and exposes noauth/oauthfield a user could set to opt in. So there is currently no file-level way to make an OAuth-protected remote server work.Suggested fix (either or both)
OAuthClientProvider(the capability already exists in-app), so remote OAuth servers work with no extra config — matching the behavior in Claude Code.auth(oroauth) field to themcp.serversschema, e.g.{"auth": {"method": "oauth"}}, letting users opt a server into the OAuth provider. This keeps the strict-schema behavior while making OAuth explicitly configurable.Impact / context
This blocks any remote MCP server that uses OAuth (Atlassian Rovo is the common case) when configured via the standard config file — which is the documented primary path. A workaround (running a local stdio server that authenticates with a static API token) exists but gives a worse experience (API token to manage, Jira-only, no Confluence/Compass) and diverges from the seamless OAuth flow available in peer tools.
I'd like ZCode + GLM to be a drop-in replacement for Claude Code for this class of integration; wiring OAuth into the config-file path would close that gap.
Supporting evidence available
mcp.server.connect.started/mcp.server.failedevents, with zero OAuth entries)._authProvidergate.