zai-org / zai-org/feedback

[Bug] Streamable HTTP MCP server: OAuth discovery drops the authorization server's path prefix

Open
#205 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

priority: P2
Dominant language
No language data
Stars
22
Forks
1
PR merge metrics
No merged PRs in 30d

Description

Summary

For a Streamable HTTP MCP server whose OAuth authorization server lives under a sub-path (not at the host root), ZCode's OAuth flow opens an authorization URL that has the authorization server's path prefix stripped, replacing the correct authorization_endpoint with a host-root-relative /authorize. Token and registration endpoints are affected the same way.

Result: OAuth login fails because the browser opens e.g. `https://example.com/authorize\` instead of the correct `https://example.com/app/oauth/authorize\`.

Other MCP clients (e.g. the ones bundled with `hermes` and `codex`) correctly follow the discovery chain and open the right URL, so the issue appears specific to ZCode's client implementation.

Environment
  • ZCode version: (latest, 3.x kernel)
  • OS: macOS (also reproducible on Linux)
  • MCP transport: Streamable HTTP (http)
Configuration

Minimal MCP config reproducing the issue (server under a sub-path):

```json
{
"mcp": {
"servers": {
"subpath-server": {
"type": "http",
"url": "https://example.com/app/mcp"
}
}
}
}
```

The server config itself is correct — the `url` points at the real MCP endpoint including the `/app` prefix. The bug is downstream, in OAuth discovery/URL construction.

Reproduction

The server publishes a standard RFC 8414 / RFC 9728 discovery chain. All responses below are observed (paths shown relative to the host):

  1. `GET https://example.com/app/mcp\` → `401`, header:
    ```
    www-authenticate: Bearer resource_metadata="https://example.com/app/.well-known/oauth-protected-resource\"
    ```

  2. `GET https://example.com/app/.well-known/oauth-protected-resource\`
    ```json
    {
    "resource": "https://example.com/app/mcp\",
    "authorization_servers": ["https://example.com/app"],
    "bearer_methods_supported": ["header"]
    }
    ```

  3. `GET https://example.com/app/.well-known/oauth-authorization-server\`
    ```json
    {
    "issuer": "https://example.com/app\",
    "authorization_endpoint": "https://example.com/app/oauth/authorize\",
    "token_endpoint": "https://example.com/app/oauth/token\",
    "registration_endpoint": "https://example.com/app/oauth/register\",
    "response_types_supported": ["code"],
    "grant_types_supported": ["authorization_code", "refresh_token"],
    "code_challenge_methods_supported": ["S256", "plain"],
    "token_endpoint_auth_methods_supported": ["none"],
    "scopes_supported": ["kmt"]
    }
    ```

This chain is internally consistent and is exactly what other clients consume successfully.

Expected behavior

ZCode follows the discovery chain (resource metadata → `authorization_servers` → `/.well-known/oauth-authorization-server`) and uses the absolute URLs returned in the authorization-server metadata as-is, without rewriting them.

Concretely, the authorization URL opened in the browser should be:
`https://example.com/app/oauth/authorize?client_id=...&...\`

Actual behavior

ZCode opens:
`https://example.com/authorize?client_id=...&...\`

i.e. the path prefix of the authorization server (`/app`) — and the `/oauth` segment — are discarded, and a literal `/authorize` is appended to the host root. The same stripping is expected to break `/token` and `/register` (would be requested at `/token` and `/register` instead of `/app/oauth/token` and `/app/oauth/register`).

Analysis / likely cause

The pattern (host root + literal `/authorize`, with both `/app` and `/oauth` dropped) is consistent with the client not following RFC 8414 discovery and instead normalizing the issuer/resource to the host root and hard-coding the endpoint suffixes. When the authorization server's `issuer` already contains a path prefix (here `/app`), that prefix is lost.

A correct implementation should:

  • Start from the `resource_metadata` URL in the `401` `WWW-Authenticate` header (RFC 9728).
  • Read `authorization_servers` from the protected-resource metadata.
  • Fetch `/.well-known/oauth-authorization-server` for each (RFC 8414), resolving the `.well-known` segment relative to the issuer's path, not the host root. (For `https://example.com/app\`, the metadata URL is `https://example.com/app/.well-known/oauth-authorization-server\`, per RFC 8414 §3.)
  • Use the absolute `authorization_endpoint` / `token_endpoint` / `registration_endpoint` values from the metadata verbatim.
Workaround

Server-side: add redirect rules mapping the host-root endpoints back to the real ones, e.g. `/authorize` → `/app/oauth/authorize`. This unblocks login but is purely a workaround; the client should be fixed to honor the discovery metadata.

Impact

Any MCP server published under a sub-path with its own OAuth 2.1 authorization server (common when multiple apps share one host) cannot complete OAuth in ZCode without a server-side redirect hack.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the Streamable HTTP client's OAuth discovery flow and reproduce the issue with the provided subpath-server configuration and discovery responses. Trace how the resource metadata, authorization server issuer, and endpoint URLs are handled. Done means the browser uses the returned /app/oauth/authorize URL and token and registration requests preserve their /app/oauth/ paths.

Written by the indexing model from the issue text.

Assessment

Domain
authentication
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.