buildmymcpserver/apps/web/app/(marketing)/guides/claude-desktop-mcp-setup/page.tsx

190 lines
8.2 KiB
TypeScript
Raw Normal View History

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 (
<>
<JsonLd
data={articleJsonLd({
title: TITLE,
description: DESCRIPTION,
path: PATH,
datePublished: '2026-07-08',
authorName: 'Marco Sadjadi',
wordCount: 1400,
})}
/>
<JsonLd
data={breadcrumbJsonLd([
{ name: 'Home', path: '/' },
{ name: 'Guides', path: '/guides' },
{ name: TITLE, path: PATH },
])}
/>
<ArticleShell
title={TITLE}
subtitle="Claude Desktop supports both local STDIO servers and remote servers over Streamable HTTP. Remote is the version that survives a laptop change — here is the exact setup, including what the OAuth consent screen is doing."
updated="July 2026"
>
<H2>Local vs. remote pick remote unless you have a reason</H2>
<P>
Most MCP tutorials wire up a <Strong>local STDIO server</Strong>: 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 <Strong>remote server over Streamable HTTP</Strong>{' '}
is one URL that every installation shares, with auth handled by OAuth instead of
plaintext keys in a JSON file.
</P>
<StaticCodeBlock code={LOCAL_VS_REMOTE} label="the difference in config terms" />
<H2>Step 1 get a server URL</H2>
<P>
You need a live MCP endpoint. If you already run one, use its URL. If not, you can{' '}
<Link href="/guides/create-mcp-server-without-code" className="text-[--color-accent] hover:underline">
generate one from a prompt
</Link>{' '}
or fork a working one from{' '}
<Link href="/templates" className="text-[--color-accent] hover:underline">
the template gallery
</Link>{' '}
either way you end up with an OAuth-protected URL like{' '}
<Strong>https://my-tools-a1.mcp.buildmymcpserver.com/mcp</Strong>.
</P>
<H2>Step 2 add the server to Claude Desktop</H2>
<OL>
<li>
Open Claude Desktop settings and go to the connectors/MCP section (on recent versions:
Settings Connectors Add custom connector), or edit{' '}
<Strong>claude_desktop_config.json</Strong> directly.
</li>
<li>Add the server entry:</li>
</OL>
<StaticCodeBlock code={CONFIG_SNIPPET} label="claude_desktop_config.json" />
<OL>
<li value={3}>Restart Claude Desktop. Config is read at startup, not live.</li>
</OL>
<Note>
Claude Desktop's settings UI changes between releases; the JSON config path is the stable
fallback. On macOS it lives at{' '}
<Strong>~/Library/Application Support/Claude/claude_desktop_config.json</Strong>, on
Windows at <Strong>%APPDATA%\Claude\claude_desktop_config.json</Strong>.
</Note>
<H2>Step 3 the OAuth flow on first use</H2>
<P>
The first time Claude touches the server it will receive a <Strong>401</Strong> 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.
</P>
<P>
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{' '}
<Link href="/docs/oauth" className="text-[--color-accent] hover:underline">
the OAuth documentation
</Link>
.
</P>
<H2>Step 4 verify the tools are there</H2>
<UL>
<li>Open a new conversation and check the tools/connectors icon your server should be listed with its tools.</li>
<li>
Ask Claude directly: <em>Use the search_pages tool to find X.</em> Naming the tool
forces Claude to attempt the call instead of answering from memory.
</li>
<li>Watch your server's dashboard logs you should see the tool call arrive with latency and status.</li>
</UL>
<H2>Troubleshooting</H2>
<Table>
<thead>
<tr>
<th>Symptom</th>
<th>Likely cause</th>
<th>Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td>Server never appears in the client</td>
<td>Config JSON is invalid, or Claude was not restarted</td>
<td>
Validate the JSON (trailing commas are the classic), restart Claude Desktop fully
(quit, not close window).
</td>
</tr>
<tr>
<td>Endless 401 loop, consent screen never opens</td>
<td>URL points at the server root instead of the /mcp endpoint</td>
<td>Use the full endpoint URL ending in /mcp, exactly as the install snippet shows.</td>
</tr>
<tr>
<td>Consent worked, tools still missing</td>
<td>Server is deployed but tool listing failed on connect</td>
<td>
Check the server's live logs for an initialize/list_tools error; redeploy if the
container restarted with a bad secret.
</td>
</tr>
<tr>
<td>Tool listed, every call errors</td>
<td>Upstream credential (e.g. your API key) is wrong or expired</td>
<td>
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.
</td>
</tr>
<tr>
<td>Worked yesterday, 401 today</td>
<td>Token expired and silent refresh failed</td>
<td>Remove and re-add the connector to force a fresh OAuth flow.</td>
</tr>
</tbody>
</Table>
<H2>One server, every client</H2>
<P>
The same URL works in Cursor, VS Code Copilot, Continue.dev and{' '}
<Link href="/guides/chatgpt-mcp-connector" className="text-[--color-accent] hover:underline">
ChatGPT custom connectors
</Link>{' '}
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{' '}
<Link href="/docs/faq" className="text-[--color-accent] hover:underline">
docs FAQ
</Link>{' '}
collects the rarer ones.
</P>
</ArticleShell>
</>
);
}