SDK reference
The Project registry, the s schema builder and the tool context.
@zenitya/sdk is the only package a project installs. It is MIT licensed and
published on npm.
bun add @zenitya/sdkimport { Project, type ToolContext } from "@zenitya/sdk";
import { s } from "@zenitya/sdk/schema";Project
The registry your entrypoint exports as its default export.
const project = new Project();
export default project;project.tool(key, config)
Registers a tool and returns the project, so calls can be chained. Throws when the key is taken, invalid, or the project already has 128 tools.
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | yes | At most 120 characters |
description | string | yes | At most 2,000 characters; agents read it |
schema | s.object(...) | yes | The input; always an object |
outputSchema | any s schema | no | Validated against what execute returns |
hints | ToolHints | no | See below |
execute | (input, context) => Output | Promise<Output> | yes | input is typed from schema |
key starts with a letter and has only letters, digits and underscores, at
most 64 characters.
ToolHints
All optional booleans, as in MCP tool annotations: readOnlyHint,
destructiveHint, idempotentHint, openWorldHint. A tool cannot be both
read-only and destructive.
ToolContext
The second argument of execute.
| Field | Type | Notes |
|---|---|---|
invocationId | string | Unique per call |
signal | AbortSignal | Aborts when the call times out |
logger | ToolLogger | debug, info, warn, error |
Each logger method takes (fields: JsonObject, message?: string):
context.logger.warn({ orderId, status: response.status }, "carrier lookup failed");s: schema builders
Every builder returns a schema that is both the JSON Schema agents see and the TypeScript type of the value.
| Builder | JSON Schema | TypeScript |
|---|---|---|
s.string() | { type: "string" } | string |
s.number() | { type: "number" } | number |
s.integer() | { type: "integer" } | number |
s.boolean() | { type: "boolean" } | boolean |
s.enum("a", "b") | { enum: ["a", "b"] } | "a" | "b" |
s.object({ ... }) | object, all properties required, no extra properties | { readonly ... } |
s.array(item, { minItems? }) | { type: "array", items }; minItems is 0 or 1 | readonly Item[] |
s.anyOf(a, b, ...) | { anyOf: [...] } | union |
s.ref("#/properties/customer") | { $ref }, local pointers only | JSON value |
String formats, each { type: "string", format } and typed as string:
s.date(), s.time(), s.datetime(), s.duration(), s.email(),
s.uuid(), s.hostname(), s.ipv4(), s.ipv6().
s.enum takes all strings or all finite numbers, unique, at least one.
Modifiers
.describe(text): adds the description agents read. At most 2,000 characters..nullable(): allowsnullas well. Properties are always required, so this is how a value becomes optional: the caller passesnull.
const schema = s.object({
query: s.string().describe("Text to search for."),
status: s.enum("open", "closed").nullable().describe("Filter by status, or null for any."),
tags: s.array(s.string(), { minItems: 1 }).describe("At least one tag."),
});References
s.ref(path) points at another node of the same schema with a local JSON
Pointer. The target must exist in the final schema:
const schema = s.object({
customer: s.object({ id: s.string() }),
previousCustomer: s.ref("#/properties/customer"),
});Limits
A schema may be at most 256 KiB, 32 levels deep and 10,000 nodes. Tool input and output are each at most 1 MiB of JSON, nested at most 64 levels.
Types
InferSchema<typeof schema> gives the TypeScript type of a schema. JsonValue,
JsonObject, JsonArray and JsonPrimitive describe JSON values.
zenitya