Skip to content

Conversation

@mkmeral
Copy link
Contributor

@mkmeral mkmeral commented Dec 5, 2025

fix: Handle all JSON Schema composition keywords in tool property normalization

Description

This PR extends #1226 to handle all JSON Schema composition keywords, not just anyOf. The original fix addressed issue #1190 where MCP tools with List[str] | None parameters caused models to return string-encoded JSON ('["foo", "bar"]') instead of proper arrays.

Root Cause: _normalize_property() was adding type: "string" to properties that already had type constraints defined via composition keywords, creating invalid JSON Schema.

Fix: Skip setting default type: "string" when any composition keyword is present:

  • anyOf - Union types (e.g., Optional[T], str | int)
  • oneOf - Exclusive unions (exactly one must match)
  • allOf - Schema intersection (all must match)
  • not - Negation (value must not match)

Added COMPOSITION_KEYWORDS constant for maintainability and applied fix to both tools.py and registry.py.

Related Issues

Changes

File Change
src/strands/tools/tools.py Added COMPOSITION_KEYWORDS constant, updated _normalize_property()
src/strands/tools/registry.py Imported constant, updated validate_tool_spec()
tests/strands/tools/test_registry.py Added unit tests for all composition keywords

Testing

Test Results

Test Suite main fix1190
Unit Tests (comprehensive) 26 failed, 43 passed 69 passed
Unit Tests (registry) 3 failed, 23 passed 26 passed
Integration Tests (with LLM) 7 failed, 8 passed 15 passed

Tool Types Tested

  • @tool decorator with Optional[List[str]], Optional[Dict], Union types
  • Class-based tools with optional parameters
  • Module tools (PythonAgentTool) with anyOf in TOOL_SPEC
  • MCP tool schemas with all composition keywords

Real LLM Integration Tests

Verified agent correctly passes Python types (not string-encoded JSON):

@tool
def tag_processor(item: str, tags: Optional[List[str]] = None) -> str: ...

agent("Process 'document' with tags 'important' and 'urgent'")
assert isinstance(received_tags, list)  # ✓ list, not '["important", "urgent"]'

Backwards Compatibility Verified

  • Empty properties still get type: "string"
  • Existing types preserved (integer, boolean, array) ✓
  • $ref properties not modified ✓
  • Property constraints preserved (enum, default, min/max) ✓

Checklist

  • I have read the CONTRIBUTING document
  • I have added tests that prove my fix is effective
  • My changes generate no new warnings
  • I ran hatch run prepare

JackYPCOnline and others added 4 commits November 21, 2025 11:00
Apply the same fix from tools._normalize_property to registry.validate_tool_spec.
Properties with anyOf (union/optional types) should not get type: 'string' added
as it causes models to return string-encoded JSON instead of proper arrays/objects.
…, not)

- Add COMPOSITION_KEYWORDS constant for reusability
- Skip default type for properties with any composition keyword
- Add comprehensive unit tests for all composition keywords
@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] normalized tool property default type incorrect for objects and arrays

3 participants