# MCP security: a threat model before you connect an account

> Tool poisoning, rug pulls, prompt injection and malicious packages, with real incidents, and a checklist for vetting an MCP server first.

Source: https://cotlet.dev/blog/mcp-server-security

Published: 2026-10-05

Tool poisoning, rug pulls, prompt injection and malicious packages, with real incidents, and a checklist for vetting an MCP server first.

Cotlet team

Connect account

Password never stored

Session saved

MCP security comes down to one fact: an MCP server is code that sits between your AI agent and your accounts, and the model trusts whatever text that code hands it. The main risks are poisoned tool descriptions, servers that change after you approve them, prompt injection through tool output, sloppy token handling and plain malicious packages. You reduce them by vetting the server, granting the narrowest access, and keeping a human on every action that reaches other people.

We are writing this as a team that will hold sessions to other people’s accounts, so we had to think it through for ourselves first. Below is the threat model, the incidents behind it, and a checklist you can reuse.

## Is MCP safe?

The protocol is a pipe. It is as safe as the server on the other end, the client you run it in, and the permissions you grant. The spec says so itself: its [tools page](https://modelcontextprotocol.io/specification/latest/server/tools) asks for a human who can deny tool calls, and tells clients to treat tool annotations as untrusted unless they come from trusted servers.

The spec’s wording is **SHOULD**, not MUST: “there SHOULD always be a human in the loop with the ability to deny tool invocations.” Whether your client actually asks you before a tool runs depends on the client and on your settings. Many people turn auto-approve on in the first week.

## The MCP security risks, with real incidents

Each row is a class of attack that has already happened or been demonstrated in public. The OWASP [MCP Top 10](https://owasp.org/www-project-mcp-top-10/) (in beta as of October 2026) lists most of them.

| Risk | What happens | Public example |
| ---------------------------------- | -------------------------------------------------------------------------- | ----------------------------------- |
| Tool poisoning | Hidden instructions in a tool description steer the model | Invariant Labs, April 2025 |
| Rug pull | A server changes its tools after you approved it | Invariant Labs, April 2025 |
| Tool shadowing | One server’s description rewrites how the model uses another server’s tool | Invariant Labs, April 2025 |
| Prompt injection via tool output | Data the tool returns (an issue, an email, a page) carries instructions | GitHub MCP, May 2025 |
| Malicious package | The server itself does something you did not ask for | postmark-mcp on npm, September 2025 |
| Vulnerable client glue | A helper library runs commands from a server’s response | mcp-remote, CVE-2025-6514 |
| Broken isolation | A hosted server shows one customer’s data to another | Asana MCP, June 2025 |
| Token passthrough, confused deputy | A proxy forwards or obtains tokens it should not | MCP spec, security best practices |

### Tool poisoning, shadowing and rug pulls

In April 2025 Invariant Labs [showed](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks) an innocent `add` tool whose description told the model to read `~/.cursor/mcp.json` and `~/.ssh/id_rsa` and pass them along as a “sidenote”, and not to mention it. In their Cursor test the agent did exactly that. The user saw a tool that adds two numbers.

_The poisoned `add` tool from Invariant Labs’ tool poisoning write-up (April 2025), screenshot October 2026._

The same post described two cousins. **Shadowing**: a malicious tool’s description redirected every email sent through a separate, trusted `send_email` tool to the attacker. **Rug pull**: a server can change its tool descriptions after you approved them, so the review you did on day one says nothing about day thirty.

The lesson for **MCP tool poisoning** is uncomfortable: the attack works even if you never call the poisoned tool. Being loaded into the model’s context is enough.

### Prompt injection through tool output

In May 2025 Invariant [showed](https://invariantlabs.ai/blog/mcp-github-vulnerability) an attack on the official GitHub MCP server. An attacker opens an issue in a public repo. You ask your agent to look at open issues. The issue tells the agent to read your private repos and post what it finds in a pull request on the public one, and it does. Invariant stressed that this is not a bug in the server code. The server did what it was told.

Simon Willison [named the pattern](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/) the lethal trifecta: private data, untrusted content and a way to send data out. Any agent that has all three can be talked into leaking. MCP makes it easy to assemble all three by accident, one server at a time.

### Malicious MCP servers on npm and PyPI

The first **malicious MCP server** found in the wild was [postmark-mcp](https://thehackernews.com/2025/09/first-malicious-mcp-server-found.html), a copy of a legitimate email server published on npm. Version 1.0.16, released September 17, 2025, added one line: a BCC of every outgoing email to an address the author controlled. It had 1,643 downloads before it was pulled. Nothing in the tool descriptions looked wrong. The code did.

A local server runs with your user’s permissions. The spec’s [security best practices](https://modelcontextprotocol.io/specification/latest/basic/security%5Fbest%5Fpractices) give the example of a startup command that installs a package and then posts your SSH key to a remote host.

### Bugs in the plumbing

[CVE-2025-6514](https://jfrog.com/blog/2025-6514-critical-mcp-remote-rce-vulnerability/) (CVSS 9.6, July 2025) was in `mcp-remote`, a popular helper that connects local clients to remote servers. A malicious server could return a crafted authorization URL, and versions 0.0.5 to 0.1.15 passed it to the operating system. On Windows that meant running arbitrary commands. Fixed in 0.1.16\. You did not have to trust the server’s tools; connecting was enough.

### Hosted does not mean isolated

In June 2025 Asana [took its MCP server offline](https://bleepingcomputer.com/news/security/asana-warns-mcp-ai-feature-exposed-customer-data-to-other-orgs/) after finding a logic flaw that could show one organization’s data to another. Asana said about 1,000 customers were affected and found no sign it was exploited. A vendor’s own hosted server is not automatically safe. Tenant isolation is a property you have to check, not assume.

## What the spec’s security best practices say

The [MCP security best practices](https://modelcontextprotocol.io/specification/latest/basic/security%5Fbest%5Fpractices) page is written for people building servers and clients, but three items matter to anyone choosing one:

- **Token passthrough is forbidden.** A server “MUST NOT accept any tokens that were not explicitly issued for the MCP server.” A server that just forwards your token to an upstream API breaks audit trails and rate limits, and turns a stolen token into a key for everything behind it.
- **Confused deputy.** A proxy server that uses one static OAuth client for all users must ask for consent per client, or an attacker can send you a link and walk away with access to your account in the upstream app.
- **Scope minimization.** Start with low-risk read scopes and ask for more only when a privileged tool is first used. Wildcard scopes like `*` or `full-access` are listed as a common mistake.

What the page does not cover is the model itself: poisoned descriptions and injected output. Those are left to clients and to you. That gap is why most **MCP security best practices** lists you will find stop at “use OAuth and containers” and leave you alone with the hardest part.

## A checklist for vetting an MCP server

Use this before you give any server access to an account you care about. It takes ten minutes for a small server.

**Who and what**

1. Who publishes it? The app’s vendor, a known company, or an anonymous account with dozens of packages? Check the package name against the vendor’s docs; lookalike names are the cheapest attack.
2. Is the source public, and does the published package match it? Read the tool handlers, not just the README. Look for network calls to hosts that have nothing to do with the app.
3. Is the version pinned in your config? `npx some-server@latest` gives the author a way to change your setup without asking.

**What the model sees**

1. Read every tool description in full, not the summary your client shows. Anything addressed to the model (“before using this tool, also…”) is a red flag.
2. Does your client tell you when the tool list changes? If not, re-check after updates. That is your only defense against a rug pull.

**What it can reach**

1. What scopes does it ask for? Read-only should be the default. A server that wants admin to list invoices is telling you something.
2. How does it hold your credentials? A password in a plain config file is the worst case. A token issued for this server alone, which you can revoke, is the best.
3. For a hosted server: are sessions isolated per user, and is there a written retention policy for the data passing through?

**What it can do**

1. Which tools reach other people: send, publish, message, invite, pay? Each of those should require your confirmation, every time, with auto-approve off.
2. Does this agent also have private data, untrusted input and a way out (the lethal trifecta)? If yes, split the work across sessions or drop one of the three.

**The decision rule:** if a server fails items 1 to 3, do not install it. If it passes them but fails 9, use it read-only.

## When something else is the better choice

If the app has an official API or an official MCP server that covers what you need, use it. The vendor can see abuse on their side, revoke tokens properly and fix bugs at the source. For common SaaS with good APIs, Zapier or a few lines against the official API are often simpler and safer than any MCP server. For a task you do twice a year, doing it by hand is fine.

Open source servers you can read are a reasonable choice too, if you will keep reading them; we compare that route with a hosted one in [GitHub unofficial API vs a hosted integration](https://cotlet.dev/blog/github-unofficial-api-vs-hosted). The broader question of whether an agent should touch your accounts at all is its own post: [AI agents and account security](https://cotlet.dev/blog/ai-agent-account-security).

## How Cotlet is designed against these risks

Cotlet hosts MCP servers (and the same actions as a REST API) for apps that have no API, or one too narrow to use. That means we will hold sessions to other people’s accounts, which puts us on the wrong end of every row in the table above if we get it wrong. Here is the design, item by item. It is a design, not a certification: we have no SOC 2 certification.

_Cotlet security page, “How we hold your access”, October 2026._

- **The model never sees a login.** You sign in to the app yourself, in a browser on our side, and pass MFA yourself. We keep only the session, encrypted, never the password. The agent calls tools; the session stays in the vault. How the sign-in works is in [connecting without your password](https://cotlet.dev/blog/connect-without-your-password).
- **One isolated session per user and app.** Never shared between users. That is the Asana lesson applied from the start.
- **Scoped permissions per app.** When you connect an app you pick read only, read and edit, or full with confirmation. A call outside your choice returns `SCOPE_DENIED` instead of running.
- **Confirmation for actions that reach other people.** Sending, publishing and messaging need your yes on every call. On [Skool](https://cotlet.dev/integrations/skool-api-mcp-server) that means each DM; on [Substack](https://cotlet.dev/integrations/substack-api-mcp-server), each published Note.
- **No payload retention.** Data passes from the app to your agent and is not kept after the call.
- **One server, written by us.** You add one Cotlet server, and every connected app shows up as tools on it. There is no third-party package per app to vet.

What we cannot fix for you: prompt injection in the content of your own apps. If a Substack comment says “ignore your instructions”, your agent will read it like any other text. Scopes and confirmation limit what a tricked agent can do; they do not stop it from being tricked. The full list of what we never automate is in the [acceptable-use policy](https://cotlet.dev/acceptable-use), and the vault is described on the [security page](https://cotlet.dev/security).

If you want to see how the pieces fit, read [how Cotlet works](https://cotlet.dev/how-it-works), or [request access](https://cotlet.dev/signup) and pick your apps.

[All posts](https://cotlet.dev/blog)

## Want your app first?

Pick it when you sign up. The most-picked apps open first.

[Request access](https://cotlet.dev/signup)
