Back to blog

Claude MCP: How To Set Up MCP Servers in Claude Desktop and Claude Code

Share article:

Claude MCP is the Model Context Protocol as Claude implements it: a server declares tools, and Claude calls them. Claude Desktop and Claude Code both use it, and each reads a different config file. This guide sets up both clients, then measures cost and hidden failures.

Lightning bolt icon with three horizontal lines on the left, inside a rounded square.

TL;DR

  • Claude Desktop reads claude_desktop_config.json at launch, so changes take effect on restart. Claude Code takes a single claude mcp add command and loads the server per scope.
  • Scope decides who gets the server. Local stays private to one project. Project is stored in .mcp.json and asks each teammate once, and the user applies it in every project.
  • Authenticate a server through the /mcp command. A server listed as connected with zero tools means authentication is still pending, not that setup failed.
  • Reference secrets with ${VAR} syntax pointing to environment variables instead of hardcoding them in the config. SSE transport is deprecated in favor of Streamable HTTP.

What Claude MCP is and how the pieces fit

Claude MCP standardizes the layer between a model and everything outside it, so one server works with every client that implements it. A Claude MCP server is one that Claude Desktop or Claude Code can call through the protocol. 4 pieces matter first:

  • Client. Claude Desktop or Claude Code. The client starts servers, reads their tool lists, and decides which tool to call.
  • Server. A process or an HTTP endpoint that answers tools/list and tools/call. Most servers give the model access to an API you already have.
  • Tools. Named capabilities with JSON Schema inputs. The model reads the schema, so schema quality affects call quality.
  • Transport. Streamable HTTP for remote servers, stdio for local processes. SSE is deprecated and scheduled for removal, so don't start anything new on it.

Those 4 pieces combine in a single request. Read the sequence top to bottom, where solid arrows are requests and dashed arrows are responses. The single highlighted arrow shows the failure mode worth noting first: a target's refusal can arrive as ordinary tool content rather than as an error:

Sequence diagram showing Claude sending a tools/call request to the MCP server, which fetches the target page. An alt block illustrates two branches (page served vs. page refused), both returning as ordinary content to Claude without an error flag.

The shaded alt block marks the one place where 2 things can happen: the target serves the page, or it refuses. When a refusal arrives this way, both branches return through the identical arrow with no isError flag. A blocked page and a real page therefore look the same until the model reads what's inside.

The protocol underneath is the same in both clients. The config files, the scopes, and the approval steps differ.

Claude Desktop MCP setup

Claude Desktop MCP setup has 2 paths, and they differ in how much control you keep. Start with the menu, which installs packaged extensions and remote connectors. Use the config file when the menu doesn't list what you need.

Both setups need Node.js on the PATH that the app itself sees, because the example server runs through npx. They also need a Decodo API token from the free Web Scraping API plan. Decodo is the worked example because it gives Claude live web access. Most other MCP servers use the same config fields with their own credentials.

Give Claude a scraper

Mount Decodo's MCP server and Claude gets live web search and scraping tools, not just static training data.

Path 1: Connectors and Extensions

Extensions are packaged servers that come as .mcpb bundles. Settings > Extensions installs them. The bundle contains a manifest.json, so the app can collect your API key through a form instead of asking you to edit JSON. The format appeared as .dxt first and was renamed to .mcpb, so older guides still name the .dxt extension.

Screenshot of Claude Desktop's empty Extensions settings screen displaying "No extensions installed" along with buttons to browse extensions or drag .MCPB / .DXT files to install.

The install hint is the rename in one line: .MCPB is the current format, .DXT is the older name that Claude Desktop still accepts. Browse extensions shows the marketplace for anything you haven't built yourself.

For a remote server, open Settings > Customize > Connectors and click Add. Paste the HTTPS endpoint into the dialog that opens. Authentication usually completes in the browser through OAuth. The token then stays in the app rather than in a file you might commit.

Screenshot of Claude Desktop's "Add custom connector" dialog box, featuring input fields for Name and Remote MCP server URL, alongside a security trust warning.

