# MCP security

An MCP server's security comes down to four questions: who may reach it, who the caller is, what the caller's tools can touch, and what a malicious tool description or result can talk the model into. The specification requires origin validation and authentication for anything non-public; the rest is the server author's design.

- **Status:** Reference
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/protocols/mcp-security/

## Validate Origin, or a web page drives your server

A local MCP server listening on `127.0.0.1` is reachable by any page the user has open, because the browser will happily send a request to localhost from any origin. Combined with DNS rebinding — a hostname that resolves to an attacker's address and then to yours — it means an ordinary website can call a tool server that was only ever meant to serve the desktop application that launched it.

The defence is small and mandatory: when an `Origin` header is present, validate it and answer `403` if it is not one you accept. An absent `Origin` is normal and allowed — a desktop MCP client is not a browser and sends none. What must never happen is accepting any origin that turns up.

Connect does exactly this. The allowed set is the public site, plus a configured public origin, plus explicitly listed extras; localhost is accepted only when the process is not running in production mode. A present, unrecognised origin gets `403` before anything is parsed.

## Authentication, scope and blast radius

Public and private tools are different problems and should not share a surface by accident. A tool that reads a published file needs no credential, because requiring one protects nothing that a browser could not already fetch. A tool that reads a workspace needs a credential, and that credential must decide which workspace — not the request body.

- **Bind scope to the credential.** If the caller can name the workspace in an argument, the tool is one typo away from a cross-tenant read. The key resolves the workspace; the arguments never do.
- **Call the domain service, not the database.** A tool that goes straight to SQL bypasses autonomy, approval, suppression, metering and audit — every rule the product enforces lives above the query.
- **Keep the tool list small and specific.** Ten narrow tools can be read and reasoned about. One general tool that takes a path or a statement cannot.
- **Log what a tool did, not just that it was called.** A refusal is a decision worth recording; a write with no trail is a write nobody can explain later.

> **Careful** The tool nobody should build is the general-purpose one: run this SQL, call this internal URL, execute this shell command, read this file path. It looks like leverage and it is a full compromise waiting for one convincing sentence in an email the model is reading. Connect has no such tool, deliberately.

## The injection surface people forget

Tool descriptions, resource contents and tool results all arrive in the model's context, and a model does not natively distinguish a description written by a server author from an instruction written by the user. A hostile server can therefore attack a client through text alone, and a benign server can be turned into a weapon if it returns attacker-supplied content verbatim.

Two habits address most of it. Read a server's tool list before enabling it, the way you would read a browser extension's permissions. And treat content that came from outside — a message, a web page, a document — as data being reported, never as instructions to follow, wherever it appears in a chain. The same reasoning applies to ordinary business mail arriving at an agent that can act, which is covered in [Prompt injection arrives as ordinary business mail](/research/prompt-injection-in-business-mail/).

## Does Connect use these protections?

Yes, and the public endpoint is the easy case: `POST /mcp` exposes documentation tools that read generated files, hold no session and reach no workspace, so the worst outcome of an unauthenticated call is that somebody learns what the website already says.

| Control | Behaviour |
|---|---|
| Origin validation | Present and unrecognised → `403`; absent → allowed |
| Rate limiting | 120 requests per minute per client address → `429` |
| Body size | 256 KB ceiling → `413` before parsing |
| Header agreement | `MCP-Protocol-Version`, `Mcp-Method`, `Mcp-Name` must match the body → `-32020` |
| Batching | Refused — one JSON-RPC message per request |
| Error detail | Internal faults answer with a generic message and no internal path |

Workspace-scoped tools are a separate surface with a separate contract: an integration key, one workspace per key, and the existing domain services underneath so that autonomy, approval, suppression, safe-sales limits, metering and audit all still apply. See [Authenticating to the MCP server](/developers/mcp-authentication/) and [Integration keys](/developers/integration-keys/).

## Questions

### My MCP client sends no Origin header. Is that a problem?

No. Desktop and command-line clients are not browsers and normally send none, which is why an absent `Origin` is allowed. The rule is about a *present* origin: if one arrives, it must be checked, because only a browser sends it and a browser can be driven by any page.

### Is rate limiting a security control or a capacity control?

Both, and here it is mostly the first. The public endpoint reads generated files, so capacity is not the constraint; a per-address ceiling keeps one misbehaving client from making the endpoint useless for everyone else.

### Can an MCP tool see my workspace data if I have not given it a key?

No. The public tools read the generated documentation corpus and nothing else. Anything that reaches a workspace requires an integration key, and that key fixes which single workspace is in scope — the caller cannot name one.

## Related

- [Model Context Protocol](https://connectbyjbrh.com/docs/protocols/mcp/)
- [MCP tools](https://connectbyjbrh.com/docs/protocols/mcp-tools/)
- [OAuth for agents](https://connectbyjbrh.com/docs/protocols/oauth-for-agents/)
- [Building an agent on Connect safely](https://connectbyjbrh.com/developers/agent-safety/)
- [Security and isolation](https://connectbyjbrh.com/docs/security/)
- [Prompt injection arrives as ordinary business mail](https://connectbyjbrh.com/research/prompt-injection-in-business-mail/)

## What this page is based on

- https://modelcontextprotocol.io/specification/2026-07-28
- `backend/app/mcp_server.py` — _origin_ok, _rate_ok, the header checks
- `docs-source/facts.py` — PROTOCOLS['mcp'].security
- Source pack: Connect as a whole §10 — security
