Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/performance-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ permissions:
env:
PYTHON_VERSION: "3.13"
UV_CACHE_DIR: /tmp/.uv-cache
# renovate: datasource=docker depName=ghcr.io/home-assistant/home-assistant
HA_IMAGE_GHCR: "ghcr.io/home-assistant/home-assistant:2026.4.1"

jobs:
performance-tests:
Expand All @@ -39,6 +41,38 @@ jobs:

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
with:
cache-binary: true

- name: Cache HA Docker image
id: cache-ha-image
uses: actions/cache@v5
with:
path: /tmp/ha-image.tar
key: ha-image-${{ env.HA_IMAGE_GHCR }}-${{ runner.arch }}

- name: Load cached HA image
if: steps.cache-ha-image.outputs.cache-hit == 'true'
run: docker load -i /tmp/ha-image.tar

- name: Pull HA image (GHCR → Docker Hub fallback)
if: steps.cache-ha-image.outputs.cache-hit != 'true'
run: |
HA_VERSION="${HA_IMAGE_GHCR##*:}"
HA_IMAGE_DOCKERHUB="homeassistant/home-assistant:${HA_VERSION}"
for registry in "$HA_IMAGE_GHCR" "$HA_IMAGE_DOCKERHUB"; do
echo "Trying $registry..."
if docker pull "$registry"; then
if [ "$registry" != "$HA_IMAGE_GHCR" ]; then
docker tag "$registry" "$HA_IMAGE_GHCR"
fi
docker save "$HA_IMAGE_GHCR" -o /tmp/ha-image.tar
echo "Pulled and cached from $registry"
exit 0
fi
done
echo "Failed to pull from any registry"
exit 1

- name: Install uv
uses: astral-sh/setup-uv@v7
Expand Down
21 changes: 16 additions & 5 deletions src/ha_mcp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ def _get_oauth_client(self) -> "HomeAssistantClient":
claims = token.claims

if not claims or "ha_token" not in claims:
logger.error(f"OAuth token missing HA credentials. Keys present: {list(claims.keys()) if claims else []}")
logger.error(
f"OAuth token missing HA credentials. Keys present: {list(claims.keys()) if claims else []}"
)
raise RuntimeError("No Home Assistant credentials in OAuth token claims")

ha_token = claims["ha_token"]
Expand Down Expand Up @@ -610,7 +612,9 @@ def register_browser_landing(mcp_instance: "FastMCP | _DeferredMCP", path: str)
path: The MCP endpoint path (e.g. "/mcp" or a secret path).
"""
if path in _registered_landing_paths:
logger.warning("register_browser_landing: %r already registered, skipping", path)
logger.warning(
"register_browser_landing: %r already registered, skipping", path
)
return
_registered_landing_paths.add(path)

Expand Down Expand Up @@ -710,6 +714,15 @@ def main_oauth() -> None:
Note: HOMEASSISTANT_TOKEN is NOT required in this mode.
Per-user tokens are collected via the OAuth consent form.
"""
# In OAuth mode, per-user tokens come from the consent form — no
# server-level HOMEASSISTANT_TOKEN is needed. Set the sentinel so
# Settings validation passes even when the env var is empty (e.g.
# Dockerfile sets HOMEASSISTANT_TOKEN=""). Fixes #886.
if not os.getenv("HOMEASSISTANT_TOKEN"):
Comment thread
kingpanther13 marked this conversation as resolved.
from ha_mcp.config import OAUTH_MODE_TOKEN

os.environ["HOMEASSISTANT_TOKEN"] = OAUTH_MODE_TOKEN

# Configure logging for OAuth mode
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
_setup_logging(log_level, force=True)
Expand Down Expand Up @@ -791,9 +804,7 @@ async def _run_oauth_server(ha_url: str, base_url: str, port: int, path: str) ->
f"Starting OAuth-enabled MCP server with {len(tools)} tools on {base_url}{path}"
)

await _run_with_shutdown(
mcp.run_async(**_http_run_kwargs("http", port, path))
)
await _run_with_shutdown(mcp.run_async(**_http_run_kwargs("http", port, path)))


if __name__ == "__main__":
Expand Down
Loading