ha-mcp-dev 7.6.0.dev623__tar.gz → 7.6.0.dev625__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. {ha_mcp_dev-7.6.0.dev623/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.6.0.dev625}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/server.py +6 -0
  4. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_service.py +4 -4
  5. ha_mcp_dev-7.6.0.dev625/src/ha_mcp/tools/validation_middleware.py +57 -0
  6. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  7. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  8. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/LICENSE +0 -0
  9. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/README.md +0 -0
  11. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/setup.cfg +0 -0
  12. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/__init__.py +0 -0
  13. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/__main__.py +0 -0
  14. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/_pypi_marker +0 -0
  15. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/_version.py +0 -0
  16. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/auth/__init__.py +0 -0
  17. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/auth/consent_form.py +0 -0
  18. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/auth/provider.py +0 -0
  19. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/backup_manager.py +0 -0
  20. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/client/__init__.py +0 -0
  21. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/client/rest_client.py +0 -0
  22. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/client/supervisor_client.py +0 -0
  23. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/client/websocket_client.py +0 -0
  24. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/client/websocket_listener.py +0 -0
  25. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/config.py +0 -0
  26. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/errors.py +0 -0
  27. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/policy/__init__.py +0 -0
  28. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/policy/approval_queue.py +0 -0
  29. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/policy/evaluator.py +0 -0
  30. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/policy/handlers.py +0 -0
  31. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/policy/middleware.py +0 -0
  32. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/policy/model.py +0 -0
  33. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/policy/persistence.py +0 -0
  34. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/policy/value_sources.py +0 -0
  35. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/py.typed +0 -0
  36. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  37. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  38. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  39. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  40. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  41. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  42. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  43. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  44. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  45. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  46. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  47. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  48. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  49. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  50. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  51. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  52. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  53. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  54. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  55. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  56. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  57. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  58. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/settings_ui.py +0 -0
  59. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/smoke_test.py +0 -0
  60. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  61. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/__init__.py +0 -0
  62. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/auto_backup.py +0 -0
  63. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/backup.py +0 -0
  64. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  65. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/device_control.py +0 -0
  66. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/enhanced.py +0 -0
  67. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/helpers.py +0 -0
  68. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/reference_validator.py +0 -0
  69. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/registry.py +0 -0
  70. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/smart_search.py +0 -0
  71. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_addons.py +0 -0
  72. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_areas.py +0 -0
  73. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  74. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  75. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_calendar.py +0 -0
  76. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_camera.py +0 -0
  77. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_categories.py +0 -0
  78. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_code.py +0 -0
  79. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  80. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  81. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  82. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  83. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  84. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  85. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_energy.py +0 -0
  86. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_entities.py +0 -0
  87. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  88. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_groups.py +0 -0
  89. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_hacs.py +0 -0
  90. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_history.py +0 -0
  91. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_integrations.py +0 -0
  92. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_labels.py +0 -0
  93. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  94. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_registry.py +0 -0
  95. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_resources.py +0 -0
  96. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_search.py +0 -0
  97. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_services.py +0 -0
  98. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_system.py +0 -0
  99. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_todo.py +0 -0
  100. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_traces.py +0 -0
  101. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_updates.py +0 -0
  102. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_utility.py +0 -0
  103. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  104. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  105. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/tools_zones.py +0 -0
  106. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/tools/util_helpers.py +0 -0
  107. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/transforms/__init__.py +0 -0
  108. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/transforms/categorized_search.py +0 -0
  109. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  110. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/__init__.py +0 -0
  111. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/config_hash.py +0 -0
  112. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/data_paths.py +0 -0
  113. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/domain_handlers.py +0 -0
  114. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  115. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  116. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/operation_manager.py +0 -0
  117. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/python_sandbox.py +0 -0
  118. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/skill_loader.py +0 -0
  119. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp/utils/usage_logger.py +0 -0
  120. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  121. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  122. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  123. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  124. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/tests/__init__.py +0 -0
  125. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/tests/test_constants.py +0 -0
  126. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev625}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.6.0.dev623
3
+ Version: 7.6.0.dev625
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.6.0.dev623"
7
+ version = "7.6.0.dev625"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13,<3.14"
@@ -190,6 +190,12 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
190
190
  # the skill guide tool are registered so it can wrap everything)
191
191
  self._apply_tool_search()
192
192
 
