ScreenshotNeo

BlogAI agents

MCP Server Security Risks and How to Mitigate Them

Learn the real MCP server risks—prompt injection, tool poisoning, token theft, and unsafe execution—and apply practical controls to reduce them.

By the ScreenshotNeo team1 October 20269 min read

Short answer: Treat every MCP server, tool definition, argument, resource, and result as an untrusted input crossing a privilege boundary. Secure the boundary with audience-bound OAuth tokens, least-privilege scopes, server-side authorization on every call, strict schema and output validation, sandboxed execution, pinned tool manifests and dependencies, expiring state, replay protection, and auditable telemetry.

The Model Context Protocol lets an AI host discover tools and dynamically choose them from natural-language context. That flexibility expands the trust boundary across the host, client, server, transport, tool implementation, credentials, and returned content. OWASP describes the resulting attack surface as a combination of prompt injection, supply-chain compromise, confused-deputy behavior, and delegated access. OWASP’s LLM security guidance and the MCP authorization specification are useful starting points.

What an MCP server can access

An MCP server can access whatever its implementation and runtime identity allow. A local server may read files, launch processes, reach internal networks, or use environment credentials. A remote server may call SaaS APIs using OAuth scopes or service credentials. The model does not create these permissions, but it can select tools and supply arguments, so an overly broad server turns an accidental or malicious instruction into a real action.

  • Files: paths permitted by the process user, mounted volumes, and file tools.
  • Credentials: environment variables, configuration files, token stores, and upstream API keys available to the process.
  • Network: destinations allowed by firewall, proxy, DNS, and container policy.
  • Actions: anything exposed as a tool, including writes, payments, code execution, or deletion.

Primary MCP security risks

Risk How it happens Impact
Tool poisoning and rug pulls Instructions hidden in tool descriptions, schemas, or results; a server changes after approval. The model follows attacker-controlled guidance or sends sensitive data.
Prompt and context injection Untrusted page, document, email, or tool output steers the model toward an unsafe call. Unauthorized tools, data disclosure, or dangerous parameters.
Confused deputy and scope creep The server has broader privileges than the user intended, or OAuth scopes are aggregated. Cross-system changes performed with delegated authority.
Token and secret exposure Long-lived keys enter prompts, logs, memory, configuration, or error messages. Credential recovery and reuse by an attacker.
Weak authorization Trusting client-supplied identity, skipping audience checks, or passing a client token upstream. Cross-service access and impersonation.
Command injection and unsafe execution Unsanitized paths, shell arguments, URLs, or package code reach a privileged process. Code execution, data destruction, or host compromise.
Supply-chain and shadow servers Unreviewed packages, dependency tampering, or an unapproved server in client configuration. Persistent compromise before a tool is called.
State-handle attacks Predictable, reusable state handles are treated as proof of identity. Session takeover or cross-user data access.
Missing telemetry and replay protection No correlation IDs, redacted audit logs, nonce checks, or anomaly alerts. Abuse goes undetected and requests can be replayed.

OWASP’s MCPTox benchmark tested 20 agents against more than 45 MCP servers containing 353 tools in August 2025. It reported a 72.8% attack-success rate for o1-mini under those test conditions; this is a benchmark result, not a general-world probability. See the OWASP GenAI Security Project for current material.

A secure MCP deployment model

  1. Inventory the boundary. Record every host, client, server, transport, tool, resource, credential, data store, and outbound dependency.
  2. Classify actions. Mark tools read-only, reversible-write, or high impact. Require explicit human approval for payments, deletion, code execution, permission changes, and production writes.
  3. Issue narrow identity. Use a dedicated service identity per server and workflow. Prefer short-lived, read-only scopes.
  4. Enforce policy in the server. Authorize every JSON-RPC call and validate arguments independently of model reasoning.
  5. Isolate execution. Use a container or sandbox, a non-root user, read-only mounts, egress allow-lists, and a minimal filesystem.
  6. Pin and monitor. Pin server versions, dependencies, and approved tool manifests; alert when definitions or scopes change.

Identity, OAuth, and token handling

MCP authorization guidance requires a server to accept only tokens intended for that server and reject tokens whose audience does not identify it. Clients should send the resource parameter. Validate issuer, audience, expiry, signature, and scopes on every request. Never pass the client’s token through to an upstream API; exchange it for a separate upstream token with the minimum required scope.

// Pseudocode for the authorization gate
claims = verify_signature_and_issuer(bearer_token)
if claims.exp < now: reject(401)
if MCP_SERVER_RESOURCE not in claims.aud: reject(403)
if required_scope not in claims.scope: reject(403)
principal = bind_to_authenticated_subject(claims.sub)
allow_only_tools_for(principal, claims.scope)

Keep secrets out of model-visible context and logs. Redact authorization headers, cookies, API keys, refresh tokens, and sensitive tool arguments before persistence.

Protect tool definitions and results

  • Maintain an allow-list of approved servers and exact tool manifests.
  • Hash or otherwise record tool names, descriptions, schemas, and versions; require review for changes.
  • Separate instructions from data before returning content to the model. Mark web pages, documents, and tool output as untrusted data.
  • Limit result size and strip active content where possible.
  • Do not let a tool result silently redefine policy, credentials, or approval requirements.

Validate every tool call

Validate JSON-RPC structure, required fields, types, lengths, numeric bounds, URLs, file paths, shell arguments, and output size. Resolve paths against an approved root and reject traversal. For network tools, allow-list schemes, hosts, ports, and redirect destinations. For shell-like tools, avoid a shell; pass an argument array to a fixed executable.

Python example: strict URL and path validation

from pathlib import Path
from urllib.parse import urlparse

ALLOWED_HOSTS = {"api.example.com"}
ROOT = Path("/srv/mcp-data").resolve()