Claude Desktop states this plainly in the dialog itself: "Anthropic does not control which tools developers make available and cannot verify that they will work as intended or that they won't change". The warning says a server can change without your knowledge, so pin a version and read the diff before you trust one.

Pick this path when the server you want is listed. Everything else needs the config file.

Path 2: claude_desktop_config.json

The config file is at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows. Settings > Developer > Edit Config opens it directly.

Screenshot of Claude Desktop Developer settings showing the Local MCP servers list with a running server named "decodo", configured via npx.

The Edit Config button opens the same file that this section edits. Once a server is added, it appears here with a live status, so you can confirm the process actually started.

This block connects the Decodo MCP server, which gives Claude live web access through a managed collection layer:

{
"mcpServers": {
"decodo": {
"command": "npx",
"args": ["-y", "@decodo/mcp-server@1.2.3"],
"env": {
"SCRAPER_API_TOKEN": "<your_basic_auth_token>",
"TOOLSETS": "web,search",
},
}
}
}

The TOOLSETS value decides how many tool schemas load into the session, so name only the groups you need. Restart Claude Desktop fully after saving, because the app reads this file at launch and won't reload it while running. Claude Desktop stores the token exactly as written, so the file's protection is all the token gets. Keep that file out of synced folders and repositories.

Once the server reconnects, open it from Settings > Customize > Connectors to see what actually loaded. Claude Desktop groups a connector's tools by capability and puts each group behind an approval setting. Every tool defaults to Needs approval until you change it.

Screenshot of Claude Desktop's Tool permissions setting for the decodo connector, showing 7 read-only tools configured to require approval by default.

The 7 tools listed here are exactly the web and search toolsets set above: scrape_as_markdown and screenshot, then google_searchgoogle_adsgoogle_lensgoogle_travel_hotels, and bing_search. Each tool has its own control, so you can allow or block a single one without editing the config file. Because of that default, your first real prompt is likely to trigger an approval.

Then test it. Name the server directly so the call reaches it. Claude can otherwise use its own web search or a public API when one exists for the target: "Use the decodo tool to scrape https://news.ycombinator.com and list the titles of the top 5 posts."

Screenshot of a permission dialog in Claude Desktop prompting the user to allow or deny the "Scrape as markdown" tool from Decodo.

Deny blocks the call, Always allow skips this prompt for every future call from the tool, and Allow once approves just this one. The Needs approval default produces this dialog when a tool runs, not just an entry on a settings page.

The approved call runs in the same turn, with the integration named above the answer.

Screenshot of Claude Desktop output displaying a numbered list of top 5 Hacker News post titles fetched using the Decodo integration.

The screenshot shows one run's actual output, not a fixed example. Hacker News's front page changes by the hour, so the titles here are unlikely to match what a later run returns. The structure of the response should stay constant: a real ranked list returned by the tool, not a paraphrase.

The full parameter reference is in the setup documentation.

Claude Code MCP setup

Claude Code MCP setup runs from the CLI, so you never have to hand-edit a config file unless you want to. One command adds a server, and one flag decides who else gets it.

A local stdio server takes the command you'd run in a shell, after a "–" separator:

claude mcp add decodo \
-e SCRAPER_API_TOKEN=<your_basic_auth_token> \
-e TOOLSETS=web,search \
-- npx -y @decodo/mcp-server@1.2.3

A successful add prints the name it registered and the file it wrote to:

Added stdio MCP server decodo with command: npx -y @decodo/mcp-server@1.2.3 to local config
File modified: /Users/you/.claude.json [project: /path/to/your/project]

The output confirms the config was written. It says nothing about whether the server runs, so check that separately.

Everything before "" belongs to Claude Code. Everything after it is the server's command line. A missing separator makes Claude Code read the server's flags as its own and reject them.

Remote servers skip the separator and take a transport flag instead:

claude mcp add --transport http sentry https://mcp.sentry.dev/mcp

Verify either one with claude mcp get <name>, which starts the server, runs the handshake, and prints the result:

decodo:
Scope: Local config (private to you in this project)
Status: ✔ Connected
Type: stdio
Command: npx
Args: -y @decodo/mcp-server@1.2.3
Environment:
SCRAPER_API_TOKEN=<your_basic_auth_token>
TOOLSETS=web,search
To remove this server, run: claude mcp remove decodo -s local

