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"]
}
}
Unique identifier the AI uses to call the tool
Explains what the tool does and when to use it
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 orderlist_orders- Retrieve multiple orderscreate_refund- Create a new refundupdate_inventory- Modify inventory levelscalculate_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"]
}
}
}
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 |
|---|---|---|
| {"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.