Integrations · September 23, 2026

Email MCP in Windsurf, Cline and Zed

One remote URL, three config shapes, no mcp-remote bridge. The current Windsurf, Cline and Zed entries, and four reasons a client cannot connect.

Most guides for wiring an email MCP server into Windsurf, Cline or Zed still tell you to run npx mcp-remote against a local process holding IMAP credentials. That advice is obsolete for all three. Each one speaks Streamable HTTP to a remote server and runs the MCP OAuth flow itself. One of them is not called Windsurf any more: windsurf.com answers 308 to https://devin.ai/desktop, and docs.windsurf.com now lands on docs.devin.ai.

This piece gives the exact entry each client wants against one endpoint, the rule that decides which file the entry belongs in, and the failure ladder nobody writes down: four distinct reasons a client cannot connect here, only one of which is a typo. For the protocol mechanics independent of any editor, read the MCP email server walkthrough. For whether you want MCP or the REST API in the first place, read MCP or API.

Every vendor claim below was re-read on their own pages on 2026-09-23, and every claim about this endpoint was taken from a live response on the same day. Several of these facts changed within the last two quarters, so check the date on anything else you read.

Windsurf, Cline and Zed: one MCP email server, three config shapes

There is one URL, and it is the same one for all three clients:

https://mcp.emailforagents.ai/mcp

No bridge, no npx, no local process, no API key in an env block. What differs is the JSON key each client reads and the word it uses for the transport.

ClientConfig keyEndpoint fieldTransport declaration
Devin Desktop (ex-Windsurf)mcpServersurl"transport": "http"
ClinemcpServersurl"type": "streamableHttp"
Zedcontext_serversurlnone, inferred from url

Devin spells the field transport and Cline spells it type, and their values are not interchangeable: Devin’s reference table documents "http" and "sse", while Cline’s documents "streamableHttp" and "sse". Copying one client’s block into another is the most common reason a paste does nothing.

There is also no authoritative table to check this against. The per-client feature matrix that used to live at modelcontextprotocol.io/clients now 308-redirects to the getting-started intro, and the surviving matrix under /extensions/client-matrix covers four official extensions across thirteen clients, none of which are these three. Anyone showing you a remote-MCP support grid for Windsurf, Cline and Zed built it themselves.

Devin Desktop, the client you installed as Windsurf

Target the Devin Local agent, not legacy Cascade. Cognition’s own page is explicit: “The MCP configuration on this page applies to the legacy Cascade agent only.”

That split is why the config snippets on the internet disagree. Cascade accepts either serverUrl or url for a remote server, and most posts were written against Cascade, so they show serverUrl. The Devin Local reference documents url and no alias. Devin Local shares its agent harness with Devin CLI, so the CLI’s MCP configuration page is the reference for both.

The config filename also changed in v3000.3, the Local 3.6 release. Before that, servers lived under an mcpServers key inside the main config files; now they live in dedicated files at the same three locations, and stray entries are migrated on startup:

{
  "mcpServers": {
    "email-for-agents": {
      "url": "https://mcp.emailforagents.ai/mcp",
      "transport": "http"
    }
  }
}

Then devin mcp login email-for-agents, which opens a browser. Devin also prompts on first use if you skip the explicit login, and re-authenticating is devin mcp logout followed by devin mcp login, because login alone reuses the stored tokens.

The remaining documented remote fields are headers, oauthClientId, oauthClientSecret, oauthResource and disabled. Leave all five alone. Devin skips dynamic client registration the moment oauthClientId is set, and this endpoint has no pre-registered client to give you. oauthResource is the one that will bite: Devin sends an RFC 8707 resource parameter defaulting to the server URL, which is exactly what this authorization server wants, and any other value is refused with Resource mismatch. before the consent screen renders (packages/domain/src/oauth-consent.ts).

One Devin behaviour matters more than the config. Its HTTP transport falls back to legacy SSE when a remote server answers a non-authentication 4xx such as 404 or 405, but a 401, or a 403 carrying a WWW-Authenticate challenge, is reported to you directly instead. As the next section shows, this endpoint answers an unauthenticated request with exactly that, so Devin surfaces an auth prompt rather than quietly degrading to a transport that does not exist here.

If you are still on legacy Cascade, note its documented ceiling of 100 total tools across all servers at any one time. Six more tools is unlikely to break you, and that ceiling is why a Cascade session sometimes drops a server’s tools with no error.

Cline: the OAuth its own docs never mention

Cline connects to remote MCP servers over Streamable HTTP with full OAuth, including PKCE and dynamic client registration. You will not learn that from the documentation. As of today the word “OAuth” does not appear anywhere on Cline’s MCP page: the remote example shows a headers block with a bearer token, and the troubleshooting table’s entry for auth errors reads “Re-check API keys/tokens and required headers.” The capability is real and traceable in the repository. Pre-registered OAuth clients for remote MCP landed in cline/cline pull request 12983, merged 7 August 2026, and surfacing OAuth authorization on a 401 instead of failing outright landed in pull request 13050. It remains undocumented, so treat it as observed behaviour rather than as a contract.

