Skip to content
Note

How to check an MCP server before your agent uses it

Fake MCP servers on npm and MCP Inspector bugs that ran code on connect. What an MCP server can reach on your machine, and how to vet one before you add it.

· 5 min read · SetupBay

Adding an MCP server takes one line of config, which is why it’s easy to forget what that line does. October gave a few reminders.

  • A fake “security” server. Elastic Security Labs traced a campaign posing as a Web3 accelerator. Its @apexacc/audit package offered an MCP server with 18 audit tools. Every one returned made-up findings, and the package’s bootstrap fetched commands from the attacker’s server and tried to register itself in Claude Desktop, Cursor and Windsurf. Its sister package wrote a remote MCP server into its agent’s config, so the attacker could feed instructions and tool descriptions into every request.
  • More malware on npm. GitHub flagged @atlisp/mcp 1.12.14 as malware on 28 October, with the usual advice: treat any machine that ran it as compromised and rotate every secret from a different computer.
  • Bugs in the tools that connect. On 21 October the MCP Inspector, the tool many people use to look at a server before trusting it, published fixes for two flaws where a malicious remote server could run commands on your machine during the OAuth sign-in. The same kind of bug in opencode on Windows was found by Pillar Security and fixed in 1.18.33. As @chevonphillip summed it up, connecting to an MCP server can be enough.

None of this means MCP is broken. It means an MCP server is a dependency with an unusually direct line to your model, and it deserves the checks you’d give any other.

What a server can reach

There are two kinds, and they reach different things.

A local server (stdio) is a program your agent starts on your machine, as you. It can read anything you can read, and its environment often carries an API token you handed it. A config line like npx -y some-mcp-server downloads and runs whatever version is newest each time the agent starts, install scripts included.

A remote server (HTTP) runs somewhere else, so it can’t read your files directly. But its tool names, descriptions and instructions go straight into the model’s context, and it sees whatever the agent sends it. Elastic’s write-up describes exactly that: a server that can see the data submitted and steer the agent’s next actions. Sign-in adds one more path, because the server chooses the URLs your client opens during OAuth, which is where the Inspector and opencode bugs lived.

Before you add one

1. Check who publishes it and how old it is. With Node.js packages, this reads the registry without installing anything:

npm view some-mcp-server time.created time.modified maintainers repository.url

A package a few days old, a maintainer with no history, or a repository link that doesn’t match the name is a reason to stop. Our dependency cooldowns loadout gives your agent the same check for any new package, plus release-age limits for npm, pnpm, Bun and uv.

2. Read the code you’ll run, not the README. npm pack downloads the exact tarball without running its install scripts:

npm pack some-mcp-server@1.4.2
tar -tzf some-mcp-server-1.4.2.tgz
npm view some-mcp-server@1.4.2 scripts

Look for preinstall or postinstall scripts, binaries downloaded at start-up, and anything that writes to another tool’s config. The Apex packages downloaded their real programs at start-up and wrote themselves into other tools’ configs.

3. Pin the version in your config. Write "args": ["-y", "some-mcp-server@1.4.2"] rather than the bare name, or uvx some-mcp-server@1.4.2 for Python servers. A pinned server only changes when you change it. Our vetting loadout has your agent pin a skill, plugin or MCP server by exact version or commit, prove the checkout matches, scan every file and write a report for you to approve.

4. Read its tools before the model does. Use your client’s server view (/mcp in Claude Code) or the MCP Inspector to list every tool, its description and the server’s instructions. Read them as you’d read a skill. The hiding tricks in our note on hidden instructions in agent skills work just as well in a tool description. If you use the Inspector, update it first: 1.0.3 or later on the 1.x line, 2.12.0 or later on 2.x.

5. Give it the least it needs. One project, read-only where the server supports it, and a token scoped to that job rather than your personal one. Our Supabase loadout is a worked example: the hosted Supabase server scoped to one development project, in read-only mode, with only the database and docs tools turned on.

Watch for servers you didn’t add

Several of this year’s attacks didn’t ask you to install anything. They added a server to a config file you already had. The Apex bootstrap went after Claude Desktop, Cursor and Windsurf configs. The testmetest worm planted a .cursor/mcp.json in 42 repositories, and a planted AGENTS.md in a Downloads folder told Codex to add one to ~/.codex/config.toml.

So check the files themselves now and then. These only read, and don’t start any server:

jq '{user: (.mcpServers // {} | keys), projects: (.projects // {} | map_values(.mcpServers // {} | keys))}' ~/.claude.json
jq '.mcpServers // {} | keys' .mcp.json ~/.cursor/mcp.json .cursor/mcp.json ~/.gemini/settings.json 2>/dev/null
grep -n '^\[mcp_servers' ~/.codex/config.toml

On a Mac, Claude Desktop keeps its list in ~/Library/Application Support/Claude/claude_desktop_config.json. Every agent’s project and user MCP files are in our agent file table.

Project files deserve extra care. In an interactive session Claude Code asks before connecting the servers in a repo’s .mcp.json, and a cloned repo can’t approve its own servers. But claude -p and the Agent SDK connect them without asking. A disabledMcpjsonServers entry rejects a server by name in every session type. Our note on Claude Code hook security covers the rest of what a cloned repo can run, and the hook audit warns when project settings switch on every .mcp.json server at once.

If you’ve already run a bad one

Follow the advisory’s advice rather than just deleting the package. Remove the server from every config above, rotate the tokens it could see from a different machine, and check for anything it left behind. For a malware advisory like @atlisp/mcp, GitHub’s wording is the right default: assume the machine is compromised.

The habit that covers most of this is a small one. Before a new server goes into a config, spend five minutes on who wrote it, what it runs and what it will tell your model. It’s far less work than the cleanup.