31 tools· updated sep 2026

MCP server security: the trust boundaries I check first

Updated Marek.

  • mcp
  • agent-security
  • security

Are MCP servers safe? Not by default. MCP is a protocol. It sets how an AI client and a server swap messages, and its own spec says it cannot enforce security at the protocol level. Consent and access control are up to whoever builds the host and runs the server. A server is as safe as the code you install and the access you hand it.

So I start every new server with one question. If the model makes the wrong call, what can this connection read, change or send? I treat each server as a new trust boundary. Not a plugin.

My minimum controls are simple:

  • verify the server, command, version, and owner;
  • give it the smallest useful credential scope;
  • keep destructive tools behind explicit approval;
  • isolate local processes and protect remote sessions;
  • log tool calls without leaking secrets;
  • test failure and revocation before production use.

What MCP security actually covers

The Model Context Protocol architecture separates the host, client, and server. The host is the AI application. A client maintains a connection to one server. The server exposes resources, prompts, and tools.

That separation is useful, but it does not make the server trusted. Tool descriptions and results become model context. Tool calls can cross from text generation into databases, browsers, files, APIs, or production systems.

I review four surfaces before connecting anything:

SurfaceQuestion I ask
InstallationWhat code or remote service am I trusting?
IdentityWhose credential is used, and what can it do?
InvocationCan the model call a risky tool without approval?
OutputCan returned data inject instructions or expose secrets?

The right controls depend on whether I use a local or remote MCP server. A local server can inherit the user's machine privileges. A remote server adds network authorization, token audience, redirects, and session handling to the threat model.

The MCP risks I check first

1. Untrusted server code

A local MCP server is a program, not a configuration file. If a client starts a command from an unknown package, that process may receive the same filesystem and network access as the client.

I verify the publisher and repository, inspect the exact startup command, pin a reviewed version, and prefer an isolated runtime. I do not turn a floating package name from a README into an automatic production install.

{
  "command": "/opt/mcp/bin/vendor-server",
  "args": ["--config", "/etc/mcp/vendor-readonly.json"],
  "env": {
    "VENDOR_TOKEN": "${VENDOR_READ_ONLY_TOKEN}"
  }
}

This example is only a pattern. The important properties are an explicit executable, a reviewed configuration, and a purpose-specific token outside the file.

2. Tool poisoning and indirect prompt injection

A server controls the descriptions and content that reach the model. A malicious description can try to influence when another tool is called. Untrusted content returned by a legitimate tool can also contain instructions aimed at the model.

I treat tool metadata and tool output as data from an untrusted boundary. The model does not get permission merely because a result tells it to take an action. Sensitive actions still pass a deterministic policy check or a human approval gate.

3. Credentials with too much power

The most common mistake is connecting a convenient administrator token. A read task should use a read-only identity. A reporting agent should not share the credential used for billing, deletion, or account administration.

For a database integration, I prefer dedicated roles and approved views, as described in my MCP database setup. I also set expiry or rotation where the provider supports it. Least privilege reduces the blast radius of a bad tool call, a compromised server, and a leaked token.

4. Token passthrough and confused deputies

The official MCP security guidance warns against token passthrough. A server should not accept a token that was minted for another service and forward it downstream as if the audiences were interchangeable.

For remote MCP, I validate the token issuer, audience, scopes, and expiry. I keep the user's authorization decision tied to the actual MCP server. The MCP authorization specification is the starting point, but implementation still needs normal OAuth security controls.

Use the remote MCP login diagnostics to inspect challenge discovery and a 401 response before retrying authorization.

5. Session hijacking and request confusion

Version boundary, checked on 22 September 2026: the Mcp-Session-Id discussion below concerns Streamable HTTP revisions 2025-03-26 through 2025-11-25, where servers may assign a session ID during initialization. The 2026-07-28 Streamable HTTP specification removes that protocol-level mechanism from this revision. Servers can support both eras concurrently, so apply the rules of the transport and protocol revision actually in use; a specification release does not upgrade a deployment.

A remote server may keep state across requests. If session identifiers are predictable, logged carelessly, or accepted without binding them to the authenticated user, one connection can affect another.

I require secure transport, generate identifiers with enough entropy, bind sessions to identity, and reject state that does not match the current connection. I also avoid using the session ID itself as authentication.

In the 2026-07-28 release, application state can instead travel as an explicit tool argument, such as a cart or workflow handle. The state-handle guidance requires authorization-enabled servers to verify every inbound request and forbids treating a handle as authentication. User binding is recommended. I have not tested these controls against a deployed server.

6. Quiet data exfiltration

An apparently useful tool can send prompts, files, database rows, or environment values to an external endpoint. The leak may happen in a request, a log, or a tool result copied into the next model call.

I allowlist destinations where practical, separate public and confidential data paths, and redact secrets before logging. If the server does not need outbound internet access, the runtime should not have it.

7. SSRF during OAuth discovery

