|
9 | 9 | import logging |
10 | 10 | from typing import Annotated, Any, Literal |
11 | 11 |
|
| 12 | +from fastmcp.exceptions import ToolError |
12 | 13 | from pydantic import Field |
13 | 14 |
|
14 | 15 | from ..errors import ErrorCode, create_error_response |
15 | | -from .helpers import exception_to_structured_error, log_tool_usage |
| 16 | +from .helpers import exception_to_structured_error, log_tool_usage, raise_tool_error |
16 | 17 | from .tools_voice_assistant import KNOWN_ASSISTANTS |
17 | 18 | from .util_helpers import coerce_bool_param, parse_json_param, parse_string_list_param |
18 | 19 |
|
@@ -445,65 +446,65 @@ async def ha_set_entity( |
445 | 446 | try: |
446 | 447 | parsed_aliases = parse_string_list_param(aliases, "aliases") |
447 | 448 | except ValueError as e: |
448 | | - return create_error_response( |
| 449 | + raise_tool_error(create_error_response( |
449 | 450 | ErrorCode.VALIDATION_INVALID_PARAMETER, |
450 | 451 | f"Invalid aliases parameter: {e}", |
451 | | - ) |
| 452 | + )) |
452 | 453 |
|
453 | 454 | parsed_labels = None |
454 | 455 | if labels is not None: |
455 | 456 | try: |
456 | 457 | parsed_labels = parse_string_list_param(labels, "labels") |
457 | 458 | except ValueError as e: |
458 | | - return create_error_response( |
| 459 | + raise_tool_error(create_error_response( |
459 | 460 | ErrorCode.VALIDATION_INVALID_PARAMETER, |
460 | 461 | f"Invalid labels parameter: {e}", |
461 | | - ) |
| 462 | + )) |
462 | 463 |
|
463 | 464 | # Parse and validate expose_to parameter |
464 | 465 | parsed_expose_to: dict[str, bool] | None = None |
465 | 466 | if expose_to is not None: |
466 | 467 | try: |
467 | 468 | parsed = parse_json_param(expose_to, "expose_to") |
468 | 469 | except ValueError as e: |
469 | | - return create_error_response( |
| 470 | + raise_tool_error(create_error_response( |
470 | 471 | ErrorCode.VALIDATION_INVALID_PARAMETER, |
471 | 472 | str(e), |
472 | | - ) |
| 473 | + )) |
473 | 474 |
|
474 | 475 | if not isinstance(parsed, dict): |
475 | | - return create_error_response( |
| 476 | + raise_tool_error(create_error_response( |
476 | 477 | ErrorCode.VALIDATION_INVALID_PARAMETER, |
477 | 478 | "expose_to must be a dict mapping assistant IDs to booleans, " |
478 | 479 | 'e.g. {"conversation": true, "cloud.alexa": false}', |
479 | | - ) |
| 480 | + )) |
480 | 481 | parsed_expose_to = parsed |
481 | 482 |
|
482 | 483 | # Validate assistant names |
483 | 484 | invalid_assistants = [ |
484 | 485 | a for a in parsed_expose_to if a not in KNOWN_ASSISTANTS |
485 | 486 | ] |
486 | 487 | if invalid_assistants: |
487 | | - return create_error_response( |
| 488 | + raise_tool_error(create_error_response( |
488 | 489 | ErrorCode.VALIDATION_INVALID_PARAMETER, |
489 | 490 | f"Invalid assistant(s) in expose_to: {invalid_assistants}. " |
490 | 491 | f"Valid: {KNOWN_ASSISTANTS}", |
491 | | - ) |
| 492 | + )) |
492 | 493 |
|
493 | 494 | # Coerce values to bool |
494 | 495 | for asst, val in parsed_expose_to.items(): |
495 | 496 | try: |
496 | 497 | coerced = coerce_bool_param(val, f"expose_to[{asst}]") |
497 | 498 | except ValueError as e: |
498 | | - return create_error_response( |
| 499 | + raise_tool_error(create_error_response( |
499 | 500 | ErrorCode.VALIDATION_INVALID_PARAMETER, |
500 | 501 | str(e), |
501 | | - ) |
| 502 | + )) |
502 | 503 | if coerced is None: |
503 | | - return create_error_response( |
| 504 | + raise_tool_error(create_error_response( |
504 | 505 | ErrorCode.VALIDATION_INVALID_PARAMETER, |
505 | 506 | f"expose_to[{asst}] must be a boolean value", |
506 | | - ) |
| 507 | + )) |
507 | 508 | parsed_expose_to[asst] = coerced |
508 | 509 |
|
509 | 510 | # Single entity case - use existing logic |
@@ -579,10 +580,12 @@ async def ha_set_entity( |
579 | 580 |
|
580 | 581 | return response |
581 | 582 |
|
| 583 | + except ToolError: |
| 584 | + raise |
582 | 585 | except Exception as e: |
583 | 586 | logger.error(f"Error updating entity: {e}") |
584 | 587 | eid_context = entity_id if isinstance(entity_id, str) else entity_ids |
585 | | - return exception_to_structured_error(e, context={"entity_id": eid_context}) |
| 588 | + exception_to_structured_error(e, context={"entity_id": eid_context}, raise_error=True) |
586 | 589 |
|
587 | 590 | @mcp.tool( |
588 | 591 | annotations={ |
@@ -768,6 +771,7 @@ async def _fetch_entity(eid: str) -> dict[str, Any]: |
768 | 771 |
|
769 | 772 | except Exception as e: |
770 | 773 | logger.error(f"Error getting entity: {e}") |
771 | | - return exception_to_structured_error( |
772 | | - e, context={"entity_id": entity_id if isinstance(entity_id, str) else entity_ids} |
| 774 | + exception_to_structured_error( |
| 775 | + e, context={"entity_id": entity_id if isinstance(entity_id, str) else entity_ids}, |
| 776 | + raise_error=True, |
773 | 777 | ) |
0 commit comments