ha-mcp-dev 7.6.0.dev623__tar.gz → 7.6.0.dev624__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.dev624}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/server.py +6 -0
  4. ha_mcp_dev-7.6.0.dev624/src/ha_mcp/tools/validation_middleware.py +57 -0
  5. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  6. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  7. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/LICENSE +0 -0
  8. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/MANIFEST.in +0 -0
  9. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/README.md +0 -0
  10. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/setup.cfg +0 -0
  11. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/__init__.py +0 -0
  12. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/__main__.py +0 -0
  13. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/_pypi_marker +0 -0
  14. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/_version.py +0 -0
  15. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/auth/__init__.py +0 -0
  16. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/auth/consent_form.py +0 -0
  17. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/auth/provider.py +0 -0
  18. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/backup_manager.py +0 -0
  19. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/client/__init__.py +0 -0
  20. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/client/rest_client.py +0 -0
  21. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/client/supervisor_client.py +0 -0
  22. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/client/websocket_client.py +0 -0
  23. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/client/websocket_listener.py +0 -0
  24. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/config.py +0 -0
  25. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/errors.py +0 -0
  26. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/policy/__init__.py +0 -0
  27. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/policy/approval_queue.py +0 -0
  28. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/policy/evaluator.py +0 -0
  29. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/policy/handlers.py +0 -0
  30. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/policy/middleware.py +0 -0
  31. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/policy/model.py +0 -0
  32. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/policy/persistence.py +0 -0
  33. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/policy/value_sources.py +0 -0
  34. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/py.typed +0 -0
  35. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  36. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  37. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  38. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  39. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  40. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  41. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  42. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  43. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  44. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  45. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  46. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  47. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  48. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  49. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  50. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  51. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  52. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  53. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  54. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  55. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  56. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  57. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/settings_ui.py +0 -0
  58. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/smoke_test.py +0 -0
  59. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  60. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/__init__.py +0 -0
  61. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/auto_backup.py +0 -0
  62. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/backup.py +0 -0
  63. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  64. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/device_control.py +0 -0
  65. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/enhanced.py +0 -0
  66. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/helpers.py +0 -0
  67. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/reference_validator.py +0 -0
  68. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/registry.py +0 -0
  69. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/smart_search.py +0 -0
  70. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_addons.py +0 -0
  71. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_areas.py +0 -0
  72. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  73. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  74. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_calendar.py +0 -0
  75. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_camera.py +0 -0
  76. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_categories.py +0 -0
  77. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_code.py +0 -0
  78. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  79. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  80. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  81. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  82. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  83. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  84. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_energy.py +0 -0
  85. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_entities.py +0 -0
  86. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  87. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_groups.py +0 -0
  88. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_hacs.py +0 -0
  89. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_history.py +0 -0
  90. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_integrations.py +0 -0
  91. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_labels.py +0 -0
  92. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  93. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_registry.py +0 -0
  94. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_resources.py +0 -0
  95. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_search.py +0 -0
  96. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_service.py +0 -0
  97. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_services.py +0 -0
  98. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_system.py +0 -0
  99. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_todo.py +0 -0
  100. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_traces.py +0 -0
  101. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_updates.py +0 -0
  102. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_utility.py +0 -0
  103. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  104. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  105. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/tools_zones.py +0 -0
  106. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/tools/util_helpers.py +0 -0
  107. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/transforms/__init__.py +0 -0
  108. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/transforms/categorized_search.py +0 -0
  109. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  110. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/__init__.py +0 -0
  111. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/config_hash.py +0 -0
  112. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/data_paths.py +0 -0
  113. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/domain_handlers.py +0 -0
  114. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  115. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  116. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/operation_manager.py +0 -0
  117. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/python_sandbox.py +0 -0
  118. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/skill_loader.py +0 -0
  119. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/src/ha_mcp/utils/usage_logger.py +0 -0
  120. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/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.dev624}/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.dev624}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  123. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/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.dev624}/tests/__init__.py +0 -0
  125. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/tests/test_constants.py +0 -0
  126. {ha_mcp_dev-7.6.0.dev623 → ha_mcp_dev-7.6.0.dev624}/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.dev624
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.dev624"
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).
@@ -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.dev624
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