Schema generation (Zod)
The TypeScript SDK converts your Zod schemas into MCP-compliant JSON Schema using zod-to-json-schema. The wrapper lives in platools/core/schema.ts and exposes three helpers:
import { buildInputSchema, buildOutputSchema, buildSchemas, SchemaError } from "@platools/sdk";buildSchemas
Used internally by the tool factory. Given a ToolOptions, it returns the inputSchema and outputSchema as JsonSchema objects:
const { inputSchema, outputSchema } = buildSchemas({ name: "list_tickets", input: z.object({ customerId: z.string().uuid() }), output: z.array(z.object({ id: z.string() })),});Internally:
zodToJsonSchema(options.input, { target: "jsonSchema7" })produces the input schema.zodToJsonSchema(options.output, ...)produces the output schema ifoutputis set,nullotherwise.- Both are validated through the
JsonSchemashape defined intypes.tsso downstream consumers (doctor, transport) get a known-good shape.
SchemaError
Raised when Zod can’t be converted - typically happens if you pass something that isn’t a ZodTypeAny (a type-level mistake the TS compiler should catch, but runtime validation still guards it).
Descriptions
Zod’s .describe() maps directly to JSON Schema description, so you can document params inline:
export const createInvoice = platools.tool( { name: "create_invoice", description: "Create a draft invoice for a customer", input: z.object({ customerId: z.string().uuid().describe("Opaque UUID of the billed customer"), amountCents: z.number().int().positive().describe("Invoice total in cents"), currency: z .enum(["usd", "eur", "gbp"]) .default("usd") .describe("ISO-4217 currency code, lowercase"), }), output: z.object({ invoiceId: z.string() }), auth: "admin", roles: ["billing"], }, async ({ customerId, amountCents, currency }) => { return billingService.createInvoice({ customerId, amountCents, currency }); },);The generated input schema will surface those descriptions in every MCP client’s tool picker.
Supported Zod shapes
Everything zod-to-json-schema handles maps cleanly, including:
z.string(),z.number(),z.boolean(),z.null(),z.undefined()z.enum(...),z.literal(...),z.nativeEnum(...)z.object({ ... }),z.array(...),z.tuple(...)z.union(...),z.discriminatedUnion(...),z.intersection(...)z.record(...),z.map(...),z.set(...).optional(),.nullable(),.default(...).min(...),.max(...),.length(...),.regex(...),.email(),.url(),.uuid()
Custom refinements (.refine(...)) don’t round-trip through JSON Schema - they still run at handler-dispatch time, but they aren’t visible to the MCP client. Flag them explicitly in the tool description if a refinement rejects inputs the JSON Schema wouldn’t reject on its own.
Sharing schemas with UI code
Because the Zod schemas are the source of truth, you can re-use them in your frontend validators, backend API routes, and Platools tool registrations without drift. A common pattern:
// src/schemas/ticket.ts - sharedimport { z } from "zod";export const TicketStatus = z.enum(["open", "pending", "resolved", "closed"]);export const Ticket = z.object({ /* ... */ });
// src/tools/list_tickets.tsimport { Ticket, TicketStatus } from "../schemas/ticket.js";export const listTickets = platools.tool(/* uses the same shapes */);
// src/api/tickets.tsimport { Ticket } from "../schemas/ticket.js";// validate API response bodies with the same schemaThis is a big win over the Python SDK’s introspection-of-type-hints approach. There’s no separate “Pydantic model / OpenAPI schema / TypeScript type” split to keep in sync.
Next steps
- Tool factory - the
platools.tool()options and handler typing. - Client - WebSocket transport with heartbeat and backoff.
- Python Schemas - the equivalent Pydantic-based approach.