🔥 0
0
Lesson 2 of 10 22 min +175 XP

Defining Tools with JSON Schema

Before an AI agent can use your tools, you need to tell it what tools exist, what they do, and what parameters they accept. This is done through tool definitions using JSON Schema.

Think of tool definitions as contracts between your system and the AI model. A well-defined tool leads to accurate usage; a poorly defined one leads to errors and frustration.

The Golden Rule of Tool Definitions

If a human developer couldn't understand how to use your tool from its definition alone, neither can an AI model. Be explicit, be precise, be helpful.

Anatomy of a Tool Definition

Every MCP tool has three essential components:

{
  "name": "get_order_details",
  "description": "Retrieves full details for a specific order including items, shipping status, and payment information",
  "inputSchema": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string",
        "description": "The unique order identifier (e.g., 'ORD-12345')"
      }
    },
    "required": ["order_id"]
  }
}
1
name

Unique identifier the AI uses to call the tool

2
description

Explains what the tool does and when to use it

3
inputSchema

JSON Schema defining parameters and validation

Tool Naming Best Practices

Tool names should be clear, consistent, and action-oriented:

Bad Good Why
data get_customer_data Specific action + resource
processPayment process_payment Use snake_case consistently
do_stuff calculate_shipping_rate Descriptive and specific
inv check_inventory_level No abbreviations

Naming Convention Pattern

{action}_{resource}[_{detail}]
Actions: get, list, create, update, delete, check, calculate, process, search Examples:
  • get_order - Retrieve a single order
  • list_orders - Retrieve multiple orders
  • create_refund - Create a new refund
  • update_inventory - Modify inventory levels
  • calculate_shipping_rate - Compute shipping cost

Writing Effective Descriptions

Descriptions are critical for AI understanding. They should answer:

  • What does this tool do?
  • When should it be used?
  • What does it return?

BAD DESCRIPTION

"Gets order"

Too vague. What order? What data is returned? When should it be used?

GOOD DESCRIPTION

"Retrieves complete order details by order ID, including line items, shipping address, payment status, and fulfillment state. Use when a customer asks about a specific order."

JSON Schema Deep Dive

The inputSchema uses JSON Schema to define parameters. Let's explore the common patterns:

Basic Types

{
  "type": "object",
  "properties": {
    "product_id": {
      "type": "string",
      "description": "Product SKU (e.g., 'SHIRT-BLUE-L')"
    },
    "quantity": {
      "type": "integer",
      "description": "Number of items",
      "minimum": 1,
      "maximum": 100
    },
    "price": {
      "type": "number",
      "description": "Price in dollars (e.g., 29.99)"
    },
    "in_stock": {
      "type": "boolean",
      "description": "Whether the item is currently available"
    }
  },
  "required": ["product_id", "quantity"]
}

Enums for Fixed Options

{
  "type": "object",
  "properties": {
    "order_status": {
      "type": "string",
      "enum": ["pending", "processing", "shipped", "delivered", "cancelled"],
      "description": "Current status of the order"
    },
    "shipping_method": {
      "type": "string",
      "enum": ["standard", "express", "overnight"],
      "description": "Selected shipping speed"
    }
  }
}

Arrays for Multiple Items

{
  "type": "object",
  "properties": {
    "product_ids": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "List of product SKUs to check inventory for",
      "minItems": 1,
      "maxItems": 50
    }
  }
}

Nested Objects

{
  "type": "object",
  "properties": {
    "shipping_address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "state": { "type": "string" },
        "zip": { "type": "string" },
        "country": { "type": "string", "default": "US" }
      },
      "required": ["street", "city", "state", "zip"]
    }
  }
}
Keep Schemas Flat When Possible

Deeply nested schemas increase token count and cognitive load for AI models. If you need complex structures, consider breaking them into separate tools.

Complete E-commerce Tool Examples

Let's build real tool definitions for common e-commerce operations:

1. Check Inventory Tool

{
  "name": "check_inventory",
  "description": "Checks current inventory levels for one or more products across all warehouses. Returns available quantity, reserved quantity, and restock dates. Use when customers ask about availability or when processing orders.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "product_ids": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Product SKUs to check (e.g., ['SHIRT-BLUE-L', 'PANTS-BLACK-M'])",
        "minItems": 1,
        "maxItems": 20
      },
      "warehouse_id": {
        "type": "string",
        "description": "Specific warehouse to check. Omit to check all warehouses.",
        "enum": ["US-EAST", "US-WEST", "EU-CENTRAL", "APAC"]
      }
    },
    "required": ["product_ids"]
  }
}

2. Process Payment Tool

{
  "name": "process_payment",
  "description": "Charges a customer's saved payment method or creates a new charge. Returns payment confirmation or error details. Only use after order has been validated and customer has confirmed purchase.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "customer_id": {
        "type": "string",
        "description": "Customer identifier (e.g., 'cus_abc123')"
      },
      "amount": {
        "type": "integer",
        "description": "Amount in cents (e.g., 2999 for $29.99)",
        "minimum": 50
      },
      "currency": {
        "type": "string",
        "enum": ["usd", "eur", "gbp", "cad"],
        "default": "usd",
        "description": "Three-letter ISO currency code"
      },
      "payment_method_id": {
        "type": "string",
        "description": "Saved payment method ID. If omitted, uses customer's default payment method."
      },
      "idempotency_key": {
        "type": "string",
        "description": "Unique key to prevent duplicate charges. Generate a UUID for each transaction."
      },
      "metadata": {
        "type": "object",
        "description": "Additional data to attach to the payment (order_id, etc.)",
        "additionalProperties": { "type": "string" }
      }
    },
    "required": ["customer_id", "amount", "idempotency_key"]
  }
}

3. Calculate Shipping Rate Tool

