# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a Model Context Protocol (MCP) server that enables AI assistants to control Philips Hue smart lighting through the Hue Bridge API v2. The server is designed to run either standalone or in a container (Podman/Docker) and communicates via stdio with MCP clients like Claude Desktop.
## Essential Commands
### Development Setup
```bash
# Install dependencies (recommended: use uv for speed)
uv venv
source .venv/bin/activate
uv pip install -e .
# Or with standard pip
python -m venv .venv
source .venv/bin/activate
pip install -e .
```
### Testing
```bash
# Run connection test (verifies bridge connectivity)
python test_connection.py
# Run light control tests (interactive - tests all fixes)
python test_light_control.py
# Run pytest suite (if available)
pip install -e ".[dev]"
pytest
```
### Code Quality
```bash
# Format code
black src/
# Lint (configured for line-length=100)
ruff check src/
# The codebase follows strict linting - all code must pass ruff with zero warnings
```
### Container Operations
```bash
# Rebuild container with code changes
podman-compose build
# Stop old container and start new one
podman stop hue-mcp-server && podman rm hue-mcp-server
podman-compose up -d
# View logs
podman logs hue-mcp-server
# Check container status
podman ps | grep hue-mcp-server
```
### Running the Server
```bash
# Standalone (requires .env file with HUE_BRIDGE_IP and HUE_API_KEY)
python -m hue_mcp_server.server
# Via installed script
hue-mcp-server
```
## Architecture
### Critical API v2 Constraints
This codebase uses **aiohue v2** which has specific requirements that MUST be followed:
1. **Light Control Methods**: The aiohue v2 library does NOT accept generic `update()` calls with kwargs. You MUST use dedicated methods:
- `bridge.lights.turn_on(light_id)` - NOT `update(on=True)`
- `bridge.lights.turn_off(light_id)` - NOT `update(on=False)`
- `bridge.lights.set_brightness(light_id, percentage)` - percentage is 0-100, not 0-254
- `bridge.lights.set_color_temperature(light_id, mireds)`
- `bridge.lights.set_color(light_id, x, y)` - x, y as separate positional args
2. **Brightness Conversion**: The Hue API v1 uses 0-254 for brightness, but aiohue v2 uses 0-100 percentage. Use the `_convert_brightness_to_percentage()` helper function defined in `hue_client.py`.
3. **Group Control**: In Hue API v2, there is NO `grouped_lights` controller. To control a group:
- Get the group from `bridge.groups.get(group_id)`
- Extract device IDs from `group.children` (which are ResourceIdentifiers)
- Find lights owned by those devices via `light.owner.rid`
- Control each light individually
- See `_collect_light_ids_from_group()` in `hue_client.py` for the implementation
### Module Structure
**`hue_client.py`** - Core Hue Bridge interaction layer
- Contains all named constants (brightness ranges, color temp ranges, CIE xy bounds)
- Provides helper functions for brightness conversion and resource type checking
- `HueClient` class handles all bridge communication
- Key method: `set_light_state()` - controls individual lights using correct aiohue v2 API calls
- Key method: `set_group_state()` - controls groups by iterating over member lights
- Critical: `_collect_light_ids_from_group()` resolves the Hue API v2 group->device->light hierarchy
**`tools.py`** - MCP tool definitions
- Exports validation helper functions (`_validate_light_id()`, `_validate_brightness()`, etc.)
- Each tool function receives `(client: HueClient, arguments: Dict)` and returns JSON-serializable results
- All validation uses constants imported from `hue_client.py` for consistency
- Tool functions are registered in the `TOOLS` dictionary with schema definitions
**`server.py`** - MCP server implementation
- `HueMCPServer` class wraps an MCP Server instance
- Configuration loaded via `_load_configuration()` from environment variables
- Connection state managed with `_ensure_client_connected()` and `_connect_if_needed()`
- Error handling distinguishes between ValueError (validation), RuntimeError (connection), and unexpected errors
- All tool calls logged with tool name for debugging
### Validation Architecture
All parameter validation is centralized in `tools.py` using shared helper functions. When adding new validation:
1. Create a new `_validate_*()` function in `tools.py`
2. Import constants from `hue_client.py` for ranges
3. Provide detailed error messages including type information
4. Use the validation function in all relevant tool functions
Example:
```python
def _validate_brightness(brightness: Any) -> None:
if not isinstance(brightness, (int, float)):
raise ValueError(f"brightness must be a number, got {type(brightness).__name__}")
if brightness < BRIGHTNESS_MIN_HUE_API or brightness > BRIGHTNESS_MAX_HUE_API:
raise ValueError(f"brightness must be between {BRIGHTNESS_MIN_HUE_API} and {BRIGHTNESS_MAX_HUE_API}")
```
### Configuration
Required environment variables (loaded from `.env` file):
- `HUE_BRIDGE_IP` - IP address of the Hue Bridge (e.g., 192.168.1.112)
- `HUE_API_KEY` - API key obtained from the bridge (40-character hex string)
The server will exit with error if these are not set.
### Container Deployment Notes
- Container uses `--network host` to access Hue Bridge on local network
- `.env` file is automatically loaded by podman-compose/docker-compose
- The `run-docker-mcp.sh` script auto-detects Podman vs Docker
- Container must be rebuilt after code changes: `podman-compose build`
- Only one container should run at a time (named `hue-mcp-server`)
## Common Pitfalls
1. **DO NOT** try to use `update()` method with kwargs on light controllers - it will fail with "unexpected keyword argument"
2. **DO NOT** pass brightness as 0-254 directly to `set_brightness()` - convert to percentage first
3. **DO NOT** try to use `bridge.grouped_lights` - this controller doesn't exist in aiohue v2
4. **DO NOT** assume `group.children` contains light IDs - they are device IDs that must be resolved
5. **DO NOT** pass xy color as a dict `{"x": x, "y": y}` - use positional args `(x, y)`
## Testing Strategy
After making changes to light control logic:
1. Run `python test_connection.py` to verify basic connectivity
2. Run `python test_light_control.py` (interactive) to test all light operations
3. Rebuild container and verify it starts: `podman logs hue-mcp-server`
4. Look for "Connected to Hue Bridge" and "Hue MCP Server started successfully" in logs
## Recent Fixes Applied
Three critical bugs were fixed in this codebase:
1. Using correct aiohue v2 methods (`turn_on()`, `turn_off()`) instead of `update(on=...)`
2. Passing x, y as positional parameters to `set_color()` instead of as dict
3. Properly resolving group->device->light hierarchy for group control
Do not revert these fixes or use the old patterns.