I Added Custom Tools to Claude Code in 30 Seconds (Without Touching the Source)

Tinkering

Claude Code is powerful. But what if you could teach it new tricks—without waiting for Anthropic to ship them?

I built a framework that lets you add custom tools to Claude Code using simple YAML files. No MCP servers. No complex protocols. Just define what you want, write a handler, and it works.

Here's a cow saying hello through Claude Code:

$ claude "use cowsay to say hello"

 _______
< hello >
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

That tool doesn't exist in Claude Code. I added it in 30 seconds.


The Problem

Claude Code ships with ~30 built-in tools: Bash, Read, Write, Grep, etc. They're powerful, but limited to what Anthropic decided to include.

Want to:

  • Post to Slack when a build finishes?
  • Query your company's internal API?
  • Roll dice for your D&D session?
  • Get the actual current time?

You're out of luck. Unless you:

  1. Build an MCP server - Requires understanding JSON-RPC 2.0, implementing the protocol, managing processes
  2. Wait for Anthropic - Hope they add your feature someday
  3. Use this framework - Define a YAML file, write 10 lines of code, done

The Solution: YAML-Defined Tools

Here's how you add a new tool:

Step 1: Define It (YAML)

# ~/.claude-extensions/tools/timestamp.yaml
name: Timestamp
description: Get the current date and time. Use when asked what time it is.
handler: timestamp.js

schema:
  properties:
    format:
      type: string
      enum: [iso, unix, human]
  required: []

Step 2: Implement It (JavaScript)

// ~/.claude-extensions/handlers/timestamp.js
module.exports = function(input) {
  const now = new Date();

  switch (input.format) {
    case 'iso': return now.toISOString();
    case 'unix': return String(Math.floor(now.getTime() / 1000));
    default: return now.toString();
  }
};

Step 3: Use It

$ claude "what time is it?"

It's Monday, January 27, 2025 at 10:42:53 AM.

That's it. No compilation. No deployment. No configuration files. Claude now knows about your tool and uses it when appropriate.


How It Works

The framework runs as a transparent proxy between Claude Code and Anthropic's API:

Claude Code → Extension Proxy → Anthropic API
                   │
                   ├── Injects your tool schemas
                   ├── Detects when Claude uses them
                   ├── Runs your handler locally
                   └── Returns results to Claude

When Claude Code sends a request, the proxy:

  1. Adds your tools to the request's tool list
  2. Forwards to Anthropic
  3. Watches the response for tool calls
  4. Intercepts calls to your tools
  5. Executes your handler
  6. Injects the result

Claude thinks the tool is built-in. Your handler runs locally. Everyone's happy.


Real Examples

1. Cowsay (Fun)

name: Cowsay
description: Display a message with ASCII art of a cow
handler: cowsay.js

schema:
  properties:
    message: { type: string }
    style: { type: string, enum: [default, think, yell] }
module.exports = function({ message, style }) {
  const eyes = style === 'dead' ? 'xx' : 'oo';
  return `
 ${'_'.repeat(message.length + 2)}
< ${message} >
 ${'-'.repeat(message.length + 2)}
        \\   ^__^
         \\  (${eyes})\\_______
            (__)\\       )\\/\\
                ||----w |
                ||     ||`;
};

2. Dice Roller (Utility)

name: Dice
description: Roll dice for games or random selection
handler: dice.js

schema:
  properties:
    sides: { type: number }
    count: { type: number }
module.exports = function({ sides = 6, count = 1 }) {
  const rolls = Array.from({ length: count },
    () => Math.floor(Math.random() * sides) + 1
  );
  return `Rolled ${count}d${sides}: [${rolls.join(', ')}] = ${rolls.reduce((a,b) => a+b)}`;
};
$ claude "roll 4d6 for my character stats"

Rolled 4d6: [5, 3, 6, 2] = 16

3. HTTP Client (Power)

name: HTTPRequest
description: Make HTTP requests to any API
handler: http.js

schema:
  properties:
    url: { type: string }
    method: { type: string, enum: [GET, POST, PUT, DELETE] }
    body: { type: string }
