Fix: Anthropic SDK MCP Tools Get Empty (2026)
The Error
When using the Claude Claude Agent SDK guide (Python) with in-process MCP servers and permission approval callbacks, all MCP tools receive empty {} arguments:
[DEBUG] Request body: {"action":"call_tool","server":"my-tools","tool_name":"read","arguments":{}}
Error: Missing required parameter 'files'
The model correctly sends the arguments in the permission request, but after approval, the tool is called with an empty object.
Quick Fix
Bypass the permission system to confirm the root cause:
agent = ClaudeAgent(
permission_mode="bypass_permissions", # Skip approval flow
mcp_servers={
"my-tools": {
"type": "sdk",
"tools": [read_files]
}
}
)
If this works, the bug is confirmed — the permission approval flow is dropping arguments.
What’s Happening
When you use the can_use_tool permission callback, the following sequence occurs:
1. Model sends correct arguments in the permission request:
{
"type": "control_request",
"request_id": "bd66e5e1-...",
"request": {
"subtype": "can_use_tool",
"tool_name": "mcp__my-tools__read",
"input": {"files": ["README.md"]},
"tool_use_id": "toolu_xyz"
}
}
2. Your callback returns approval:
async def can_use_tool(tool_name, arguments, context):
return PermissionResultAllow() # Simple approval
3. SDK sends buggy approval response:
{
"type": "control_response",
"response": {
"subtype": "success",
"request_id": "bd66e5e1-...",
"response": {
"behavior": "allow",
"updatedInput": {} // BUG: Empty object overwrites original arguments
}
}
}
4. Claude CLI uses the empty updatedInput instead of the original arguments:
{"action":"call_tool","server":"my-tools","tool_name":"read","arguments":{}}
Root cause: The SDK’s permission handler always includes updatedInput in the approval response, even when PermissionResultAllow() is called without explicitly setting updated_input. The updatedInput field defaults to an empty object {}, which Claude CLI interprets as “replace the original arguments with this empty object.”
The correct behavior: when updated_input is not explicitly set, the approval response should omit the updatedInput field entirely, and Claude CLI should use the original arguments.
Step-by-Step Solution
Option 1: Pass Original Arguments Through
Explicitly forward the original arguments in your approval callback:
async def can_use_tool(tool_name: str, arguments: dict, context) -> PermissionResult:
# Explicitly pass the original arguments back
return PermissionResultAllow(updated_input=arguments)
This ensures updatedInput contains the correct values instead of an empty object.
Option 2: Bypass Permissions for Trusted Tools
If you do not need per-tool approval, skip the permission flow:
agent = ClaudeAgent(
permission_mode="bypass_permissions",
mcp_servers={
"my-tools": {
"type": "sdk",
"tools": [read_files, write_files, search_files]
}
}
)
Option 3: Implement Selective Permission with Argument Forwarding
If you need to approve some tools and deny others, forward arguments on approval:
DANGEROUS_TOOLS = {"mcp__my-tools__delete", "mcp__my-tools__format_disk"}
async def can_use_tool(
tool_name: str,
arguments: dict,
context
) -> PermissionResult:
if tool_name in DANGEROUS_TOOLS:
return PermissionResultDeny(reason=f"Tool {tool_name} is not allowed")
# IMPORTANT: Forward the original arguments
return PermissionResultAllow(updated_input=arguments)
Option 4: Monkey-Patch the SDK (Temporary)
If you cannot modify the approval callback to forward arguments (e.g., using a framework that wraps it):
# WARNING: This is a temporary workaround. Pin your SDK version.
import anthropic._internal.permission_handler as ph
_original_build_response = ph._build_approval_response
def _patched_build_response(result, request_id):
response = _original_build_response(result, request_id)
# Remove updatedInput if it's empty
if (
"response" in response
and "response" in response["response"]
and response["response"]["response"].get("updatedInput") == {}
):
del response["response"]["response"]["updatedInput"]
return response
ph._build_approval_response = _patched_build_response
Prevention
- Always pass
updated_input=argumentswhen returningPermissionResultAllow()— even if you are not modifying the arguments - Test your permission callbacks with a tool that has required parameters to catch this bug early
- Use
bypass_permissionsfor development and trusted MCP servers - Pin your SDK version and test after each upgrade to catch regressions
Try it: Paste your error into our Error Diagnostic for an instant fix.
Configure it → Build your MCP config with our MCP Config Generator.
Related Issues
Configure permissions → Build your settings with our Permission Configurator.
- Advanced Claude Skills with Tool Use and Function Calling
- Fix: Claude Code MCP Server Disconnected
- Fix: Claude API Error 400 Invalid Request
Tools That Help
When debugging MCP server interactions and permission flows, a dev tool extension can help inspect the JSON-RPC message exchanges between the SDK and Claude CLI. For a deeper dive, see Fix Claude API Error 500 — Internal Server Error.
Related Articles
- Fix Anthropic SDK IndexError When Streaming
- Fix: Anthropic SDK toolRunner Drops Headers
- Fix: Structured Output + Thinking + Tool Use Bugs
Common Questions
What causes fix: anthropic sdk mcp tools get empty issues?
Common causes include misconfigured settings, outdated dependencies, and environment conflicts. Check your project configuration and ensure all dependencies are up to date.
How do I prevent this error from recurring?
Set up automated checks in your development workflow. Use Claude Code’s built-in validation tools to catch configuration issues before they reach production.
Does this fix work on all operating systems?
The core fix applies to macOS, Linux, and Windows. Some path-related adjustments may be needed depending on your OS. Check the platform-specific notes in the guide above.