Skip to content

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.

Claude Desktop  →  npx mcp-remote  →  HTTPS + CF-Access headers  →  Cloudflare Access  →  365-mcp /sse

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-resource
  • https://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:

CF-Access-Client-Id:     <client-id>.access
CF-Access-Client-Secret: <secret>

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 npm and npx. 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 --header flags 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:

node --version
npx --version

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:

%APPDATA%\Claude\claude_desktop_config.json

The Claude folder 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-mcpheaders block). Copy them from there. Treat the secret like a password.

JSON tip: if you add mcpServers after 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 --header value is literally Name:valueno https:// prefix, and Id/Secret are two separate --header entries.
  • No browser this time: mcp-remote presents 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.