examples/MCP-tools Public
import { z } from "zod";import { ToolError } from "./errors.ts";import { defineTool } from "./types.ts";const QUANTITIES = ["length", "mass", "volume", "temperature"] as const;type Quantity = (typeof QUANTITIES)[number];// Each unit converts to and from its quantity's base unit: metres, kilograms, litres, kelvin.type Unit = { symbol: string; // Other ways people and models write the unit, matched case-insensitively. names: string[]; quantity: Quantity; toBase: (v: number) => number; fromBase: (v: number) => number;};function linear(quantity: Quantity, symbol: string, factor: number, names: string[]): Unit { return { symbol, names, quantity, toBase: (v) => v * factor, fromBase: (v) => v / factor };}// Imperial volumes are US customary; the description says so.const UNITS: Unit[] = [ linear("length", "mm", 0.001, ["millimeter", "millimeters", "millimetre", "millimetres"]), linear("length", "cm", 0.01, ["centimeter", "centimeters", "centimetre", "centimetres"]), linear("length", "m", 1, ["meter", "meters", "metre", "metres"]), linear("length", "km", 1000, ["kilometer", "kilometers", "kilometre", "kilometres"]), linear("length", "in", 0.0254, ["inch", "inches", "\""]), linear("length", "ft", 0.3048, ["foot", "feet", "'"]), linear("length", "yd", 0.9144, ["yard", "yards"]), linear("length", "mi", 1609.344, ["mile", "miles"]), linear("length", "nmi", 1852, ["nautical mile", "nautical miles"]), linear("mass", "mg", 1e-6, ["milligram", "milligrams"]), linear("mass", "g", 0.001, ["gram", "grams"]), linear("mass", "kg", 1, ["kilogram", "kilograms", "kilo", "kilos"]), linear("mass", "t", 1000, ["tonne", "tonnes", "metric ton", "metric tons"]), linear("mass", "oz", 0.028349523125, ["ounce", "ounces"]), linear("mass", "lb", 0.45359237, ["lbs", "pound", "pounds"]), linear("mass", "st", 6.35029318, ["stone", "stones"]), linear("volume", "ml", 0.001, ["milliliter", "milliliters", "millilitre", "millilitres"]), linear("volume", "l", 1, ["liter", "liters", "litre", "litres"]), linear("volume", "m3", 1000, ["m³", "cubic meter", "cubic meters", "cubic metre", "cubic metres"]), linear("volume", "tsp", 0.00492892159375, ["teaspoon", "teaspoons"]), linear("volume", "tbsp", 0.01478676478125, ["tablespoon", "tablespoons"]), linear("volume", "fl-oz", 0.0295735295625, ["fl oz", "floz", "fluid ounce", "fluid ounces"]), linear("volume", "cup", 0.2365882365, ["cups"]), linear("volume", "pt", 0.473176473, ["pint", "pints"]), linear("volume", "qt", 0.946352946, ["quart", "quarts"]), linear("volume", "gal", 3.785411784, ["gallon", "gallons"]), { symbol: "C", names: ["°C", "celsius", "centigrade"], quantity: "temperature", toBase: (v) => v + 273.15, fromBase: (v) => v - 273.15 }, { symbol: "F", names: ["°F", "fahrenheit"], quantity: "temperature", toBase: (v) => ((v - 32) * 5) / 9 + 273.15, fromBase: (v) => ((v - 273.15) * 9) / 5 + 32 }, { symbol: "K", names: ["kelvin"], quantity: "temperature", toBase: (v) => v, fromBase: (v) => v },];const key = (text: string) => text.trim().toLowerCase().replace(/\s+/g, " ");const BY_KEY = new Map(UNITS.flatMap((u) => [u.symbol, ...u.names].map((n) => [key(n), u] as const)));export function findUnit(text: string): Unit | undefined { return BY_KEY.get(key(text));}function unitOrThrow(text: string): Unit { const unit = findUnit(text); if (!unit) throw new ToolError(`Unknown unit "${text}". Known units, by quantity: ${knownUnits()}.`); return unit;}function knownUnits(): string { return QUANTITIES.map((q) => `${q}: ${UNITS.filter((u) => u.quantity === q).map((u) => u.symbol).join(", ")}`).join("; ");}// Twelve significant digits hides float noise (0.30000000000000004) without losing anything a// person would read.function tidy(v: number): number { return Number(v.toPrecision(12));}export const convertUnits = defineTool({ name: "convert_units", title: "Convert units", description: "Convert a value between units of length, mass, volume or temperature. " + "Use it instead of doing the arithmetic yourself whenever the user needs an exact figure. " + "Give units as symbols or names (km or kilometers, F or fahrenheit). Symbols: length mm, cm, m, km, in, ft, yd, mi, nmi; mass mg, g, kg, t, oz, lb, st; " + "volume ml, l, m3, tsp, tbsp, fl-oz, cup, pt, qt, gal (US customary); temperature C, F, K. " + "Both units must measure the same quantity.", input: z.object({ value: z.number().describe("The number to convert, in the `from` unit."), from: z.string().min(1).describe("The unit the value is in, for example km, pounds or °F."), to: z.string().min(1).describe("The unit to convert to, for example mi, kg or celsius."), }), output: z.object({ quantity: z.enum(QUANTITIES), value: z.number(), from: z.string(), result: z.number(), to: z.string(), }), run({ value, from, to }) { const a = unitOrThrow(from); const b = unitOrThrow(to); if (a.quantity !== b.quantity) { throw new ToolError(`Cannot convert ${a.symbol} (${a.quantity}) to ${b.symbol} (${b.quantity}). Pick two units of the same quantity.`); } const base = a.toBase(value); if (a.quantity === "temperature" && base < 0) { throw new ToolError(`${value} ${a.symbol} is below absolute zero (${tidy(a.fromBase(0))} ${a.symbol}).`); } return { quantity: a.quantity, value, from: a.symbol, result: tidy(b.fromBase(base)), to: b.symbol }; },});