Connected means the process started and answered. Connected doesn't mean the credentials are valid, and it doesn't mean the tools work, so continue checking. The Environment block echoes whatever you passed literally, including the token. Treat the output of this command as sensitive when you paste it anywhere.

Claude Code MCP server setup: Scopes and where config lives

Claude Code MCP server setup starts with scope, the setting worth deciding first. The default is the narrowest option, and the confirmation line names it without saying that it was a choice.

Claude Code scope

Where config is stored

Who gets it

When to use it

local (default)

~/.claude.json, keyed by project path

Just you, in the current project

Experiments, personal credentials

project

.mcp.json at the repo root

Every teammate who clones and approves

Servers the whole team needs

user

~/.claude.json, top level

Just you, in every project

Personal tools you want everywhere

A project-scoped server waits for approval before it starts. Until someone approves it, claude mcp get reports it as pending:

decodo:
Scope: Project config (shared via .mcp.json)
Status: ⏸ Pending approval (run `claude` to approve)
To remove this server, run: claude mcp remove decodo -s project

The approval step is the point of project scope. A cloned repository can define a server that runs any local commands, so Claude Code asks once per project before starting anything from a committed file.

Screenshot of a terminal prompt in Claude Code notifying the user that a new project MCP server ("Decodo") was found and asking for permission to run it.

The screenshot shows the actual prompt behind that pending status. Read the warning literally: trusting the server here doesn't skip approval for individual tool calls. Each one still needs its own approval.

Transports, headers, and auth

Use Streamable HTTP for anything remote. Use stdio for anything local.

The CLI still accepts the deprecated transport: claude mcp add –transport sse prints the same confirmation as any other add, with no warning. The deprecation is real, but the CLI says nothing about it. For a server that requires a static token, pass a header when you add it:

claude mcp add --transport http internal https://mcp.example.com/mcp \
--header "Authorization: Bearer $INTERNAL_MCP_TOKEN"

For OAuth servers, add them with no credentials and finish the flow with claude mcp login <name>, or open /mcp inside a session. A connected server with 0 tools almost always needs that step.

Screenshot of the /mcp panel in Claude Code, showing connected local MCP servers (Decodo), claude.ai connectors, and built-in tools.

The panel groups servers by scope: Local MCPs for this project, claude.ai for connectors managed elsewhere, and Built-in MCPs that come with the client. The decodo line matches the web and search toolsets from the add command above: 2 tools plus 5.

If your servers already work in Claude Desktop, claude mcp add-from-claude-desktop reads that config and offers them in a checklist on macOS and WSL. A name with a space in it still appears in that list, pre-checked like any other. The error arrives only after you confirm:

Successfully imported 1 MCP server to local config.
Could not import bad name test: Invalid name bad name test. Names can
only contain letters, numbers, hyphens, and underscores.

The valid servers are imported and the rest report one line each, so a bad name skips that entry rather than the whole import. Rename anything outside [a-zA-Z0-9_-] in the Desktop config before you start, or read that summary carefully.

The import command also needs a real terminal. If you run it with stdin redirected, the way a setup script does, it produces no output, no error, and no exit. Exclude it from anything automated.

What the client actually sends over the wire

Protocol revisions matter when you build a server or evaluate one, and your client may use a different one. Check it by pointing the client at a server that logs every frame.

A minimal stdio server answers that question in one run. The server appends each inbound message to a file, and it echoes the client's protocol version back so the handshake always completes. The echo suits a probe and would be wrong in a real server:

// probe.js - logs every JSON-RPC frame, then answers the handshake.
const fs = require('fs');
const log = (dir, obj) =>
fs.appendFileSync('frames.log', dir + ' ' + JSON.stringify(obj) + '\n');
const send = (obj) => process.stdout.write(JSON.stringify(obj) + '\n');
let buf = '';
process.stdin.on('data', (chunk) => {
buf += chunk.toString();
let i;
while ((i = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, i).trim();
buf = buf.slice(i + 1);
if (!line) continue;
let msg;
try { msg = JSON.parse(line); } catch { continue; }
log('>>IN', msg);
if (msg.method === 'initialize') {
send({ jsonrpc: '2.0', id: msg.id, result: {
protocolVersion: msg.params.protocolVersion,
capabilities: { tools: {} },
serverInfo: { name: 'probe', version: '0.0.1' } } });
} else if (msg.method === 'tools/list') {
send({ jsonrpc: '2.0', id: msg.id, result: { tools: [] } });
}
}
});

