Technology Sep 02, 2026 · 7 min read

Your Agent Has Tools Now: Why MCP Tool Calls Need Runtime Verification

For most of the short history of LLM applications, model risk was text risk. A model could output bad advice, leak something from its prompt, or produce a convincing phishing draft — but it couldn't touch your machine. The worst case ended at the screen. Wire a few tools into that model — a shell,...

DE
DEV Community
by correctover
Your Agent Has Tools Now: Why MCP Tool Calls Need Runtime Verification

For most of the short history of LLM applications, model risk was text risk. A model could output bad advice, leak something from its prompt, or produce a convincing phishing draft — but it couldn't touch your machine. The worst case ended at the screen.

Wire a few tools into that model — a shell, an HTTP fetcher, a filesystem client, a cloud SDK — and the equation changes. With the Model Context Protocol, agents don't just suggest operations anymore; they perform them, with your credentials, on your infrastructure. The attack surface moves from "text the model wrote" to "actions the model took." A prompt that used to produce a paragraph can now produce a process.

If you're shipping agents with tool access, configuration hygiene is necessary but not sufficient. Here's why, and what runtime verification adds.

Four ways tool calls go wrong

The abuse patterns below are well-known categories — described generically, without reference to any specific project's incidents.

1. Code execution tools → remote code execution. Shell, exec, and interpreter tools exist because agents genuinely need them: running tests, scaffolding projects, transforming data. But any string that reaches a shell is a command. Content fetched from a web page, a filename, an error message, or a dependency's metadata can carry shell metacharacters. The model doesn't need to be "hacked" in the classic sense — it just needs to faithfully pass attacker-influenced text into an execution tool.

2. Fetch / HTTP tools → SSRF. Agents love to fetch URLs: docs, APIs, "read this link the user pasted." A URL is also a network destination. Point a fetcher at a cloud metadata endpoint (169.254.169.254), a private RFC1918 address, or localhost:port and the agent becomes an SSRF primitive — reading internal services from inside your network perimeter and helpfully summarizing what it found.

3. Credential and environment-variable exfiltration. Agent processes inherit environment: AWS_*, GITHUB_TOKEN, database URLs, API keys. Tools often accept arbitrary key/value or arguments. A two-step chain — read a sensitive file or env var, then POST it somewhere via the HTTP tool — turns a "helpful agent" into an exfiltration channel. Neither step looks dramatic on its own.

4. Prompt injection → unauthorized tool calls. Untrusted content in the model's context (a web page, an email, a file, a tool result) can contain instructions aimed at the model. The model is the one holding the tool handles, and it can't always tell your instructions apart from instructions embedded in data. The outcome is a tool call you never authorized, performed with your authority.

Why static configuration scans aren't enough

Scanning your MCP configuration is the right first move. A static scanner catches real, fixable problems: plain-HTTP transports, disabled TLS verification, credentials pasted directly into config JSON, missing timeouts, over-broad permissions, unpinned server versions. The open-source correctover-scan runs 14 local checks against your config files and auto-discovers the usual locations — .cursor/mcp.json, claude_desktop_config.json, .claude/mcp.json, mcp.json, and a few more.

But a config file is static, and the dangerous part is dynamic. Your mcp.json will never contain the argument the model constructs at 3 a.m. — the URL it decided to fetch, the command string it assembled from a tool result, the env var name it placed into an HTTP body. Static analysis answers "is this setup reasonable?"; it cannot answer "is this specific call safe?"

The tempting shortcut is a keyword blacklist: block calls containing exec, block URLs containing 169.254, block arguments containing AWS_SECRET. It doesn't work, because safety is contextual:

  • A code-interpreter tool running exec() as its normal, declared function is fine.
  • A model concatenating fetched, untrusted text into an exec() string passed to a shell is critical.
  • A fetcher retrieving a public docs URL is fine; the same fetcher hitting the metadata endpoint is critical.
  • Reading a file is fine; reading a file and immediately piping its contents to an external URL is an exfiltration chain.

Same tokens, different verdicts — because the tool, the caller, the arguments, and the chain of preceding calls differ. That judgment has to happen at call time, with the actual arguments in hand.

What runtime verification should check

