Skip to main content
Every tool in Reacher is a single file in src/tools/. There is no framework to configure, no plugin system to learn. You create a file, register it in one place, and it appears in Claude’s tool list. This guide walks through the complete pattern: the file structure, handler signatures, registration, audit logging, and how to test your new tool.

The tool file pattern

Every tool exports four things: Here’s the minimal shape, taken directly from the codebase:
src/tools/gist_kb.js
Put .describe() on every Zod field. These strings are what Claude reads when it decides how to fill in parameters — they are your tool’s inline documentation. A field without a description leaves Claude guessing.

Handler signature options

Different tools receive different parameters depending on what they need. The server passes only what’s required — this limits each tool’s access to credentials it doesn’t use. Choose the most restrictive signature that covers your tool’s actual needs.

Step-by-step: creating a new tool

This example builds a disk_usage tool that checks free disk space on a remote host. It’s new — not already in the codebase — and demonstrates the full pattern cleanly.
1

Create the tool file

Create src/tools/disk_usage.js:
The file is entirely self-contained. It imports only what it needs (z from Zod, spawn from Node’s child_process), defines its own schema, and handles its own errors.
2

Register the tool in mcp-server.js

Open src/mcp-server.js and add two things: the import at the top, and a server.tool(...) call in the body.
The four arguments to server.tool() are always: name, description, schema, and an async wrapper that calls the handler and passes the result to auditLog.
3

Add audit logging

Every tool registration in mcp-server.js follows this wrapper pattern:
src/mcp-server.js
auditLog writes to reacher-audit.log with the tool name, timestamp, arguments, and result. Sensitive keys (authorization headers, tokens) are stripped automatically before writing. You do not need to redact values yourself — just always call auditLog in the wrapper, never inside the tool handler.
4

Restart the server

5

Verify with tools/list

Send a tools/list request to confirm your tool appears:
You should see your tool’s name, description, and the full parameter schema in the response.
6

Test in Claude

Start a new Claude conversation and ask something that naturally invokes your tool:
“How much disk space is left on homelab?”
Claude will call disk_usage with hostname: "homelab" and return the result. If it doesn’t pick up the tool, check that your description clearly states the tool’s purpose and when to use it — Claude reads that string to decide whether to invoke it.

Zod schema reference

The schema export is a plain object whose values are Zod validators. The MCP SDK converts it to a JSON Schema for Claude automatically.
Write descriptions from Claude’s perspective. "SSH user (default: ubuntu)" tells Claude what the value is and what to assume when the user doesn’t specify. "string" tells Claude nothing.

Accessing environment variables

If your tool needs API keys or config values from .env, accept env as a second parameter and read from it:
Then in mcp-server.js, pass env when calling the handler:
The env object is process.env (or a subset of it) passed into createMCPServer(env) at startup.
Add any new environment variables to .env.example with a comment explaining what they’re for. This keeps your setup reproducible.