Skip to content

(fix): serve legacy OAuth discovery endpoints for 2025-03-26 MCP clients#77

Merged
ChiragAgg5k merged 1 commit into
mainfrom
fix/legacy-oauth-discovery
Jul 9, 2026
Merged

(fix): serve legacy OAuth discovery endpoints for 2025-03-26 MCP clients#77
ChiragAgg5k merged 1 commit into
mainfrom
fix/legacy-oauth-discovery

Conversation

@ChiragAgg5k

Copy link
Copy Markdown
Member

Problem

Connecting Raycast to https://mcp.appwrite.io/mcp fails with:

Invalid response: not found

A logging proxy showed Raycast (1.104.21) implements the 2025-03-26 MCP authorization spec: after the 401 it ignores the resource_metadata URL in WWW-Authenticate and instead fetches authorization-server metadata from the MCP origin itself:

POST /mcp -> 401   (initialize, clientInfo com.raycast.macos/1.104.21, protocolVersion 2025-03-26)
GET /.well-known/oauth-authorization-server -> 404   ← Raycast aborts here

Fix

Add the backwards-compatibility shim the current MCP spec recommends for exactly this case:

  • /.well-known/oauth-authorization-server — mirrors the Appwrite authorization server's discovery document verbatim (already fetched and cached by auth.authorization_server_metadata()), so legacy clients find the real authorize/token/register endpoints.
  • /.well-known/oauth-protected-resource (root form) — same handler as the existing /mcp-suffixed route, for clients that don't apply RFC 9728 path insertion.

Verification

  • Ran the HTTP transport locally; all three well-known endpoints return the expected documents with CORS headers.
  • Unit tests for both endpoints + route registration (including restoring the _UPLOAD_TRANSPORT module global that build_app() flips, so the stdio-path tests keep passing).
  • ruff, black --check, pyright, and the full unit suite (165 tests) pass.

Note

This gets legacy clients through discovery and the OAuth flow. If Raycast then fails post-login with a 401, the next (separate) issue would be RFC 8707 audience binding — 2025-03-26 clients don't send the resource parameter, and AppwriteTokenVerifier hard-rejects tokens not audience-bound to https://mcp.appwrite.io/mcp.

Raycast (and other clients on the 2025-03-26 MCP authorization spec) ignore
the resource_metadata hint in the 401 challenge and instead fetch
/.well-known/oauth-authorization-server from the MCP origin, which 404'd and
surfaced as "Invalid response: not found". Serve the Appwrite authorization
server's discovery document there, and also serve the protected-resource
metadata at its root form for clients that don't apply RFC 9728 path
insertion.
@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds two backwards-compatibility discovery shims to fix OAuth flow failures for MCP clients (e.g. Raycast) that implement the 2025-03-26 spec and fetch authorization-server metadata directly from the MCP origin rather than following the resource_metadata pointer in the 401 challenge.

  • Adds /.well-known/oauth-authorization-server backed by a new authorization_server_metadata_endpoint that mirrors the Appwrite upstream discovery document verbatim.
  • Adds a root-form /.well-known/oauth-protected-resource (no /mcp suffix) reusing the existing protected resource metadata handler, for clients that skip RFC 9728 path insertion.
  • New unit tests cover response body, CORS headers, and route registration, and correctly restore the _UPLOAD_TRANSPORT module global so stdio tests remain unaffected.

Confidence Score: 4/5

Safe to merge — the changes are additive (new routes only), well-tested, and do not touch the existing auth or MCP handler paths.

Both new routes are unauthenticated, read-only, and additive. The main open question is whether the /.well-known/oauth-authorization-server document — which carries an issuer pointing at cloud.appwrite.io rather than mcp.appwrite.io — will be accepted by stricter future client versions (RFC 8414 §3 requires the issuer to match the discovery URL origin). A cold-start discovery failure also produces an unhandled 500 on these pre-auth endpoints, which would block any client from completing OAuth until Appwrite is reachable again.

Both changed files are straightforward; src/mcp_server_appwrite/http_app.py is the one worth a second look for the issuer-mismatch and error-handling points above.

Important Files Changed

Filename Overview
src/mcp_server_appwrite/http_app.py Adds authorization_server_metadata_endpoint for /.well-known/oauth-authorization-server and a root-form /.well-known/oauth-protected-resource route. Logic is straightforward; two minor concerns around RFC 8414 issuer validation and unhandled discovery errors on cold start.
tests/unit/test_http_app.py New WellKnownMetadataEndpointTests class covers the new endpoint response, CORS headers, protected resource fields, and route registration. Correctly saves and restores _UPLOAD_TRANSPORT to avoid polluting stdio-transport tests.

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/mcp_server_appwrite/http_app.py:302-312
**RFC 8414 issuer mismatch may cause strict clients to reject the document**