193
+ # Convert Pydantic type-validation errors to structured ToolErrors so
194
+ # models get actionable guidance instead of raw Pydantic messages.
195
+ from .tools.validation_middleware import ValidationErrorMiddleware
196
+
197
+ self.mcp.add_middleware(ValidationErrorMiddleware())
198
+
193
199
  # Wire tool security policies middleware (#966) — opt-in via
194
200
  # ENABLE_TOOL_SECURITY_POLICIES. Must come last so the middleware
195
201
  # wraps the final tool surface (including the search proxies).
@@ -572,14 +572,15 @@ class ServiceTools:
572
572
  @log_tool_usage
573
573
  async def ha_bulk_control(
574
574
  self,
575
- operations: str | list[dict[str, Any]],
575
+ operations: list[dict[str, Any]],
576
576
  parallel: bool = True,
577
577
  ctx: Context | None = None,
578
578
  ) -> dict[str, Any]:
579
579
  """Control multiple devices with bulk operation support and WebSocket tracking."""
580
580
  parallel_bool = parallel
581
581
 
582
- # Parse JSON operations if provided as string
582
+ # FastMCP validates operations as list[dict] before this runs.
583
+ # parse_json_param is kept as a defensive passthrough for the list case.
583
584
  try:
584
585
  parsed_operations = parse_json_param(operations, "operations")
585
586
  except ValueError as e:
@@ -591,8 +592,7 @@ class ServiceTools:
591
592
  )
592
593
  )
593
594
 
594
- # Ensure operations is a list of dicts
595
- if parsed_operations is None or not isinstance(parsed_operations, list):
595
+ if not isinstance(parsed_operations, list):
596
596
  raise_tool_error(
597
597
  create_validation_error(
598
598
  "Operations parameter must be a list",
@@ -0,0 +1,57 @@
1
+ """FastMCP middleware that converts Pydantic validation errors to structured ToolErrors.
2
+
3
+ When a model passes the wrong type for a tool parameter (e.g. a JSON string where
4
+ a dict is required), FastMCP raises a PydanticValidationError with a raw message
5
+ like "Input should be a valid dictionary". This middleware intercepts those errors
6
+ and converts them to ha-mcp's structured format with actionable guidance.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from typing import Any
13
+
14
+ from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
15
+ from pydantic import ValidationError as PydanticValidationError
16
+
17
+ from ..errors import create_validation_error
18
+ from .helpers import raise_tool_error
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Maps Pydantic error types to model-readable fix hints.
23
+ # FastMCP uses non-strict Pydantic: scalar mismatches (bool, int) are coerced
24
+ # rather than rejected, so only dict_type and list_type fire in practice.
25
+ _TYPE_HINTS: dict[str, str] = {
26
+ "dict_type": (
27
+ "expected a JSON object. "
28
+ 'Pass {"key": "value"} directly, not a JSON-encoded string.'
29
+ ),
30
+ "list_type": (
31
+ "expected a JSON array. Pass [...] directly, not a JSON-encoded string."
32
+ ),
33
+ }
34
+
35
+
36
+ class ValidationErrorMiddleware(Middleware):
37
+ """Convert PydanticValidationError from argument validation into ToolErrors."""
38
+
39
+ async def on_call_tool(
40
+ self, context: MiddlewareContext, call_next: CallNext
41
+ ) -> Any:
42
+ try:
43
+ return await call_next(context)
44
+ except PydanticValidationError as exc:
45
+ errors = exc.errors(include_url=False)
46
+ parts: list[str] = []
47
+ for err in errors:
48
+ loc = err.get("loc", ())
49
+ param = ".".join(str(p) for p in loc if p != "__root__")
50
+ hint = _TYPE_HINTS.get(err["type"], err["msg"])
51
+ parts.append(f"`{param}`: {hint}" if param else hint)
52
+ raise_tool_error(
53
+ create_validation_error(
54
+ "; ".join(parts) if parts else "Invalid argument types.",
55
+ details=", ".join(err["type"] for err in errors),
56
+ )
57
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.6.0.dev623
3
+ Version: 7.6.0.dev625
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -99,6 +99,7 @@ src/ha_mcp/tools/tools_voice_assistant.py
99
99
  src/ha_mcp/tools/tools_yaml_config.py
100
100
  src/ha_mcp/tools/tools_zones.py
101
101
  src/ha_mcp/tools/util_helpers.py
102
+ src/ha_mcp/tools/validation_middleware.py
102
103
  src/ha_mcp/transforms/__init__.py
103
104
  src/ha_mcp/transforms/categorized_search.py
104
105
  src/ha_mcp/transforms/lite_docstrings.py