An MCP server with read access can expose an ad-account report. Write
access adds another failure mode: a misplaced decimal can turn a
proposed \$500 daily budget into \$5,000 even when the request passes
API validation. Protect reads with scoped credentials and limits on what
the server can reach. Protect writes with those controls plus enforced
spend limits; an expensive mistake does not require an attacker.
The controls below cover tool definitions, credentials, process access
and spend-capable writes, with a verification check for each.
What Is MCP Server Security?
**MCP server security is the set of controls that decide who can connect
to a Model Context Protocol (MCP) server, which tools an AI agent can
call through it, what those tools can reach, and how every call is
recorded so a person can review or reverse it.**
The July 28, 2026 MCP revision leaves enforcement to the implementation:
the protocol [cannot enforce consent and access
controls](https://modelcontextprotocol.io/specification/2026-07-28)
on its own. Tools are model-controlled, so clients should let a person
[inspect and deny a
call](https://modelcontextprotocol.io/specification/2026-07-28/server/tools).
How MCP Servers Work (and Where the Attack Surface Sits)
A client in a host such as Claude Code or Cursor reads each server's
tool definitions and sends the calls the model chooses. Three trust
boundaries matter: the definitions the model reads, the credentials the
server holds, and whatever its tools can touch.
Local MCP Servers
A local stdio server runs alongside its client with the [same
operating-system
privileges](https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/local-server-security).
Unless you isolate it, the process can inherit environment variables,
read files accessible to your account and open outbound connections.
Stdio credentials come from the environment rather than the protocol's
HTTP OAuth flow. Files, SSH keys and cloud credentials are therefore
part of the local attack surface.
Remote MCP Servers
A remote server runs elsewhere and is reached over HTTP. It runs no code
on your machine, and when it implements authorization, it acts as an
OAuth 2.1 resource server. Authorization is optional in the spec, so
check that a remote server requires it. **The attack surface moves to
tokens, consent screens, and what the server does on your behalf.** For
our hosted server, [Synter's install
guide](https://synterai.com/installation-instructions) has
you add one URL and sign in through the browser; an unauthenticated
request gets a 401 pointing to its OAuth metadata. The current revision
also removed protocol-level sessions, so a server that needs state mints
a handle passed back as a tool argument. The server must never treat
that handle as proof of who is calling.
The Biggest MCP Server Security Risks
These five risks affect tool definitions, delegated access, dependencies
and the machine running the server.
Prompt Injection and Tool Poisoning
[Prompt injection can arrive in tool
output](https://genai.owasp.org/resource/owasp-genai-llm-top-10-2026/),
such as competitor ad copy retrieved for analysis. OWASP classifies it
as LLM01:2026. Tool poisoning puts instructions in the definitions the
model reads; a server can also change those definitions after approval,
a rug pull. Enforce permissions and limits outside the model so
instructions in its context cannot expand what a tool may do.
Confused Deputy and Token Passthrough
A proxy can become a confused deputy when it shares one upstream client
ID, accepts dynamically registered MCP clients and relies on an upstream
consent cookie [without obtaining per-client
consent](https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices).
An attacker can exploit that combination to obtain authorization codes
without the user's approval. Token passthrough crosses a different
boundary: accepting a token issued for another resource and forwarding
it downstream can bypass controls and obscure caller identity in logs.
Require per-client consent, exact redirect URI matching, audience
validation and separate upstream credentials.
Over-Permissioned Tools and Excessive Scopes
A pacing monitor needs spend reads, not budget writes. Granting both
exposes a capability the job does not need and increases what a stolen
token can do. Start with minimal scopes and request additional
authorization when a privileged operation needs it.
Malicious or Compromised Servers (Supply Chain)
Typosquatted packages, taken-over projects, poisoned dependencies, and
vulnerable bridges count:
exposed mcp-remote to OS command injection through a crafted
authorization endpoint URL from an untrusted server. Every server you
add also shares the model's context and widens the surface for the
others; for ad accounts, we've covered [the trade-offs of stacking
single-platform ad
MCPs](https://synterai.com/blog/why-we-dont-use-ad-platform-mcps).
The control is provenance plus pinning, with OWASP's caveat that
pinning cannot stop a payload already in the pinned version.
Local Server Compromise and Command Injection
A local server runs with your privileges, so a malicious startup command
in a client config means arbitrary code execution, as does a tool
handler that passes model-supplied arguments to a shell. MCP Inspector
versions below 0.14.1 allowed remote code execution because its client
and proxy didn't authenticate each other
Clients also face server-side request forgery (SSRF), where a malicious
MCP server points OAuth discovery at internal addresses such as
- 169.254.169.254. The control is containment, starting with installs
that show the exact command before it runs.
How to Secure an MCP Server
1. Use OAuth for Remote Servers and Never Pass Tokens Through
Implement the [authorization
flow](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)
on HTTP servers. Clients must use PKCE (S256) and the RFC 8707 resource
parameter naming the server. Before redeeming a code, validate a
returned iss against the recorded issuer; reject a missing iss when the
authorization server advertises support for it. Servers must validate
the token's audience and reject tokens issued for other resources
rather than forwarding them. Dynamic Client Registration is deprecated
in favor of Client ID Metadata Documents.
Check it. Send a valid token issued for a different resource and
confirm a 401. Our install prompt also forbids asking for API keys in
chat, covered below.
2. Scope Every Tool to Least Privilege
Expose only the tools a job needs and request the narrowest scope that
covers them. A negative-keyword agent needs keyword writes, not budget
writes.
Check it. Compare the scopes your token actually holds with the
tools the task calls, treating scopes_supported in the server's
metadata as a starting set, not a full permission map. Then confirm an
out-of-scope call gets a 403 with insufficient_scope.
3. Validate Inputs and Lock Down Tool Schemas
Servers must validate all tool inputs, and inputSchema now accepts any
JSON Schema 2020-12 keyword, so put bounds in the schema and again in
server code. A hypothetical budget tool:
{\
"name": "set_daily_budget",\
"description": "Set the daily budget for one campaign, in the account
currency.",\
"inputSchema": {\
"type": "object",\
"properties": {\
"campaign_id": { "type": "string", "pattern":
"\^\[0-9\]{6,20}\$" },\
"daily_budget": { "type": "number", "exclusiveMinimum": 0,
"maximum": 2000 },\
"reason": { "type": "string", "maxLength": 280 }\
},\
"required": \["campaign_id", "daily_budget", "reason"\],\
"additionalProperties": false\
}\
}
Check it. Call the server directly with out-of-range values,
bypassing the client, and confirm it rejects them. A schema judges one
call at a time, so ten in-range increases in an hour still pass;
cumulative caps cover that.
4. Verify What You Install (Signed Manifests, Pinned Versions)
Identify the publisher and pin versions. Pinning through npx or uvx
fixes only the top-level package; a container image digest freezes its
dependency tree. A [signed tool
manifest](https://genai.owasp.org/resource/a-practical-guide-for-secure-mcp-server-development/)
can bind the description, schema, version and permissions to the
artifact checked at load time. Keep checking runtime definitions too:
the NSA's May 2026 guidance identifies [changes to an already trusted
server's
capabilities](https://www.nsa.gov/Portals/75/documents/Cybersecurity/CSI_MCP_SECURITY.pdf)
that can bypass renewed approval.
Check it. Snapshot tools/list when you approve a server, diff it on
every connection, and prefer clients that re-prompt when a definition
changes.
5. Sandbox Local Servers
If a server only calls a remote API, consider a hosted version to avoid
running its code with laptop access. When local files or tools are
required, use a container with only the necessary mounts and
\--network=none when network access is unnecessary. Give each server
separate credentials rather than globally exported shell secrets.
Authenticate local HTTP connections or use restricted IPC; a localhost
port alone is not an access boundary.
Check it. Inspect the container's mounts and environment; the
server should see one directory and its own token.
6. Log Every Call and Require Approval for Irreversible Actions
Record each tool call with allowlisted parameters and old and new values
for writes, storing the log immutably. Redact or hash secrets and
personal data before logging. Centralize authentication, tool filtering
and logging in a policy or gateway layer, and require approval for
high-risk actions such as sending money. Match the approval policy to
consequences: easily reversed, low-consequence actions can run
automatically; irreversible or high-consequence actions need review.
Restoring a setting does not recover money already spent. An
[ad-account audit
trail](https://synterai.com/blog/ai-agent-audit-trails-ad-spend)
should let you reconstruct the actor and change.
Check it. Reconstruct one write from yesterday, including actor,
before and after values, and approver, from logs alone.
When an MCP Server Can Spend Money: The Extra Controls You Need
A spend-capable server must handle mistaken authorized calls as well as
attacks. Our June 2026 safety-gates article illustrates how [valid API
parameters can still produce an unintended
change](https://synterai.com/blog/how-synter-safety-gates-protect-ad-spend).
Complete mediation puts the decision in code: every downstream request
is checked against policy instead of letting the model decide whether it
is allowed. For spend-capable writes, use a fixed sequence of checks:
- OAuth-scoped connection. A token issued for this server, scoped
to the task.
- Tool allowlist. Unregistered tools are blocked.
- Schema and limit validation. Out-of-range arguments are
rejected.
- Hard budget cap. Anything over the cap is blocked, with no
override.
- Approval gate. Higher-risk writes, such as launching or
re-enabling spend, wait for a person.
- Ad platform API call.
- Audit log entry. Before and after values are kept for rollback.

**Alt text: Flowchart of one budget change passing through seven checks
on an MCP server, OAuth-scoped connection, tool allowlist, schema and
limit validation, hard budget cap, approval gate, ad platform API call
and audit log entry, showing where a call is blocked or held.**
Check the cap before the approval gate. Under our current approval
model, a write blocked by workspace policy [remains blocked even if
someone wants to approve
it](https://docs.synterai.com/mcp/approval-model).
Spend Approval Gates on Risky Writes
Hold writes that start spend or can't be undone; let low-risk changes
through inside hard caps, with an audit record. Under the default
Suggest level in our approval model, enabling, deleting, creating
campaigns, sending creative, and unrecognized write scripts are held,
while pausing and changing a budget, bid, or keyword run immediately and
are audited. New campaigns start paused. A held write returns
pending_review with an audit_id. Approval happens in the Synter app: the
requester, or an active workspace owner or admin for a teammate's
action, checks the reviewed action and replies "I APPROVE"; an API key
can't approve. As of October 2026, hosted MCP writes in production are
held as REST writes.
Your MCP client's confirmation prompt is a second layer. We mark write
tools with destructiveHint, but that prompt runs on your machine, and we
don't enforce it. Budget changes aren't held by default, so caps carry
that load.
Hard Budget Caps Enforced Before the Platform API Call
A prompt asks the model to respect a cap. Server code enforces it before
the platform call. It should also cover cumulative change, rather than
validating each amount in isolation. Synter's [hard budget caps and
change journal](https://synterai.com/security-governance)
set maximum daily and campaign-level spend, enforced before platform API
calls, with soft limits that alert without pausing. Our hosted OAuth
advertises tools:read and tools:write; finer limits such as
budget:decrease, which lowers budgets but never raises them, are API-key
scopes.
OAuth Connections (No API Keys Pasted Into Chat)
Our hosted server uses browser OAuth, and the install prompt tells the
agent: "Never ask me to paste an API key, token, or other secret into
this chat." MCP OAuth authorizes your client to use Synter, and a
separate advertiser OAuth authorizes each ad platform connection, so the
token your client holds is not the credential that reaches the ad
platform. Headless setups send a key in X-Synter-Key, never in the
Authorization header on that host.
An Immutable Audit Trail With Rollback
Every safety-gate decision in Synter, whether allowed, changed, held, or
blocked, is written to the workspace audit log. The change journal
records timestamp, actor, entity, field, old and new values, rationale,
and expected metrics impact, with CSV or JSON export, and rollback
covers the last 10 changes per entity. Platform-side checks are
narrower: ad readback covers Meta, Reddit, LinkedIn, and Google Ads, and
change history covers Google Ads only, for 30 days.
Signed Skill Manifests (Synter's Open-Source Agent-Shield as the Worked Example)
is our MIT-licensed library of signing, integrity, prompt-scanning, and
policy helpers. Its integrity guard writes a manifest for a skill
directory and later flags tampered, missing, or unexpected files. The
signature is HMAC-SHA256 with a shared secret, not a public-key
signature. It proves the directory matches a manifest made earlier with
the same secret, so anyone holding that secret can also sign, and the
secret belongs in a KMS. Its signer does the same for execution intents,
so a budget value changed after signing fails verification. The
execution guard returns allow or deny decisions, such as on a budget
increase above a percentage cap, but doesn't enforce anything itself.
MCP Server Security Checklist
Classify the [tool
list](https://docs.synterai.com/mcp/tools) into reads and
writes, then verify the controls in this checklist.
Control Risk it reduces How to verify
- ----------------------- ----------------------- ----------------------------------
OAuth 2.1 with PKCE Code interception, Authorization server metadata
(S256) on remote unauthenticated access lists
servers code_challenge_methods_supported
with S256
Audience-bound tokens, Token passthrough, A token issued for another
no passthrough confused deputy resource gets a 401
Minimal starting Over-broad tokens, A write returns 403
scopes, step-up for excessive agency insufficient_scope until the
writes client re-authorizes
Tool allowlist per Excessive tools/list for that agent matches
agent functionality, tool the approved list
shadowing
Server-side input Out-of-range arguments Direct out-of-range calls are
validation rejected by the server
Pinned tool definitions Rug pulls, tool A diff of tools/list against the
poisoning approved snapshot is empty
Pinned versions or Compromised Configs reference a version or
image digests dependencies sha256 digest, never latest
Sandboxed local servers Local compromise, data No home-directory mount;
exfiltration \--network=none unless egress is
needed
Private IP blocking in SSRF A discovery URL pointing at
OAuth discovery 169.254.169.254 is refused
Hard spend caps before Runaway or mistaken A call above the cap is blocked
the platform call spend and logged, with no override
Approval for writes Excessive autonomy Held writes stay pending until a
that start spend or named person approves
can't be undone
Audit log with before Unattributable changes One change can be reconstructed
and after values and reversed from the log
FAQ
What is MCP server security?
Controlling who can connect to an MCP server, which tools an agent can
call, what those tools can reach, and how each call is logged. The
protocol can't enforce its own security principles, so the server,
client, and infrastructure have to.
What are the security risks of MCP servers?
Prompt injection and tool poisoning, confused deputy and token
passthrough, over-broad scopes, malicious or compromised servers, and
local compromise through command injection or SSRF, plus valid but
mistaken calls, which need no attacker at all, once tools can move
money.
How do you secure an MCP server in production?
Use OAuth with PKCE and audience-bound tokens, never pass tokens
downstream, start on minimal scopes, validate inputs server-side, pin
what you install, sandbox local servers, and log every call. If tools
spend money, add hard caps before the downstream API call and approval
for writes that start spend.
How do you audit MCP server security?
Inventory every client configuration and remove unused servers. Diff
each tool list against an approved snapshot, test that foreign tokens
are rejected, check scopes against the tools in use, and reconstruct one
recent write, including who approved it, from logs alone.
Are remote MCP servers safer than local ones?
They remove the need to run server code on your machine, which reduces
exposure of local files and credentials. A hosted server still handles
sensitive data and downstream access. Authorization is optional in MCP,
so confirm that the remote deployment requires OAuth rather than
assuming it does.
Conclusion
Use the checklist to test boundaries directly: reject foreign tokens and
out-of-scope calls, constrain local process access, and reconstruct a
write from the audit log.
If your agents touch ad budgets, start with the spend controls: caps the
model can't override, holds on writes that start spend, and a log you
can roll back from. Try them on [Synter's MCP
server](https://synterai.com/mcp), which currently lists an
MCP100 code for a \$100 ad credit voucher, and read our security and
governance controls before connecting a production account.