{
  "name": "calculate_shipping_rate",
  "description": "Calculates shipping costs for an order based on destination, weight, and shipping method. Returns available shipping options with prices and estimated delivery dates. Use before checkout to show shipping options.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "origin_zip": {
        "type": "string",
        "description": "Warehouse zip code (defaults to nearest warehouse)",
        "pattern": "^[0-9]{5}(-[0-9]{4})?$"
      },
      "destination": {
        "type": "object",
        "properties": {
          "zip": { "type": "string", "pattern": "^[0-9]{5}(-[0-9]{4})?$" },
          "country": { "type": "string", "default": "US" }
        },
        "required": ["zip"]
      },
      "package": {
        "type": "object",
        "properties": {
          "weight_oz": {
            "type": "number",
            "description": "Total weight in ounces",
            "minimum": 0.1
          },
          "dimensions": {
            "type": "object",
            "properties": {
              "length": { "type": "number" },
              "width": { "type": "number" },
              "height": { "type": "number" }
            },
            "description": "Dimensions in inches"
          }
        },
        "required": ["weight_oz"]
      },
      "shipping_methods": {
        "type": "array",
        "items": {
          "type": "string",
          "enum": ["standard", "express", "overnight", "economy"]
        },
        "description": "Which shipping methods to quote. Omit for all options."
      }
    },
    "required": ["destination", "package"]
  }
}

Implementing Tools in Python

Here's how these definitions translate to actual MCP server code:

from mcp.server import Server
from mcp import types
import json

app = Server("ecommerce-tools")

@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="check_inventory",
            description="Checks current inventory levels for products...",
            inputSchema={
                "type": "object",
                "properties": {
                    "product_ids": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "Product SKUs to check",
                        "minItems": 1
                    },
                    "warehouse_id": {
                        "type": "string",
                        "description": "Specific warehouse to check",
                        "enum": ["US-EAST", "US-WEST", "EU-CENTRAL"]
                    }
                },
                "required": ["product_ids"]
            }
        ),
        types.Tool(
            name="process_payment",
            description="Charges a customer's payment method...",
            inputSchema={
                "type": "object",
                "properties": {
                    "customer_id": {"type": "string"},
                    "amount": {"type": "integer", "minimum": 50},
                    "idempotency_key": {"type": "string"}
                },
                "required": ["customer_id", "amount", "idempotency_key"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "check_inventory":
        # Validate and execute inventory check
        product_ids = arguments["product_ids"]
        warehouse = arguments.get("warehouse_id")

        result = await inventory_service.check(product_ids, warehouse)
        return [types.TextContent(type="text", text=json.dumps(result))]

    elif name == "process_payment":
        # Process payment with Stripe
        result = await payment_service.charge(
            customer_id=arguments["customer_id"],
            amount=arguments["amount"],
            idempotency_key=arguments["idempotency_key"]
        )
        return [types.TextContent(type="text", text=json.dumps(result))]

    raise ValueError(f"Unknown tool: {name}")

Implementing Tools in TypeScript

import { Server } from "@modelcontextprotocol/sdk/server";
import { Tool, TextContent } from "@modelcontextprotocol/sdk/types";

const server = new Server({
  name: "ecommerce-tools",
  version: "1.0.0"
});

server.setRequestHandler("tools/list", async () => {
  return {
    tools: [
      {
        name: "check_inventory",
        description: "Checks current inventory levels for products across warehouses",
        inputSchema: {
          type: "object",
          properties: {
            product_ids: {
              type: "array",
              items: { type: "string" },
              description: "Product SKUs to check",
              minItems: 1
            },
            warehouse_id: {
              type: "string",
              enum: ["US-EAST", "US-WEST", "EU-CENTRAL"]
            }
          },
          required: ["product_ids"]
        }
      },
      {
        name: "calculate_shipping_rate",
        description: "Calculates shipping costs based on destination and package details",
        inputSchema: {
          type: "object",
          properties: {
            destination_zip: { type: "string", pattern: "^[0-9]{5}$" },
            weight_oz: { type: "number", minimum: 0.1 },
            shipping_method: {
              type: "string",
              enum: ["standard", "express", "overnight"]
            }
          },
          required: ["destination_zip", "weight_oz"]
        }
      }
    ]
  };
});

server.setRequestHandler("tools/call", async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case "check_inventory":
      const inventory = await checkInventory(args.product_ids, args.warehouse_id);
      return { content: [{ type: "text", text: JSON.stringify(inventory) }] };

    case "calculate_shipping_rate":
      const rate = await calculateRate(args.destination_zip, args.weight_oz, args.shipping_method);
      return { content: [{ type: "text", text: JSON.stringify(rate) }] };

    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

Common Validation Patterns

Pattern JSON Schema Use Case
Email {"type": "string", "format": "email"} Customer emails
UUID {"type": "string", "format": "uuid"} Idempotency keys
Date {"type": "string", "format": "date"} Order dates
Currency {"type": "integer", "minimum": 0} Amounts in cents
Phone {"type": "string", "pattern": "^\\+[1-9]\\d{1,14}$"} E.164 format

Key Takeaways

  • Tool names should be action_resource format with snake_case
  • Descriptions should explain what, when, and what it returns
  • inputSchema uses JSON Schema with properties and required fields
  • Keep schemas flat when possible to reduce token usage
  • Add constraints like enums, patterns, and min/max for validation

Next up: Payment Processing Tools - Building tools to integrate Stripe and handle payments in your e-commerce AI agent.

🧠 Quick Quiz

Test your understanding of this lesson.

1

What format does MCP use for defining tool input parameters?

2

Why should tool descriptions be clear and specific?

3

What is the correct structure for a tool's inputSchema?

What is MCP & Tool Use?