import { JsonLd } from '@/components/json-ld'; import { StaticCodeBlock } from '@/components/static-code-block'; import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo'; import Link from 'next/link'; import { ArticleShell, H2, Note, OL, P, Strong, Table, UL } from '../article-shell'; const PATH = '/guides/claude-desktop-mcp-setup'; const TITLE = 'Connect a custom MCP server to Claude Desktop (step by step)'; const DESCRIPTION = 'How to add a remote MCP server to Claude Desktop: the config snippet, the OAuth flow on first use, and a troubleshooting table for 401s, missing servers and invisible tools.'; export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH }); const CONFIG_SNIPPET = `{ "mcpServers": { "my-tools": { "url": "https://my-tools-a1.mcp.buildmymcpserver.com/mcp", "auth": "oauth2" } } }`; const LOCAL_VS_REMOTE = `# Local (STDIO) — runs on your machine, per-machine setup "command": "npx", "args": ["-y", "@your/mcp-server"] # Remote (Streamable HTTP) — hosted, one URL for every machine "url": "https://my-tools-a1.mcp.buildmymcpserver.com/mcp"`; export default function Page() { return ( <>

Local vs. remote — pick remote unless you have a reason

Most MCP tutorials wire up a local STDIO server: Claude Desktop spawns a process on your machine and talks to it over stdin/stdout. That works, but it is per-machine — every teammate repeats the setup, secrets live in local config files, and nothing works from a second device. A remote server over Streamable HTTP{' '} is one URL that every installation shares, with auth handled by OAuth instead of plaintext keys in a JSON file.

Step 1 — get a server URL

You need a live MCP endpoint. If you already run one, use its URL. If not, you can{' '} generate one from a prompt {' '} or fork a working one from{' '} the template gallery {' '} — either way you end up with an OAuth-protected URL like{' '} https://my-tools-a1.mcp.buildmymcpserver.com/mcp.

Step 2 — add the server to Claude Desktop

  1. Open Claude Desktop settings and go to the connectors/MCP section (on recent versions: Settings → Connectors → Add custom connector), or edit{' '} claude_desktop_config.json directly.
  2. Add the server entry:
  1. Restart Claude Desktop. Config is read at startup, not live.
Claude Desktop's settings UI changes between releases; the JSON config path is the stable fallback. On macOS it lives at{' '} ~/Library/Application Support/Claude/claude_desktop_config.json, on Windows at %APPDATA%\Claude\claude_desktop_config.json.

Step 3 — the OAuth flow on first use

The first time Claude touches the server it will receive a 401 — that is correct behavior, not an error. The client then discovers the authorization server, registers itself via Dynamic Client Registration, and opens a browser window for consent. You approve once; the client stores the token and refreshes it silently afterwards.

Under the hood this is the OAuth 2.1 handshake the MCP spec requires for remote servers: PKCE so the code exchange cannot be intercepted, and Resource Indicators (RFC 8707) so a token issued for this server cannot be replayed against another. Details in{' '} the OAuth documentation .

Step 4 — verify the tools are there

  • Open a new conversation and check the tools/connectors icon — your server should be listed with its tools.
  • Ask Claude directly: “Use the search_pages tool to find X.” Naming the tool forces Claude to attempt the call instead of answering from memory.
  • Watch your server's dashboard logs — you should see the tool call arrive with latency and status.

Troubleshooting

Symptom Likely cause Fix
Server never appears in the client Config JSON is invalid, or Claude was not restarted Validate the JSON (trailing commas are the classic), restart Claude Desktop fully (quit, not close window).
Endless 401 loop, consent screen never opens URL points at the server root instead of the /mcp endpoint Use the full endpoint URL ending in /mcp, exactly as the install snippet shows.
Consent worked, tools still missing Server is deployed but tool listing failed on connect Check the server's live logs for an initialize/list_tools error; redeploy if the container restarted with a bad secret.
Tool listed, every call errors Upstream credential (e.g. your API key) is wrong or expired Update the secret in the dashboard — secrets are injected at runtime, so a redeploy picks up the new value. They are never shown back, so re-enter rather than inspect.
Worked yesterday, 401 today Token expired and silent refresh failed Remove and re-add the connector to force a fresh OAuth flow.

One server, every client

The same URL works in Cursor, VS Code Copilot, Continue.dev and{' '} ChatGPT custom connectors {' '} — the point of hosting a remote MCP server is that the client config is the only per-client step left. If you hit a case this guide does not cover, the{' '} docs FAQ {' '} collects the rarer ones.

); }