examples/MCP-tools Public
# mcp-toolsA small [Model Context Protocol](https://modelcontextprotocol.io) server on Cloudflare Workers. It gives models three exact, read-only tools for things they tend to get wrong: unit arithmetic, the current time somewhere else, and counting words.The server speaks Streamable HTTP at `/mcp`, holds no sessions, and counts tool calls per day in a KV namespace.## Tools### `convert_units`Converts between units of length, mass, volume (US customary) and temperature. Units can be symbols or names: `km`, `kilometres`, `°F`, `pounds`.```json{ "value": 26.2, "from": "miles", "to": "km" }``````json{ "quantity": "length", "value": 26.2, "from": "mi", "result": 42.1648128, "to": "km" }```Converting between quantities, an unknown unit, or a temperature below absolute zero returns an error result that says what to send instead.### `time_in_zone`The local date, time, weekday and UTC offset in an IANA time zone, now or at a given instant. A city that names one zone (`tokyo`, `new york`) works too.```json{ "zone": "tokyo", "at": "2026-10-14T17:00:00Z" }``````json{ "zone": "Asia/Tokyo", "instant": "2026-10-14T17:00:00.000Z", "date": "2026-10-15", "time": "02:00:00", "weekday": "Thursday", "utc_offset": "+09:00", "utc_offset_minutes": 540, "formatted": "Thursday, October 15, 2026 at 2:00:00 AM GMT+9"}```Leave out `at` for the current time. An instant without `Z` or an offset is refused, since it would be read in the server's own zone.### `text_stats`Characters (as a reader sees them, so an emoji counts once), words, sentences, paragraphs and reading time.```json{ "text": "MCP gives models tools. This server has three of them." }``````json{ "characters": 54, "characters_excluding_spaces": 45, "words": 10, "sentences": 2, "paragraphs": 1, "reading_time_seconds": 3, "reading_time": "under a minute"}````words_per_minute` sets the reading speed (default 238).## Connecting a clientThe endpoint is `http://localhost:8787/mcp` locally, or `https://mcp-tools.<your subdomain>.workers.dev/mcp` once deployed. No auth.**MCP Inspector.** Run `npx @modelcontextprotocol/inspector`, choose the Streamable HTTP transport, and enter the URL.**Claude Code.**```shclaude mcp add --transport http mcp-tools http://localhost:8787/mcp```**Claude Desktop.** Desktop starts local servers over stdio, so bridge with `mcp-remote` in `claude_desktop_config.json`:```json{ "mcpServers": { "mcp-tools": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:8787/mcp"] } }}```**curl.** Each message is its own POST, and `Accept` must list both types:```shcurl -s http://localhost:8787/mcp \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"convert_units","arguments":{"value":100,"from":"C","to":"F"}}}'````GET /stats?days=7` returns calls and errors per tool for the last seven UTC days.## Running itNode 24 or later.```shnpm installnpm test # node:test, no Workers runtime needednpm run typechecknpm run dev # wrangler dev on http://localhost:8787````npx wrangler deploy` deploys it. The `USAGE` namespace in `wrangler.jsonc` has no id, so the first deploy creates it.## Design notes- **Stateless.** Every POST gets a fresh `McpServer` and a `WebStandardStreamableHTTPServerTransport` with no session id and JSON responses. Any isolate can answer any request; nothing needs a Durable Object.- **Tools are pure.** Each tool in `src/tools/` is a name, a description written for the model, zod input and output schemas, and a function from input to output. `src/server.ts` is the only file that knows about MCP.- **Errors are results.** A tool throws `ToolError` with advice ("Use an IANA name such as Europe/Paris"), and the server returns it with `isError: true` so the model can read it and try again.- **Counting stays off the request path.** Usage writes go through `ctx.waitUntil`, after the response. The counts are approximate, which is fine for seeing what gets used.The longer versions are in the knowledge base under `.rebasely/kb/`, and the component map is `.rebasely/architecture/system.yaml`.## Layout```src/ index.ts Worker entry worker.ts routes: /mcp, /stats, CORS server.ts createServer(): registers the tools with McpServer usage.ts per-day counters in KV tools/ convert-units.ts time-in-zone.ts text-stats.ts errors.ts ToolError types.ts defineTool and the Tool typetest/ node:test suites, plus an in-memory client and KV fakes.rebasely/ architecture/ components, views and flows kb/ concepts, decisions and guides workflows/ci.yml typecheck and tests on every push```## LicenseMIT