Skip to content

Bug #22: Parameter Normalization

Status: RESOLVED in v4.0.2 Category: Parameter Normalization Severity: High (tools fail silently or with cryptic errors) Resolution Date: 2026-03-16

Claude Desktop sends MCP tool parameters in formats that don’t match the tool schema:

PatternWhat Claude SendsWhat the Tool Expects
Wrong param nameold_str: "hello"old_text: "hello"
Wrong param namenew_str: "world"new_text: "world"
String booleanforce: "true"force: true
String booleancount_only: "true"count_only: true
String booleandry_run: "true"dry_run: true
Raw JSON arrayedits_json: [{...}]edits_json: "[{...}]"
Wrong field namepipeline step type: "search"action: "search"

These mismatches caused tools to fail with “missing required parameter” or type assertion panics.

Claude Desktop’s tool calling doesn’t always match the declared JSON schema precisely. Parameter names get abbreviated (old_textold_str), boolean parameters arrive as strings, and JSON payloads arrive as raw objects instead of serialized strings.

Solution: Self-Learning Request Normalizer

Section titled “Solution: Self-Learning Request Normalizer”

Instead of patching each tool handler individually (which is what Bug #18 did), we implemented a data-driven normalization engine that sits between the MCP transport and tool handlers.

Claude Desktop → MCP Transport → auditWrap() → Normalizer → Tool Handler
Audit Logger
(normalizer_stats.json)
TypeDescriptionExample
param_aliasRename parameterold_strold_text
param_defaultAdd missing parameter with default valueMissing id → auto-generated
type_coerceConvert string to correct type"true"true (bool)
json_accept_bothAccept raw JSON or string[{...}]"[{...}]"
nested_aliasRename field inside JSON payloadpipeline step typeaction
nested_defaultAdd missing field inside JSON payloadpipeline step missing id → auto-generated

The normalizer ships with 14 rules covering all known Claude Desktop patterns:

  1. edit-old_strold_strold_text (edit_file)
  2. edit-new_strnew_strnew_text (edit_file)
  3. multi_edit-old_strold_strold_text (multi_edit)
  4. multi_edit-new_strnew_strnew_text (multi_edit)
  5. force-bool-coerceforce: "true"force: true (all tools)
  6. count_only-bool-coercecount_only: "true"count_only: true (all tools)
  7. dry_run-bool-coercedry_run: "true"dry_run: true (all tools)
  8. permanent-bool-coercepermanent: "true"permanent: true (all tools)
  9. recursive-bool-coercerecursive: "true"recursive: true (all tools)
  10. multi_edit-edits-coerce — raw JSON array → JSON string (multi_edit)
  11. pipeline-type-alias — step typeaction (batch_operations)
  12. pipeline-step-id-default — auto-generate missing step id (batch_operations)
  13. batch-type-alias — step typeaction (batch_operations)
  14. batch-step-id-default — auto-generate missing step id (batch_operations)

Custom rules can be added via --normalizer-rules rules.json:

[
{
"id": "custom-alias",
"tools": ["read_file"],
"type": "param_alias",
"from": "filename",
"to": "path"
}
]

Two new dashboard pages track normalizer activity:

  • Normalizer: Total processed/normalized, by-tool breakdown, by-rule hits, recent normalizations feed
  • Error Patterns: Groups recurring errors by tool and pattern, shows trends, suggests new normalizer rules

Normalizer stats are written to normalizer_stats.json every 30 seconds (when --log-dir is set), preserving data across server restarts.

FileChange
core/normalizer.goNew: 563-line normalizer engine
core/engine.goNormalizer initialization
core/audit_logger.goNormalizations field in audit entries
main.goauditWrap() middleware, --normalizer-rules flag
cmd/dashboard/main.go/api/normalizer and /api/error-patterns endpoints
cmd/dashboard/static/index.htmlNormalizer and Error Patterns pages
cmd/dashboard/static/app.jsDashboard rendering functions
Terminal window
go test ./tests/ -run TestNormalizer -v # 15 normalizer tests
go test ./tests/ -run TestBug22 -v # Bug #22 regression tests

Test files: tests/normalizer_test.go, tests/bug22_multi_edit_test.go