Connecting the Claude Desktop App to 365-MCP (Windows)¶
How to get the Claude Desktop app talking to the 365-mcp server, and why the "normal" connector flow fails.
- Server URL:
https://365-mcp.erebor.au/sse - Tested working in: Claude Code CLI (uses the same auth described below)
- Applies to: Windows desktop (
mark-desktop), config at%APPDATA%\Claude\claude_desktop_config.json
TL;DR¶
The desktop app's "Add custom connector → log in via browser" flow cannot work against this server. The server sits behind Cloudflare Access and authenticates with a service token (static headers), not interactive OAuth. The fix is to point the desktop app at the server through the mcp-remote bridge, which injects those same headers — exactly what the CLI does.
No server-side or Cloudflare changes are needed, so the working CLI connection is never put at risk.
Why the browser/OAuth flow fails¶
There are two independent auth layers on this stack:
| Layer | What it protects | How it authenticates | Status |
|---|---|---|---|
| Microsoft Graph (app-only) | Reading 365 data | MSAL client credentials (AZURE_CLIENT_ID/SECRET/TENANT) | ✅ Healthy |
| Cloudflare Access | The /sse endpoint itself | Cf-Access-Jwt-Assertion JWT, minted from a service token | ⚠️ The sticking point |
When you add the server as a custom connector by URL, the desktop app first does OAuth discovery — it fetches:
https://365-mcp.erebor.au/.well-known/oauth-protected-resourcehttps://365-mcp.erebor.au/.well-known/oauth-authorization-server
But Cloudflare Access intercepts those URLs and returns its own HTML login/error page instead of the JSON OAuth metadata the app needs. Discovery fails instantly, so the app shows "Couldn't connect" / "failed to Auth" — before it ever reaches a login or email-OTP prompt. Enabling a login method (e.g. email OTP) in Cloudflare Access does not help, because the failure happens earlier, during discovery.
The CLI avoids all of this by sending the Cloudflare Access service token as static headers on every request:
The desktop app has no UI field for custom headers, so we use mcp-remote to add them.
What the pieces are¶
- Node.js — a runtime for running JavaScript outside the browser. Installing it also provides
npmandnpx. Standard, safe dev tool. - npx — a launcher bundled with Node that downloads-and-runs an npm package on demand.
- mcp-remote — a small npm package that runs locally as a "local MCP server," then forwards every request to a remote MCP server over HTTPS. Its
--headerflags let us attach the Cloudflare Access service-token headers. This is the missing piece the desktop UI doesn't expose.
Step-by-step fix¶
1. Install Node.js¶
Download the LTS installer from https://nodejs.org and run it with default options.
Verify in a new PowerShell or CMD window:
Both should print a version number. (Open a fresh terminal after installing so the updated PATH is picked up.)
2. Open the desktop config file¶
Easiest: inside Claude Desktop, go to Settings → Developer → Edit Config. That button creates and opens claude_desktop_config.json for you.
Manual path (Windows): press Win+R, paste, Enter:
The
Claudefolder only exists after the app has been installed and launched at least once. If it's missing, launch the app first (or use the Edit Config button above).
3. Add the mcpServers block¶
Merge the "365-mcp" entry into the file — keep any existing keys (preferences, coworkUserFilesPath, etc.) and just add mcpServers alongside them. Example of a merged file:
{
"preferences": { "...your existing settings...": true },
"mcpServers": {
"365-mcp": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://365-mcp.erebor.au/sse",
"--header",
"CF-Access-Client-Id:<CLIENT-ID>.access",
"--header",
"CF-Access-Client-Secret:<CLIENT-SECRET>"
]
}
}
}
The real service-token values live in the CLI config on the Erebor server at ~/.claude.json (under the 365-mcp → headers block). Copy them from there. Treat the secret like a password.
JSON tip: if you add
mcpServersafter an existing block, remember the comma between the two top-level entries, and don't leave a trailing comma after the last one.
4. Restart the app properly¶
Fully quit Claude Desktop — right-click the system-tray icon → Quit. Closing the window is not enough; it keeps running and won't reload the config. Then relaunch.
5. Test¶
Ask the app: "list my 365 users." It should return the tenant user list with no browser popup. 365-mcp should also appear in the tools/connectors list, and under Settings → Developer you can see whether it started cleanly.
Notes & gotchas¶
- Header format: each
--headervalue is literallyName:value— nohttps://prefix, and Id/Secret are two separate--headerentries. - No browser this time:
mcp-remotepresents the service token directly, so Cloudflare Access authenticates non-interactively (just like the CLI). The broken OAuth-discovery path is bypassed entirely. - Plaintext secret: the token sits in plaintext in
claude_desktop_config.json, same trust level as the CLI's~/.claude.json. If the PC isn't trusted, mint a separate, revocable Cloudflare Access service token scoped just for it. - The CLI is untouched: this only adds a local shim on the Windows side. Nothing on the server or in Cloudflare changes, so the existing CLI connection cannot break.
Troubleshooting¶
| Symptom | Likely cause / fix |
|---|---|
npx not found | Node.js not installed, or terminal opened before install finished. Reinstall Node, open a fresh terminal. |
| Still "Couldn't connect" via the Add connector UI | Expected — don't use that flow. Use the mcpServers config-file method above. |
| Server doesn't appear after restart | You closed the window instead of Quit from the tray; config didn't reload. |
403 Forbidden in Developer logs | Service-token headers wrong/expired, or a typo (extra space, missing .access suffix on the Id). Re-copy from ~/.claude.json. |
Stale auth errors from mcp-remote | Delete %USERPROFILE%\.mcp-auth and restart. |
| Want to test the bridge by hand | Run in a terminal: npx -y mcp-remote https://365-mcp.erebor.au/sse --header "CF-Access-Client-Id:<id>.access" --header "CF-Access-Client-Secret:<secret>" — it should connect and wait. |
Reference: the working CLI config¶
For comparison, the CLI authenticates the same way (in ~/.claude.json on the server):
"365-mcp": {
"type": "http",
"url": "https://365-mcp.erebor.au/sse",
"headers": {
"CF-Access-Client-Id": "<client-id>.access",
"CF-Access-Client-Secret": "<secret>"
}
}
The desktop mcp-remote setup is just this same request, wrapped in a local bridge because the desktop app can't send custom headers on its own.