ScreenshotNeo

BlogAI agents

How to Identify and Mitigate Malicious MCP Server Risks

Learn how to vet MCP servers, spot prompt injection and tool poisoning, constrain permissions, and validate OAuth before connecting an AI client.

By the ScreenshotNeo team1 October 20268 min read

treat every MCP connection as a trust and permissions decision. An MCP client can invoke the tools, read the resources, and follow prompts exposed by a server. A local server runs as software on your machine and may inherit the files, network access, and process privileges available to the client. The official project states: MCP clients trust MCP servers they connect to. (MCP SECURITY.md).

Before connecting, verify the server’s provenance, inspect its complete launch command, review its tools and schemas, constrain its operating-system permissions, and validate authorization for every remote request. Re-check those decisions after updates because a trusted server can change its definitions later.

What an MCP server can access

MCP does not automatically grant every server unrestricted access. The practical boundary is the process that runs the server and the permissions assigned by the client, operating system, container, or hosting environment. A local stdio server can read files, open network connections, invoke programs, or access environment variables if those capabilities are available to its process. A remote HTTP server cannot read your local disk merely because you connect to it, but it can receive the data and credentials your client sends and can expose tools that act on behalf of your account.

  • Local process: inspect filesystem paths, network destinations, child-process rights, environment variables, and user identity.
  • Remote server: inspect issuer, audience/resource, scopes, redirect URIs, token storage, TLS, and per-tool authorization.
  • Both: treat tool descriptions, parameter schemas, prompts, and returned content as untrusted input until reviewed.

Threats to check before approval

Command and package compromise

A client configuration may contain the exact executable and arguments used to launch a local server. Read the complete command, including shell wrappers, substitutions, downloaded scripts, and package-install steps. Obfuscated commands, shell chaining, broad home-directory paths, unexpected outbound network access, or requests for elevated privileges require a clear explanation. The MCP security guidance recommends showing the exact command before one-click setup and obtaining explicit consent because setup executes code (Security Best Practices).

Prompt injection and tool poisoning

Instructions can be hidden in tool descriptions, schemas, resource content, or tool results. An attacker might tell the model to upload secrets, disable a safety check, or call an unrelated tool. Tool poisoning can be subtle: the visible name appears harmless while a description adds instructions that alter model behavior. OWASP also documents rug-pull attacks, where a server changes tool definitions after approval (OWASP MCP Security Cheat Sheet).

Authorization and token confusion

A valid token is not sufficient. It must be issued for the MCP server (the intended audience/resource), carry only required scopes, and be checked on every request. Do not forward an MCP client’s token unchanged to an upstream API; obtain a separate upstream credential when needed. Use HTTPS in production, short-lived encrypted tokens, exact registered redirect URIs, and strict URL-scheme validation. See the MCP Authorization Security Considerations and Understanding Authorization in MCP.

A practical vetting procedure

  1. Establish provenance. Identify the maintainer, source repository, release process, package registry, signing or checksum practice, issue history, and update cadence. Prefer a distribution channel you can independently verify.
  2. Define the purpose. Write down the one job the server must perform. Reject capabilities unrelated to that job.
  3. Review the launch path. Inspect the full executable, arguments, working directory, environment variables, downloaded files, and package hooks. Avoid opaque shell pipelines.
  4. Inventory permissions. List required directories, network destinations, child processes, devices, secrets, and operating-system privileges. Remove everything not required.
  5. Read every tool definition. Compare names, descriptions, schemas, defaults, and return values with the documented purpose. Treat embedded instructions as data, not authority.
  6. Test in an isolated account. Use a disposable user, container, virtual machine, or sandbox with synthetic files and non-production credentials. Observe filesystem and network activity.
  7. Approve narrowly. Use explicit consent for installation and for sensitive tool calls. Record the version, hash, permissions, and reviewed tool list.
  8. Re-review changes. Diff definitions, dependencies, launch commands, and requested scopes after every update. A previously approved server is not permanently trusted.

Restrict a local stdio server

Stdio is often appropriate when the client and server share one machine and no network listener is needed. Run it with a dedicated unprivileged account and a minimal working directory. Expose only a temporary data directory, deny access to your home directory and SSH keys, and restrict outbound traffic to documented destinations.

# Example: run a reviewed server with a dedicated home and read-only input
mkdir -p /tmp/mcp-review/input /tmp/mcp-review/output
chmod 700 /tmp/mcp-review
HOME=/tmp/mcp-review \
  timeout 300s \
  /usr/local/bin/reviewed-mcp-server \
  --input-dir /tmp/mcp-review/input \
  --output-dir /tmp/mcp-review/output

The command is an example boundary, not a universal sandbox. Add operating-system controls such as containers, seccomp, AppArmor, or a platform sandbox where available. If a local HTTP transport is required, bind to loopback, require authentication, and prevent access from other hosts.

Secure a remote MCP server

  • Require TLS and validate certificates.
  • Discover the authorization issuer and resource/audience from trusted configuration.
  • Send the resource parameter in authorization and token requests.
  • Validate issuer, audience, signature, expiry, not-before, and scopes on every request.
  • Authorize each route and tool; do not rely on a one-time connection check.
  • Use exact redirect URI matching and reject non-HTTPS redirect targets in production.
  • Store tokens encrypted with access controls, redact them from logs, and rotate or revoke them quickly.
  • Use a separate credential for each upstream API instead of passing through the MCP token.

