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.
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.
| Server | What it can do | Narrowest credential or setting |
|---|---|---|
| Filesystem (reference server) | Read, write, edit, move and search files; create and delete directories | No 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 security | A 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 more | OAuth 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 server | Execute SQL against the connection string you supply | A 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 servers | Load pages, click, read content | No 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
{
"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.Invariant Labs: MCP Security Notification: Tool Poisoning Attacks
- 2.Model Context Protocol specification (2025-06-18): Security and Trust and Safety
- 3.Model Context Protocol: Security Best Practices
- 4.Supabase docs: Model context protocol (MCP)
- 5.GitHub MCP Server (github/github-mcp-server) README
- 6.MCP reference servers: Filesystem server README