Register it with claude mcp add probe – node ./probe.js, run claude mcp get probe, and read the log. Every run opened with the same frame:

>>IN {"method":"initialize","params":{"protocolVersion":"2025-11-25",
"capabilities":{"roots":{"listChanged":true},"elicitation":{}},
"clientInfo":{"name":"claude-code","title":"Claude Code","version":"2.1.255"}},
"jsonrpc":"2.0","id":0}

The frame above is the stateful handshake, at revision 2025-11-25, from a build released well after the 2026-07-28 revision made the protocol stateless and removed initialize entirely. Setting MCP_SDK_GENERATION=v2 and MCP_PROTOCOL_NEGOTIATION=auto against an HTTP server did produce a server/discover probe with mcp-protocol-version: 2026-07-28. The run still finished on the legacy handshake, even when the test server advertised support for both revisions.

Those runs give 2 practical conclusions. If you write an MCP server today, target 2025-11-25 and keep the initialize path working, because the client you're using sends that revision. And if you buy one, the vendor's claimed spec revision tells you less than a 5-minute frame log does.

Claude Code also runs as a server. claude mcp serve returned 30 tools and 119,069 bytes of schema on build 2.1.216, about 6 times the schema of a focused data server with the same tool count. That number changes with the client. An older 2.1.108 on the same machine returned 26 tools and 80,859 bytes, so measure your own build before you nest one Claude instance inside another workflow.

Managing large tool sets and the context they cost

Every tool that a connected server exposes has a name, a description, and a JSON Schema, and the model reads that text to choose between tools. Cost scales with tools exposed, not tools used.

The Decodo server makes this measurable, because its TOOLSETS variable controls which groups load. Measuring the serialized tools array from tools/list per toolset, on version 1.2.3, gives these numbers:

Decodo TOOLSETS value

Tools returned

tools/list bytes

web

2

1,299

search

5

3,999

ai

3

1,771

social_media

8

3,545

ecommerce

12

9,333

all 5 (the default)

30

19,943

Leaving TOOLSETS unset loads the full set, 30 tools in that version. Naming web, search instead reduces the schema payload by about 73%. If your workflow never scrapes Amazon or TikTok, it loses nothing it uses.

Claude Code limits this on its own: tool search is on by default, so MCP tool definitions are loaded on demand and aren't included in every request. Tool search is a client behavior, not a protocol guarantee, and Claude Desktop, a custom agent, or an older build won't necessarily do it. Scope the toolset anyway.

Output is the other half of the cost, and it has a fixed limit. Claude Code caps MCP tool results at 25,000 tokens by default through MAX_MCP_OUTPUT_TOKENS, which the Claude Code MCP reference documents alongside the other timeout and transport variables. Past that limit, Claude Code tells you rather than truncating silently. A probe tool asked for 400,000 characters returned this instead:

Error: result (400,000 characters across 1 line) exceeds maximum allowed
tokens. Output has been saved to /Users/you/.claude/projects/<project>/
<session>/tool-results/mcp-bigsrv-emit-1788324612809.txt.
Format: Plain text
Use offset and limit parameters to read specific portions of the file...

A 40,000-character result returned inline, and Claude Code wrote a 90,000-character result to disk, so the character count that crosses the limit depends on how your content tokenizes.

The variable moved in one direction only. Setting it to 500 sent that same 40,000-character result to disk, so lowering the cap works. Raising it to 100,000 and then 200,000 still sent 60,000-character and 90,000-character results to disk, exactly as at the default. If you need a larger result to arrive complete, set the per-tool anthropic/maxResultSizeChars annotation on the server rather than changing the client environment.