Minimal audience and scope check (Python)

from dataclasses import dataclass
from datetime import datetime, timezone

@dataclass
class Claims:
    issuer: str
    audience: str
    expires_at: int
    scopes: set[str]

def authorize(claims: Claims, *, issuer: str, resource: str, required_scope: str) -> None:
    now = int(datetime.now(timezone.utc).timestamp())
    if claims.issuer != issuer:
        raise PermissionError("unexpected token issuer")
    if claims.audience != resource:
        raise PermissionError("token is not intended for this MCP server")
    if claims.expires_at <= now:
        raise PermissionError("expired token")
    if required_scope not in claims.scopes:
        raise PermissionError("missing scope")

Use a maintained OAuth/OIDC library for signature and key-set validation; the snippet shows policy checks only and is not a complete token verifier.

Detect suspicious behavior after connection

  • Tool names or schemas change without a documented release.
  • A harmless task suddenly requests secrets, unrelated files, or broad network access.
  • Descriptions contain instructions aimed at the model rather than operational parameters.
  • Results include links, scripts, or commands that the client wants to execute automatically.
  • Unexpected child processes, persistence, DNS lookups, uploads, or large file reads appear in system logs.
  • Authorization prompts request broader scopes or a different audience than the original setup.

When any signal appears, stop tool calls, revoke tokens, disconnect the server, preserve logs, and compare the installed package and tool manifest with the last known-good version. Reconnect only after review in an isolated environment.

Configuration checklist

Area Questions to answer
Provenance Who maintains it? Where is the source and release artifact? Can changes be reviewed?
Launch What exact executable, arguments, environment, and hooks run?
Permissions Which files, hosts, processes, and OS privileges are necessary?
Tools Do names, schemas, descriptions, and results match the stated purpose?
Isolation Can it run as an unprivileged user, container, or sandbox with synthetic data?
Authorization Are issuer, audience/resource, scopes, expiry, redirects, and TLS validated?
Change control Will updates trigger a manifest diff and renewed approval?

Troubleshooting common failures

The client starts the wrong program

Cause: a relative executable, shell wrapper, or changed package on PATH. Fix: use an absolute path, print the complete command, pin the package version, and verify its checksum before launch.

The server can read more files than expected

Cause: it runs under your normal account with a home directory as its working area. Fix: use a dedicated account or container, mount only required directories, and remove inherited credentials and environment variables.

A tool description contains an instruction to reveal secrets

Cause: prompt injection or tool poisoning. Fix: treat metadata as untrusted, do not follow the instruction, disable the tool, capture the manifest, and report the package or server maintainer.

OAuth succeeds but calls return 401 or 403

Cause: wrong audience/resource, missing scope, expired token, issuer mismatch, or per-tool authorization failure. Fix: inspect claims without logging the token, request the correct resource, reduce scopes to the required set, and validate every route.

Redirect validation fails

Cause: a redirect URI differs by scheme, host, port, path, or trailing slash. Fix: register and compare exact URIs; reject dynamic or untrusted redirect targets.

The server changed after an update

Cause: dependency change or rug-pull behavior. Fix: pin versions, diff manifests and lockfiles, review release notes, and require renewed approval before reconnecting.

Performance, reliability, and operating cost

Security controls add small operational costs: isolated environments consume memory, manifest reviews take release time, and short-lived tokens require refresh logic. They reduce the blast radius of a compromised server. Cache only non-sensitive metadata, keep audit logs free of credentials, and set timeouts for local and remote calls. For production, monitor denied authorizations, tool-definition changes, process launches, outbound destinations, and unusual data volume. No source in the available guidance establishes a universal “safest” transport or a percentage reduction in incidents; choose controls based on the server’s actual capabilities and your data sensitivity.

Or skip the browser setup

If your agent only needs website screenshots, ScreenshotNeo provides a purpose-built MCP server and API. Its take_screenshot, get_page_info, and capture_pdf tools can be reviewed like any other MCP integration; keep the same least-privilege and change-review practices above. For a direct request, see the ScreenshotNeo documentation:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. The MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Can an MCP server access my files?

A local server can access files available to its process. Limit mounts and run it under a dedicated unprivileged identity. A remote server receives only data your client sends, but may process and retain that data according to its service.

Popularity helps with provenance but does not replace reviewing the current artifact, launch command, dependencies, permissions, and tool definitions.

Is stdio always safer than HTTP?

No transport is universally safest. Stdio can avoid a network listener for local use; HTTP requires careful binding, authentication, TLS, and authorization. Choose the smallest exposure that meets the task.

How often should I re-review a server?

Review on installation, every version or dependency change, any new scope or permission request, and whenever behavior differs from the documented purpose.

What should I do after suspected compromise?

Disconnect the server, revoke credentials, preserve logs and package hashes, inspect affected systems, and restore from a known-good version after an isolated review.

Sources