How do input schemas work for MCP tools, and why use JSON Schema?

5 minintermediatemcptoolsjson-schemavalidation

Quick Answer

Every tool declares its arguments as a JSON Schema object in inputSchema — types, required fields, enums, nested objects, descriptions per field. JSON Schema was chosen because it's language-agnostic, widely supported by tooling, and expressive enough to constrain both structure (which fields exist, their types) and semantics (via description, enum, minimum/maximum, etc.) without inventing a new schema language. Before executing a tools/call, a well-behaved server should validate the incoming arguments against this schema and return an error result for anything that doesn't conform, rather than trusting the model's output blindly — the model can and does occasionally produce malformed or incomplete arguments.

Detailed Answer

The inputSchema is the contract. It says what the model can produce, and what your function actually needs.

A richer example

{
  "name": "create_issue",
  "description": "Create a GitHub issue in the given repository.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "repo": { "type": "string", "description": "owner/repo, e.g. 'acme/webapp'" },
      "title": { "type": "string", "minLength": 1 },
      "labels": { "type": "array", "items": { "type": "string" } },
      "priority": { "type": "string", "enum": ["low", "medium", "high"] }
    },
    "required": ["repo", "title"]
  }
}

Why JSON Schema specifically

  • It's the schema language most LLM providers already expect for function-calling. A host can pass an MCP tool's inputSchema straight through to the model's API with little or no transformation.
  • It supports enough richness (enum, pattern, nested object/array, minimum/maximum) to catch a large class of malformed calls structurally, before your handler code runs.
  • It's self-describing. Per-field description strings give the model extra guidance beyond the top-level tool description, like the expected format of a date string or ID.

Server-side validation is still your job

MCP doesn't validate arguments for you at the protocol level. That's the server's responsibility:

from jsonschema import validate, ValidationError

def call_tool(name, arguments):
    schema = TOOLS[name]["inputSchema"]
    try:
        validate(instance=arguments, schema=schema)
    except ValidationError as e:
        # Skipping this check means a model hallucinating a wrong field name
        # or type reaches business logic directly — a common source of
        # confusing runtime errors, and in the worst case a security gap.
        return {"isError": True, "content": [{"type": "text", "text": f"Invalid arguments: {e.message}"}]}
    return execute(name, arguments)

Related Resources