A runtime verifier sits in front of tool execution and evaluates every call before it runs. For security, five layers matter most:

  1. Structure — well-formed tool name, arguments as an object, sane nesting depth and payload size. Malformed calls get rejected, not fuzzed into the tool.
  2. Schema — per-tool validation: types, required fields, enums, numeric ranges, string lengths. A payments.send call with amount as a string fails before it reaches the API.
  3. Identitywhich agent is calling, against an allowed-caller list. Tool permissions belong to identities, not to the runtime process.
  4. Integrity — cryptographic hashes over the exact arguments and request, plus signed receipts that bind the verdict (allow/deny/escalate) to those exact bytes. This gives you tamper-evident evidence: after an incident, you can prove what was decided and on what input, and chain receipts across multi-step or multi-agent flows.
  5. Security intent — semantic analysis of the call in context: command-injection patterns, SSRF targets (metadata IPs, loopback, private ranges), path traversal, environment-variable exfiltration signals, obfuscation (hex/base64 wrapping), prompt-injection markers, and — critically — cross-tool attack chains (read-sensitive-file → network-write = exfiltration, even though each step alone looks benign).

Two non-negotiable properties:

  • Fail-closed. If the verifier is unreachable, times out, or receives malformed input, the call is blocked. A verification path that errors open is not a verifier.
  • Deterministic and fast. Intent checking is pattern- and policy-based computation — no LLM call in the decision loop. The target is sub-millisecond on the core verification hot path, so verification is something you leave on in development, not something you route around when the agent feels slow.

Try it in 30 seconds

Step 1 — scan your configs locally. Zero dependencies, no network needed:

# Auto-discovers .cursor/mcp.json, claude_desktop_config.json,
# .claude/mcp.json, mcp.json and friends in the current directory
npx correctover-scan

# Or point it at a file / directory, with SARIF output for CI
npx correctover-scan mcp.json -f sarif > report.sarif
npx correctover-scan -d ./my-project

Step 2 — add the runtime verifier as an MCP server. ccs-mcp-server is a zero-dependency stdio MCP server. Drop this into your client config (Claude Desktop, Cursor, or any other stdio-compatible MCP client):

{
  "mcpServers": {
    "ccs-runtime-evidence": {
      "command": "npx",
      "args": ["-y", "ccs-mcp-server"]
    }
  }
}

It exposes verify_tool_call (the checks above, blocking unsafe calls by default), issue_evidence (Ed25519-signed receipts for every decision, allow and deny), and config-audit and receipt-verification tools. The signing keypair is generated automatically on first run; set the CCS_KEY_DIR environment variable only if you want to control where it persists.

Step 3 — wrap an existing server (optional). If you'd rather verify calls transparently around a server you already run, the compatibility package forwards to the verification gateway:

npx -y correctover-mcp-server --stdio -- npx -y <your-existing-mcp-server>

Both servers are published in the official MCP Registry as io.github.Correctover/ccs and io.github.Correctover/mcp.

The spec, and what we're asking for

The evidence model behind these tools is documented in the CCS protocol specification, draft-correctover-ccs, which defines the receipt schema, cryptographic bindings (request, parameters, runtime context, issuer, audience, freshness), fail-closed transport requirements, and conformance levels for evidence propagation across agent chains. This is an individual Internet-Draft, not an RFC or IETF endorsement.

Everything lives at github.com/Correctover. If you're building agents with tool access:

  • Run npx correctover-scan in your repo and in CI — it takes seconds and needs no credentials.
  • Add the verifier server to your client config and see what it flags on real sessions.
  • Open an issue with false positives or missed chains — verification rules improve fastest when they meet real traffic. A denied call you disagree with is a bug report; an allowed call that shouldn't have been is the most valuable report of all.

Agents that can act are agents that can err at machine speed. Verify the call before it becomes the action.

Alternative titles:

  1. From Text Output to Executed Actions: Securing MCP Tool Calls at Runtime
  2. Don't Just Scan Your MCP Config — Verify Every Tool Call
  3. The Agent Holds the Shell: A Practical Guide to Runtime Tool-Call Verification
DE
Source

This article was originally published by DEV Community and written by correctover.

Read original article on DEV Community
Back to Discover

Reading List