Skip to content

bug: workflow tools fail with parameter type mismatch - request passed as string instead of dict #116

@dougborg

Description

@dougborg

Description

Workflow tools that accept a complex request parameter (like review_urgent_order_requirements, forecasts_get_for_products, etc.) fail with a Pydantic validation error because the MCP layer is passing the request parameter as a JSON string instead of a dictionary.

Error

1 validation error for call[review_urgent_order_requirements]
request
  Input should be a valid dictionary or instance of ReviewUrgentOrdersRequest [type=model_type, input_value='{"days_threshold": 30}', input_type=str]

Affected Tools

All workflow tools that use the nested request pattern:

  • review_urgent_order_requirements
  • forecasts_get_for_products
  • forecasts_update_and_monitor
  • generate_purchase_orders_from_urgent_items
  • create_supplier_with_products
  • configure_product
  • update_forecast_settings
  • products_configure_lifecycle
  • Potentially others

Root Cause

The MCP tool registration is receiving the request parameter as a JSON string, but the Pydantic validation expects a dictionary or model instance. This is likely happening in the tool wrapper/adapter layer between FastMCP and our tool implementations.

Reproduction

# Via MCP
mcp__stocktrim-dev__review_urgent_order_requirements(
    request={"days_threshold": 30}
)
# Error: Input should be a valid dictionary...

Expected Behavior

The MCP layer should deserialize the JSON parameter into a dictionary before passing it to the Pydantic model, or the tool should accept a string and deserialize it internally.

Investigation Needed

  1. Check how FastMCP handles complex nested parameters
  2. Review the tool wrapper implementation in tools/workflows/
  3. Determine if this is a FastMCP issue or our implementation issue
  4. Check ADR 002 (tool-interface-pattern) for parameter flattening guidance

Proposed Solutions

Option 1: Pre-deserialize in tool wrapper

@mcp.tool()
async def review_urgent_order_requirements(
    request: str,  # Accept as string
    context: Context | None = None,
) -> str:
    # Deserialize manually
    request_dict = json.loads(request) if isinstance(request, str) else request
    request_obj = ReviewUrgentOrdersRequest(**request_dict)
    return await _review_urgent_order_requirements(request_obj, context)

Option 2: Flatten parameters per ADR 002

Follow the parameter flattening pattern and avoid nested request objects:

@mcp.tool()
async def review_urgent_order_requirements(
    days_threshold: int = 30,
    location_codes: list[str] | None = None,
    category: str | None = None,
    supplier_codes: list[str] | None = None,
    context: Context | None = None,
) -> str:
    request = ReviewUrgentOrdersRequest(
        days_threshold=days_threshold,
        location_codes=location_codes,
        category=category,
        supplier_codes=supplier_codes,
    )
    return await _review_urgent_order_requirements(request, context)

Impact

  • Severity: HIGH - Blocks all workflow tools (the most valuable MCP features)
  • Affected Tools: ~8 workflow tools
  • Workaround: None - these tools are completely non-functional
  • Users Affected: Anyone trying to use high-level workflow operations

Priority

This should be P0 as it blocks the most important and advertised features of the MCP server.

Related Files

  • stocktrim_mcp_server/src/stocktrim_mcp_server/tools/workflows/*.py
  • docs/architecture/decisions/002-tool-interface-pattern.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions