In my vision of the digital landscape, the electric sheep are the software artifacts we build and ship: the containers, deployments, and pipelines running at all hours with varying degrees of human supervision. The question I keep coming back to is not whether AI agents are useful. They clearly are. The question is whether the people building and deploying them have thought carefully about what they are actually doing when they hand an autonomous system a set of tools and tell it to get to work.
Agents and the Harness That Runs Them
Let’s be precise about this, because the word “agent” has been stretched to cover everything from a chatbot with a system prompt to a fully autonomous pipeline orchestrator.
In practical terms, an agent is a program that uses a language model to make decisions, calls tools to act on those decisions, observes the results, and loops until the task is complete or something goes wrong. The canned description is “autonomous AI that executes multi-step tasks.” The real description is a program with a while loop, a model in the middle, and access to things that can cause harm if misused.
The LLM is not the agent. The LLM is the brain: the reasoning and planning layer that decides what to do next. The harness is everything else: the execution environment, the memory, the tool access, and the security layer that keeps the whole thing from doing something catastrophic. At the center of it sits the tool registry, which routes every tool call through a single chokepoint before anything executes. That chokepoint is where most harness implementations are severely under built.
The core of the harness is the execution loop. The harness packages conversation history, system prompt, and available tools, sends them to the model, and waits. The model responds with text, a tool call, or both. Tool calls get intercepted, validated, executed, and appended back to the conversation as observations, then the loop runs again. The state machine is strict: idle, thinking, executing, completed, failed. Iteration limits are not optional, and neither is context pruning. An agent without hard caps on both can run indefinitely or crash ungracefully when the token budget runs out. These are requirements, not optimizations.
STAY TUNED!
Learn more about JAX London
Tool Calls: The Same Idea, Two Contexts
Here is the thing that trips people up when they first encounter MCP: tool calls in an agent harness and tool calls via an MCP server are the same concept at different levels of abstraction.
In a local harness, the model emits a tool use request, your code dispatches to a local function, and the result comes back as a tool result. The tool is code you wrote, running in the same process or at most a subprocess you control. You can read it, audit it, and change it.
MCP extends that pattern outward. Instead of dispatching to a local function, the agent sends a JSON-RPC 2.0 request to an MCP server, which could be running locally, on another machine, or as a remote service. The model does not know the difference. From the model’s perspective, it asked for a tool and got a result. The protocol abstraction is completely transparent to the reasoning layer, which is exactly the problem.
This is why MCP is powerful and why MCP is dangerous. The model’s trust relationship with the tool does not change when you move from local dispatch to remote server. But your control over what that tool actually does changes enormously. A local function call lives in your codebase and gets reviewed. A remote MCP server is a network endpoint with its own code, its own attack surface, and its own trust assumptions.
The Attack Surface You Just Opened
When you move from local tool dispatch to MCP, you inherit the full threat model of a network service. The model does not know this happened. It still trusts the tool results the same way it trusted your local functions. That gap between model trust and actual security posture is where things go wrong.
The most dangerous attack vector here is prompt injection via tool results. The model calls a tool (say, a search server or a file reader) and the result contains adversarial instructions embedded in the content. The model, having no way to distinguish “data I retrieved” from “instructions I should follow,” processes the injected content as part of its reasoning context. This is not hypothetical. It has been demonstrated repeatedly against production systems. An attacker who can influence what a tool returns can influence what the agent does next.
Supply chain attacks against MCP servers follow the same pattern we see everywhere else, with the added dimension that a compromised tool server can exfiltrate context, inject instructions, and escalate to infrastructure access, all through the normal tool call flow your harness was designed to trust.
Excessive agency is the failure mode of treating agent autonomy as a feature with no downside. The agent has a `run_command` tool and no policy about which commands are acceptable. The agent has database credentials and no read-only enforcement. These are not AI problems. They are authorization problems. We just forgot to apply the same controls we would apply to any other automated system.
Still, eventually we all build something to run arbitrary commands in our pipelines. Passwordless SSH, anyone? I am looking at you, Ansible. Now add non-deterministic reasoning and unfettered access to your infrastructure. Are the electric sheep safe?
Agents and MCP Lethal Trifecta
The Lethal Trifecta.
– Private Data → Tools that can read secrets, repos, files
– Untrusted Content → Attacker-controlled input (issues, PRs, web content)
– External Communication → Any channel that can exfiltrate data (PRs, webhooks, email)
With all three, an attacker can trick it into reading private data and sending it out.
This is known as “the lethal trifecta for AI agents”. I think it applies to all our tools, MCP servers included. The danger comes from the combination: each capability alone is manageable; together they enable straightforward exfiltration. An example of such an attack was recently demonstrated by the GitHub MCP exploit, where a malicious actor is able to exfiltrate sensitive data from a GitHub repository by injecting a prompt into an AI agent that was able to access the repository.
It is best to implement a preventive design (isolation, policy) and require runtime detection (alerts, rate limits) as part of the controls.
Mitigations (high level)
– Enforce least privilege: separate tools that read private data from tools that can publish externally
– Treat untrusted inputs as hostile: validate, sanitize, and isolate before exposing to agents
– Block or tightly control outbound channels from tools that access sensitive data
– Monitor for suspicious patterns: unexpected reads + outbound actions trigger alerts
Keeping the Agent in Check
An autonomous agent is effectively remote code execution as a feature. If that framing makes you uncomfortable, it should. Without a robust security layer, an LLM can delete files, exfiltrate data, probe internal services, or crash the host system, and it will do all of this while helpfully explaining that it is trying to complete your task. A production-grade harness needs real security gates built into the execution path.
Command Validation
Every command needs to be inspected before execution, and that inspection needs to happen in the harness, not in the model’s judgment. That means a deny list for operations that are categorically off-limits: recursive deletes, disk formatting, system shutdown. An allow list for commands that are known-safe for the specific task. And regex pattern matching for dangerous flags, because the obvious cases are obvious but the non-obvious ones are not. Commands that are ambiguous should require explicit human approval before they execute. A command rejected by a human takes seconds. A command that deletes the wrong directory takes hours to recover from, if recovery is even possible.
Path Validation and the Sandbox
File system access must be restricted to a defined working directory. Every path the agent attempts to access needs to be resolved to its canonical absolute form first, then checked to ensure that resolved path falls within the sandbox root. This prevents directory traversal attacks where a model (confused, manipulated, or just trying to be helpful in the wrong direction) attempts to access something two or three levels above where it should be looking.
Canonicalization has to happen before the check, not after. A relative path that looks harmless can resolve to something dangerous after symlink expansion. A symlink inside the sandbox can point to a target outside the sandbox, and a naive prefix check on the unresolved path will miss it. Resolve first, check the resolved path. Both steps, in that order, on every access.
Human-in-the-Loop
For sensitive operations, the harness should pause and require explicit human approval. Interactive mode prompts for every tool call: maximum transparency, maximum friction. Autonomous mode auto-approves reads and requires confirmation for writes and executes, the right default for most production workloads. Full auto approves everything without human input. Full auto has legitimate uses, but most production systems that have had serious incidents were running in it with no monitoring, which is roughly equivalent to handing someone the keys to your infrastructure and leaving for the weekend.
Network Safety
If the agent has a fetch tool, it needs protection against server-side request forgery. The harness must block localhost and loopback addresses, deny requests resolving to private IP ranges, and restrict protocols to HTTP and HTTPS. IP address validation needs to happen after DNS resolution. DNS rebinding works by returning a public IP for the initial lookup and a private IP for subsequent ones, and a check that only runs on the hostname will miss it every time.
Resource Limits
Iteration limits cap thought loops. Context limits trigger FIFO pruning when history approaches the token budget. Output limits truncate tool results so a massive log file cannot flood the context and push out the system instructions the agent is supposed to be following; a truncated log is almost always more useful than a crashed agent. Execution timeouts kill shell commands running far longer than the task warrants. All four belong in the execution engine, not in individual tool implementations; if enforcement lives in the tools, someone will eventually write one that skips it.
MCP: Useful Standard, New Attack Surface
MCP is the right idea. Standardizing how agents discover and call external tools is genuinely useful, and the protocol (JSON-RPC 2.0 over HTTP or stdio) is simple enough that implementations are not inherently dangerous. The problem is not MCP itself. The problem is the same one we see every time a new integration pattern becomes popular: developers adopt the pattern faster than they adopt the security model that should accompany it.
The architecture puts an MCP server between the AI client and your tools and data. The client discovers what tools are available, calls them by name, and processes the results. This is clean and composable. Treat an MCP server like any other server in your pipeline: same security review, same access control analysis, same monitoring. The “AI” label does not exempt it from basic hardening.
The threat vectors are specific enough to be worth naming individually.
Tool poisoning embeds malicious instructions inside tool descriptions. The model reads tool descriptions to understand what each tool does, and an attacker who controls a tool server can include instructions that execute before, during, or after the intended operation. The model has no way to distinguish legitimate documentation from adversarial directives. The 2025-11-25 specification explicitly states that tool descriptions should be treated as untrusted unless obtained from a verified server, a formal acknowledgment of what attackers have been exploiting for months.
The confused deputy emerges when an MCP server proxies a third-party API. The attack works when a static client ID is combined with dynamic client registration: an attacker crafts a request with a malicious redirect URI, the browser already has a consent cookie from a prior legitimate authorization, and the authorization code goes to the attacker’s server instead of the legitimate one. The spec now requires per-client consent stored server-side and checked before any third-party authorization flow begins. OAuth state parameters must be generated securely, stored only after consent is given, and treated as single-use with short expiration. Setting the consent cookie before consent is approved (a common shortcut) completely negates the protection.
Token passthrough was a common anti-pattern: the MCP server accepts client tokens without validation and forwards them to downstream APIs. The spec now explicitly forbids it with MUST NOT language. Servers must not accept any tokens not specifically issued for them. Passthrough bypasses rate limiting that depends on proper token validation, breaks audit trails because the server cannot distinguish between callers, and means a single compromised token works across every service in the chain.
Session hijacking takes two forms. In the prompt injection variant, an attacker sends a malicious event to a second server using a stolen session ID: that server enqueues the payload and the first server delivers it to the legitimate client, which then executes the attacker’s instructions. In the impersonation variant, the attacker makes direct calls using the stolen session ID. The spec requires session IDs to be cryptographically secure, non-deterministic, and bound to user-specific information. Using a session ID for authentication rather than state management is explicitly prohibited.
Local server compromise is the newest addition to the threat catalog. Local MCP servers run on the user’s machine with direct system access, which makes them an attractive target. Attackers embed malicious startup commands in server configurations, distribute payloads inside seemingly legitimate packages, or access insecure localhost servers via DNS rebinding. Users typically have no visibility into what commands execute, and obfuscation makes malicious commands look routine. The spec now requires consent dialogs for one-click configuration that display the exact command (untruncated) before execution.
Scope minimization failures happen when MCP servers expose broad or wildcard permissions upfront. A single stolen token with `files:*` or `admin:*` scope enables access far beyond the original operation, makes selective revocation impossible without disrupting everything, and creates audit noise that obscures what users actually intended to do. The spec now requires progressive scope elevation: start with minimal baseline scopes and escalate only as specific privileged operations are needed. This limits blast radius when tokens are compromised and makes intent legible in audit logs.
Orchestrators and Subagents
The pattern that emerges in production agentic systems is an orchestrator model: one agent that plans and routes work, multiple specialist subagents that execute specific types of tasks, each with its own tool access and its own scope.
The orchestrator knows the goal and knows which subagent should handle which part of it. It does not have direct access to production systems; it communicates with subagents that do. A coding subagent has access to repositories and test runners. A search subagent has access to retrieval infrastructure. An infrastructure subagent has scoped deployment credentials. The orchestrator has direct access to none of these.
This pattern is the same separation of concerns we apply everywhere else in software architecture, applied to agent systems. It limits blast radius when something goes wrong, and in autonomous systems with external tool access, something will eventually go wrong. Each subagent gets a minimal tool set scoped to its role. The boundaries are structural where possible, not just policy. Policy can be bypassed. Structure requires deliberate circumvention.
The Monitoring You Actually Need
Monitoring an agentic system means monitoring tool calls. That is the unit of action that matters. Log every tool call with the tool name, inputs, result summary, and the model response that preceded it. This gives you a complete audit trail of what the agent decided to do and what happened when it did it.
Watch for tool invocation patterns that do not fit the expected task. An agent that starts calling filesystem tools in the middle of a code review task is either confused or compromised. Rate limiting on tool calls is not just a cost control measure; it is an anomaly detection mechanism. A sudden spike in tool call volume is a signal worth investigating.
Tool definition changes are worth alerting on. An MCP server that returns different tool descriptions than it returned yesterday has changed in a way that warrants a review. This is the tool poisoning vector made detectable, if you are looking for it.
Scope elevation attempts (cases where the agent tries to access resources outside its authorized scope) should generate alerts, not just access-denied failures. The failure is the security control working. The alert is how you know someone is testing your boundaries, or the model is confused, or something upstream has changed.
The Takeaway
Agentic AI in the SDLC is not a future concern. Development teams are building and deploying agent harnesses today, integrating them with MCP servers, and connecting them to production systems. The security analysis for these systems is not novel; it applies the same principles we have always applied to automated systems with external access. What is novel is the attack surface created by model trust in tool results, and the tendency of developers to ship first and think about authorization second.
Understand the loop before you ship it. Define tool sets by what each agent actually needs for its specific role. Validate tool results before acting on them. Build the security gates into the execution engine where they cannot be skipped. Treat MCP servers like the network services they are. Monitor tool calls as the primary unit of auditable action.
The electric sheep are only as safe as the shepherds watching them, and the shepherds need to know what tools they handed the gremlins before the gremlins got into the infrastructure.
Author
🔍 FAQ
1. What is an AI agent in software development?
An AI agent is a program that uses a language model to make decisions, call tools, observe the results, and repeat that process until it completes a task or fails. The language model provides reasoning, while the surrounding agent harness controls execution, memory, tool access, and security.
2. What is an AI agent harness?
An agent harness is the execution environment around the language model. It manages the agent loop, memory, available tools, tool validation, security controls, iteration limits, and execution state. A key part of the harness is the tool registry. It acts as a chokepoint where tool calls can be inspected, approved, rejected, or restricted before anything executes.
3. What is MCP in AI agents?
Model Context Protocol, or MCP, is a standard that allows AI systems to discover and call external tools and services. Instead of calling only local functions, an agent can send requests to an MCP server running locally, on another machine, or as a remote service. From the model's perspective, both local and MCP tools return tool results. The security difference is that a remote MCP server introduces its own code, permissions, network exposure, and trust assumptions.
4. Why can MCP increase the attack surface of an AI agent?
MCP can expand the attack surface because it moves tool execution outside the local agent environment without changing how the model interprets the result. A local tool may be code that your team owns and audits. An MCP server can be an external network service. If that server is compromised or returns manipulated content, the model may still process the result as trusted context.
5. How does prompt injection through AI tools work?
Prompt injection through tools happens when an agent retrieves content that contains malicious instructions. For example, a file, search result, pull request, or tool response can contain attacker-controlled text. Because the model may not reliably separate retrieved data from instructions, the malicious content can influence what the agent does next.
6. What is the lethal trifecta for AI agents?
The lethal trifecta describes the combination of three capabilities: Access to private data, such as repositories, files, or secrets. Access to untrusted content, such as web pages, issues, or pull requests. Access to an external communication channel, such as email, webhooks, or publishing tools. When an agent has all three, an attacker may be able to manipulate it into reading sensitive information and sending that information outside the trusted environment.
7. What is tool poisoning in MCP?
Tool poisoning occurs when malicious instructions are embedded inside an MCP tool description. AI models use tool descriptions to understand when and how a tool should be called. If an attacker controls or compromises those descriptions, they may influence the agent before or during tool execution. Tool descriptions therefore need to be treated as untrusted unless they come from a verified server.





