Is This MCP Server Safe? How to Check Before You Connect
A practical MCP server security check: tool poisoning, prompt injection, scopes, read-only hints, vendor verification, npm supply chain, and a checklist.
TL;DR. An MCP server is code you let an AI assistant call on your behalf, so "is it safe" depends on who wrote it, what it can touch, and what it tells the model. Before you connect one, read its tool list, check who runs it, give it the narrowest scope you can, pin the version if it runs locally, and keep your client's approval prompts on. The checklist at the end takes about ten minutes per server.
What you are trusting when you connect
An MCP server does three things that matter for security. It sends your assistant a list of tools with text descriptions, it runs those tools when the model asks, and it returns results that go straight back into the model's context. The MCP specification warns about the last two: tools "represent arbitrary code execution and must be treated with appropriate caution", and descriptions of tool behavior "should be considered untrusted, unless obtained from a trusted server" (spec, Security and Trust & Safety).
So the risk comes from three places: the people behind the server, the access you grant it, and the text it feeds your model. The sections below take them one at a time.
Tool poisoning and prompt injection
The model reads every tool description the server sends. You usually do not. That gap is what tool poisoning exploits.
Invariant Labs named and described the attack in April 2025: an add tool whose description, invisible in the client's UI, told the model to read ~/.cursor/mcp.json and the user's SSH private key and pass them along as a "sidenote" parameter, while explaining the arithmetic to keep the user from noticing (Invariant Labs). Rug pulls and shadowing are variants of the same attack. In a rug pull, the server shows a clean description when you approve it and changes it later. In shadowing, a malicious server's description tells the model how to use a different server's tools, for example by redirecting every email sent through a trusted mail tool to an attacker's address.
Prompt injection is the wider version of the same problem. Any text a tool returns, whether a web page, an issue comment or an error message, can contain instructions, and a model may follow them. The server does not have to be malicious; a fetch tool that reads an attacker's page is enough.
What you can do before connecting:
- Read the tool descriptions, not the README. For a remote server, call
tools/listyourself (snippet below). mcp.tc listings show each tool's name and a short summary, so you can see what a server exposes before installing it. - Look for instructions aimed at the model: "before using this tool", "always", "do not tell the user", references to other tools or to files outside the tool's purpose. Pointing to another tool on the same server is normal (both of Context7's tools tell the model which one to call first); the red flags are references to other servers' tools, files outside the tool's job, or secrecy.
- Run a scanner. Agent Scan (formerly Invariant's mcp-scan, now published by Snyk as
snyk-agent-scan) finds the MCP servers in your client config files and checks their tools for prompt injection, tool poisoning, tool shadowing and toxic flows. To read the tool descriptions it starts the local servers in your config and connects to the remote ones, asking first in an interactive run, so scan configs you don't trust inside a container or VM. It needs a Snyk account and API token, and it sends your server configs, tool names and descriptions to Snyk's Agent Scan API for analysis, with secrets redacted first. Pin its version like any other tool (0.6.8 was the current release on 4 October 2026):
export SNYK_TOKEN=your-snyk-api-token
uvx snyk-agent-scan@0.6.8- Connect fewer servers at once. Shadowing only works when a bad server sits next to a good one in the same session.
Permissions and scopes
The access a server gets is the blast radius if anything above goes wrong. The spec's security best practices describe the failure mode: a token with broad scopes such as files:* or admin:*, granted up front because the client asked for everything the server listed, lets an attacker who steals it reach unrelated tools and resources. The recommendation is a minimal initial scope and incremental elevation when a privileged tool is first used.
As a user you control two things.
For a remote server with OAuth, read the consent screen. If a server that summarizes issues asks for permission to delete repositories, close the tab. GitHub's own server (mcp.tc/i/github) signs you in with OAuth or a personal access token; if you use a token, make a fine-grained one limited to the repositories and permissions you need, not a classic token with everything.
For a server that takes an API key, make a key just for it, with the smallest role the vendor offers, so you can revoke it without touching anything else.
The spec also forbids token passthrough: servers "MUST NOT accept any tokens that were not explicitly issued for the MCP server". A setup guide that tells you to paste a token meant for a different service is a sign its authors have not read the authorization spec.
Read-only versus destructive: what the hints mean
Each tool can carry annotations that describe its behavior. The schema defines four, with these defaults:
{
"readOnlyHint": false, // true: the tool does not modify its environment
"destructiveHint": true, // false: only additive updates
"idempotentHint": false, // true: repeated calls have no extra effect
"openWorldHint": true // true: may interact with external entities
}Note the defaults. A tool that declares nothing is, by the spec's own reading, a non-read-only, possibly destructive tool. A server that marks a delete_file tool as readOnlyHint: true is lying, and nothing in the protocol stops it. The spec says clients "MUST consider tool annotations to be untrusted unless they come from trusted servers" (spec, Tools).
mcp.tc shows these as Read-only, Writes and Can delete next to each tool, with the same caveat on the page: they are hints the server declares, and your client decides whether to ask before running a tool. Use them to decide where to look. A server where every tool is read-only, like the two tools on Context7, has a small blast radius even if its descriptions were poisoned. A server with write and delete tools deserves a closer read of its descriptions.
The spec asks clients to "prompt for user confirmation on sensitive operations" and to "show tool inputs to the user before calling the server". Most clients do, and most let you turn it off per server. Leave it on for anything that can write.
Vendor verification, and what the mcp.tc checkmark does not mean
Who runs the server is the first thing to establish, because every other check assumes you are looking at the real thing.
For a remote server, check that the URL is on the vendor's own domain. api.githubcopilot.com is GitHub; github-mcp.example.io is someone else, however good the README looks. For a local server, check that the package is the one the vendor's repository names, scope and all.
mcp.tc puts a checkmark next to listings where we confirmed who runs the server. The verification page spells out what that covers: official servers of well-known vendors get it after a check by hand that the URL or code lives on the vendor's own domain or repository; anyone else proves control of the domain with a DNS TXT record. That is all it proves. We do not audit code, test tools, or check how a server handles your data, and a verified server can still have bugs or change its tools tomorrow. A listing without a checkmark is not suspect either; most owners have not asked for one. The server owners docs explain how the record works if you run a server yourself. Once a listing is verified, our automatic updates no longer change its URLs, package or vendor.
Supply chain for local servers
A remote server runs on someone else's machine. A local one runs on yours, with your user's privileges, and installing it usually means npx or uvx pulling a package and executing whatever it contains. The spec's own threat list for local servers starts with a malicious startup command in a client config and a malicious payload inside the server itself.
Both have happened. In September 2025, an npm package called postmark-mcp, published by a developer with no connection to Postmark, copied the official server's code in every version up to 1.0.15 (BleepingComputer) and then added one line in version 1.0.16 that BCC'd every outgoing email to the author's address. It had 1,643 downloads before its author deleted it from npm (The Hacker News). In June 2026, an unscoped mcp-server-github package squatted the name of the official @modelcontextprotocol/server-github, with a postinstall script that posted the hostname, working directory and Node version to a remote endpoint on every install (OSV MAL-2026-5479).
Practical defenses:
- Install the exact package the vendor names, including the npm scope.
@modelcontextprotocol/server-filesystemandmcp-server-filesystemare not the same thing. The Filesystem listing and every other local listing on mcp.tc names the package the vendor publishes. - Pin a version.
npx -y @scope/pkg@1.4.2instead ofnpx -y @scope/pkg, so a compromised release does not reach you on the next restart. npm's docs: installingname@version"will fail if the version has not been published to the registry", which is what you want (npm install). - Block install scripts.
postinstallis where the second attack above ran its payload. npm 12, released in July 2026, no longer runs dependencies' install scripts unless you allow the package, andnpxfollows the sameallow-scriptssetting; on older npm,npm install --ignore-scriptsskips them. Some legitimate packages need their scripts, so install with scripts off, read what the package wanted to run, then decide. - Prefer a sandbox. Run local servers in a container or as a restricted user, with only the directories they need. The Filesystem server takes its allowed directories as arguments; give it a project folder, never your home directory.
- Read the config line your client is about to add. The spec asks clients to show "the exact command that will be executed, without truncation" before a one-click install.
A quick manual check of a remote server
You can read a remote server's tool list from a terminal before adding it to a client. This sends initialize and tools/list over Streamable HTTP; a server that requires OAuth answers 401 on the first call.
URL=https://mcp.example.com/mcp
curl -s "$URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'
curl -s "$URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2025-06-18' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| sed 's/^data: //' | grep '^{' | jq '.result.tools[] | {name, description, annotations}'Many servers reply with an event stream (data: {...} lines); the sed and grep turn it back into plain JSON for jq. If the first reply carries an Mcp-Session-Id header (add -i to the first command to see the headers), send it back on the second with -H 'Mcp-Session-Id: ...': older servers that keep sessions need it.
If a description contains instructions for the model that have nothing to do with the tool's job, stop there.
The checklist
Who runs it
- The URL is on the vendor's own domain, or the package is the one the vendor's repository names, scope included.
- The repository has history, more than one contributor, and a release that matches the package version.
- The mcp.tc listing carries a checkmark, or you confirmed the vendor some other way. The checkmark is identity, not an audit.
What it can touch
- The scopes on the consent screen match what the tools do.
- API keys are created for this server only, with the smallest role available.
- It does not ask for a token meant for another service.
What it tells the model
- You read every description from
tools/list, not the README. - No description talks about other servers' tools, hidden parameters, or things to keep from the user.
- Tools that write or delete are marked that way, and your client prompts before running them.
- A scanner reports nothing on the config.
If it runs locally
- The version is pinned.
- You know what its install scripts do, or they never ran (npm 12's default, or
--ignore-scripts). - It runs in a container or as a restricted user, with only the directories it needs.
- You read the exact command your client will run.
After connecting
- Approval prompts stay on for write tools.
- When a server updates, you re-read its tool list. Rug pulls depend on nobody doing this.
- You remove servers you stopped using.
FAQ
Does a verified checkmark on mcp.tc mean the server is safe?
No. It means we confirmed who runs it, either by checking that an official vendor's server lives on the vendor's own domain or repository, or because the owner added a DNS TXT record on the server's domain. We do not audit the code or test the tools. The verify page says the same thing.
Can I trust readOnlyHint?
Only as far as you trust the server. The server declares the hint, and the spec requires clients to treat annotations as untrusted unless the server is trusted. A read-only hint from GitHub's official server means something; the same hint from an unknown package means nothing until you have read the code. And a tool with no annotations counts as writable and possibly destructive by default.
Is a remote server safer than a local one?
They have different risks. A remote server never runs code on your machine, but it sees every argument you send and holds a token for your account at the vendor. A local server runs with your privileges and can read whatever your user can, but needs no account and sends nothing anywhere unless its code does. For the same vendor, a remote server with narrow OAuth scopes is usually easier to reason about; for a tool that works on your files, a local server with a pinned version and a short directory list is fine. Remote vs local MCP servers compares the two in more detail.
Do I need to redo these checks when a server updates?
At least the tool list. A rug pull is a server that was clean at approval time and changed later, and the only defense is noticing. Re-run tools/list or a scanner after each version bump.
What if a server I use turns out to be malicious?
Disconnect it, revoke the token or API key you gave it, and rotate any secret it could have read; for a local server that includes SSH keys and anything in environment variables under your home directory. Then report it to the registry it came from and, if it is listed on mcp.tc, through the report link on the listing page.
Sources
The pages this article cites. We draft posts with AI from the sources each one cites and check them against those sources before publishing. Numbers about the directory come from mcp.tc’s own database.
- modelcontextprotocol.io/specification/2025-06-18/index
- modelcontextprotocol.io/specification/2025-06-18/server/tools
- github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-06-18/schema.ts
- modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices
- invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks
- thehackernews.com/2025/09/first-malicious-mcp-server-found.html
- www.bleepingcomputer.com/news/security/unofficial-postmark-mcp-npm-silently-stole-users-emails/
- osv.dev/vulnerability/MAL-2026-5479
- github.com/snyk/agent-scan
- pypi.org/project/snyk-agent-scan/
- docs.npmjs.com/cli/v12/commands/npm-install