VibeSecurity

Tools and workflow

MCP Server Security: Risks for Vibe Coders and a Checklist

This post is for founders and indie builders who have started plugging Model Context Protocol servers into Cursor, Claude Code, Windsurf or Claude Desktop so the agent can touch their files, GitHub, database or browser. MCP is useful, and it also hands the agent real credentials and real reach. After reading you will understand the main ways that goes wrong, including tool poisoning, prompt injection through fetched content, over-broad tokens and servers left listening on the network, and you will have a checklist to run before you connect any server, drawn from the MCP specification and the researchers who first documented the attacks.

By the VibeSecurity team11 min read

What is an MCP server, in one paragraph?

The Model Context Protocol is an open protocol for connecting LLM applications to external tools and data. The application you type into, such as Cursor or Claude Code, is the host. It runs a client that talks to one or more servers, and each server offers tools the model can call, resources it can read, and prompts it can use. A filesystem server offers read and write tools over a set of folders. A GitHub server offers tools for issues, pull requests and repositories. A database server offers a tool that runs SQL.

Two facts about that design drive every risk below. First, tools represent arbitrary code execution: the specification says so in those words. Second, the model decides which tool to call and what to send it based on text it reads, including tool descriptions and tool results. Anyone who can influence that text can influence what the agent does with your credentials.

Tool poisoning: hidden instructions in tool descriptions

In April 2025 Invariant Labs published research on what they called tool poisoning attacks. The mechanism is simple. An MCP server describes each of its tools in text so the model knows when and how to use them. The model sees the full description. The user typically sees a shortened version in the UI, or nothing at all. A malicious server can put instructions in that description that the model follows and the user never reads.

Their demonstration was an ordinary looking add tool whose description told the model to read the user's ~/.cursor/mcp.json file and an SSH private key and pass the contents along as a side note parameter. The tool still added two numbers, so the visible behaviour looked correct. They also described two variants: a rug pull, where a server changes a tool description after you approved it, and cross-server shadowing, where a poisoned tool on one server changes how the model uses a trusted tool on another. In their Cursor experiment, a poisoned tool redirected outgoing email to the attacker despite the user naming a different recipient.

The MCP specification anticipates this. Its Security and Trust and Safety section says descriptions of tool behaviour, such as annotations, should be considered untrusted unless obtained from a trusted server. A tool description is input from a stranger, not documentation.

Prompt injection through the content tools fetch

The second path in is content. If the agent has a browser or fetch tool, a web page can contain text addressed to the model. If it has a database tool, a row can contain that text. If it has a GitHub tool, an issue or a pull request comment can. The model reads it as part of doing its job, and if the instruction is convincing enough, it may act on it with whatever other tools it holds.

Supabase's own documentation for its MCP server names this directly. It says connecting an LLM to your Supabase projects carries security risks, that the primary concern is prompt injection where malicious instructions embedded in database content could trick the LLM into executing unauthorized queries, and that you should keep manual approval of each tool call enabled for interactive work. The same logic applies to any server that returns text a stranger could have written: a support inbox, a comments table, a scraped page.

Think in combinations. A read-only browser tool alone can be tricked into reading; it cannot delete. A browser tool next to a write-capable database tool in the same session is a different machine.

Over-broad tokens and unvetted servers

Most servers need a credential. The easy path is to paste in whatever you already have, which is usually an admin token with every scope. The MCP security best practices document treats this as an attack surface in its own right under the heading scope minimization: a stolen token with broad scopes such as files, db or admin enables lateral data access and privilege chaining, and revoking it disrupts every workflow at once. Its common mistakes list includes wildcard or omnibus scopes and bundling unrelated privileges to preempt future prompts.

The other habit is running community servers you have not looked at. A local MCP server is a binary or package that runs on your machine with your privileges. The best practices document describes the failure modes: a malicious startup command in a client configuration, a malicious payload inside the server itself, and its example is a package that posts your SSH private key to a remote URL as part of installing. It also notes users have no visibility into what commands are executed, and warns that MCP servers run with the same privileges as the client.

Servers exposed on the network

MCP servers speak over two transports. Stdio runs the server as a child process and talks over its standard input and output, so only the client that launched it can reach it. HTTP runs the server as a network service. The best practices document is direct about which is safer for a local server: use the stdio transport to limit access to just the MCP client, and if you use HTTP, restrict access, for example by requiring an authorization token or using a Unix domain socket.

The danger case is a server bound to 0.0.0.0 with no token: every device on the same network can call a tool that runs SQL or writes files as you. The document also lists an attacker reaching an insecure local server left running on localhost via DNS rebinding, where a web page tricks your browser into talking to the local service. A server that only listens on stdio has neither problem.

What token does each common server actually need?

The table lists the servers vibe coders most often connect and the narrowest credential that still does the job, taken from each project's own documentation. The pattern is the same in every row: start read-only, scope to one project, widen only when a task needs it.

Common MCP servers and the least-privilege token or configuration each needs
ServerWhat it can doNarrowest credential or setting
Filesystem (reference server)Read, write, edit, move and search files; create and delete directoriesNo token. Access is limited to the directories passed as command-line arguments (or client-provided roots), so pass only the project folder, never your home directory.
GitHub (official server)Issues, pull requests, repository contents, Actions, code securityA personal access token or OAuth login. Use --read-only unless you need writes (write tools are skipped when it is set). Limit toolsets with --toolsets repos,issues,pull_requests. Grant only the permissions you are comfortable giving an AI tool.
Supabase (hosted server)Run SQL, manage projects, edge functions, branches and moreOAuth login rather than a hand-made PAT. Add read_only=true to run every query as a read-only Postgres user, project_ref=<id> to scope to one project and disable account tools, and features=<groups> to enable only the tool groups you need. Never give it to customers or end users.
Any Postgres or database serverExecute SQL against the connection string you supplyA dedicated database role with SELECT only on the schemas the task needs. Point it at a staging or branch database by default, and at production only when the task needs production evidence.
Browser or fetch serversLoad pages, click, read contentNo credential, but treat every page as untrusted input. Do not run it in the same session as write-capable database or shell tools unless you are approving each call.

