MCP server security: what to check before you connect one
Stefan-Iulian Tesoi · · 7 min read

Check four things: what each tool can change, how the server authenticates and how narrowly its tokens are scoped, whether its tool descriptions can change after you approved them, and what it records about each call. A server exposing broad write tools behind one long-lived token is the risk, however reputable its publisher.
MCP server security is mostly decided before the first call, in a configuration a developer pasted in a minute.
What makes MCP server security different from API security?
The caller is a model, and the model reads the server's own text as part of its instructions. With an ordinary API, a developer reads the documentation and writes code that calls named endpoints, and nothing the vendor writes later changes what that code does.
With the Model Context Protocol, the client puts every tool's name and description into the model's context, and the model decides which tool to call and with what arguments. The server's descriptions are inputs to that decision. So is everything else the agent has read in the session, which is why prompt injection through tickets and issues can end in a tool call nobody asked for.
The MCP specification's tools page is plain about where this leaves the user. There "SHOULD always be a human in the loop with the ability to deny tool invocations", and clients "MUST consider tool annotations to be untrusted unless they come from trusted servers". The protocol does not decide which servers are trusted; the team connecting them does.
So "are MCP servers safe" has no general answer. A given server is as safe as its widest tool, its longest-lived token and its least reviewed description. The background is in what MCP is and why it matters for backlogs.
Four things to check before you connect a server
Each has a good answer and a red flag, and one red flag is reason enough to wait.
| Check | What good looks like | Red flag |
|---|---|---|
| What each tool can change | Narrow tools, reads separated from writes | One run_sql or shell tool taking free text |
| Authentication and token scope | OAuth with short-lived tokens bound to the server | A personal access token pasted into a config file |
| Whether descriptions can change | A pinned version you can diff | npx -y some-server fetching the latest release on every start |
| What it records | A server-side log of each call and who made it | Nothing outside the developer's terminal |
What each tool can change. Read the tool list before the marketing page. Narrow tools such as create_issue can be allowed or refused one by one; a tool that runs arbitrary SQL is limited only by the database user behind it. Annotations such as destructiveHint are claims the server makes about itself, useful for a prompt and worthless as a control, the same distinction behind what permissions a coding agent should have.
Authentication and token scope. For remote servers, MCP OAuth is the spec's answer. The authorization specification builds on OAuth 2.1 and requires the client to name the server a token is for, so a token issued to one server cannot be replayed against another. A server must also "not accept or transit any other tokens", which rules out passing your token straight through to an upstream API. Then ask the two questions OAuth does not answer: what is the widest thing the token permits, and how long does a stolen copy keep working?
Whether descriptions can change. A description you approved in March is not necessarily the description the model reads in October. Pin the version, and treat an upgrade like any dependency upgrade: read what changed.
What it records. The spec tells clients to log tool usage, but a client's log lives on one laptop. A server-side record of each call, with the tool, the caller and the outcome, is what an investigation needs when that laptop is the thing being investigated.
Laimonade's own server is one worked example of these answers. People sign in with OAuth and PKCE and choose which projects to grant. Access tokens last one hour, refresh tokens rotate on use, and removing someone from a project drops it from their connection at the next refresh. Headless use gets a project API key that cannot reach any other project. Each of its 58 tools is declared Read, Write or Destructive in the MCP tool reference, six of them destructive, and every call is logged with the tool, the project, the person whose agent made it and the outcome.
What is tool poisoning, and how does it work?
Tool poisoning is hiding instructions for the model in a tool's description, where the user rarely looks and the model always does. Invariant Labs published the attack on 1 April 2025. Their demonstration was a tool presented as adding two numbers, whose description told the agent to read ~/.cursor/mcp.json and ~/.ssh/id_rsa and pass their contents back in an argument. The user saw "add". The model saw the instructions.
The same post named two variants:
- Rug pull. A server changes a description after the client approved it, so a server that was clean on the day of review stops being clean later.
- Shadowing. A malicious description changes how the agent uses a different, trusted server. In the demonstration, a poisoned
addtool redirected email meant for a trustedsend_emailtool.
MCP tool poisoning is prompt injection that arrives through the tool list rather than through data. The defences: fewer servers, pinned versions, a client that shows full descriptions, and no server holding a credential it does not need.
Local or remote: which is the safer default?
Neither by default, because they fail in different ways. A local MCP server speaking stdio runs as a process under your account. It can read every file you can, including SSH keys, and the spec says stdio servers should take their credentials from the environment rather than use its OAuth flow. Its security is the security of your machine and of the package you installed.
A remote server cannot read your disk. It sees only what the agent sends it, and it holds tokens for whatever it connects to. Its risks are token theft, the vendor's infrastructure, and data leaving your network.
A workable rule: local servers for tools that need local files and no third-party credentials, remote servers for hosted services, where OAuth, short token lifetimes and revocation at the source are available.
How do you review the servers already connected?
List them, then remove every server nobody used last month. In Claude Code, claude mcp list prints the configured servers and /mcp shows their status. A repository can also carry a .mcp.json file; Claude Code asks before using a server defined there, but every clone inherits the file.
A review that fits in an afternoon:
- Collect every server from user configs, project
.mcp.jsonfiles and chat connectors. - For each one, record its owner, its pinned version, its credential type and that credential's scope.
- Remove the ones nobody can name a use for.
- Rotate every long-lived token that has sat in a config file.
A team of ten developers who each added three servers has thirty trust decisions that nobody reviewed together. The broader frame for that review is in what a coding agent can reach.
Frequently asked questions
Should each developer pick their own MCP servers?
Not for servers that reach company systems. A server one developer connects acts with that developer's access, and nobody else knows it exists. Keep an approved list for anything touching company data, reviewed like any third-party integration, and leave individual choice to servers that read only local, non-sensitive files.
Does the MCP specification require authentication?
No. The specification makes authorization optional. When a server on an HTTP transport supports it, the server should follow the spec's OAuth 2.1 flow, with tokens bound to that server. A local server over stdio should take credentials from its environment instead. An unauthenticated remote server is permitted by the protocol, so the check falls to whoever connects it.
How do you revoke an MCP server's access?
At the place that issued the credential, not in the client. Deleting a server from a client's configuration leaves its token valid wherever it was issued. Revoke the OAuth grant or API key at the provider first, then remove the configuration. In Laimonade, disconnecting a connection revokes all of its live tokens, and its next request is refused.