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
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 adisk_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 The file is entirely self-contained. It imports only what it needs (
src/tools/disk_usage.js: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 The four arguments to
src/mcp-server.js and add two things: the import at the top, and a server.tool(...) call in the body.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
- Docker
- Docker Compose
- Bare Node (dev)
5
Verify with tools/list
Send a You should see your tool’s name, description, and the full parameter schema in the response.
tools/list request to confirm your tool appears: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
Theschema export is a plain object whose values are Zod validators. The MCP SDK converts it to a JSON Schema for Claude automatically.
Accessing environment variables
If your tool needs API keys or config values from.env, accept env as a second parameter and read from it:
mcp-server.js, pass env when calling the handler:
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.