Skip to content

MCP remote server OAuth authentication#51768

Merged
tomhoule merged 47 commits intomainfrom
tomhoule-ylrvyrnurluk
Mar 23, 2026
Merged

MCP remote server OAuth authentication#51768
tomhoule merged 47 commits intomainfrom
tomhoule-ylrvyrnurluk

Conversation

@tomhoule
Copy link
Copy Markdown
Contributor

@tomhoule tomhoule commented Mar 17, 2026

Closes #43162

Implements the OAuth 2.0 Authorization Code + PKCE authentication flow for remote MCP servers using Streamable HTTP transport, as specified by the MCP auth specification.

Previously, connecting to a remote MCP server that required OAuth would silently fail with a timeout — the server's 401 response was never handled. Now, Zed detects the 401, performs OAuth discovery, and guides the user through browser-based authentication.

Step-up authentication and pre-registered clients are not in scope for this PR, but will be done as follow-ups.

Overview

  • 401 detection — When the HTTP transport receives a 401 during server startup, it surfaces a typed TransportError::AuthRequired with parsed WWW-Authenticate header info.
  • OAuth discovery — Protected Resource Metadata (RFC 9728) and Authorization Server Metadata (RFC 8414) are fetched to locate the authorization and token endpoints.
  • Client registration — Zed first tries CIMD (Client ID Metadata Document) hosted at zed.dev. If the server doesn't support CIMD, falls back to Dynamic Client Registration (DCR).
  • Browser flow — A loopback HTTP callback server starts on a preferred fixed port (27523, listed in the CIMD), the user's browser opens to the authorization URL, and Zed waits for the callback with the authorization code.
  • Token exchange & persistence — The code is exchanged for access/refresh tokens using PKCE. The session is persisted in the system keychain so subsequent startups restore it without another browser flow.
  • Automatic refresh — The HTTP transport transparently refreshes expired tokens using the refresh token, and persists the updated session to the keychain.

UI changes

  • Servers requiring auth show a warning indicator with an "Authenticate" button
  • During auth, a spinner and "Waiting for authorization..." message are shown
  • A "Log Out" option is available in the server settings menu for OAuth-authenticated servers
  • The configure server modal handles the auth flow inline when configuring a new server that needs authentication.

Release Notes:

  • Added OAuth authentication support for remote MCP servers. Servers requiring OAuth now show an "Authenticate" button when they need you to log in. You will be redirected in your browser to the authorization server of the MCP server to go through the authorization flow.

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Mar 17, 2026
@zed-community-bot zed-community-bot bot added the staff Pull requests authored by a current member of Zed staff label Mar 17, 2026
@swannysec swannysec self-requested a review March 17, 2026 18:11
@tomhoule tomhoule force-pushed the tomhoule-ylrvyrnurluk branch from 3e23841 to 3f1ef87 Compare March 19, 2026 10:17
@github-actions
Copy link
Copy Markdown

📏 PR Size: 4173 lines changed (size/XL)

Please note: this PR exceeds the 400 LOC soft limit.

  • Consider splitting into separate PRs if the changes are separable
  • Ensure the PR description includes a guided tour in the "How to Review" section so reviewers know where to start

@tomhoule tomhoule force-pushed the tomhoule-ylrvyrnurluk branch from 3f1ef87 to fd04ab0 Compare March 19, 2026 10:19
@github-actions github-actions bot added size/XL and removed size/XL labels Mar 19, 2026
@tomhoule tomhoule force-pushed the tomhoule-ylrvyrnurluk branch from 3cc3a73 to 335405b Compare March 20, 2026 14:27
@tomhoule tomhoule marked this pull request as ready for review March 20, 2026 15:57
@zed-codeowner-coordinator zed-codeowner-coordinator bot requested review from a team, benbrandt and rtfeldman and removed request for a team March 20, 2026 15:57
@tomhoule tomhoule marked this pull request as draft March 21, 2026 08:27
@tomhoule
Copy link
Copy Markdown
Contributor Author

I found a bug in the browser flow where it will sometimes hang at the last redirect. @bennetbo potentially found a second one that isn't as easy to reproduce (yet). So I'm converting this PR back to draft until these are addressed.

@tomhoule tomhoule force-pushed the tomhoule-ylrvyrnurluk branch from 335405b to f536d50 Compare March 21, 2026 19:58
@tomhoule
Copy link
Copy Markdown
Contributor Author

The bug I could easily reproduce is fixed in f536d50. I'll investigate the other one next.

@tomhoule
Copy link
Copy Markdown
Contributor Author

I can reproduce the redirects on safari with the Linear MCP server, not with the Notion MCP server. But I think it's a Linear + Safari issue rather than linked to this PR, because I see the same redirect loop when logging in through Claude Code. Let's discuss tomorrow.

tomhoule and others added 12 commits March 23, 2026 12:01
When you logged out, then logged back in, sometimes the browser would hang on the redirect to the Zed callback server if both log in attempts used the same port (common case because there is a preferred port).

This happened because on the second log in flow, the editor spawned a new callback server on the same port, and the browser still has a connection pool to the old one (keep alive).

To avoid this stale connection state, this commit makes the server return a Keep-Alive header with timeout=0,max=0 to disable keep alive.
@tomhoule tomhoule force-pushed the tomhoule-ylrvyrnurluk branch from f536d50 to fd89c2d Compare March 23, 2026 11:01
@tomhoule tomhoule marked this pull request as ready for review March 23, 2026 11:36
@zed-codeowner-coordinator zed-codeowner-coordinator bot requested a review from a team March 23, 2026 11:36
@tomhoule
Copy link
Copy Markdown
Contributor Author