{
  "mcpServers": {
    "email-for-agents": {
      "type": "streamableHttp",
      "url": "https://mcp.emailforagents.ai/mcp"
    }
  }
}

Set type explicitly. Cline’s docs are unambiguous that omitting it “defaults to the legacy sse transport for backward compatibility”. There is no server-initiated SSE stream on this endpoint, so the omission costs you an evening debugging a transport you never chose.

The file to edit depends on which Cline you run. The docs give ~/.cline/mcp.json for the CLI, and for the IDE extensions they give no path at all, only a route through the panel: MCP Servers, then Configure, then Configure MCP Servers. Take the route they give. Add the server through the extension UI or the cline mcp wizard, let Cline write the file, then read back whichever file it touched. That is your path, and it beats any path a blog post gives you.

There is no project-level MCP config in Cline. One machine-wide list, every workspace, which is the single biggest difference between it and the other two.

Zed: context_servers, CIMD first, DCR second

Zed calls MCP servers context servers, and remote OAuth is native. It arrived in zed-industries/zed pull request 51768, merged 23 March 2026, which closed an issue where “connecting to a remote MCP server that required OAuth would silently fail with a timeout”, because the server’s 401 was never handled. The mcp-remote bridge that every older Zed guide recommends has been unnecessary since.

Settings live in ~/.config/zed/settings.json on macOS and Linux, %APPDATA%\Zed\settings.json on Windows, or .zed/settings.json for a single project.

{
  "context_servers": {
    "email-for-agents": {
      "url": "https://mcp.emailforagents.ai/mcp"
    }
  }
}

Leave the headers block out. Zed’s documentation is precise about why: “When a remote MCP server has no configured Authorization header, Zed will prompt you to authenticate yourself against the MCP server using the standard MCP OAuth flow.” Adding a header suppresses the flow you want.

What Zed does next is worth knowing because it is the one place these three clients genuinely diverge in protocol. Zed registers itself by Client ID Metadata Document first, from a document it hosts at zed.dev, and only falls back to dynamic client registration when the server does not support CIMD. This server does not. Its authorization server metadata reports "client_id_metadata_document_supported": false, set by clientIdMetadataDocumentEnabled: false in workers/mcp/src/index.ts, so Zed’s first attempt is refused by design and its second one succeeds. The visible symptom is nothing at all, which is the point, but it explains why the flow takes an extra round trip here and not against a CIMD-aware server.

Two more Zed specifics. It implements MCP’s Tools and Prompts, not Resources, which is fine when the six tools are the whole surface. And every worktree starts in Restricted Mode: a context server declared in a project .zed/settings.json does not start until you trust the worktree, while a user-level entry starts regardless, because “Zed still trusts tools it installs globally.” The symptom of an untrusted worktree is a server that never appears rather than one that errors.

Where the entry belongs, per client

The endpoint holds no secret, which removes the usual reason to keep MCP config out of a repository. What remains is a question about blast radius, and it resolves differently per client.

WantDevin DesktopClineZed
Every teammate gets it on clone.devin/mcp_config.jsonnot possible.zed/settings.json, after trust
Just you, on this repo.devin/mcp_config.local.jsonnot possible.zed/settings.json, gitignored
Just you, everywhere~/.config/devin/mcp_config.json~/.cline/mcp.json~/.config/zed/settings.json
Per-tool prompt before a sendpermission rules, MCP-scopedtool auto-approve listmcp:email-for-agents:send_message

The recommendation flips on one fact. Commit the entry in Devin and Zed, because a committed url with no credential is a shared convenience and nothing more. Do not reach for the equivalent in Cline, because there is none, and a teammate who follows your README will put it in their global file and connect it to their own grant, which is the correct outcome anyway.

The last row is the one people miss. Zed gained per-tool permission rules keyed mcp:<server>:<tool_name> in v0.224.0, so mcp:email-for-agents:send_message with a default of "confirm" gives you a prompt on send and silence on reads. Zed’s own docs warn that the default key is the primary mechanism for MCP tools “since pattern-based rules match against an empty string for MCP tools and most patterns won’t match”, so a glob you write by analogy with file rules will quietly match nothing. Devin Local can scope its Ask and Deny rules to MCP tools the same way.

Client-side prompts are a real control and a different one from the grant. An agent with write access to the repository can edit the files that constrain it, and both .devin/mcp_config.json and .zed/settings.json sit in a tree the agent is already modifying. The grant is the boundary. The prompt catches honest mistakes faster.

