DEV Community

Kiell Tampubolon
Kiell Tampubolon

Posted on

A happy-path MCP demo proves almost nothing about tenant isolation

Most MCP demos look the same. A tool is registered. A client calls it. The tool returns the expected data. Everyone nods.

That demo tells you the tool can work. It tells you nothing about what happens when the tool is given the wrong tenant ID, or when it declares scope it should not have, or when it is about to reach users who did not write it.

I built a preflight scanner for exactly that gap. This post is about what a bounded preflight can actually check, and what it honestly cannot.

The tools you expose are part of your attack surface

An MCP tool is a capability. Some capabilities are safe to hand out freely. Others are not.

Common risky declarations:

  • A tool that runs shell commands from user input.
  • A tool with filesystem:* or network:* scope when it only needs to read one directory.
  • A tool whose description contains a secret-like value from a copy-pasted config.
  • A tool that takes an unvalidated URL or file path from an agent.
  • A tool that writes without any approval requirement.

These are not exotic. They show up in real MCP servers being shipped right now.

What a bounded preflight does

The scanner I built runs two kinds of checks: static and behavioral.

Static rules

Static rules look at the tool metadata: name, description, declared scopes, and any command patterns. Four rules cover the common cases:

  • MCP-001: unsafe command declaration. Anything that runs a shell or takes a free-form command string.
  • MCP-002: excessive filesystem or network scope. Anything that declares filesystem:*, network:*, or network:egress when the tool clearly does not need it.
  • MCP-003: secret-like value in tool metadata. Anything that looks like an API key, token, or password embedded in a description or default config.
  • MCP-004: untrusted input reaching a sensitive operation. Anything where user input flows into a command, a file path, or a URL without an obvious boundary.

Behavioral checks

Behavioral checks actually call a local fixture server and observe what happens:

  • Tenant boundary: does a tenant-a token get tenant-b data?
  • Write approval: does a write tool run without an explicit, request-bound approval?
  • Quota: does a runaway loop get stopped, or does it keep calling the tool forever?

Each check produces a pass, fail, blocked, or incomplete status. Nothing is reported as a pass if it was not actually run.

What the report looks like

One run produces both a Markdown report and a JSON report. Each finding has:

  • A rule ID (MCP-001, etc.).
  • A severity (info, low, medium, high).
  • The affected tool.
  • A sanitized evidence line, with any secret-like value redacted.
  • A remediation.
  • A status (open, accepted, resolved).

The point is that a finding becomes an engineering task, not a vague warning.

What a preflight is not

It is not a penetration test. It is not a certification. It is not a scan of a real customer environment. It does not replace a real security review.

It is a bounded, repeatable first pass. It catches the obvious things before they ship, and it does so without pretending to be more than it is.

The one honest limit

A preflight can tell you that a tool declares too much scope. It cannot tell you that the scope is intentional and appropriate for the business. That is a decision for the team.

A preflight can tell you that a write tool ran without an approval. It cannot tell you whether the approval was correctly granted in a real workflow. That is a process question.

The scanner is a filter. It narrows the surface for the human review that still has to happen.

The code is at github.com/glatinone/mcp-security-preflight. If your team is about to expose MCP tools to users or internal agents and wants a bounded first pass, I take short sprints on exactly this. kielltampubolon.id

Top comments (2)

Collapse
 
mateo_ruiz_6992b1fce47843 profile image
Mateo Ruiz

The strongest point is treating “incomplete” as a real result rather than quietly turning it into a pass. For tenant isolation, I’d also test the boundary independently of the tenant ID supplied by the caller. An agent-controlled tenant_id should never be the authority for determining which tenant the request belongs to; that identity should come from an authenticated context and be enforced again at the data-access layer. Otherwise a preflight can confirm that tenant-a behaves correctly while missing the more fundamental confused-deputy problem. The static + behavioral combination is a good pattern because neither metadata inspection nor happy-path execution is enough to establish isolation.

Collapse
 
raknaos profile image
Baptiste Le Bouquin

That's the framing more MCP writeups need: a happy-path demo proves the tool can work, not that it's safe to expose. The scope-declaration problem especially: I've seen agents get a filesystem-scoped tool handed to them because it was the only thing that worked in the demo, and nobody revisits that grant when the deployment changes.

The honest part is what your preflight can't check: a tool can declare tight scope and still leak via its description or its error strings. I ended up treating the tool description itself as attack surface after a prompt injected through one. Are you parsing descriptions for secret-like content in the scanner, or is that still manual review?