module.exports = async function({ url, method = 'GET', body }) {
  const response = await fetch(url, {
    method,
    body,
    headers: { 'Content-Type': 'application/json' }
  });
  return await response.text();
};
$ claude "fetch the GitHub API status"

GitHub API is operational. All systems normal.

The CLI

Managing extensions is simple:

# Create a new extension (scaffolds YAML + handler)
claude-ext create SlackNotify

# List installed extensions
claude-ext list

# Test a handler directly
claude-ext test dice '{"sides": 20, "count": 3}'

# Start the proxy
claude-ext start

# Start with hot reload (auto-reload on file changes)
claude-ext start --watch

Why Not Just Use MCP?

MCP (Model Context Protocol) is Anthropic's official extension system. It's powerful but complex:

Aspect MCP This Framework
Setup JSON-RPC server, process management YAML file + JS function
Protocol Full JSON-RPC 2.0 implementation None
Learning curve Steep Minimal
Time to first tool Hours Minutes
Best for Production systems Quick tools, prototypes

MCP is the right choice for robust, production-grade integrations. This framework is for when you just want to add a tool and move on.


Under the Hood

The magic is in how Claude Code's API works. Every request includes a tools array:

{
  "model": "claude-opus-4-5-20251101",
  "tools": [
    { "name": "Bash", "description": "...", "input_schema": {...} },
    { "name": "Read", "description": "...", "input_schema": {...} },
    // ... 28 more built-in tools
  ]
}

The proxy simply appends your tools:

{
  "tools": [
    // ... built-in tools ...
    { "name": "Cowsay", "description": "...", "input_schema": {...} },
    { "name": "Dice", "description": "...", "input_schema": {...} }
  ]
}

Anthropic's API doesn't validate tool names against a whitelist. It accepts whatever you send. Claude sees your tools alongside the built-ins and uses them when appropriate.

When Claude calls your tool, the response includes:

{
  "type": "tool_use",
  "name": "Cowsay",
  "input": { "message": "hello" }
}

The proxy intercepts this, runs your handler, and injects the result into the next request. Claude receives the output and continues naturally.


Getting Started

1. Clone & Install

git clone https://github.com/hexcreator/claude-code-extensions
cd claude-code-extensions
npm install
npm link  # Makes 'claude-ext' available globally

2. Initialize

claude-ext init

3. Create Your First Tool

claude-ext create MyTool

4. Edit the Files

vim ~/.claude-extensions/tools/mytool.yaml
vim ~/.claude-extensions/handlers/mytool.js

5. Start the Proxy

claude-ext start

6. Use It

ANTHROPIC_BASE_URL=http://localhost:8892 claude "use MyTool"

What Can You Build?

The framework supports any tool that:

  • Takes JSON input
  • Returns a string result
  • Can be implemented in JS, Python, or Shell

Ideas:

  • Slack/Discord notifications - Post messages when tasks complete
  • Database queries - Query your local or remote databases
  • API integrations - Connect to any REST API
  • File processors - Transform, convert, analyze files
  • DevOps tools - Deploy, monitor, manage infrastructure
  • Fun stuff - Games, generators, creative tools

Limitations

  • Local execution only - Handlers run on your machine
  • No streaming - Results are returned all at once
  • Trust required - You're running arbitrary code
  • Proxy dependency - Must route through the proxy

The Bigger Picture

This framework emerged from reverse-engineering Claude Code's architecture. By intercepting API traffic, I discovered:

  • Tool schemas are sent with every request
  • The API accepts any valid schema
  • Tool execution happens client-side
  • Results are injected via tool_result messages

These insights enabled building a transparent extension layer that works without modifying Claude Code itself.


Try It

The framework is open source and ready to use:

GitHub: claude-code-extensions

Create your first custom tool in under a minute. See what Claude can do when you teach it new tricks.


Have questions or built something cool? Share in the comments!


TL;DR

  • Claude Code's tools can be extended via a simple proxy
  • Define tools in YAML, implement in JS/Python/Shell
  • No MCP complexity, no protocol knowledge required
  • Works transparently with existing Claude Code installation
  • Open source, ready to use today
# From zero to custom tool in 4 commands
claude-ext init
claude-ext create MyTool
# edit files
claude-ext start

Now go build something.