The checklist before you connect a server

  1. 1Pin the server. Install a specific version from the project's own repository or registry, not whatever npx resolves today. Invariant Labs recommends pinning MCP server versions with checksums so a rug pull cannot silently change tool descriptions after you approve them.
  2. 2Read the tool descriptions. Open the server's source or its tool listing and read every description in full. Instructions to read files, pass extra parameters, or contact other tools are red flags, and the spec says to treat descriptions as untrusted unless the server is trusted.
  3. 3Give it the narrowest token. Create a new credential for the server rather than reusing an admin token, scope it to one project or repository, and prefer read-only flags where the server offers them.
  4. 4Use a read-only database user. For any server that runs SQL, create a Postgres role with SELECT only, or use the vendor's read-only mode, and keep write access as a separate, deliberate step.
  5. 5Run it locally on stdio, not exposed. Prefer stdio servers. If a server must listen on HTTP, bind it to localhost and require a token, never 0.0.0.0 with no auth.
  6. 6Keep per-call approval on and read what each call sends. The spec says hosts must obtain explicit user consent before invoking any tool; make that consent real by reading the arguments, especially anything that looks like file contents or keys.
  7. 7Keep the agent out of production credentials. Default every server to staging, a branch or a local copy. Connect to production only when the task genuinely needs production evidence, and disconnect afterwards.

A minimal safe configuration

The example below shows the shape of a careful setup: a filesystem server limited to one folder, the GitHub server in read-only mode with a limited toolset, and Supabase in read-only mode scoped to a staging project. Adapt the exact keys to your client.

Notice what is absent: no admin token, no home directory, no production connection string. That absence is the security control. If a task needs more, add it for that task and take it away again.

Example MCP client config with least privilege
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/projects/my-app"]
    },
    "github": {
      "command": "github-mcp-server",
      "args": ["stdio", "--read-only", "--toolsets", "repos,issues,pull_requests"],
      "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "<fine-grained token scoped to one repo>" }
    },
    "supabase": {
      "url": "https://mcp.supabase.com/mcp?read_only=true&project_ref=<staging-project-ref>"
    }
  }
}

What a scan can and cannot see here

Most of what this post covers lives on your laptop: the servers you installed, the tokens in your config, the approval prompts you click through. No external scan can see any of that. The controls are habits, and the checklist above is the whole of them.

What an external check can see is the downstream result. If an agent with a broad token was tricked into loosening a database policy or committing a key, that shows up in the deployed app: a table readable without a login, a secret in the JavaScript bundle. A read-only external scan such as VibeSecurity looks at that public surface. It is the backstop for the day a habit slips, not a substitute.

  • On your machine: pinned servers, read descriptions, scoped tokens, stdio transport, approval on.
  • In the deployed app: RLS and rules intact, no secrets in the bundle, checked after every release.

Frequently asked questions

What is an MCP tool poisoning attack?

A tool poisoning attack hides instructions inside an MCP tool's description. The model reads the full description and may follow the hidden instructions, while the user sees only a summary or nothing. Invariant Labs demonstrated it in April 2025 with an add tool whose description told the model to read the user's SSH key and MCP config and send them along. The MCP spec says to treat tool descriptions as untrusted unless the server is trusted.

Is it safe to connect an MCP server to my production database?

Not by default. Supabase's MCP documentation says connecting an LLM to your projects carries security risks, mainly prompt injection through database content, and recommends connecting to a production project only when the task requires production evidence. Use a read-only mode or a SELECT-only database role, scope the server to one project, keep per-call approval on, and prefer staging or a branch database.

Should MCP servers run over stdio or HTTP?

For a server on your own machine, stdio. The MCP security best practices document says to use the stdio transport to limit access to just the MCP client, and if you use HTTP to restrict access by requiring an authorization token or using a Unix domain socket. A server bound to 0.0.0.0 without a token can be called by any device on your network, and one on localhost can be reached via DNS rebinding.

What token scope should I give the GitHub MCP server?

The narrowest that works. The official server accepts a personal access token or OAuth login and supports a --read-only flag under which write tools are skipped even if requested, plus a --toolsets flag to enable only groups such as repos, issues and pull_requests. Its documentation says to grant only the permissions you are comfortable granting your AI tools, and never to commit the token.

Can a security scanner detect a malicious MCP server?

Not from outside. MCP servers, their tool descriptions and their tokens live on your machine, so the defences are habits: pin versions, read descriptions, scope tokens, keep approval on. What an external scan can detect is the aftermath in your deployed app, such as a loosened database policy or a leaked key, which is why the checklist and a regular scan of the public surface belong together.

Put it into practice

Sources

  1. 1.Invariant Labs: MCP Security Notification: Tool Poisoning Attacks
  2. 2.Model Context Protocol specification (2025-06-18): Security and Trust and Safety
  3. 3.Model Context Protocol: Security Best Practices
  4. 4.Supabase docs: Model context protocol (MCP)
  5. 5.GitHub MCP Server (github/github-mcp-server) README
  6. 6.MCP reference servers: Filesystem server README