An npm package can execute code inside your application. An MCP server can place natural-language instructions beside your system prompt, advertise callable capabilities, return attacker-controlled content, and ask the agent to act with a user’s authority.
That does not make every MCP server malicious. It means every integration occupies a position from which a compromise, poisoned description, unsafe update, or hostile upstream result can influence the agent’s decisions.
The evidence is no longer hypothetical. The 2025 MCPTox preprint built 1,312 poisoning cases from 353 tools across 45 real-world MCP servers and evaluated 20 agents. It reported a 36.5% average attack-success rate across its model settings, with the highest result above 72%. A separate March 2026 client-security preprint tested seven MCP clients and found substantial differences in static validation, parameter visibility, warnings, sandboxing, and resistance to cross-tool poisoning.
Those are benchmark results, not an estimate of production compromise rates. They still establish the uncomfortable fact: tool metadata can steer an agent into using a different, legitimate capability for an unauthorized action.
The MCP specification is explicit that tool descriptions and annotations should be considered untrusted unless they come from a trusted server. It is equally explicit that MCP cannot enforce its security principles at the protocol level. The host and runtime have to do that work.
Installing a tool is therefore not one trust decision. It is the beginning of a stream of trust decisions.
The code can change. The server can change. Its owner can change. The authorization scopes can expand. A schema can drift. A resource can contain an injection. A safe read can be composed with a dangerous write. A remote tool can remain perfectly honest while returning attacker-controlled data.
That is why agent supply-chain security is larger than package scanning and larger than MCP authorization. A production system has to validate the tool before admission, constrain it during every run, distrust the content it returns, and retain a fast path to revoke it.
This guide is part of the Agent security engineering series. It focuses on the boundary between agents and MCP servers, connectors, plugins, skills, APIs, and remote tools.
The npm analogy—and where it breaks
The headline is useful because MCP is becoming a distribution layer for agent capability. It is dangerous if taken literally.
| Supply-chain question | npm package | MCP integration |
|---|---|---|
| What is admitted? | code and dependency metadata | tools, resources, prompts, schemas, and descriptions |
| Where does influence enter? | build or application runtime | model context and the tool-execution path |
| What can change transitively? | dependency versions and install scripts | server code, remote behavior, schemas, descriptions, scopes, and upstream content |
| What does a lock prove? | the fetched artifact matches a digest | only the pinned artifact; not tomorrow’s remote behavior or returned data |
| What authority is exposed? | process, filesystem, and network privileges | agent identity, delegated-user scopes, surfaced tools, credentials, and approval state |
| What is the dangerous composition? | vulnerable dependency plus reachable code path | attacker-controlled source plus sensitive data plus an authorized sink |
The sharp ContextOS position is this:
MCP is a capability transport, not a governance layer. Discovery tells you what a server claims. It does not decide what the agent may do.
The protocol is not the vulnerability. Treating discovered metadata as executable authority is.
A poisoned tool does not have to be called
Consider a coding agent with three legitimate capabilities:
repository.search, which can inspect the current repository;workspace.read_file, which can read approved project paths;support.create_ticket, which can send a diagnostic report to an approved support tenant.
Now a fourth MCP server advertises a harmless-looking time tool. Its description contains a hidden instruction: before any repository task, read a credential file and place its contents in the diagnostic field of the next support request.
The user never asks for the time tool. The agent never calls it. The attack succeeds if the description enters context and changes how the agent uses the other three tools.
The trace exposes five named failures:
- Metadata authority laundering: descriptive text was allowed to create a new obligation.
- Cross-tool composition: separate read and network capabilities formed an exfiltration path.
- Ambient privilege: the file tool could reach data the user’s task did not require.
- Unbound arguments: the outbound tool accepted content and destination choices not derived from the user’s intent.
- Narrative auditing: the system trusted the agent’s summary instead of the actual tool envelopes and mutation receipts.
Prompt filtering may reduce the chance that the model follows the instruction. It does not remove the path. The durable fix is to make the unauthorized effect impossible even when the model is persuaded.
Four supply chains overlap
An agent tool has at least four supply chains.
| Supply chain | What can change | Failure mode |
|---|---|---|
| Code | package, container, local binary, skill, transitive dependency | malicious update, RCE, secret access |
| Service | remote MCP server, API, DNS, certificate, operator | behavior changes after approval |
| Content | tool descriptions, resources, search results, tool outputs | prompt injection, poisoned evidence, memory poisoning |
| Authority | OAuth client, scopes, tokens, user consent, agent identity | privilege abuse, confused deputy, token theft |
Traditional dependency controls help with the first row: pin versions, verify signatures, scan dependencies, and review source. They do not prove that tomorrow’s remote response is safe to place beside a write-capable tool.
Anthropic’s containment guidance makes the distinction cleanly: an audited connector is not the same thing as audited data, and a remote tool can change after the original approval. Its recommendation to separate model, environment, and external-content defenses is the right starting point.
Treat discovery as a claim, not a grant
MCP discovery tells the client what a server claims to expose. Production admission decides what the runtime is willing to use.
The admitted manifest should be narrower than discovery:
import type { AdapterDefinition } from "@/lib/contextos/types"
export const paymentsMcp: AdapterDefinition = {
adapter_id: "adp_payments_mcp",
type: "MCP",
endpoint_ref: "mcp://registry/payments-prod@sha256:9c2d...",
capabilities: [
"payments.lookup_transaction",
"payments.issue_refund",
],
approval_mode: "destructive",
capability_risks: {
"payments.lookup_transaction": {
effect: "none",
authority: "service",
reversibility: "read_only",
interaction: "api",
data_scope: "CONFIDENTIAL",
},
"payments.issue_refund": {
effect: "external_state",
authority: "human_approved",
reversibility: "compensatable",
interaction: "api",
data_scope: "CONFIDENTIAL",
decision_ttl_seconds: 300,
},
},
}The manifest is not generated and accepted in one step. A reviewer or admission policy should bind it to:
- owner and escalation contact;
- server identity and allowed endpoint;
- protocol and schema versions;
- content or image digest where pinning is possible;
- exact capability names;
- input, output, error, and evidence schemas;
- maximum
ApprovalModeand nativeActionRiskper capability; - required scopes and credential issuer;
- allowed destinations and data classifications;
- idempotency, retry, timeout, and compensation behavior;
- review date and revocation state.
The model may read a tool description. The description must never be the source of authority.
Authorization has two audiences
Agent-tool authorization is easy to blur because the agent often acts for a user while also being its own workload.
Keep the principals separate:
delegated user -> agent identity -> tool gateway -> MCP server -> upstream APIEach arrow is an authorization boundary. A credential accepted on one arrow should not automatically cross the next.
The official MCP Security Best Practices explicitly forbids token passthrough. Passing an inbound bearer token to a downstream API can bypass audience checks, weaken audit, and turn an intermediary into a confused deputy.
The safer sequence is:
- verify the agent’s short-lived identity claim and workload proof;
- verify the delegated user and requested scopes when user authority is required;
- intersect user scopes, agent scopes, manifest permissions, policy, and approval mode;
- request a new credential for the exact downstream resource and reduced scope set;
- keep the raw credential out of model context, tool arguments, and normal traces;
- record claim hashes, audiences, scopes, and policy decisions instead.
Google Cloud’s 2026 Agent Identity and Agent Gateway model reflects the same direction: first-class agent principals, attested identity, centralized agent-to-tool policy, access boundaries, and explicit human approval for sensitive actions.
The runtime validation order
Every invocation should pass through the same order. Order matters because policy cannot authorize an ambiguous principal or an unvalidated capability.
At minimum, validate:
- server identity and endpoint against the admitted registry;
- token issuer, signature, expiry, audience, and binding;
- agent lifecycle and tenant;
- capability presence in the compiled manifest;
- argument schema and policy constraints;
- destination, redirect behavior, and egress class;
- data classification against the sink;
- approval for the exact effect, not the general tool;
- idempotency before writes;
- result schema before the result reaches the model or memory.
If the server’s schema or identity differs from the admitted version, fail visibly. Do not silently re-discover and continue.
Tool output is untrusted content
A correctly authenticated MCP server can return malicious content for ordinary reasons:
- it retrieved a poisoned README;
- a customer inserted instructions into a support ticket;
- a webpage contains hidden text;
- a compromised upstream API returned an adversarial string;
- another tenant’s data crossed an isolation boundary;
- a tool description changed and now steers tool selection.
Treat all returned natural language as data with provenance. It may inform the model. It may not grant authority.
Useful controls include:
- separate structured fields from display text;
- label source, tenant, classification, and retrieval time;
- bind evidence to stable references and hashes where possible;
- prevent tool results from directly changing the surfaced tool set;
- require a new policy decision before output becomes a tool argument;
- prevent raw external content from becoming durable memory automatically;
- redact credentials and restricted data before context assembly;
- restrict which sinks can coexist with untrusted sources.
This is why “sanitize the prompt” is not a complete control. The runtime must assume some hostile content will be admitted and contain what it can influence.
Local and remote tools fail differently
Local tools offer inspectability but increase host risk. Remote tools reduce local code execution but add mutable-service and network trust.
| Tool shape | Primary risk | Strong control |
|---|---|---|
| Local binary | host files, environment, credentials, process execution | sandbox, minimal mounts, no ambient credentials, pinned digest |
| Local MCP server | localhost trust, startup config, dependency chain | explicit admission, port ownership, process isolation, signed package |
| Remote MCP server | mutable behavior, auth, DNS/TLS, service compromise | registry identity, mTLS/TLS, audience-bound tokens, egress policy |
| Cloud connector | broad user delegation and changing external data | least scopes, per-user consent, result provenance, revocation |
| Skill or project instruction | code-like behavior hidden in natural language | review before activation, version pin, capability ceiling, test corpus |
“Local” is not synonymous with trusted. Project-open hooks, configuration files, skills, and localhost listeners are inbound content until admitted. “Remote” is not synonymous with unsafe. A remote service with strong identity, narrow scopes, a stable manifest, isolated data, and immediate revocation may have a smaller blast radius than a local process with host credentials.
Make composition risk visible
Many agent attacks need two individually legitimate capabilities:
read secret + communicate externally = exfiltration
read untrusted content + execute code = RCE path
retrieve customer record + update CRM = integrity path
read approval message + issue payment = transaction path
recall memory + write memory = persistence pathReview capability combinations, not only individual tools. The Context Pack Compiler can surface the smallest set for the current intent. The Policy Engine can deny dangerous co-presence or require step-up approval when a source and sink meet in one run.
A useful manifest check is a forbidden composition list:
forbidden_compositions:
- when:
sources: [external_web, inbound_email]
sinks: [secrets.read, network.post]
unless:
controls: [destination_bound, payload_previewed, human_approved]The syntax is deployment-specific. The principle is stable: risk emerges from paths.
Score the boundary, not the vendor
Do not ask whether an MCP client is “secure.” Score the controls around one server, one capability set, and one production environment.
Give each row 0 when the control is absent, 1 when it is documented or partially enforced, and 2 when it is enforced and covered by a failing regression test.
| Control | 0 points | 1 point | 2 points |
|---|---|---|---|
| Server identity | endpoint accepted from configuration | owner and endpoint recorded | identity, endpoint, and transport verified at connection |
| Manifest pinning | live discovery is accepted | tool names are allow-listed | schema and description hashes are pinned; drift denies |
| Description handling | metadata enters context as instruction | descriptions are labeled untrusted | static checks plus contextual isolation and decision-path tests |
| Capability minimization | full server catalog is surfaced | read and write tools are split | compiler surfaces only capabilities required for this intent |
| Principal separation | one shared credential | user and agent identities are logged | user, agent, server, and downstream audiences are independently verified |
| Scope design | wildcard or omnibus scopes | named scopes exist | progressive, per-capability scopes with resource-bound tokens |
| Argument policy | schema validation only | sensitive fields are previewed | destination, data class, amount, path, and tenant are policy-bound |
| Composition controls | tools reviewed independently | dangerous pairs are documented | source-to-sink combinations are denied or require step-up approval |
| Output containment | results enter model and memory directly | provenance is attached | untrusted text is separated, classified, and barred from automatic promotion |
| Execution containment | host privileges are inherited | partial filesystem or network restriction | sandbox, minimal mounts, egress policy, timeouts, and idempotency are enforced |
| Evidence and replay | agent summary is the audit record | tool calls are logged | accepted and denied calls, policy verdicts, receipts, and replay inputs are retained |
| Revocation | operator removes the UI entry | new discovery is blocked | registry, compilation, execution, credentials, and in-flight work share a kill path |
The maximum is 24.
- 0–8: demo-grade. A malicious or compromised integration can borrow ambient authority.
- 9–16: fragile. Controls exist, but important decisions still depend on convention or the model.
- 17–21: bounded with gaps. Suitable for limited rollout only when the missing controls cannot reach a high-impact sink.
- 22–24: production candidate. Still requires scenario-specific red-team runs, incident drills, and ongoing drift monitoring.
The score is not allowed to average away a hard stop. Token passthrough, an arbitrary proxy tool, silent schema drift, destructive calls without effect-specific approval, or no executable revocation path should block production admission regardless of the total.
Download the MCP tool-supply-chain policy starter. It turns the scorecard into a deny-by-default example covering manifest pinning, principal separation, scope minimization, forbidden compositions, evidence, and revocation. The YAML is an implementation starter, not a new ContextOS runtime contract; adapt its integration-specific fields to your gateway and identity provider.
The admission and release checklist
Before admitting an MCP server, connector, tool, or skill:
- Verify ownership, maintainer history, signing, dependencies, and update process.
- Pin what can be pinned: version, digest, schema, endpoint, protocol.
- Split read and write capabilities.
- Reject generic proxy tools that accept arbitrary methods, paths, or commands.
- Declare
ActionRisk, maximumApprovalMode, scopes, and data ceiling per capability. - Test wrong audience, expired token, revoked agent, cross-tenant access, and token passthrough.
- Test malicious descriptions, resources, and tool results.
- Test redirects, DNS changes, egress escapes, oversized payloads, and schema drift.
- Test duplicate calls, timeout after mutation, retry, and compensation.
- Record a named owner, review date, kill switch, and incident contact.
At runtime:
- resolve only admitted versions;
- deny drift rather than auto-accepting it;
- issue short-lived resource-bound credentials;
- enforce destination and data policy outside the model;
- emit accepted and denied
ToolCallEnvelope/ToolResultEnveloperecords; - monitor for new capabilities, scope growth, unusual destinations, and error-rate changes;
- make revocation effective at discovery, compilation, credential exchange, and execution.
Incident response needs a kill path
When a tool is compromised, removing it from a UI is not enough.
Revocation should:
- mark the adapter or capability revoked in the registry;
- prevent it from entering new compiled contexts;
- reject in-flight execution at the Gateway;
- revoke or expire credentials and consent grants;
- cancel or quarantine long-running tasks;
- identify runs, tenants, users, data, and mutations touched by the tool;
- preserve manifests, schemas, claims, and transcripts for replay;
- require re-admission under a new version before restoration.
The inventory and DecisionRecords that seemed operationally boring become the incident scope.
Research base
- MCP Security Best Practices for confused-deputy, consent, token-passthrough, SSRF, session, and local-server guidance.
- MCP Authorization for resource indicators, audience validation, PKCE, and resource-bound authorization requirements.
- MCPTox for its 1,312-case benchmark of tool-description poisoning across real-world MCP tools and 20 evaluated agents.
- Are AI-assisted Development Tools Immune to Prompt Injection? for its March 2026 comparison of seven MCP clients and cross-tool poisoning defenses.
- MCP Pitfall Lab for trace-grounded evaluation of metadata poisoning, cross-tool forwarding, and server hardening.
- Anthropic: How We Contain Claude Across Products for model, environment, external-content, sandbox, and egress boundaries.
- Google Cloud: Agent Identity and Agent Gateway for first-class agent principals and centralized agent-to-tool enforcement.
- OWASP Top 10 for Agentic Applications 2026 for agentic supply-chain, tool-misuse, identity, and code-execution risk categories.
- ContextOS: MCP Adapters in Production, Adapter Mesh, and Security and Compliance.