OAuth discovery can make an MCP client fetch attacker-chosen URLs before any tool call. The 2026-07-28 security guide identifies metadata URLs and discovered OAuth endpoints as entry points.

Boundary to checkStatus in that guide
A server-hosted MCP client fetches OAuth URLsMUST assess SSRF and implement suitable mitigations
Production URLs and resolved destinationsSHOULD require HTTPS and block private/reserved addresses, including loopback and cloud metadata; explicit development exceptions need separate handling
An HTTP redirect points somewhere newSHOULD apply the same URL checks to redirect targets; consider checking every hop
DNS changes between validation and connectionConsider pinning resolution; combine with other defenses

These are documentation checks, not results from my own SSRF test. Assess SSRF whenever a client fetches OAuth URLs, regardless of protocol version. Keep the egress policy outside the model.

Controls that survive model mistakes

Prompts are helpful, but I do not use a prompt as the only security boundary. A model can misunderstand an instruction, follow malicious content, or choose the wrong tool. The control must live outside the model when the consequence matters.

My usual control stack is:

  1. Install gate: an operator approves the server source, version, command, and checksum.
  2. Identity gate: every connection receives a narrow, separate credential.
  3. Tool allowlist: the agent sees only the tools needed for its current job.
  4. Argument validation: schemas, paths, hosts, and row limits are checked before execution.
  5. Approval gate: writes, payments, deletion, messaging, and deployment wait for a human.
  6. Runtime isolation: local servers run with restricted filesystem and network access.
  7. Audit trail: tool name, sanitized arguments, result status, actor, and correlation ID are recorded.
  8. Kill path: I can revoke the token and disable the connection quickly.

This matches a broader lesson from Anthropic's work on building trustworthy agents: useful autonomy needs legibility and controls, not only a more capable model.

How to secure a custom or private MCP server

A custom MCP server is code you wrote, so nobody reviews it for you. A private one is a server only your own team can reach. Both get the same boundary checks I run on a third-party server. Only now every control is yours to build.

The safest network setup is none. With stdio the client spawns the server itself, and they talk over stdin and stdout. The transport never opens a port, so nobody else gets a URL to hit. Of the two servers I wrote, the smaller one works like that. A stdio server still gets whatever access your own account has on the machine, so read its code like you would any install.

Credentials stay out of the code. The MCP authorization specification says stdio implementations should not use its OAuth flow and should retrieve credentials from the environment instead. My smaller server follows that: the feed URL sits in an environment variable. The larger one, for an ad platform, takes its token at runtime through a dedicated tool instead, so the token lives in the session rather than on disk. Neither the feed URL nor the token has ever been in the repository, and neither is printed in a tool response.

A private server that runs remotely needs real authentication. Sitting on an internal network is not that. An HTTP server still validates every token it accepts, as covered in the token passthrough section above.

Pick the safe default before you write the handler. Four of my fourteen ad platform tools create things that can spend money. Each one creates its object paused. A person turns it on, outside the agent path.

Do not rely on tool hints. The specification lets a tool carry annotations such as destructiveHint. The spec also says clients must not trust them unless the server itself is trusted. A hint describes a tool. It does not block a call.

Keep the tool list short. Every tool is one more option the model can pick by mistake, and one more description to review. I would not go much past fourteen on one server.

The build notes for both servers are in how to build your own MCP server.

My connection review checklist

Before I approve an MCP server, I want concrete answers:

  • Is this the official or otherwise verified distribution?
  • Is the version pinned and update behavior understood?
  • Which files, hosts, APIs, and database objects can it reach?
  • Are credentials injected at runtime rather than committed?
  • Is the credential read-only where the task is read-only?
  • Which tools can write, delete, publish, pay, or message?
  • Do those tools require an approval outside the model?
  • Are remote OAuth audience and redirect checks correct?
  • Can a tool result trigger another sensitive action automatically?
  • Do logs redact tokens, personal data, and model context?
  • Has revocation been tested?
  • Is there an owner for updates and incident response?

If I cannot answer one of these, the connection stays in a sandbox. For implementation mechanics, see how to build your own MCP server. For the wider release process, use the production-ready AI agent checklist.

Frequently asked questions

Are MCP servers safe by default?

No. MCP standardizes communication, but the safety of a server depends on its code, permissions, authentication, tool design, runtime, and operating controls.

Is a local MCP server safer than a remote one?

Not automatically. Local transport removes some network risks, but the process may inherit powerful machine access. Remote servers add authorization and session risks but can be isolated from the workstation.

Should an MCP server receive my main API key?

Usually no. Create a separate credential with the minimum scopes, resources, and lifetime required for that server and task.

Can a system prompt prevent unsafe tool calls?

It can reduce mistakes, but it is not a hard security boundary. Enforce permissions, validation, and approvals in code or infrastructure outside the model.

What should I log for MCP tool calls?

Log the actor, server, tool, sanitized arguments, decision, status, latency, and correlation ID. Do not log raw secrets or unnecessary sensitive content.