Rebasely Beta: data may be purged

examples/MCP-tools Public

Markdown · 54 lines · 1.9 KiB · at dae155a

---id: adding-a-tooltype: guideowner: justincomponent: toolsdescribes:  - src/tools/**reviewed: 2026-10-13---# Adding a toolRead [[tool-design]] first; this is the checklist.## 1. Write the moduleCreate `src/tools/<name-in-kebab>.ts` and export a `defineTool(...)`:```tsimport { 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 `run` pure. 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 throw `ToolError` for the rest.- Describe every input field with an example value.## 2. Register itAdd 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`: call `run` directly. Cover a normal case, an edge case and every `ToolError` message.- `test/server.test.ts`: add the name to the `tools/list` assertion, 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 itAdd 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.