def safe_url(value: str) -> str:
    p = urlparse(value)
    if p.scheme != "https" or p.hostname not in ALLOWED_HOSTS:
        raise ValueError("URL is outside the network allow-list")
    return value

def safe_path(value: str) -> Path:
    candidate = (ROOT / value).resolve()
    if candidate != ROOT and ROOT not in candidate.parents:
        raise ValueError("Path escapes the approved directory")
    return candidate

print(safe_url("https://api.example.com/v1/items"))
print(safe_path("reports/today.json"))

Node.js example: bounded arguments and fixed execution

import { spawn } from 'node:child_process';

export function runApprovedTool(args) {
  if (!Array.isArray(args) || args.length > 3 || args.some(x => typeof x !== 'string' || x.length > 200)) {
    throw new Error('Invalid arguments');
  }
  return new Promise((resolve, reject) => {
    const child = spawn('/usr/local/bin/report-reader', args, {
      shell: false,
      env: { PATH: '/usr/bin:/bin' },
      timeout: 10_000
    });
    let output = '';
    child.stdout.on('data', chunk => {
      output += chunk;
      if (output.length > 1_000_000) child.kill('SIGKILL');
    });
    child.on('error', reject);
    child.on('close', code => code === 0 ? resolve(output) : reject(new Error(`exit ${code}`)));
  });
}

Local server hardening

  • Run as a dedicated unprivileged OS user; never as root.
  • Mount only required directories, preferably read-only.
  • Deny-by-default outbound network access and allow-list required hosts.
  • Disable access to cloud metadata endpoints and host sockets.
  • Pin dependencies and verify package provenance and signatures where available.
  • Scan source, packages, images, and configuration for vulnerabilities and secrets.
  • Keep the MCP client configuration under code review; remove unknown or shadow servers.

Remote transport, sessions, and replay resistance

Use TLS for remote transports and validate the expected origin where applicable. State handles are not authentication: the MCP security guidance says servers must not treat possession of a state handle as proof of identity. Generate unpredictable handles, expire them quickly, bind them server-side to the authenticated user and client, and reject reuse. Add nonces or equivalent replay protection to state-changing operations.

Logging and incident response

For each call, log the authenticated principal, server ID, tool name, validated arguments after redaction, policy decision, result status, duration, and correlation ID. Alert on tool-definition changes, scope expansion, repeated authorization failures, unusual destinations, output spikes, and sensitive-data egress. Preserve enough context to reconstruct an incident without storing secrets or full confidential payloads.

Testing checklist

  • Attempt prompt injection through every untrusted resource and tool result.
  • Change a tool description after approval and verify the client detects or blocks the change.
  • Send expired, wrong-audience, wrong-issuer, and over-scoped tokens.
  • Try path traversal, shell metacharacters, oversized JSON, invalid types, SSRF URLs, and redirect escapes.
  • Replay state handles and write requests; verify rejection.
  • Run the server with the container network disabled and confirm safe failure.
  • Check that logs contain correlation IDs but no secrets.

Troubleshooting common failures

Symptom Likely cause Fix
401 or 403 from a remote server Expired token, wrong issuer/audience, or missing scope. Request a token for the MCP server resource and validate claims; grant only the required scope.
Tool appears, then behaves differently Manifest rug pull or unpinned server version. Pin versions and manifest hashes; review changes before reload.
Agent follows instructions in a document Prompt injection in untrusted content. Label content as data, isolate it from policy instructions, and require approval for sensitive calls.
Local tool can read unexpected files Broad mount or path traversal. Use an approved root, resolve paths, read-only mounts, and a non-root user.
Duplicate writes after a retry No idempotency key or replay protection. Use request IDs, nonces, and server-side deduplication for state-changing calls.
Useful requests fail after sandboxing Network or filesystem allow-list is too narrow. Review the exact destination and permission needed; add one explicit rule and monitor it.

Performance, reliability, and cost

Security checks add small validation and logging work, but the largest latency usually comes from upstream APIs, model calls, and browser or process startup. Reuse isolated workers where safe, cache immutable metadata rather than credentials, cap output sizes, and set explicit timeouts. Retries must distinguish transient transport failures from authorization failures and must not repeat non-idempotent actions without an idempotency key.

Budget for dependency scanning, manifest review, audit-log retention, sandbox overhead, and human approval queues. A narrow server with fewer tools is usually easier to operate and cheaper to monitor than one broad “do everything” server.

Or skip the browser setup

If an agent needs screenshots as one bounded capability, ScreenshotNeo provides a website screenshot API and MCP server. The MCP tools are take_screenshot, get_page_info, and capture_pdf; expose only the tool and URL scope your workflow needs and apply the same token, manifest, and approval controls described above.

One request returns a PNG, JPEG, WebP, or PDF. Cookie and consent banners are accepted before capture, and more than 60 known consent platforms, newsletter popups, and chat widgets are removed. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for the full option set, including selector or full-page capture, devices and viewport, custom headers and cookies, blocking rules, waits, caching, signed links, asynchronous jobs, bulk capture, and usage reporting. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Should I trust a local MCP server?

Trust the reviewed, pinned implementation and its sandbox, not its location. A local process can have more host access than a remote service.

Is a state handle a login credential?

No. It must be unpredictable, short-lived, bound to the authenticated user, and protected against replay.

Can prompt injection be completely prevented?

No. Reduce impact with least privilege, untrusted-content isolation, strict validation, and approval gates for high-impact tools.

Should every tool call require a human?

Use risk-based approval. Read-only, bounded operations can be automated; payments, deletion, permission changes, and code execution deserve explicit approval.

How often should MCP security controls be reviewed?

Review on every server, dependency, manifest, scope, transport, or protocol change, and repeat penetration and injection tests periodically.