main.py•5.24 kB
"""
MCP Server for controlling Philips Hue lights.
This server provides a tool to change Hue light color and brightness.
"""
from mcp.server.fastmcp import FastMCP
from phue import Bridge
from typing import Optional
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Create FastMCP server instance
mcp = FastMCP(
name="Hue Light Controller",
instructions="A server to control Philips Hue lights. Use this to change light colors and brightness."
)
# Initialize Hue Bridge connection
# The bridge IP can be configured via environment variable
BRIDGE_IP = os.getenv("HUE_BRIDGE_IP", "")
# Cache the bridge connection
_bridge_instance = None
def get_bridge() -> Bridge:
"""Get or initialize the Hue Bridge connection."""
global _bridge_instance
if not BRIDGE_IP:
raise ValueError(
"HUE_BRIDGE_IP environment variable not set. "
"Please set it to your Hue Bridge IP address."
)
# Reuse existing connection or create new one
if _bridge_instance is None:
_bridge_instance = Bridge(BRIDGE_IP)
return _bridge_instance
@mcp.tool()
def change_hue_light(
light_id: int,
brightness: Optional[int] = None,
hue: Optional[int] = None,
saturation: Optional[int] = None,
on: Optional[bool] = None
) -> str:
"""
Change the state of a Philips Hue light.
Args:
light_id: The ID of the light to control (1-based index)
brightness: Brightness level (0-254), where 0 is minimum and 254 is maximum
hue: Hue color value (0-65535), where 0 and 65535 are red, 25500 is green, and 46920 is blue
saturation: Color saturation (0-254), where 0 is white and 254 is most saturated
on: Turn the light on (True) or off (False)
Returns:
A message indicating the result of the operation
"""
try:
bridge = get_bridge()
# Verify we have lights
if not bridge.lights:
return "Error: No lights found on the Hue Bridge. Please check your bridge connection."
# Get the light (phue uses 1-based indexing)
if light_id < 1 or light_id > len(bridge.lights):
return f"Error: Light ID {light_id} is out of range. Available lights: 1-{len(bridge.lights)}"
light = bridge.lights[light_id - 1]
# Build the command dictionary
command = {}
if on is not None:
command['on'] = on
if brightness is not None:
if not 0 <= brightness <= 254:
return f"Error: Brightness must be between 0 and 254, got {brightness}"
command['bri'] = brightness
if hue is not None:
if not 0 <= hue <= 65535:
return f"Error: Hue must be between 0 and 65535, got {hue}"
command['hue'] = hue
if saturation is not None:
if not 0 <= saturation <= 254:
return f"Error: Saturation must be between 0 and 254, got {saturation}"
command['sat'] = saturation
# Apply the command
if command:
# Use the light's state property to set multiple values at once
for key, value in command.items():
setattr(light, key, value)
status_parts = []
if 'on' in command:
status_parts.append(f"turned {'on' if command['on'] else 'off'}")
if 'bri' in command:
status_parts.append(f"brightness set to {command['bri']}")
if 'hue' in command:
status_parts.append(f"hue set to {command['hue']}")
if 'sat' in command:
status_parts.append(f"saturation set to {command['sat']}")
return f"Successfully updated light {light_id} ({light.name}): {', '.join(status_parts)}"
else:
return f"No changes specified for light {light_id} ({light.name})"
except ValueError as e:
return f"Configuration error: {str(e)}"
except IndexError:
return f"Error: Light {light_id} not found. Please check the light ID."
except Exception as e:
return f"Error controlling light: {str(e)}"
@mcp.tool()
def list_hue_lights() -> str:
"""
List all available Philips Hue lights.
Returns:
A formatted list of all lights with their IDs, names, and current states
"""
try:
bridge = get_bridge()
lights = bridge.lights
if not lights:
return "No lights found on the Hue Bridge"
result = ["Available Hue Lights:", ""]
for idx, light in enumerate(lights, 1):
state = "ON" if light.on else "OFF"
brightness = light.brightness if hasattr(light, 'brightness') else "N/A"
result.append(f" {idx}. {light.name} - {state} (Brightness: {brightness})")
return "\n".join(result)
except Exception as e:
return f"Error listing lights: {str(e)}"
def main():
"""Entry point for the MCP server."""
mcp.run()
if __name__ == "__main__":
main()