Skip to content

Schema Validation ​

mcp.zig provides utilities for working with JSON Schema, commonly used for tool input validation.

Overview ​

JSON Schema defines the structure of tool arguments, ensuring valid inputs.

Basic Schemas ​

Creating a Schema ​

zig
var builder = mcp.schema.SchemaBuilder.init();

const schema = builder
    .string_()
    .description("A user's name")
    .build();

Schema Types ​

TypeMethodDescription
Object.object_()JSON object
Array.array_()JSON array
String.string_()Text value
Number.number_()Floating point
Integer.integer_()Whole number
Boolean.boolean_()true/false

InputSchemaBuilder ​

Build complete input schemas for tools:

zig
var schema = mcp.schema.InputSchemaBuilder.init(allocator);
defer schema.deinit(allocator);

// Add string property (required)
_ = try schema.addString(allocator, "name", "User's name", true);

// Add number property (optional)
_ = try schema.addNumber(allocator, "age", "User's age", false);

// Add boolean property
_ = try schema.addBoolean(allocator, "active", "Is user active", false);

// Add enum property
_ = try schema.addEnum(
    allocator,
    "role",
    "User's role",
    &.{ "admin", "user", "guest" },
    true,
);

// Build the schema
const inputSchema = try schema.toInputSchema(allocator);

Using with Tools ​

zig
try server.addTool(.{
    .name = "create_user",
    .description = "Create a new user",
    .handler = createUserHandler,
    .inputSchema = try schema.toInputSchema(allocator),
});

Schema Properties ​

String Constraints ​

zig
builder
    .string_()
    .pattern("^[a-zA-Z]+$")
    .format("email")

Number Constraints ​

zig
builder
    .number_()
    .minimum(0)
    .maximum(100)

InputSchemaBuilder Constraints ​

zig
// For string length constraints on tool arguments:
_ = schema.setPropertyLength("name", 1, 100);

// For numeric ranges on tool arguments:
_ = schema.setPropertyRange("age", 18, 120);

Common Formats ​

FormatDescription
emailEmail address
uriURI/URL
date-timeISO 8601 datetime
uuidUUID string

Common Schemas ​

Pre-defined schemas for common types:

zig
// String schema
const str = try mcp.schema.CommonSchemas.string_schema(allocator);

// Number schema
const num = try mcp.schema.CommonSchemas.number_schema(allocator);

// URI schema
const uri = try mcp.schema.CommonSchemas.uri_schema(allocator);

// DateTime schema
const dt = try mcp.schema.CommonSchemas.datetime_schema(allocator);

Converting to JSON ​

Schemas can be converted to JSON for the protocol:

zig
const json_value = try schema.toJson(allocator);

Complete Example ​

zig
const std = @import("std");
const mcp = @import("mcp");

pub fn setupServer(allocator: std.mem.Allocator) !mcp.Server {
    var server: mcp.Server = .init(allocator, .{
        .name = "validated-server",
        .version = "1.0.0",
    });

    // Build input schema
    var schema = mcp.schema.InputSchemaBuilder.init(allocator);
    defer schema.deinit(allocator);

    _ = try schema.addString(allocator, "username", "Unique username", true);
    _ = try schema.addString(allocator, "email", "Email address", true);
    _ = try schema.addInteger(allocator, "age", "User's age (must be 18+)", false);
    _ = try schema.addBoolean(allocator, "newsletter", "Subscribe to newsletter", false);
    _ = try schema.addEnum(
        allocator,
        "plan",
        "Subscription plan",
        &.{ "free", "basic", "pro" },
        true,
    );

    try server.addTool(.{
        .name = "register_user",
        .description = "Register a new user account",
        .handler = registerHandler,
        .inputSchema = try schema.toInputSchema(allocator),
    });

    return server;
}

fn registerHandler(
    _: ?*anyopaque,
    _: std.Io,
    allocator: std.mem.Allocator,
    args: ?std.json.Value,
) mcp.tools.ToolError!mcp.tools.ToolResult {
    // Arguments are already validated against schema by client
    const username = mcp.tools.getString(args, "username") orelse {
        return error.InvalidArguments;
    };

    const email = mcp.tools.getString(args, "email") orelse {
        return error.InvalidArguments;
    };

    const plan = mcp.tools.getString(args, "plan") orelse "free";

    const message = try std.fmt.allocPrint(
        allocator,
        "User {s} registered with email {s} on {s} plan",
        .{ username, email, plan },
    );

    return mcp.tools.textResult(allocator, message);
}

Best Practices ​

Do

  • Always define schemas for tools
  • Use descriptive descriptions
  • Mark required fields appropriately
  • Use appropriate types (integer vs number)

Don't

  • Skip validation in handlers
  • Use overly permissive schemas
  • Forget to document optional fields

Next Steps ​