ha-mcp-dev 6.7.2.dev251__tar.gz → 6.7.2.dev253__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 (94) hide show
  1. {ha_mcp_dev-6.7.2.dev251/src/ha_mcp_dev.egg-info → ha_mcp_dev-6.7.2.dev253}/PKG-INFO +1 -1
  2. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/pyproject.toml +1 -1
  3. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/client/rest_client.py +8 -4
  4. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/client/websocket_client.py +105 -23
  5. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  6. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/LICENSE +0 -0
  7. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/MANIFEST.in +0 -0
  8. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/README.md +0 -0
  9. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/setup.cfg +0 -0
  10. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/__init__.py +0 -0
  11. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/__main__.py +0 -0
  12. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/_pypi_marker +0 -0
  13. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/client/__init__.py +0 -0
  17. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/client/websocket_listener.py +0 -0
  18. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/config.py +0 -0
  19. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/errors.py +0 -0
  20. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/py.typed +0 -0
  21. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/card_types.json +0 -0
  22. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/dashboard_guide.md +0 -0
  23. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  24. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  25. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  26. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  27. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  28. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  29. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  30. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  31. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  32. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  33. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  34. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  35. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  36. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  37. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  38. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  39. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/server.py +0 -0
  40. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/smoke_test.py +0 -0
  41. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/__init__.py +0 -0
  42. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/backup.py +0 -0
  43. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/device_control.py +0 -0
  44. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/enhanced.py +0 -0
  45. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/helpers.py +0 -0
  46. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/registry.py +0 -0
  47. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/smart_search.py +0 -0
  48. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_addons.py +0 -0
  49. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_areas.py +0 -0
  50. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  51. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  52. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_calendar.py +0 -0
  53. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_camera.py +0 -0
  54. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  55. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  56. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  57. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  58. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_config_info.py +0 -0
  59. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  60. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_entities.py +0 -0
  61. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  62. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_groups.py +0 -0
  63. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_hacs.py +0 -0
  64. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_history.py +0 -0
  65. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_integrations.py +0 -0
  66. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_labels.py +0 -0
  67. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  68. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_registry.py +0 -0
  69. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_resources.py +0 -0
  70. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_search.py +0 -0
  71. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_service.py +0 -0
  72. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_services.py +0 -0
  73. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_system.py +0 -0
  74. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_todo.py +0 -0
  75. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_traces.py +0 -0
  76. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_updates.py +0 -0
  77. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_utility.py +0 -0
  78. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  79. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/tools_zones.py +0 -0
  80. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/tools/util_helpers.py +0 -0
  81. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/utils/__init__.py +0 -0
  82. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/utils/domain_handlers.py +0 -0
  83. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  84. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/utils/operation_manager.py +0 -0
  85. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/utils/python_sandbox.py +0 -0
  86. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp/utils/usage_logger.py +0 -0
  87. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  88. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  89. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  90. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  91. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  92. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/tests/__init__.py +0 -0
  93. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/tests/test_constants.py +0 -0
  94. {ha_mcp_dev-6.7.2.dev251 → ha_mcp_dev-6.7.2.dev253}/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: 6.7.2.dev251
3
+ Version: 6.7.2.dev253
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 = "6.7.2.dev251"
7
+ version = "6.7.2.dev253"
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"
@@ -684,8 +684,10 @@ class HomeAssistantClient:
684
684
  async def send_websocket_message(self, message: dict[str, Any]) -> dict[str, Any]:
685
685
  """Send message via WebSocket and wait for response.
686
686
 
687
- Uses the global WebSocket singleton to avoid race conditions from
688
- parallel tool calls creating multiple simultaneous connections.
687
+ Uses a per-client WebSocket connection keyed to the client's own
688
+ credentials (base_url + token). This ensures OAuth mode uses the
689
+ real HA credentials from the token claims, not the global sentinel
690
+ settings.
689
691
  """
690
692
  from .websocket_client import get_websocket_client
691
693
 
