- Owner
- justin
- Reviewed
- 2026-10-13
- Component
- tools
- Describes
- src/tools/**
Read Tool design first; this is the checklist.
1. Write the module
Create src/tools/<name-in-kebab>.ts and export a defineTool(...):
import { z } from "zod";
import { ToolError } from "./errors.ts";
import { defineTool } from "./types.ts";
// toRoman is the usual greedy walk over M, CM, D, ... I; left out here.
export const romanNumeral = defineTool({
name: "roman_numeral",
title: "Roman numerals",
description: "Write a whole number from 1 to 3999 as a Roman numeral. Use it instead of converting by hand.",
input: z.object({ value: z.number().int().describe("The number to convert, such as 2026.") }),
output: z.object({ value: z.number().int(), numeral: z.string() }),
run({ value }) {
if (value < 1 || value > 3999) throw new ToolError(`${value} is out of range. Roman numerals cover 1 to 3999.`);
return { value, numeral: toRoman(value) };
},
});
- Keep
runpure. If it needs the time, take it from the context:run(input, { now }). - Put limits in the schema when the model can see them there (
.int(),.max()), and throwToolErrorfor the rest. - Describe every input field with an example value.
2. Register it
Add it to TOOLS in src/server.ts. The order there is the order of tools/list. Update the server's instructions string if the summary of what the server does has changed.
3. Test it
test/<name>.test.ts: callrundirectly. Cover a normal case, an edge case and everyToolErrormessage.test/server.test.ts: add the name to thetools/listassertion, and add a call through the client if the tool has anything unusual (a default, a context value).
Run npm test and npm run typecheck.
4. Document it
Add the tool to the README with an example call and result. If it changes how tools work in general, update Tool design in the same change.