examples/MCP-tools Public
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";import { convertUnits } from "./tools/convert-units.ts";import { ToolError } from "./tools/errors.ts";import type { AnyTool } from "./tools/types.ts";export const SERVER_INFO = { name: "mcp-tools", version: "0.1.0" };// Every tool the server offers, in the order tools/list returns them.export const TOOLS: AnyTool[] = [convertUnits];export type ServerOptions = { // The clock tools read; tests pin it. now?: () => Date;};// Builds an MCP server with every tool registered. The server holds no state between requests,// so src/worker.ts makes one per request and each test makes its own.export function createServer(options: ServerOptions = {}): McpServer { const server = new McpServer(SERVER_INFO, { instructions: "Small, exact helpers: unit conversion. Each tool is read-only and answers from its input alone.", }); for (const tool of TOOLS) register(server, tool, options); return server;}function register(server: McpServer, tool: AnyTool, options: ServerOptions): void { server.registerTool( tool.name, { title: tool.title, description: tool.description, inputSchema: tool.input, outputSchema: tool.output, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }, }, (args) => call(tool, args, options), );}// The SDK has already checked `args` against the input schema; a mismatch never gets here and// comes back to the model as an isError result, the same as a ToolError.function call(tool: AnyTool, args: Record<string, unknown>, options: ServerOptions): CallToolResult { try { const result = tool.run(args, { now: options.now?.() ?? new Date() }); // Structured output for clients that read it, and the same JSON as text for those that don't. return { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result }; } catch (err) { if (err instanceof ToolError) return failure(err.message); console.error(`${tool.name} failed`, err); return failure(`${tool.name} failed on the server. The input looked valid, so retrying with the same input will fail the same way.`); }}function failure(text: string): CallToolResult { return { isError: true, content: [{ type: "text", text }] };}