RFC 8414 §3 requires that the `issuer` value in an `/.well-known/oauth-authorization-server` response equals the URL prefix used to construct the discovery request (i.e. `https://mcp.appwrite.io`). This endpoint returns the raw Appwrite upstream document, whose `issuer` is `https://cloud.appwrite.io/v1/oauth2/console`. Strictly conformant clients (and any client that adds RFC 8414 validation in a future update) will verify this and reject the document. The current fix relies on Raycast not enforcing this check. Worth noting explicitly in the docstring that this is a deliberate deviation, and consider whether an `issuer`-patched copy could work (though it would break any client that validates token issuers against this document).

### Issue 2 of 2
src/mcp_server_appwrite/http_app.py:302-312
**Unhandled discovery failure returns 500 with no client-facing error**

`authorization_server_metadata()` raises when the upstream discovery fetch fails and there is no stale cached document (e.g. first request after a cold start during an Appwrite outage). The exception propagates unhandled and Starlette returns a 500 response. The existing `protected_resource_metadata_endpoint` has the same gap, but this new endpoint is on the unauthenticated discovery path — a 500 here prevents any client from completing OAuth at all. A `try/except` that returns a `503 Service Unavailable` with a `Retry-After` header would give clients a clearer signal.

Reviews (1): Last reviewed commit: "(fix): serve legacy OAuth discovery endp..." | Re-trigger Greptile

Comment on lines +302 to +312
async def authorization_server_metadata_endpoint(request: Request) -> JSONResponse:
"""Legacy discovery shim (MCP authorization spec 2025-03-26).

Older clients fetch authorization-server metadata from the MCP server's own
origin rather than the ``authorization_servers`` entry in the protected
resource metadata. Serve the Appwrite authorization server's discovery
document verbatim so those clients find the real authorize/token/register
endpoints (the ``issuer`` inside points at the Appwrite endpoint, not here).
"""
metadata = await authorization_server_metadata()
return JSONResponse(metadata, headers=dict(CORS_HEADERS))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 RFC 8414 issuer mismatch may cause strict clients to reject the document

RFC 8414 §3 requires that the issuer value in an /.well-known/oauth-authorization-server response equals the URL prefix used to construct the discovery request (i.e. https://mcp.appwrite.io). This endpoint returns the raw Appwrite upstream document, whose issuer is https://cloud.appwrite.io/v1/oauth2/console. Strictly conformant clients (and any client that adds RFC 8414 validation in a future update) will verify this and reject the document. The current fix relies on Raycast not enforcing this check. Worth noting explicitly in the docstring that this is a deliberate deviation, and consider whether an issuer-patched copy could work (though it would break any client that validates token issuers against this document).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/mcp_server_appwrite/http_app.py
Line: 302-312

Comment:
**RFC 8414 issuer mismatch may cause strict clients to reject the document**

RFC 8414 §3 requires that the `issuer` value in an `/.well-known/oauth-authorization-server` response equals the URL prefix used to construct the discovery request (i.e. `https://mcp.appwrite.io`). This endpoint returns the raw Appwrite upstream document, whose `issuer` is `https://cloud.appwrite.io/v1/oauth2/console`. Strictly conformant clients (and any client that adds RFC 8414 validation in a future update) will verify this and reject the document. The current fix relies on Raycast not enforcing this check. Worth noting explicitly in the docstring that this is a deliberate deviation, and consider whether an `issuer`-patched copy could work (though it would break any client that validates token issuers against this document).

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

Comment on lines +302 to +312
async def authorization_server_metadata_endpoint(request: Request) -> JSONResponse:
"""Legacy discovery shim (MCP authorization spec 2025-03-26).

Older clients fetch authorization-server metadata from the MCP server's own
origin rather than the ``authorization_servers`` entry in the protected
resource metadata. Serve the Appwrite authorization server's discovery
document verbatim so those clients find the real authorize/token/register
endpoints (the ``issuer`` inside points at the Appwrite endpoint, not here).
"""
metadata = await authorization_server_metadata()
return JSONResponse(metadata, headers=dict(CORS_HEADERS))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unhandled discovery failure returns 500 with no client-facing error

authorization_server_metadata() raises when the upstream discovery fetch fails and there is no stale cached document (e.g. first request after a cold start during an Appwrite outage). The exception propagates unhandled and Starlette returns a 500 response. The existing protected_resource_metadata_endpoint has the same gap, but this new endpoint is on the unauthenticated discovery path — a 500 here prevents any client from completing OAuth at all. A try/except that returns a 503 Service Unavailable with a Retry-After header would give clients a clearer signal.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/mcp_server_appwrite/http_app.py
Line: 302-312

Comment:
**Unhandled discovery failure returns 500 with no client-facing error**

`authorization_server_metadata()` raises when the upstream discovery fetch fails and there is no stale cached document (e.g. first request after a cold start during an Appwrite outage). The exception propagates unhandled and Starlette returns a 500 response. The existing `protected_resource_metadata_endpoint` has the same gap, but this new endpoint is on the unauthenticated discovery path — a 500 here prevents any client from completing OAuth at all. A `try/except` that returns a `503 Service Unavailable` with a `Retry-After` header would give clients a clearer signal.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

@ChiragAgg5k ChiragAgg5k merged commit a08665d into main Jul 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant