Skip to app content
Skip to main content
Back to Research & Articles
October 9, 2026•
MCPSecurityAI Agents

MCP Server Security: Risks, Controls, and What Changes When an Agent Can Spend Money

MCP server security risks, what controls exist, and what changes when an agent can spend money in ad accounts.

JH
Joel Horwitz
Founder & CEO, Synter

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:

CVE-2025-6514

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

(CVE-2025-49596).

Clients also face server-side request forgery (SSRF), where a malicious

MCP server points OAuth discovery at internal addresses such as

  1. 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:

  1. OAuth-scoped connection. A token issued for this server, scoped

to the task.

  1. Tool allowlist. Unregistered tools are blocked.
  1. Schema and limit validation. Out-of-range arguments are

rejected.

  1. Hard budget cap. Anything over the cap is blocked, with no

override.

  1. Approval gate. Higher-risk writes, such as launching or

re-enabling spend, wait for a person.

  1. Ad platform API call.
  1. 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)

Agent-Shield

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.

Share Article

Stay Ahead of AI Growth Trends

Get the latest strategies on AI agent marketing, autonomous growth loops, and programmatic campaigns delivered weekly.

Synter

The AI Agent Operator for Ads.

Direct API connections to 43 advertising platforms, including Google, Meta, LinkedIn, TikTok, and Amazon DSP. One interface. No tab hell.

No credit card required$25 free execution creditInstant 30s setup
Free Account Audit

Find Wasted Spend Across Your Ad Accounts

Synter audits 43 advertising platforms in seconds — detecting keyword leaks, attribution gaps, and budget misallocations with zero connector fees.