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.
| Client | Config key | Endpoint field | Transport declaration |
|---|---|---|---|
| Devin Desktop (ex-Windsurf) | mcpServers | url | "transport": "http" |
| Cline | mcpServers | url | "type": "streamableHttp" |
| Zed | context_servers | url | none, 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:
.devin/mcp_config.json, project scope, committed.devin/mcp_config.local.json, personal, gitignored, and the default scope fordevin mcp add~/.config/devin/mcp_config.json, user-global, or%APPDATA%\devin\mcp_config.jsonon Windows
{
"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.
| Want | Devin Desktop | Cline | Zed |
|---|---|---|---|
| Every teammate gets it on clone | .devin/mcp_config.json | not possible | .zed/settings.json, after trust |
| Just you, on this repo | .devin/mcp_config.local.json | not 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 send | permission rules, MCP-scoped | tool auto-approve list | mcp: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.
- 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
headersblock and nothing else, the next step is the REST API, not more configuration. - OAuth, but plain PKCE. The authorization server advertises
"code_challenge_methods_supported": ["S256"]and nothing else, andallowPlainPKCEis false inworkers/mcp/src/index.ts. Acode_challengethat is not exactly 43 base64url characters is refused, as is acode_verifieroutside 43 to 128 unreserved characters. - 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.1or[::1], rejects a fragment or embedded credentials, and caps a client at ten URIs. The response isinvalid_redirect_uriwith “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. - 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
MCP Email Server: Give Your Agent Client a Real Inbox
How a remote MCP email server works, what the consent screen must show, how to scope a grant to one inbox, and how to verify it before allowing sends.
MCP or API Key for Agent Email? How to Choose
Remote MCP over OAuth or a project-scoped REST key: what each can and cannot do, who authorises it, how revocation differs, and why teams use both.
Email MCP Server OAuth vs API Key: the 401 Trap
Email MCP server OAuth vs API key: a valid key gets 401 invalid_token at /mcp, not 403, so rotation never helps. What each credential carries.