Ok I think we're in a good place now. The consecutive redirects happen only with the Linear MCP with Safari, and they happen in that setup with Claude Code as well, so I don't think this is a problem on our end. I confirmed that linear, notion and cloudflare MCPs work with OAuth and DCR just fine on Firefox, Chrome and Safari (with the exception of linear + safari, but even there, it works in the end).

I removed the preferred port binding. The spec says the port should not matter, so let's go with a random port always at first. That eliminates the keepalive issues with the browser hanging on to connections for which the tiny_http server doesn't exist anymore and us rebinding to the same port.

Happy to pair on any of this if there are questions.

@tomhoule tomhoule merged commit 302aa85 into main Mar 23, 2026
32 checks passed
@tomhoule tomhoule deleted the tomhoule-ylrvyrnurluk branch March 23, 2026 11:54
tomhoule added a commit that referenced this pull request Mar 23, 2026
The feature was implemented in #51768.
AmaanBilwar pushed a commit to AmaanBilwar/zed that referenced this pull request Mar 23, 2026
Closes zed-industries#43162

Implements the OAuth 2.0 Authorization Code + PKCE authentication flow
for remote MCP servers using Streamable HTTP transport, as specified by
the [MCP auth
specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization).

Previously, connecting to a remote MCP server that required OAuth would
silently fail with a timeout — the server's 401 response was never
handled. Now, Zed detects the 401, performs OAuth discovery, and guides
the user through browser-based authentication.

Step-up authentication and pre-registered clients are not in scope for
this PR, but will be done as follow-ups.

## Overview

- **401 detection** — When the HTTP transport receives a 401 during
server startup, it surfaces a typed `TransportError::AuthRequired` with
parsed `WWW-Authenticate` header info.
- **OAuth discovery** — Protected Resource Metadata (RFC 9728) and
Authorization Server Metadata (RFC 8414) are fetched to locate the
authorization and token endpoints.
- **Client registration** — Zed first tries CIMD (Client ID Metadata
Document) hosted at `zed.dev`. If the server doesn't support CIMD, falls
back to Dynamic Client Registration (DCR).
- **Browser flow** — A loopback HTTP callback server starts on a
preferred fixed port (27523, listed in the CIMD), the user's browser
opens to the authorization URL, and Zed waits for the callback with the
authorization code.
- **Token exchange & persistence** — The code is exchanged for
access/refresh tokens using PKCE. The session is persisted in the system
keychain so subsequent startups restore it without another browser flow.
- **Automatic refresh** — The HTTP transport transparently refreshes
expired tokens using the refresh token, and persists the updated session
to the keychain.

## UI changes

- Servers requiring auth show a warning indicator with an
**"Authenticate"** button
- During auth, a spinner and **"Waiting for authorization..."** message
are shown
- A **"Log Out"** option is available in the server settings menu for
OAuth-authenticated servers
- The configure server modal handles the auth flow inline when
configuring a new server that needs authentication.

Release Notes:

- Added OAuth authentication support for remote MCP servers. Servers
requiring OAuth now show an "Authenticate" button when they need you to
log in. You will be redirected in your browser to the authorization
server of the MCP server to go through the authorization flow.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
tomhoule added a commit that referenced this pull request Mar 24, 2026
The feature was implemented in
#51768.

Release Notes:

- N/A

---------

Co-authored-by: Kunall Banerjee <hey@kimchiii.space>
@gohryt
Copy link
Copy Markdown

gohryt commented Mar 26, 2026

@tomhoule can you check this is working with figma remote mcp (https://mcp.figma.com/mcp)? It is not working for me with

2026-03-26T18:17:27+03:00 ERROR [project::context_server_store] figma OAuth authentication failed: Failed to resolve OAuth client registration

Caused by:
    DCR failed with status 403 Forbidden: Forbidden

errr but i cannot understand if it is my or implementation problem

@tomhoule
Copy link
Copy Markdown
Contributor Author

Hey @gohryt, it look like Figma has an allow list of clients they accept dynamic client registration from, and Zed is not in it (yet). The workaround is a preregistered client. Support will be added with that issue: #52198 (WIP, should land very soon). So as of right now I don't think there's a workaround, but soon there will be. I'll see if we can reach out to Figma to add us to the allow list, so no workaround is needed in the first place.

@tomhoule
Copy link
Copy Markdown
Contributor Author

Quick update on this: Figma apps can't be configured with the mcp scope expected by the Figma MCP server, so we'll have to get on their allowlist for Zed to work as a client to Figma MCP.

@morgankrey
Copy link
Copy Markdown
Contributor

@tomhoule can you check this is working with figma remote mcp (https://mcp.figma.com/mcp)? It is not working for me with

2026-03-26T18:17:27+03:00 ERROR [project::context_server_store] figma OAuth authentication failed: Failed to resolve OAuth client registration

Caused by:
    DCR failed with status 403 Forbidden: Forbidden

errr but i cannot understand if it is my or implementation problem

working on this with the figma folks now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement large-pr staff Pull requests authored by a current member of Zed staff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP remote server (streamable HTTP) auth flow doesn't trigger

6 participants