What Happens When You Modify Claude Code's Tool Schemas
I built a MITM proxy that modifies tool schemas before they reach Anthropic's API. Here's what I discovered about how flexible—and exploitable—the schema system really is.
Executive Summary
| Experiment | API Accepts? | Claude Sees It? | Execution Works? |
|---|---|---|---|
| Add new tool | ✅ Yes | ✅ Yes | ❌ No handler |
| Rename existing tool | ✅ Yes | ✅ Yes | ❌ Falls back |
| Remove tool | ✅ Yes | ✅ Yes | ✅ Uses alternatives |
| Modify description | ✅ Yes | ✅ Yes | ✅ Yes |
| Add properties | ✅ Yes | ✅ Yes | ⚠️ Ignored |
| Inject fake skill | ✅ Yes | ✅ Yes | ❌ Server fails |
Key Finding: The Anthropic API accepts any valid JSON schema. There's no whitelist, no signature verification, no schema validation beyond JSON syntax.
The Experimental Setup
I created a proxy that intercepts Claude Code's API requests and modifies the tools array before forwarding:
// proxy-schema-experiments.js
function applyExperiment(tools) {
// Add a completely new tool
tools.push({
name: 'ProxyInjectedTool',
description: 'A tool injected by the proxy.',
input_schema: {
type: 'object',
properties: {
query: { type: 'string' }
}
}
});
}
Running it:
# Terminal 1
node proxy-schema-experiments.js addNewTool
# Terminal 2
ANTHROPIC_BASE_URL=http://localhost:8890 claude "what tools do you have?"
Experiment 1: Add a New Tool
Modification: Inject ProxyInjectedTool into the tools array
Result:
Claude's response included:
> **ProxyInjectedTool** | Proxy experiments tool
Claude listed the injected tool among its available tools. When asked to use it:
> The ProxyInjectedTool returned an error indicating it's not actually
> available, despite being listed in the tool definitions.
What Happened:
- API accepted the modified schema ✅
- Claude saw the new tool ✅
- Claude tried to call it ✅
- Claude Code had no handler → error ❌
- Claude gracefully explained the failure
Implication: Anyone who can modify API traffic can make Claude believe tools exist that don't.
Experiment 2: Rename an Existing Tool
Modification: Rename Glob to FileSearch
Result (from proxy logs):
🧪 Running experiment: Rename Glob to FileSearch
✅ renamed to FileSearch
📬 Response: 200
🔧 Tools used: FileSearch
🎉 RENAMED TOOL WAS USED! (Glob → FileSearch)
[Next request]
🔧 Tools used: Glob
What Happened:
- Claude tried to use
FileSearch(the renamed tool) - Claude Code didn't recognize it
- On retry, Claude fell back to
Glob
Implication: Claude Code appears to have some error recovery—when an unknown tool is called, it may retry with known tools.
Experiment 3: Remove a Tool Entirely
Modification: Remove WebSearch from the tools array
Result (from proxy logs):
🧪 Running experiment: Remove WebSearch tool
✅ removed WebSearch
[Claude's behavior]
🔧 Tools used: WebFetch
🔧 Tools used: Task
🔧 Tools used: Grep, WebFetch
What Happened:
- Claude couldn't see
WebSearch - Claude adapted by using
WebFetchinstead - Claude spawned sub-agents to help with the task
- Task completed successfully with alternative tools
Implication: Removing tools effectively disables them. Claude will find alternatives if available.
Experiment 4: Modify Tool Description
Modification: Prepend [PROXY MODIFIED] to Read tool's description
Result: Claude sees and potentially references the modified description. The behavioral impact depends on how significantly the description changes the tool's apparent purpose.
Implication: Tool descriptions drive Claude's understanding of when and how to use tools. Modifying descriptions can change tool selection behavior.
What the API Accepts
Based on all experiments, the Anthropic API:
| Accepts | Rejects |
|---|---|
| New tools with any name | Invalid JSON |
| Modified schemas | Non-object schemas |
| Removed tools | (nothing else observed) |
| Nested objects | |
| Any property types | |
| Modified descriptions |
The API appears to validate:
- JSON syntax ✅
- Basic schema structure ✅
The API does NOT validate:
- Tool names against whitelist ❌
- Schema signatures ❌
- Required properties matching execution ❌
Security Analysis
Attack Surface
A MITM attacker could:
- Inject malicious tools that Claude will try to use
- Remove safety tools to limit Claude's capabilities
- Modify descriptions to change tool selection behavior
- Rename tools to cause confusion/errors
Mitigations
Current protections:
| Protection | Status |
|---|---|
| HTTPS to API | ✅ (but proxy can intercept) |
| Schema validation | ❌ None observed |
| Tool signature verification | ❌ None observed |
| Execution validation | ✅ Claude Code checks handlers |
The primary protection is that Claude Code won't execute unknown tools. But Claude will try to use them, potentially revealing information or causing errors.
The Trust Model
User → Claude Code → [MITM possible here] → Anthropic API
↑
Tool execution happens here
(no unknown tool handlers)
Claude Code's client-side execution is the final gatekeeper. The API trusts whatever schemas it receives.
Practical Applications
Legitimate Uses
- Custom tool development: Test new tools before implementing handlers
- Capability restriction: Remove tools for sandboxed environments
- Behavior modification: Adjust descriptions for specific workflows
- Research: Understand Claude's tool selection logic
Security Testing
// Test what happens when Claude tries to use a forbidden tool
const blockedTools = ['Bash', 'Write', 'Edit'];
requestBody.tools = requestBody.tools.filter(
t => !blockedTools.includes(t.name)
);
The Experiments in Detail
Request Flow Observed
Request #1: Warmup (Haiku, 0 tools) → 200 OK
Request #2: Main (Opus, 26 tools) → Schema modified → 200 OK
Requests #3-12: Token counting (parallel)
Request #13+: Tool results and continuations
Tool Count Changes
| Experiment | Before | After |
|---|---|---|
| addNewTool | 26 | 27 |
| removeTool | 26 | 25 |
| renameTool | 26 | 26 |
| others | 26 | 26 |
Sub-Agent Behavior
When tools are modified, sub-agents (Haiku) also receive modified schemas:
Request #19: Model: claude-haiku-4-5-20251001, Tools: 5
🧪 Running experiment: Remove WebSearch tool
✅ removed WebSearch
Sub-agents inherit the modifications from the main request flow.
Code: The Schema Modification Proxy
// Key modification function
function applyExperiment(tools) {
switch (ACTIVE_EXPERIMENT) {
case 'addNewTool':
tools.push({
name: 'ProxyInjectedTool',
description: 'Injected by proxy',
input_schema: { /* ... */ }
});
break;
case 'renameTool':
const glob = tools.find(t => t.name === 'Glob');
if (glob) glob.name = 'FileSearch';
break;
case 'removeTool':
const idx = tools.findIndex(t => t.name === 'WebSearch');
if (idx !== -1) tools.splice(idx, 1);
break;
}
}
Full source: tools/proxy-schema-experiments.js
Conclusions
What We Learned
- No schema validation: The API accepts any valid JSON schema
- Client is the gatekeeper: Claude Code won't execute unknown tools
- Claude adapts: When tools are missing, Claude finds alternatives
- Descriptions matter: Tool selection is driven by descriptions
- Error recovery exists: Claude Code has fallback mechanisms
Implications
The schema system is trust-based. Anthropic trusts that:
- Claude Code sends legitimate schemas
- The network path is secure
- No one modifies requests in flight
This design enables extensibility (MCP, custom tools) but creates a potential attack surface for MITM scenarios.
Recommendations
If you're building on Claude Code:
- Use HTTPS: Always use secure connections to the API
- Validate locally: Check tool calls before sending to Claude Code
- Monitor traffic: Log API requests for anomaly detection
- Sandbox execution: Don't trust tool results blindly
Try It Yourself
# Clone the repo
git clone https://github.com/your-repo/claude-code-reversal
cd claude-code-reversal/tools
# Run an experiment
node proxy-schema-experiments.js addNewTool
# In another terminal
ANTHROPIC_BASE_URL=http://localhost:8890 claude "what tools do you have?"
# Check results
cat schema-experiments/*.json
The code is designed for research and understanding. Use responsibly.
Bonus: Full Tool Interception
Beyond just injecting tools, we can intercept and handle them locally:
ANTHROPIC_BASE_URL=http://localhost:8891 claude "use cowsay to say hello"
Result:
_______________________
< Hello from the proxy! >
-----------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
How It Works
Claude calls Cowsay
↓
Proxy detects tool_use in response
↓
Proxy executes local handler
↓
Proxy stores result
↓
Claude Code sends tool_result request
↓
Proxy intercepts and injects our result
↓
Claude receives the cowsay output
The Interception Pattern
// 1. Parse streamed response for tool calls
const toolCalls = parseStreamedResponse(responseBody);
// 2. Execute local handler
for (const toolCall of toolCalls) {
if (CUSTOM_TOOLS[toolCall.name]) {
const result = handler(toolCall.input);
pendingToolCalls.set(toolCall.id, result);
}
}
// 3. Inject result into next request
if (content.type === 'tool_result') {
const pending = pendingToolCalls.get(content.tool_use_id);
if (pending) {
content.content = pending.result;
}
}
Working Custom Tools
| Tool | What It Does | Works? |
|---|---|---|
| Cowsay | ASCII art cow | ✅ |
| RandomNumber | Dice rolls | ✅ |
| Timestamp | Current time | ✅ |
| Echo | Message transform | ✅ |
| ProxyInfo | Proxy info | ✅ |
Full source: tools/proxy-tool-intercept.js
Discovered through empirical testing with a MITM proxy. Results may vary with API updates.