Writing to disk has 2 consequences worth planning for. The model may read that file back in chunks and spend more context than a plain truncation would have. And that file goes under your project directory, so customer data in the payload is now on disk.

Ask the server to trim instead. The Decodo tools accept a tokenLimit argument, and in testing it trimmed exactly: a value of 2000 returned 2000 characters of payload. Read it as a character budget rather than a token count, and check the figure against your own content.

Scoping and trimming work with what both clients give you today. Code execution removes the problem outright: the agent writes code that calls the servers, so schemas and intermediate results never reach the model's context. One published workflow dropped from 150,000 tokens to 2,000. It's the same tradeoff as picking a CLI script over an MCP server in the first place, covered in MCP vs CLI.

That pattern is an architecture for agents you build yourself, not a setting in either client. The protocol is moving in the same direction, with progressive discovery named on its roadmap, where clients load a server's tools as they need them instead of taking the whole catalog at the start.

When a tool call succeeds and the data is wrong

MCP has an error channel, and a page that refuses you doesn't reliably use it. That gap is the expensive part of connecting a web data server, and it stays invisible until you look at the raw result.

A transport failure that never reaches the target is marked as an error. Calling scrape_as_markdown against a domain that doesn't resolve returns this:

{
"content": [
{ "type": "text",
"text": "Scraper API request failed (400): Request processing failed" }
],
"isError": true
}

The model receives isError: true and can react to it. The same tool against a Cloudflare-protected category page, with default settings, returned a block page instead:

Please enable cookies.
# Sorry, you have been blocked
## You are unable to access g2.com
## Why have I been blocked?
This website is using a security service to protect itself from online attacks...

That result had no isError field at all. At the protocol level, it's a successful tool call. A model asked for a summary can summarize the block page instead.

Measure how often a block page arrives rather than assuming it's rare. Across 28 runs against 6 pages, the 2 open pages and 2 of the 4 protected ones returned real content every time. A third protected page failed with an error on all 4 of its runs, with isError: true and a 49-character message. The fourth returned a block page as ordinary content on 6 runs out of 8, and gave an error on the other 2.

That fourth page is the whole problem. A refusal reached the model as content repeatedly, with nothing in the result marking it as a failure. Most pages never behave this way, and the rarity is exactly why it surprises people. You check the error field; it works for weeks, and then one target puts a block page into a summary with no error.

Turning on browser rendering changed the result on this target. The same call with jsRender: true returned the real category page:

* [Home](https://www.google.com/url?q=http://docs.google.com/&sa=D&source=docs&ust=1772802610260273&usg=AOvVaw2e81jL5P9Y1Y3-s64cE7mI)
* [Leave a Review](https://www.google.com/url?q=http://docs.google.com/review&sa=D&source=docs&ust=1772802610260383&usg=AOvVaw0_d70rBqB00mGf7kR_npxC)
* Browse
* Top Categories
* [AI Chatbots Software](https://www.g2.com/categories/ai-chatbots)
* [CRM Software](https://www.g2.com/categories/crm)

Geo routing is the second control, and each request exited in the country it asked for against an IP echo endpoint. geo: "United States" came from Los Angeles on those runs, Germany from Berlin, and Japan from Tokyo. The test specified a country, so the exit city varied within it. Geo routing matters when a target serves different inventory or pricing per country.

Those findings give you 4 defaults, and they apply to web data MCP servers generally, not only this one.

  • Check for an endpoint before you scrape anything. Asked to list the top Hacker News posts, Claude skipped the scrape tool, found the public Firebase API, and called that instead. The API was the cheaper call, and it returned 4,501 bytes of structured JSON with a title and score on every item, against 34,384 characters of markdown for the same front page. A tool that renders pages is worth using on targets that block you or publish nothing structured, and where an endpoint exists, use it instead.
  • Turn on rendering for protected targets. A blocked page is easy to fetch and dangerous to trust. Server-side rendering plus a residential exit changed the response here. The Web Scraping API and residential proxies do that job underneath a server like this one. Rendered and premium requests bill at a higher rate than plain ones, which is why they're per-call switches rather than a global default.
  • Ask the model to verify before it summarizes. A prompt line as short as If the page mentions being blocked, verification, or enabling cookies, say so and stop converts a silent failure into a visible one.
  • Retry a failure before you trust it. Most pages answered the same way on every run. The ones that vary can give you a different result each time: one run of the same rendered request returned a JavaScript TypeError string as tool text, and the next run returned the page. Error text from a tool isn't a stable interface, so branch on the structure of the result rather than on the wording.

For a wider comparison of what different servers give an agent, the guide to the best MCP servers for AI workflows covers the categories worth connecting.

Custom implementations: Your own scripts as Claude MCP tools

An internal script becomes a Claude MCP tool once something answers tools/list and tools/call on its behalf. The wrapper needs 3 things: a JSON Schema for each input, a timeout, and a mapping from your exceptions to isError. If you need procedure rather than connectivity, the comparison of Claude Skills and MCP servers covers which one does which job.

For servers that need raw JSON rather than flags, claude mcp add-json takes the whole entry:

claude mcp add-json reporting \
'{"type":"stdio","command":"/usr/local/bin/python3",
"args":["/opt/tools/reporting_mcp.py"],
"env":{"DB_DSN":"${REPORTING_DSN}"}}'

${REPORTING_DSN} in the config, rather than the value itself, keeps the secret out of the file you commit. Claude Code expands shell-style variables in commandargsenvurl, and headers, and it reads them from the environment it was launched in. ${VAR:-default} supplies a fallback when the variable is unset.

One expansion detail costs real debugging time. In the build tested, ${CLAUDE_PROJECT_DIR} inside args didn't expand, and the server failed with CONNECTION_CLOSED: Connection closed. An ordinary variable in the same file expanded correctly. Presetting CLAUDE_PROJECT_DIR in the shell before launch made the same config connect, so the placeholder resolves only against variables that already exist in the launching environment.

Read the value inside your server instead, because Claude Code injects it into the child process either way:

import os
project_dir = os.environ["CLAUDE_PROJECT_DIR"] # injected by Claude Code, no expansion needed

claude mcp get prints the config unexpanded, so ${VAR} appears literally whether or not it resolved. Log the value from inside the server when you need to confirm it.

Production considerations: Teams, security, and CI

Setup stops being a solo activity the moment a config file enters a repository, and 2 risks arrive with it. You handle both when you set it up rather than later.

The first is supply chain. An npx -y <package> line resolves to whatever version is current at launch, so the code Claude runs tomorrow need not be the code you reviewed today. The risk has already happened twice.

An npm package named postmark-mcp released a version that added a hidden BCC recipient to every email the agent sent, documented in a published malware report. The widely used mcp-remote bridge had a critical remote-code-execution flaw tracked as CVE-2025-6514. Pin the version, and review the diff before you move the pin.

The second is credentials. An MCP server runs with your permissions and can hold a token that reaches production data, so give it the narrowest key the job needs.

The quick-start configs keep the token inline for speed. The config below passes a code review, with a pinned version, a scoped toolset, and no secret in the file:

{
"mcpServers": {
"decodo": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@decodo/mcp-server@1.2.3"],
"env": {
"SCRAPER_API_TOKEN": "${DECODO_SCRAPER_TOKEN}",
"TOOLSETS": "web,search"
},
"timeout": 120000
}
}
}

Commit the config as .mcp.json. Every teammate then gets the same server after one approval prompt, while the token stays in each person's shell or secret manager.

Brief the team on one behavior before they see it. If a teammate never exports DECODO_SCRAPER_TOKEN, Claude Code passes the literal ${DECODO_SCRAPER_TOKEN} text through to the server.

The server starts on it, and claude mcp get reports a green Connected. The failure appears only on the first real tool call, as Scraper API request failed (401): Authentication failed. Tell people to make one call before they trust the check mark.

Local and user scopes stay on one machine, so a team server belongs in project scope.

For CI and other non-interactive runs, no one can answer the trust prompt, so pass the server set explicitly. --mcp-config points at a JSON file, --strict-mcp-config ignores every server not in it, and --allowedTools names the exact tools that may run. A headless call returns its result and never prompts when the tool is allowlisted. The same call without the tool is refused rather than left waiting.

Apply the same review to web access for AI agents that you would apply to database access, because an outbound fetch is a privilege too.

Troubleshooting Claude MCP: the errors everyone hits

Claude Code prints a different string for each failure class, and the string tells you which one you have.

One habit is worth more than the list itself. Across everything tested here, a broken setup usually reported success or said nothing at all.

A server holding no credentials showed Connected, a deprecated transport was accepted without comment, an output limit raised past its maximum was read and ignored, and a refusal from a target arrived as ordinary content. A green status here isn't evidence that anything works. Make one real call and read what it returns.

Match yours below before changing anything.

  • ENOENT: Executable not found in $PATH. The command doesn't exist where Claude looks. Check that which node returns a path at all before anything else, because a missing Node install and a hidden one produce this same error. Desktop apps generally don't read ~/.zshrc, so a version managed by nvm can be invisible to them. Put the absolute path from which node in command.
  • -32000: Connection closed. The process started and exited. Run the exact command after "" in your own shell. If it fails there, the problem is the server, the package, or an unexpanded variable, not Claude.
  • ConnectionRefused: Unable to connect. Nothing is listening at that URL. Check the port and whether the server is running at all.
  • Connected, but 0 tools. Authentication is pending. Open /mcp and complete the flow, or run claude mcp login <name>.
  • Pending approval. A .mcp.json server is waiting for the trust prompt. Start an interactive session and approve it, or reset earlier choices with claude mcp reset-project-choices.
  • Tool call timed out after Ns. The per-server timeout in .mcp.json is a fixed limit on a single call, and it appears as MCP server "name" tool "toolname" timed out after 5s. Raise it for genuinely slow work like a rendered scrape, or lower it to fail fast.
  • The server starts on macOS and fails on Windows. npm installs command-line tools as .cmd shim files, which can't always be spawned directly. Wrap the command as cmd /c npx -y <package> outside WSL.
  • The server works in one directory and not another. Local scope is keyed by project path. Run claude mcp list from the directory you're actually working in, or move the server to user scope.

The usual advice says any stray line on stdout corrupts the JSON-RPC stream and kills the connection. Some servers wrote plain text to stdout alongside real frames, and even bare JSON objects. They still connected on every run, because unparseable lines were skipped.

Keep logs on stderr anyway, since the specification asks for that and other clients are stricter. Stdout noise is unlikely to be the cause of the failure you're investigating.

Final thoughts

Use 2 defaults in every future setup: Streamable HTTP for anything remote, and project scope with a pinned version for anything a team shares. Scope your toolsets from the start, because tool count is the part of the context cost that you control in advance.

Treat a tool result as unverified content rather than an answer, since a block page can arrive with no error flag and look exactly like data. The server you connect decides what live data means for your agent, so judge it on how reliably it returns real pages and not on how many tools it lists. Point the frame probe at any server you're evaluating and read the handshake yourself before it reaches production.

The next step takes one run: point the setup above at the targets you actually care about and read the result. Pages that return clean markdown need nothing more than what you have configured now. A block page tells you which server you actually need.

Set it up once, use forever

One Decodo config entry gives Claude Desktop or Claude Code a working scraping tool, ready whenever a task needs live data.

Share article:

About the author

Justinas Tamasevicius

Director of Engineering

Justinas Tamaševičius is Director of Engineering with over two decades of expertise in software development. What started as a self-taught passion during his school years has evolved into a distinguished career spanning backend engineering, system architecture, and infrastructure development.

Connect with Justinas via LinkedIn.

All information on Decodo Blog is provided on an as is basis and for informational purposes only. We make no representation and disclaim all liability with respect to your use of any information contained on Decodo Blog or any third-party websites that may belinked therein.

Frequently asked questions

How do I connect an MCP server in Claude Desktop?

Open Settings > Customize > Connectors and click Add to paste a remote server's HTTPS endpoint, or use Settings > Extensions to install a packaged .mcpb bundle. For anything else, open claude_desktop_config.json from Settings > Developer > Edit Config and restart the app fully.

Does Claude Desktop support remote MCP servers?

Yes. Claude Desktop connects to remote servers over Streamable HTTP through Settings > Customize > Connectors, where clicking Add opens a dialog for the HTTPS endpoint and browser authentication. Local servers use stdio instead and run as child processes defined in the config file.

How do I add an MCP server to Claude Code CLI?

Run claude mcp add <name> – <command> for a local stdio server, or claude mcp add –transport http <name> <url> for a remote one. Add -s project to share it through a committed .mcp.json. Confirm with claude mcp get <name>, which runs the handshake and prints the status.

How to test if MCP is working?

Run claude mcp get <name> to see the status, then make one real tool call. Connected only means the process started and answered. A server holding no credentials can still report Connected. The failure appears on the first call, so make that call your test.

Why can't Claude connect to my MCP server?

Read the error string first. ENOENT means the command isn't where Claude looks, so check which node returns anything at all, then use an absolute path. Connection closed means the process started and exited. Pending approval means a project server waits for the trust prompt.

Are MCP servers a security risk?

Yes, in the same way any dependency is. A server runs with your permissions and can hold your tokens, and npx -y can pull a new version on launch. Documented incidents include CVE-2025-6514 in the widely used mcp-remote bridge, and postmark-mcp, which added a hidden BCC recipient to every email it sent. Pin versions and scope tokens narrowly.

Is Claude MCP free?

The protocol is an open standard and costs nothing to use, and many MCP servers are open source. You pay for whatever the server wraps: a data API, a SaaS seat, or the model usage itself. The server used throughout this guide has a free plan. Claude plan limits apply to the conversation regardless of which servers you connect.

Do MCP servers use context?

Yes. Every tool that a connected server exposes gives the model its name, description, and JSON Schema. The Decodo MCP server scopes this with one variable: 19,943 bytes of schema on version 1.2.3 with all toolsets on, and 1,299 bytes with one toolset on. Claude Code defers these lookups by default, but other clients may not.

What is a MCP for Claude?

For Claude specifically, an MCP server exposes a set of tools, such as web search, file access, or a scraping API, that Claude can discover and call during a conversation without a developer writing custom integration code for each one. Claude Desktop, Claude Code, and the API all support MCP servers, so the same server, whether local or hosted, works across every surface where Claude runs. It's effectively a standard plug that lets Claude reach whatever service sits behind it, rather than a one-off wire built for a single app.

How is MCP different from an API?

An API is a general contract for two pieces of software to exchange data, and each one typically comes with its own endpoints, authentication, and response format that a developer has to learn individually. MCP is a protocol built specifically for AI models, and it standardizes how a model discovers available tools, their parameters, and how to call them, so the same client logic works against any MCP server. An MCP server often wraps one or more APIs internally – Decodo's own MCP server sits in front of the Web Scraping API, for instance – but Claude interacts with it through one consistent interface rather than a custom integration per endpoint.

Top 10 MCPs for AI Workflows in 2026

MCP has shifted from niche adoption to widespread use, with major platforms like OpenAI, Microsoft, and Google supporting it natively. Public directories now feature thousands of MCP servers from community developers and vendors, covering everything from developer tools to business solutions.

In this guide, you'll learn what MCP is and why it matters for real-world AI agents, which 10 MCP servers are currently most useful, and how to safely choose and combine MCPs for your setup.

Claude Skills vs. MCP: What's the Difference and When To Use Each

Claude Skills and MCP both extend what Claude can do, but they solve different problems. A Skill packages knowledge and procedure into a folder Claude reads when a task calls for it. MCP is a protocol that connects Claude to live tools and data. This guide gives you plain definitions, an honest comparison, and a working example of both on the same task.

Cursor AI

Using Cursor AI To Build a Web Scraper: From Setup to Production With Decodo

Cursor AI is a code-aware IDE that generates, debugs, and refines scraper code through natural language, advancing AI-assisted scraping from concept to production. Building scrapers by hand means dealing with selector breakage, anti-bot walls, and proxy rotation logic that compounds every time a target site changes. This article covers setup, Cursor rules, scraper types, Decodo MCP integration, and project maintenance.

© 2018-2026 decodo.com (formerly smartproxy.com). All Rights Reserved