Four ways a client cannot connect here

An API key is rejected at https://mcp.emailforagents.ai/mcp by design. Only an OAuth connection grant may call the tools, and no header, flag or config key changes that. That single rule produces four distinct failures, and telling them apart saves the afternoon.

  1. No OAuth in the client’s MCP implementation. It cannot connect at all. Not with reduced features, at all. If your editor’s remote MCP support is a headers block and nothing else, the next step is the REST API, not more configuration.
  2. OAuth, but plain PKCE. The authorization server advertises "code_challenge_methods_supported": ["S256"] and nothing else, and allowPlainPKCE is false in workers/mcp/src/index.ts. A code_challenge that is not exactly 43 base64url characters is refused, as is a code_verifier outside 43 to 128 unreserved characters.
  3. OAuth, but a callback this server will not register. Registration rejects any redirect URI that is not HTTPS or loopback HTTP on localhost, 127.0.0.1 or [::1], rejects a fragment or embedded credentials, and caps a client at ten URIs. The response is invalid_redirect_uri with “Use HTTPS or a native loopback callback.” A client whose callback is a custom URI scheme never reaches a consent screen, so the symptom is a browser window that never opens.
  4. OAuth, but no dynamic client registration. Registration is the only way in. There is no pre-registered client on offer, and CIMD is switched off, so a client that supports neither DCR nor a client ID you supply has no path.

Two rate limits sit on top, keyed by IP. Registration allows 20 attempts an hour, and /authorize and /oauth/token allow 120 a minute; over either you get HTTP 429 with temporarily_unavailable and “Too many authorization requests.” A client that registers a fresh client_id on every launch, or a team behind one office NAT, can reach the registration ceiling without doing anything wrong.

A registered client also expires after 30 days, independently of the connection. When it does, /authorize answers invalid_client with HTTP 400 rather than prompting you to sign in, which looks nothing like an expired connection and is fixed by letting the client register again. The full account of what a rejected credential returns and why rotating the key never helps is in the API key rejected at the MCP endpoint.

Cognition, Cline and Zed do not endorse this server, and there is no marketplace entry or install button for it in any of the three. They are third-party products implementing a protocol revision, and the integrations hub is the honest list of what has been exercised against this endpoint.

The scope in the 401, and what it silently costs you

An unauthenticated request to /mcp returns HTTP 401 with an empty body and this header, copied from a live response today:

www-authenticate: Bearer realm="OAuth",
  resource_metadata="https://mcp.emailforagents.ai/.well-known/oauth-protected-resource/mcp",
  scope="email.read"

Read the last parameter. The challenge names email.read, and so does the protected resource metadata it points at. The authorization server metadata at /.well-known/oauth-authorization-server advertises all four values, email.read, email.draft, email.send and offline_access. Two documents, two answers, both live right now.

A client that takes its requested scope from the challenge lands read-only. Zed parses this header during 401 detection, and Devin reports the challenge rather than falling back, so this is not a theoretical path. A read grant yields four visible tools out of six, with create_draft and send_message absent, and the connection looks perfectly healthy while being unable to write. Ask for email.send explicitly if you want the write tools, and add offline_access if you want the connection to outlive a 15-minute access token. Read the consent screen before clicking, because the scope the client requested is the ceiling and no console setting widens it afterwards.

That is a safe failure rather than a dangerous one. It is also indistinguishable from the product having dropped your connection, which is why it belongs in a setup guide.

Verify the grant instead of the green dot

Every one of these clients shows a connected indicator. It reports that a transport handshake succeeded, not what you granted.

Count the tools. Six means a full grant. Five, with send_message missing, means read and draft. Four, with create_draft also missing, means the read-only path above. The tool surface is the permission boundary: tools/list is filtered by the grant and tools/call re-checks it, so a tool outside your grant is not refused, it is absent, and a model cannot be argued into calling a name it was never shown. Why the tool surface is the permission boundary works that property through properly.

Then confirm the identity behind the grant by calling list_inboxes. The result should be exactly the inboxes you selected at consent. A test inbox is obvious from its address: test inboxes use a non-routable .mail.invalid domain and a simulated transport, and cannot reach an internet recipient. That is where to exercise a new client. Live mail is sent and received on agents.emailforagents.ai, and promotion is a credential swap rather than a code change.

For an editor agent, read and draft is usually the right ceiling, and not out of timidity. The mail that makes a repository-resident agent better is mail arriving: build failures, advisories, deprecation notices, review requests. Grant read and draft and every message the agent composes lands in the approval queue, which emits approval.requested and, if you decline, approval.rejected. create_draft cannot transmit under any circumstances, because the server strips messages:send from the calling principal before the draft path runs. If something has to happen while the editor is closed, that is REST work and the quickstart is the shorter road.


Keep reading

All guides · Documentation · Integrations