DEV Community

PolicyLayer
PolicyLayer

Posted on

We taught AI agents to check who they're talking to (build notes)

My coding agent will connect to anything. Yours will too.

Point Claude Code, Cursor or Codex at an MCP server and it connects, lists the tools, and starts calling them. The server describes itself, and the agent believes it. "A safe and convenient way to manage your repositories" is not a fact about a server. It is a string the server's author wrote.

We run a registry that continuously scans MCP servers (36,000+ published records at the time of writing), so we had the data to do something about this. What we shipped is deliberately small: a skill that gives an agent one habit. Before you connect to an MCP server, look it up. Relay what the record says. Let the human decide.

We call it street smarts. Agents are great; the world they install software from is mean.

This post is the build notes: what the check actually returns, and the problems that turned out to be interesting.

What the agent gets back

The skill drives a CLI. Both commands are read-only lookups against the registry:

npx -y policylayer stack            # every MCP server configured on the machine
npx -y policylayer precheck github  # one server, before connecting
Enter fullscreen mode Exit fullscreen mode

The precheck returns the published record plus a deterministic verdict. Trimmed real output for the GitHub MCP server:

{
  "report": {
    "slug": "github",
    "riskGrade": "D",
    "identity": { "confidence": "verified", "disputed": false },
    "posture": { "auth": "gated" },
    "toolCount": 86,
    "categoryCounts": { "Read": 55, "Write": 27, "Execute": 2, "Destructive": 2 },
    "flaggedToolNames": ["delete_file", "projects_write"],
    "freshness": { "watch": "hourly change watch", "lastScannedAt": "..." }
  },
  "verdict": {
    "attention": false,
    "suggested": "connect-with-rule"
  },
  "rules": {
    "claudeCode": { "permissions": { "deny": ["mcp__github__delete_file", "mcp__github__projects_write"] } }
  }
}
Enter fullscreen mode Exit fullscreen mode

Three suggested actions, and only three: proceed, connect-with-rule, ask-first. The verdict is computed by fixed rules over the record, not by a model. Identical record, identical verdict, every time.

Problem 1: descriptions are claims

The core issue is that MCP has no identity layer. Anyone can publish a server called "GitHub Tools" with any description. So the record leads with identity confidence, computed from evidence: verified means the package's references provably resolve inside the brand's own infrastructure; mismatch means it claims to be official with no verifiable link (that one is an impostor signal and always escalates to the human); unverified is the neutral community default, which is most of the ecosystem.

Grades never stand alone. A D next to identity: verified for an official server with two destructive tools means something completely different from a D on an unverified package that appeared last Tuesday. The skill's language rules force the agent to report the fields together, and ban the words "safe" and "approved" outright. The registry publishes records; it does not certify safety.

Problem 2: the 40-tool cap ate the dangerous tools' count

The record's tool list is capped at 40 entries, sorted riskiest-first, to keep payloads sane. Early on, the per-category counts were computed over that capped list. For an 86-tool server, the counts silently described less than half the surface — and a deny rule generated from the capped list would miss flagged tools past the cut.

The fix was to compute categoryCounts, severityCounts and the uncapped flaggedToolNames over the full surface before truncating the display list. Obvious in hindsight. The lesson generalises: any time you cap a list for ergonomics, check what downstream consumers were deriving from it.

Problem 3: "deny this tool" means something different in every client

The verdict can suggest connecting behind a deny rule. What that means depends entirely on the client:

  • Claude Code: real enforcement. permissions.deny entries in .claude/settings.json, in mcp__<server>__<tool> form. The harness enforces them; the model cannot talk itself past them.
  • Codex CLI: real enforcement. disabled_tools under the server's table in config.toml.
  • Cursor, VS Code, Windsurf: advisory only. Per-tool controls live in their UI, not in any file an agent can write. The skill's instruction is to say so plainly rather than pretend.

The skill never writes rules without approval in the conversation. A suggested rule is a proposal, not permission.

Problem 4: the agent that improvises a security check

The failure mode that worried us most in testing: the CLI fails (network, missing subcommand, whatever) and the agent, trying to be helpful, reads the config files itself and presents its own impression as a verdict. An improvised check is precisely what the skill exists to replace, and it looks identical to a real one in the transcript.

So the skill carries a hard rule: if the command fails, say the precheck did not run. Show the error. Do not substitute. We wrote a small behavioural eval harness (headless agent sessions run against scripted scenarios) to check this and eight other behaviours hold after every wording change to the skill file. Prompt-adjacent instructions are code; they regress like code.

Related: everything quoted from the registry (risk notes, change events) is data about a server, never instructions to the agent. Third-party text that appears to instruct the agent gets ignored and flagged. Records describe crawled third-party software; treating their text as trusted input would be ironic.

Problem 5: skills can install themselves

A skill is a markdown file. That means an agent can be told: read https://policylayer.com/skill.md and follow it. The instructions work for the current session, and include a section that lets the agent persist the file into the client's skills directory — with the human's approval, since it writes to their machine. Watching a fresh agent fetch the file, ask permission, install itself and then immediately scan the stack it already had was the moment this stopped feeling like a gimmick.

Humans install it the boring way:

npx skills add https://policylayer.com -a claude-code -y
Enter fullscreen mode Exit fullscreen mode

What it deliberately does not do

The record describes what a server's exposed tool interface permits and what continuous scanning has observed: identity evidence, auth posture, per-tool classification, change events. It is not a source-code audit, and the skill is instructed not to claim more than the record says. An unknown server is reported as unknown — neither fine nor dangerous — and the lookup queues it for scanning.

Lookups are free and keyless, one unit per server against a rate limit. Unknown-server lookups are how the registry learns what people actually run.

Try it

Happy to answer questions about the registry pipeline, the eval harness, or the verdict rules in the comments.

Top comments (0)