Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d0ee42d
feat: convert 4 doc tools to MCP resources and skill references
kingpanther13 Mar 21, 2026
5f1b2b2
fix: remove unused asyncio import, update resource tests
kingpanther13 Mar 21, 2026
477bc6a
fix: add input sanitization and 404 handling for doc resources
kingpanther13 Mar 21, 2026
baf222a
refactor: remove ha://docs/ resource templates, use skills framework
kingpanther13 Mar 21, 2026
8e0adfd
fix: tighten test assertion and clean up stale pyproject globs
kingpanther13 Mar 21, 2026
6578f45
feat: Search-based tool discovery with categorized call proxies (#727)
kingpanther13 Mar 21, 2026
eb81e43
chore: migrate from pre-commit to lefthook for parallel hook executio…
sergeykad Mar 22, 2026
0dfd427
merge: resolve conflict with upstream/master
kingpanther13 Mar 22, 2026
741ee5c
fix: add skill/resource tools to s13 UAT story tools_should_use
kingpanther13 Mar 22, 2026
9eef2b5
fix: restore skills-vendor submodule to correct commit
kingpanther13 Mar 22, 2026
6288ba6
ci(deps): bump renovatebot/github-action in the github-actions group …
dependabot[bot] Mar 23, 2026
625390a
deps(deps-dev): bump the dev-dependencies group with 2 updates (#808)
dependabot[bot] Mar 23, 2026
e307835
deps(deps-dev): bump openai from 2.28.0 to 2.29.0 (#809)
dependabot[bot] Mar 23, 2026
adafce8
deps(deps-dev): bump testcontainers from 4.14.1 to 4.14.2 (#810)
dependabot[bot] Mar 23, 2026
5cbcb37
feat: add Python 3.14 support (#700)
kingpanther13 Mar 23, 2026
7fb4170
fix: surface connection errors in ha_get_overview instead of returnin…
sergeykad Mar 24, 2026
eb7f552
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.11.0 (#816)
github-actions[bot] Mar 24, 2026
f0c0c83
chore(ci): bump uv in PR workflow from 0.9.30 to 0.11.0 and add Renov…
kingpanther13 Mar 24, 2026
4fd0587
Merge branch 'master' into feat/docs-tools-to-resources
kingpanther13 Mar 25, 2026
9d9ebb9
fix: remove stale jq code from merge resolution
kingpanther13 Mar 25, 2026
c97d839
Clarify Claude.ai connection error and stateless session logs (#805)
kingpanther13 Mar 25, 2026
6595540
chore(deps): bump smol-toml from 1.6.0 to 1.6.1 in /site (#818)
dependabot[bot] Mar 25, 2026
943c6f4
deps(deps-dev): bump requests from 2.32.5 to 2.33.0 (#819)
dependabot[bot] Mar 25, 2026
a26280f
chore(deps): bump yaml from 2.8.2 to 2.8.3 in /site (#820)
dependabot[bot] Mar 25, 2026
004fe26
chore(deps): bump picomatch in /site (#821)
dependabot[bot] Mar 26, 2026
f62e800
chore(deps): bump astro from 5.16.11 to 5.18.1 in /site (#826)
dependabot[bot] Mar 26, 2026
caf8f49
fix: update test to match enable_skills_as_tools=True default
kingpanther13 Mar 26, 2026
2ed3887
fix: add exact_match to all search tools, badge search, and dashboard…
kingpanther13 Mar 27, 2026
e354782
Potential fix for code scanning alert no. 31: Workflow does not conta…
sergeykad Mar 27, 2026
dc9f6d8
deps(deps): bump cryptography from 46.0.5 to 46.0.6 (#830)
dependabot[bot] Mar 27, 2026
761e5c3
docs: document OAuth v7.0.0 breaking change (HOMEASSISTANT_URL requir…
sergeykad Mar 27, 2026
7a573f8
merge: resolve conflict with upstream/master
kingpanther13 Mar 28, 2026
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
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ docs/

# CI/CD
.github/
.pre-commit-config.yaml
lefthook.yml

# Docker
docker-compose*.yml
Expand Down
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ ENABLE_WEBSOCKET=true
DEBUG=false
LOG_LEVEL=INFO

# Tool Search: replace the full tool catalog (~80 tools) with search-based
# discovery (~4 proxy tools). Reduces idle context from ~46K to ~5K tokens.
# Recommended for models without deferred tool support or with limited context.
# ENABLE_TOOL_SEARCH=false

# Optional: MCP Server Configuration
MCP_SERVER_NAME=ha-mcp
# MCP_SERVER_VERSION defaults to the package version (e.g. 6.7.2)
12 changes: 8 additions & 4 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ jobs:
name: Check uv.lock
runs-on: ubuntu-latest
container:
image: ghcr.io/astral-sh/uv:0.9.30-python3.13-trixie-slim
# renovate: datasource=docker depName=ghcr.io/astral-sh/uv
image: ghcr.io/astral-sh/uv:0.11.0-python3.13-trixie-slim
timeout-minutes: 2

steps:
Expand All @@ -31,7 +32,8 @@ jobs:
name: Ruff Lint
runs-on: ubuntu-latest
container:
image: ghcr.io/astral-sh/uv:0.9.30-python3.13-trixie-slim
# renovate: datasource=docker depName=ghcr.io/astral-sh/uv
image: ghcr.io/astral-sh/uv:0.11.0-python3.13-trixie-slim
timeout-minutes: 5

steps:
Expand All @@ -47,7 +49,8 @@ jobs:
name: Mypy Type Check
runs-on: ubuntu-latest
container:
image: ghcr.io/astral-sh/uv:0.9.30-python3.13-trixie-slim
# renovate: datasource=docker depName=ghcr.io/astral-sh/uv
image: ghcr.io/astral-sh/uv:0.11.0-python3.13-trixie-slim
timeout-minutes: 5

steps:
Expand All @@ -66,7 +69,8 @@ jobs:
name: Unit Tests
runs-on: ubuntu-latest
container:
image: ghcr.io/astral-sh/uv:0.9.30-python3.13-trixie-slim
# renovate: datasource=docker depName=ghcr.io/astral-sh/uv
image: ghcr.io/astral-sh/uv:0.11.0-python3.13-trixie-slim
timeout-minutes: 5

steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/renovate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
uses: actions/checkout@v6

- name: Self-hosted Renovate
uses: renovatebot/github-action@v46.1.5
uses: renovatebot/github-action@v46.1.6
with:
configurationFile: renovate.json
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/semver-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ on:
default: 'false'
type: boolean

permissions:
contents: read

env:
PYTHON_VERSION: "3.13"

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test-installer-scripts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ on:
- cron: '0 21 * * *' # Daily at 4pm Eastern
workflow_dispatch:

permissions:
contents: read

jobs:
test-macos:
runs-on: macos-latest
Expand Down
25 changes: 0 additions & 25 deletions .pre-commit-config.yaml

This file was deleted.

10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ def register_<domain>_tools(mcp, client, **kwargs):
@log_tool_usage
async def ha_<verb>_<noun>(param: str) -> dict[str, Any]:
"""One-line summary starting with action verb."""
# For complex schemas, add: "Use ha_get_domain_docs('<domain>') for details."
# For complex schemas, add: "Use ha_get_skill_home_assistant_best_practices for details."
```

### Safety Annotations
Expand Down Expand Up @@ -693,14 +693,14 @@ Context engineering treats LLM context as a finite resource with diminishing ret

| Pattern | Example |
|---------|---------|
| **Docs on demand** | Tool descriptions reference `ha_get_domain_docs()` instead of embedding full documentation |
| **Docs on demand** | Tool descriptions reference the `ha_get_skill_home_assistant_best_practices` skill instead of embedding full documentation |
| **Hints in UX flow** | First tool in a workflow hints at related tools (e.g., `ha_search_entities` suggests `ha_get_state`) |
| **Error-driven discovery** | When a tool fails, the error response hints at `ha_get_domain_docs()` for syntax help |
| **Error-driven discovery** | When a tool fails, the error response hints at the skill guidance tool for help |
| **Layered parameters** | Required params first, optional params with sensible defaults |
| **Focused returns** | Return essential data; let user request details via follow-up tools |

**Practical examples in this codebase:**
- `ha_config_set_helper` has minimal docstring, points to `ha_get_domain_docs()` for each helper type
- `ha_config_set_helper` has minimal docstring, points to the skill guidance tool for each helper type
- Search tools return entity IDs and names; full state requires `ha_get_state`
- Error responses include `suggestions` array guiding next steps

Expand All @@ -718,7 +718,7 @@ Task tool with model=haiku or model=sonnet:

This reveals:
- What the model knows from training (no need to document)
- What gaps exist (target these with `ha_get_domain_docs()` hints)
- What gaps exist (target these with skill guidance hints)
- Confidence levels across model tiers (haiku vs sonnet vs opus)

**Important: Fact-check model claims.** Models can hallucinate plausible-sounding syntax. Always verify against HA Core source:
Expand Down
16 changes: 13 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Thank you for your interest in contributing!

1. **Fork and clone** the repository
2. **Install**: `uv sync --group dev`
3. **Install hooks**: `uv run pre-commit install`
3. **Install hooks**: `uv run lefthook install`
4. **Test**: `uv run pytest tests/src/e2e/ -v` (requires Docker)
5. **Make changes** and commit
6. **Open Pull Request**
Expand All @@ -21,7 +21,7 @@ See **[tests/README.md](tests/README.md)**.
```bash
cp .env.example .env # Edit with your HA details
uv sync --group dev
uv run pre-commit install # Install pre-commit hooks
uv run lefthook install # Install git hooks
```

**Code quality:**
Expand All @@ -31,7 +31,17 @@ uv run ruff check --fix src/ tests/ # Lint
uv run mypy src/ # Type check
```

On every commit, a `pre-commit` hook runs `ruff check --fix` to auto-fix and catch lint violations. The **Ruff Lint** CI job also enforces this on pull requests.
On every commit, hooks run `ruff check --fix` (lint), `mypy` (type check), and unit tests in parallel via [lefthook](https://github.com/evilmartians/lefthook). The **Ruff Lint** CI job also enforces this on pull requests.

## 🔄 Migrating from pre-commit to lefthook

If you had pre-commit installed from a previous checkout:

```bash
uv run pre-commit uninstall
uv sync --group dev
uv run lefthook install --reset-hooks-path
```

## 📋 Guidelines

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Base images pinned by digest - Renovate will create PRs for updates

# --- Build stage: install dependencies with uv ---
FROM ghcr.io/astral-sh/uv:0.10.11-python3.13-trixie-slim@sha256:e2fd64bdac73bd01b5013d324d9fe2e82055dfd661bc55f8006c2796da9b1d04 AS builder
FROM ghcr.io/astral-sh/uv:0.11.0-python3.13-trixie-slim@sha256:5b216b72b3bc10f983f82b39b5386455bfa08d2139afb4cb3f6c9f060484ea5d AS builder

WORKDIR /app

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
> ⚠️ **Breaking change in OAuth (beta) mode** — v7.0.0 requires `HOMEASSISTANT_URL` to be set server-side. [See issue #749 for migration instructions.](https://github.com/homeassistant-ai/ha-mcp/issues/749)
> ⚠️ **Breaking change in OAuth mode (v7.0.0)** — Set `HOMEASSISTANT_URL` server-side. The consent form now accepts only the token. [Migration guide →](docs/OAUTH.md#migrating-from-v6x)

<div align="center">
<img src="docs/img/ha-mcp-logo.png" alt="Home Assistant MCP Server Logo" width="300"/>
Expand Down Expand Up @@ -138,7 +138,7 @@ Spend less time configuring, more time enjoying your smart home.
| **Automations** | `ha_config_get_automation`, `ha_config_set_automation`, `ha_config_remove_automation` |
| **Scripts** | `ha_config_get_script`, `ha_config_set_script`, `ha_config_remove_script` |
| **Helper Entities** | `ha_config_list_helpers`, `ha_config_set_helper`, `ha_config_remove_helper` |
| **Dashboards** | `ha_config_get_dashboard`, `ha_config_set_dashboard`, `ha_config_delete_dashboard`, `ha_get_dashboard_guide`, `ha_get_card_documentation` |
| **Dashboards** | `ha_config_get_dashboard`, `ha_config_set_dashboard`, `ha_config_delete_dashboard` + dashboard skill references |
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_set_area`, `ha_config_remove_area`, `ha_config_list_floors`, `ha_config_set_floor`, `ha_config_remove_floor` |
| **Labels** | `ha_config_get_label`, `ha_config_set_label`, `ha_config_remove_label`, `ha_manage_entity_labels` |
| **Zones** | `ha_get_zone`, `ha_set_zone`, `ha_remove_zone` |
Expand All @@ -154,7 +154,7 @@ Spend less time configuring, more time enjoying your smart home.
| **Automation Traces** | `ha_get_automation_traces` |
| **System & Updates** | `ha_check_config`, `ha_restart`, `ha_reload_core`, `ha_get_system_info`, `ha_get_system_health`, `ha_get_updates` |
| **Backup & Restore** | `ha_backup_create`, `ha_backup_restore` |
| **Utility** | `ha_get_logbook`, `ha_eval_template`, `ha_get_domain_docs`, `ha_get_integration` |
| **Utility** | `ha_get_logbook`, `ha_eval_template`, `ha_get_integration` |

</details>

Expand All @@ -173,7 +173,7 @@ Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](
| Setting | Default | Description |
|---------|---------|-------------|
| `ENABLE_SKILLS` | `true` | Serve skills as MCP resources. Resources are not auto-injected into context — clients must explicitly request them. |
| `ENABLE_SKILLS_AS_TOOLS` | `false` | Also expose skills via `list_resources`/`read_resource` tools for clients that don't support MCP resources natively. |
| `ENABLE_SKILLS_AS_TOOLS` | `true` | Expose skills and doc resources via `list_resources`/`read_resource` tools. Resource-capable clients can set to `false` to reduce tool count. |

Skills can still be installed manually for clients that prefer local skill files — see the [skills repo](https://github.com/homeassistant-ai/skills) for instructions.

Expand Down
38 changes: 37 additions & 1 deletion docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,42 @@ The demo environment resets weekly. Your changes won't persist.

## Troubleshooting

### OAuth stopped working after upgrading to v7.0.0

v7.0.0 removed the Home Assistant URL field from the OAuth consent form to fix security vulnerabilities (SSRF and XSS). Set `HOMEASSISTANT_URL` as a server-side environment variable before starting ha-mcp. See the [OAuth migration guide](OAUTH.md#migrating-from-v6x) for instructions.

### Claude.ai says "Couldn't reach the MCP server"

**This is normal.** Claude.ai shows this error during its initial connection handshake, but the server connects successfully afterward. To verify you're actually connected:

1. Look for a **"Configure"** button on the connector — click it
2. If you see tools listed, you're connected and ready to go

You can also start a new conversation and ask Claude if it can see your Home Assistant via the MCP connection — this is the easiest way to confirm it's truly connected. Checking your server logs for successful requests (HTTP 200) after the initial error also confirms the connection is working.

This is a known Claude.ai behavior that affects all MCP servers, not just ha-mcp.

### "Terminating session: None" in server logs

**This is normal.** ha-mcp runs in stateless HTTP mode, which means each request creates and discards a temporary session. The `Terminating session: None` log message is the MCP SDK reporting this routine cleanup — the connection stays active.

### Cloudflare: LLM can't connect ("Block AI training bots")

If you're using Cloudflare and your LLM client can't connect to the MCP server (but visiting the URL in your browser works), Cloudflare's **"Block AI training bots"** setting is almost certainly the cause. This is the most common connection issue for Cloudflare users.

To disable it:

1. Log in to [Cloudflare](https://dash.cloudflare.com)
2. In the left sidebar, click **Domains**, then click **Overview**
3. Click on the domain you use for connecting to Home Assistant
4. On the right side of the page, find **"Control AI Crawlers"**
5. Under **"Block AI training bots"**, open the dropdown
6. Select **"do not block (allow crawlers)"**

![Cloudflare AI Crawlers Setting](https://homeassistant-ai.github.io/ha-mcp/images/cloudflare-ai-crawlers-setting.jpg)

See [#783](https://github.com/homeassistant-ai/ha-mcp/issues/783) for more details.

### SSL certificate errors (self-signed certificates)

If your Home Assistant uses HTTPS with a self-signed certificate or custom CA, you may see SSL verification errors.
Expand Down Expand Up @@ -146,7 +182,7 @@ uvx --refresh ha-mcp@latest
uvx ha-mcp@latest --version
```

The version should match the [latest release](https://github.com/homeassistant-ai/ha-mcp/releases/latest) (currently 6.x). If you see version 3.x or older, the cache needs clearing.
The version should match the [latest release](https://github.com/homeassistant-ai/ha-mcp/releases/latest). If you see a much older version, the cache needs clearing.

---

Expand Down
35 changes: 32 additions & 3 deletions docs/OAUTH.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
# OAuth Authentication for ha-mcp (Beta)

> **Status:** Beta - OAuth provides an alternative to the private URL method. It's fully functional but still being refined.
> **Status:** Beta OAuth provides an alternative to the private URL method. Fully functional but still being refined.

> **Breaking change:** `HOMEASSISTANT_URL` is now a required environment variable in OAuth mode. The consent form no longer accepts a Home Assistant URL for security reasons.
OAuth authentication lets multiple users authenticate with their own Home Assistant Long-Lived Access Token via a consent form.

OAuth authentication allows multiple users to authenticate with their own Home Assistant Long-Lived Access Token via a consent form.
## Migrating from v6.x

v7.0.0 removed the Home Assistant URL field from the OAuth consent form. You must now set `HOMEASSISTANT_URL` as a server-side environment variable.

**Why:** The consent form had two security vulnerabilities:
- **SSRF** ([GHSA-fmfg-9g7c-3vq7](https://github.com/homeassistant-ai/ha-mcp/security/advisories/GHSA-fmfg-9g7c-3vq7)): The URL field let an attacker submit arbitrary URLs to probe internal networks through the ha-mcp server.
- **XSS** ([GHSA-pf93-j98v-25pv](https://github.com/homeassistant-ai/ha-mcp/security/advisories/GHSA-pf93-j98v-25pv)): Unescaped HTML in the consent form allowed cross-site scripting.

Removing the URL field and sanitizing form output eliminates both attack surfaces.

**What to do:** Add `HOMEASSISTANT_URL` to your server environment before starting ha-mcp:

**Docker:**
```bash
docker run -d -p 8086:8086 \
-e HOMEASSISTANT_URL=https://your-ha-instance.example.com \
-e MCP_BASE_URL=https://your-mcp-server.example.com \
ghcr.io/homeassistant-ai/ha-mcp:latest ha-mcp-oauth
```

**uvx:**
```bash
HOMEASSISTANT_URL=https://your-ha-instance.example.com \
MCP_BASE_URL=https://your-mcp-server.example.com \
uvx --from=ha-mcp@latest ha-mcp-oauth
```

The consent form now accepts only the Long-Lived Access Token. Everything else stays the same.

---

## When to Use OAuth

Expand Down
2 changes: 1 addition & 1 deletion homeassistant-addon-dev/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# WARNING: This is unstable and may break at any time

# --- Build stage: install dependencies with uv ---
FROM ghcr.io/astral-sh/uv:0.10.11-python3.13-trixie-slim@sha256:e2fd64bdac73bd01b5013d324d9fe2e82055dfd661bc55f8006c2796da9b1d04 AS builder
FROM ghcr.io/astral-sh/uv:0.11.0-python3.13-trixie-slim@sha256:5b216b72b3bc10f983f82b39b5386455bfa08d2139afb4cb3f6c9f060484ea5d AS builder

WORKDIR /app

Expand Down
2 changes: 2 additions & 0 deletions homeassistant-addon-dev/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ options:
backup_hint: "normal"
enable_skills: true
enable_skills_as_tools: false
enable_tool_search: false
schema:
backup_hint: list(strong|normal|weak|auto)
secret_path: str?
enable_skills: bool?
enable_skills_as_tools: bool?
enable_tool_search: bool?
ports:
9583/tcp: 9583
8 changes: 8 additions & 0 deletions homeassistant-addon-dev/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ configuration:
description: >-
Expose skills via list_resources/read_resource tools for MCP clients
that don't support resources natively. Adds 3 extra tools.
enable_tool_search:
name: Enable tool search
description: >-
Replace the full tool catalog with search-based discovery. Reduces
idle context from ~46K to ~5K tokens. Use this if using an LLM without
deferred tools or with smaller context windows. Tools are found via
ha_search_tools and executed via categorized proxies (read/write/delete).
Requires restart to take effect.
Loading
Loading