MCPSafe.io
RegistryThreatsMethodologyDocsPricingScanSign in
MCPSafe.io

Security checks for MCP servers — public packages and private repos, fast or deep.

Legal

Privacy PolicyCookie PolicyTerms of ServiceSecurity disclosure

Resources

State of MCP SecuritySupportSystem statusMade in Germany 🇩🇪

© 2026 MCPSafe. All rights reserved.

GDPR — Privacy Policy
← Threat Catalog

Tool Definition & Lifecycle

Overbroad String Schema on Risky Field

MEDIUMCWE: CWE-20Rule: MCP-206

MCP tool input schemas that accept unconstrained strings for fields like command, sql, or path allow any caller to pass arbitrary values, dramatically widening the tool's blast radius.

What it is

An unconstrained string field in a tool's input schema imposes no validation on the values callers can supply. When that field carries a semantically dangerous name — command, sql, code, script, url, path, expr, eval — the absence of an enum, regex, or length bound means the field functions as an open injection surface. This is a specific instance of CWE-20 (Improper Input Validation) at the schema-definition layer, before any handler logic runs.

Why it matters for MCP

MCP tools are invoked by LLM agents that construct arguments programmatically from natural-language context, meaning adversarial content in the conversation can flow directly into tool parameters without a human review step. A loose schema signals to the framework — and to the model — that any value is acceptable, so prompt injection payloads or jailbreak-crafted arguments pass validation silently. Unlike a traditional REST endpoint where a human developer typically controls the request, MCP's tool-composition model means one tool's unconstrained output can become another tool's unconstrained input, cascading the risk across an entire agent pipeline.

Vulnerable example

example.js
1
import { z } from "zod";
2
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
4
const server = new McpServer({ name: "shell", version: "1.0.0" });
5
6
server.tool(
7
  "run_command",
8
  { command: z.string() },
9
  async ({ command }) => {
10
    const { stdout } = await import("node:child_process").then(m => m.execAsync(command));
11
    return { content: [{ type: "text", text: stdout }] };
12
  }
13
);

Secure example

example.js
1
import { z } from "zod";
2
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
4
const server = new McpServer({ name: "shell", version: "1.0.0" });
5
6
server.tool(
7
  "run_command",
8
  { command: z.enum(["git status", "ls", "df -h"]) },
9
  async ({ command }) => {
10
    const { stdout } = await import("node:child_process").then(m => m.execAsync(command));
11
    return { content: [{ type: "text", text: stdout }] };
12
  }
13
);

How MCPSafe detects this

MCPSafe performs taint-aware schema analysis, flagging any `z.string()` or `z.any()` node bound to an object key whose name matches the risky-field lexicon (command, query, sql, code, script, url, path, expr, eval) with no chained refinement calls (`.enum()`, `.regex()`, `.max()`, `.url()`, `.uuid()`). Fields whose key names do not appear in the risky-field list, and any string field that chains at least one constraining method, are explicitly excluded from the finding.

See the full threat catalog for every documented detection.

Further reading

  • CWE-20: Improper Input Validation
  • Zod Schema Validation — refinements and constraints
  • OWASP Input Validation Cheat Sheet
  • Model Context Protocol: Tool Definition Specification

Scan an MCP server for this issue

MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.

Scan now