Rebasely Beta: data may be purged

examples/MCP-tools Public

Markdown · 1.9 KiB · at 2090b14

id: adding-a-tool
type: guide
owner: justin
component: tools
describes:
  - src/tools/**
reviewed: 2026-10-13

Adding a tool

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 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 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: 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 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.