@@ -694,8 +696,10 @@ class HomeAssistantClient:
694
696
 
695
697
  for attempt in range(max_retries):
696
698
  try:
697
- # Use singleton WebSocket client (shared, reused connection)
698
- ws_client = await get_websocket_client()
699
+ # Use per-client WebSocket keyed to this client's credentials
700
+ ws_client = await get_websocket_client(
701
+ url=self.base_url, token=self.token
702
+ )
699
703
 
700
704
  # Special handling for render_template which returns an event with the actual result
701
705
  if message.get("type") == "render_template":
@@ -8,6 +8,7 @@ This module handles WebSocket connections to Home Assistant for:
8
8
  """
9
9
 
10
10
  import asyncio
11
+ import hashlib
11
12
  import json
12
13
  import logging
13
14
  import time
@@ -553,11 +554,21 @@ class HomeAssistantWebSocketClient:
553
554
 
554
555
 
555
556
 
557
+ MAX_POOL_SIZE = 50
558
+
559
+
556
560
  class WebSocketManager:
557
- """Singleton manager for Home Assistant WebSocket connections."""
561
+ """Singleton manager for Home Assistant WebSocket connections.
562
+
563
+ Maintains a pool of WebSocket connections keyed by (url, token) so that
564
+ multiple OAuth users can have concurrent connections without interfering
565
+ with each other. The pool is bounded to ``MAX_POOL_SIZE`` entries; when
566
+ this limit is exceeded the least-recently-used connection is evicted.
567
+ """
558
568
 
559
569
  _instance = None
560
- _client = None
570
+ _clients: dict[str, HomeAssistantWebSocketClient]
571
+ _last_used: dict[str, float]
561
572
  _current_loop: asyncio.AbstractEventLoop | None = None
562
573
  _lock: asyncio.Lock | None = None
563
574
  _lock_loop: asyncio.AbstractEventLoop | None = None
@@ -566,6 +577,8 @@ class WebSocketManager:
566
577
  def __new__(cls) -> "WebSocketManager":
567
578
  if cls._instance is None:
568
579
  cls._instance = super().__new__(cls)
580
+ cls._instance._clients = {}
581
+ cls._instance._last_used = {}
569
582
  cls._instance._lock = None
570
583
  cls._instance._lock_loop = None
571
584
  cls._instance._client_factory = HomeAssistantWebSocketClient
@@ -600,8 +613,28 @@ class WebSocketManager:
600
613
  self._lock_loop = current_loop
601
614
  logger.debug("Created new WebSocketManager lock for current event loop")
602
615
 
603
- async def get_client(self) -> HomeAssistantWebSocketClient:
604
- """Get WebSocket client, creating connection if needed."""
616
+ @staticmethod
617
+ def _client_key(url: str, token: str) -> str:
618
+ """Create a cache key from credentials."""
619
+ return hashlib.sha256(f"{url.rstrip('/')}:{token}".encode()).hexdigest()
620
+
621
+ async def get_client(
622
+ self,
623
+ url: str | None = None,
624
+ token: str | None = None,
625
+ ) -> HomeAssistantWebSocketClient:
626
+ """Get WebSocket client, creating connection if needed.
627
+
628
+ Maintains a pool of connections keyed by credentials. In OAuth mode,
629
+ each user gets their own connection. In non-OAuth mode, the global
630
+ settings are used as the key.
631
+
632
+ Args:
633
+ url: Optional HA URL. If provided with token, uses these
634
+ credentials instead of global settings. This is required
635
+ for OAuth mode where each request has its own credentials.
636
+ token: Optional HA token. Must be provided with url.
637
+ """
605
638
  current_loop = asyncio.get_event_loop()
606
639
 
607
640
  self._ensure_lock()
@@ -610,47 +643,96 @@ class WebSocketManager:
610
643
  raise Exception("Lock not initialized")
611
644
  async with self._lock:
612
645
  if self._current_loop is not None and self._current_loop != current_loop:
613
- if self._client:
646
+ # Event loop changed — disconnect all clients
647
+ for client in self._clients.values():
614
648
  try:
615
- await self._client.disconnect()
649
+ await client.disconnect()
616
650
  except Exception:
617
651
  pass
618
- self._client = None
652
+ self._clients.clear()
653
+ self._last_used.clear()
619
654
 
620
655
  self._current_loop = current_loop
621
656
 
622
- if self._client and self._client.is_connected:
623
- return self._client
657
+ # Determine credentials to use
658
+ if url and token:
659
+ ws_url = url
660
+ ws_token = token
661
+ else:
662
+ settings = get_global_settings()
663
+ ws_url = settings.homeassistant_url
664
+ ws_token = settings.homeassistant_token
665
+
666
+ key = self._client_key(ws_url, ws_token)
667
+
668
+ # Return existing connected client for these credentials
669
+ existing = self._clients.get(key)
670
+ if existing and existing.is_connected:
671
+ self._last_used[key] = time.monotonic()
672
+ return existing
673
+
674
+ # Remove stale client if present
675
+ if existing:
676
+ self._clients.pop(key, None)
677
+ self._last_used.pop(key, None)
624
678
 
625
- settings = get_global_settings()
626
679
  factory = self._client_factory or HomeAssistantWebSocketClient
627
- self._client = factory(
628
- settings.homeassistant_url, settings.homeassistant_token
629
- )
680
+ client = factory(ws_url, ws_token)
630
681
 
631
- connected = await self._client.connect()
682
+ connected = await client.connect()
632
683
  if not connected:
633
684
  raise Exception("Failed to connect to Home Assistant WebSocket")
634
685
 
635
- return self._client
686
+ self._clients[key] = client
687
+ self._last_used[key] = time.monotonic()
688
+
689
+ # Evict least-recently-used connection if over limit
690
+ if len(self._clients) > MAX_POOL_SIZE:
691
+ oldest_key = min(self._last_used, key=self._last_used.get)
692
+ stale = self._clients.pop(oldest_key, None)
693
+ self._last_used.pop(oldest_key, None)
694
+ if stale:
695
+ try:
696
+ await stale.disconnect()
697
+ except Exception:
698
+ logger.warning(
699
+ "Error disconnecting evicted WebSocket client",
700
+ exc_info=True,
701
+ )
702
+
703
+ return client
636
704
 
637
705
  async def disconnect(self) -> None:
638
- """Disconnect WebSocket client."""
706
+ """Disconnect all WebSocket clients."""
639
707
  self._ensure_lock()
640
708
 
641
709
  if not self._lock:
642
710
  raise Exception("Lock not initialized")
643
711
  async with self._lock:
644
- if self._client:
645
- await self._client.disconnect()
646
- self._client = None
647
- self._current_loop = None
712
+ for client in self._clients.values():
713
+ try:
714
+ await client.disconnect()
715
+ except Exception:
716
+ logger.warning(
717
+ "Error disconnecting WebSocket client", exc_info=True
718
+ )
719
+ self._clients.clear()
720
+ self._last_used.clear()
721
+ self._current_loop = None
648
722
 
649
723
 
650
724
  # Global WebSocket manager instance
651
725
  websocket_manager = WebSocketManager()
652
726
 
653
727
 
654
- async def get_websocket_client() -> HomeAssistantWebSocketClient:
655
- """Get the global WebSocket client instance."""
656
- return await websocket_manager.get_client()
728
+ async def get_websocket_client(
729
+ url: str | None = None,
730
+ token: str | None = None,
731
+ ) -> HomeAssistantWebSocketClient:
732
+ """Get the global WebSocket client instance.
733
+
734
+ Args:
735
+ url: Optional HA URL for per-client credentials (OAuth mode).
736
+ token: Optional HA token for per-client credentials (OAuth mode).
737
+ """
738
+ return await websocket_manager.get_client(url=url, token=token)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 6.7.2.dev251
3
+ Version: 6.7.2.dev253
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