Add MCP server command for LLM integration#102
Conversation
Introduce 'openhue mcp' command that exposes a Model Context Protocol server, enabling LLMs to control Philips Hue lights through standardized tools: - list_lights: List all lights with optional room filter - list_rooms: List all rooms with status and brightness - list_scenes: List all scenes with optional room filter - set_light: Control light (on/off, brightness, color, temperature) - set_room: Control room lights (on/off, brightness, color, temperature) - activate_scene: Activate scene with action mode (active/static/dynamic) The server uses stdio transport and is compatible with Claude Desktop, Cursor, and other MCP-compatible LLM clients. Co-Authored-By: Warp <agent@warp.dev>
📝 WalkthroughWalkthroughAdds an MCP subcommand and a stdio-based Model Context Protocol server that connects to OpenHue/Hue, registers tools to list and control lights, rooms, and scenes, and exposes the subcommand in the root CLI. New MCP and JSON-related dependencies were added. Changes
Sequence DiagramsequenceDiagram
participant User as User/Client
participant CLI as CLI Command
participant MCP as MCP Server
participant Home as OpenHue Home
participant Hue as Hue Bridge/API
User->>CLI: invoke `mcp` command
CLI->>MCP: NewCmdMcp -> runMcpServer
MCP->>Hue: connect / authenticate
MCP->>Home: initialize home representation
MCP->>MCP: register tools (list/set/activate)
MCP->>MCP: start stdio-based MCP server
User->>MCP: tool request (e.g., list_lights)
MCP->>Home: load state
Home->>Hue: fetch lights/rooms/scenes
MCP->>User: return JSON result
User->>MCP: tool request (e.g., set_light)
MCP->>Home: validate params, apply changes
Home->>Hue: update device state
MCP->>User: return operation results
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@cmd/mcp/mcp.go`:
- Around line 38-52: NewCmdMcp currently accepts *openhue.Config but should
accept *openhue.Context for consistency and access to BuildInfo; change the
function signature of NewCmdMcp to take ctx *openhue.Context, update the
internal use to pass ctx (and extract ctx.Config if runMcpServer still expects
Config), and update the RunE closure to call runMcpServer with the appropriate
argument (or refactor runMcpServer to accept *openhue.Context). Also update any
callers (e.g., where NewCmdMcp is invoked in cmd/root.go) to pass the
*openhue.Context instead of *openhue.Config so all command constructors follow
the same pattern.
In `@cmd/mcp/tools.go`:
- Around line 99-104: The code calls room.GroupedLight.IsOn() without guarding
for nil, risking a panic; update the loop that builds info to check if
room.GroupedLight != nil before calling IsOn (same pattern used for the
brightness check later), and set the "on" field to a safe default (e.g., false
or omit) when GroupedLight is nil; reference the room.GroupedLight and IsOn call
in the loop that constructs the info map so the nil-check mirrors the existing
brightness guard.
In `@cmd/root.go`:
- Line 95: The MCP command is inconsistent: root.AddCommand currently calls
mcp.NewCmdMcp(&c) where NewCmdMcp accepts *openhue.Config but all other commands
take *openhue.Context; change NewCmdMcp to accept *openhue.Context instead of
*openhue.Config and update its implementation in cmd/mcp/mcp.go to use the
Context for shared state (IOStreams, BuildInfo, Home, Config) so callers use the
same pattern (pass the existing openhue.Context instance from root). Ensure any
references inside NewCmdMcp that used the Config are adapted to access config
via the Context.
🧹 Nitpick comments (4)
cmd/mcp/mcp.go (2)
62-66: Hardcoded version "1.0.0" should useBuildInfo.Version.The MCP server version is hardcoded as
"1.0.0". If you refactor to accept*openhue.Context, you can usectx.BuildInfo.Versionto report the actual CLI version to MCP clients.♻️ Suggested fix (after accepting Context)
// Create MCP server s := server.NewMCPServer( "OpenHue", - "1.0.0", + ctx.BuildInfo.Version, server.WithToolCapabilities(true), )
54-59: Redundant Home client creation.A
Homeclient is already created inExecute()(cmd/root.go:81) and stored inopenhue.Context. Creating another instance here is redundant. If you refactor to accept*openhue.Context, you can reusectx.Hinstead.♻️ Suggested fix (after accepting Context)
-func runMcpServer(config *openhue.Config) error { - // Create Hue API client - h, err := op.NewHome(config.Bridge, config.Key) - if err != nil { - return fmt.Errorf("failed to connect to Hue bridge: %w", err) - } +func runMcpServer(ctx *openhue.Context) error { + h := ctx.Hcmd/mcp/tools.go (2)
217-246: Significant code duplication betweensetLightHandlerandsetRoomHandler.The option-building logic (handling
on,brightness,temperature, andcolor) is nearly identical in both handlers. Consider extracting a helper function to reduce duplication.♻️ Suggested helper function
func buildSetOptions(req mcp.CallToolRequest) (*openhue.SetLightOptions, error) { opts := openhue.NewSetLightOptions() if args := req.GetArguments(); args != nil { if _, exists := args["on"]; exists { if req.GetBool("on", false) { opts.Status = openhue.LightStatusOn } else { opts.Status = openhue.LightStatusOff } } if _, exists := args["brightness"]; exists { brightness := req.GetFloat("brightness", -1) if brightness < 0 || brightness > 100 { return nil, fmt.Errorf("brightness must be between 0 and 100") } opts.Brightness = float32(brightness) } if _, exists := args["temperature"]; exists { tempInt := int(req.GetFloat("temperature", -1)) if tempInt < 153 || tempInt > 500 { return nil, fmt.Errorf("temperature must be between 153 and 500") } opts.Temperature = tempInt } } if colorHex := req.GetString("color", ""); colorHex != "" { rgb, err := color.NewRGBFomHex(colorHex) if err != nil { return nil, fmt.Errorf("invalid color: %w", err) } opts.Color = *rgb.ToXY() } return opts, nil }Also applies to: 315-344
266-266: Silently ignoredjson.Marshalerrors.The
json.Marshalcalls on lines 266, 368, and 439 discard errors with_. While marshalingmap[string]anywith string values is unlikely to fail, silently ignoring errors is not a best practice. Consider handling or at least logging these errors for robustness.♻️ Suggested fix pattern
- jsonData, _ := json.Marshal(map[string]any{"results": results}) - return mcp.NewToolResultText(string(jsonData)), nil + jsonData, err := json.Marshal(map[string]any{"results": results}) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("failed to marshal results: %v", err)), nil + } + return mcp.NewToolResultText(string(jsonData)), nilAlso applies to: 368-368, 439-439
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (5)
cmd/mcp/mcp.gocmd/mcp/tools.gocmd/root.gogo.modopenhue/openhue-cli
🧰 Additional context used
📓 Path-based instructions (1)
cmd/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
cmd/**/*.go: Use Cobra framework for CLI command structure, with two command groups:hue(light control) andconfig(setup commands)
Passopenhue.Contextto all commands to hold shared state (IOStreams, BuildInfo, Home, Config)
Files:
cmd/root.gocmd/mcp/mcp.gocmd/mcp/tools.go
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: This is a Cobra-based CLI for controlling Philips Hue smart lighting using the openhue-go library for API communication
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to cmd/**/*.go : Use Cobra framework for CLI command structure, with two command groups: `hue` (light control) and `config` (setup commands)
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to cmd/**/*.go : Use Cobra framework for CLI command structure, with two command groups: `hue` (light control) and `config` (setup commands)
Applied to files:
cmd/root.gocmd/mcp/mcp.gocmd/mcp/tools.gogo.mod
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to cmd/hue/**/*.go : Implement `LoadHomeIfNeeded` in PersistentPreRun for hue group commands to load the full HomeModel from the bridge
Applied to files:
cmd/root.gocmd/mcp/tools.go
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to openhue/config.go : Define commands that can run without setup in the `CommandsWithNoConfig` list
Applied to files:
cmd/root.gocmd/mcp/mcp.gogo.mod
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to cmd/**/*.go : Pass `openhue.Context` to all commands to hold shared state (IOStreams, BuildInfo, Home, Config)
Applied to files:
cmd/root.go
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: The entry point for the CLI is main.go → cmd/root.go (Execute function)
Applied to files:
cmd/root.go
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: This is a Cobra-based CLI for controlling Philips Hue smart lighting using the openhue-go library for API communication
Applied to files:
cmd/root.gocmd/mcp/mcp.gogo.mod
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to openhue/home_model.go : Search functions (SearchLights, SearchRooms, SearchScenes) should accept name OR id for flexibility
Applied to files:
cmd/mcp/tools.go
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to **/*_test.go : Use `openhue/test/assert` custom assertions in test files
Applied to files:
go.mod
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to openhue/home_model.go : Resources in the HomeModel should wrap raw HueData from the API and add convenience methods
Applied to files:
go.mod
🧬 Code graph analysis (1)
cmd/root.go (1)
cmd/mcp/mcp.go (1)
NewCmdMcp(39-52)
🔇 Additional comments (9)
cmd/root.go (1)
23-24: LGTM!The MCP command group is correctly defined and registered with the root command, following the established pattern for command groups.
Also applies to: 53-56
cmd/mcp/mcp.go (3)
1-12: LGTM!Package declaration and imports are well-organized and appropriate for the MCP server functionality.
13-36: LGTM!Documentation is clear and includes a helpful example for Claude Desktop configuration.
71-76: LGTM!The stdio server startup and error handling are correctly implemented.
cmd/mcp/tools.go (4)
15-26: LGTM!Tool registration is clean and well-organized, separating list tools from control tools.
41-77: LGTM!The
listLightsHandlercorrectly handles errors, optional room filtering, and safe nil checks for brightness and room data.
136-167: LGTM!The
listScenesHandlercorrectly handles optional room filtering and nil checks for parent room.
393-441: LGTM!The
activateSceneHandlercorrectly validates the action parameter and handles scene activation with proper error reporting.go.mod (1)
12-12: No issues found. Themcp-gov0.43.2 dependency is current (released as a patch after v0.43.0) with only documented bug fixes and no known CVEs specific to the library.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| // NewCmdMcp returns an initialized Command instance for the 'mcp' sub command | ||
| func NewCmdMcp(config *openhue.Config) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "mcp", | ||
| Short: mcpDocShort, | ||
| Long: mcpDocLong, | ||
| Example: mcpDocExample, | ||
| GroupID: "mcp", | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return runMcpServer(config) | ||
| }, | ||
| } | ||
|
|
||
| return cmd | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Consider accepting *openhue.Context for consistency.
As noted in cmd/root.go, the function signature should accept *openhue.Context to align with other commands and the coding guidelines. This would also provide access to BuildInfo for the server version (see next comment).
♻️ Suggested signature change
-func NewCmdMcp(config *openhue.Config) *cobra.Command {
+func NewCmdMcp(ctx *openhue.Context) *cobra.Command {
cmd := &cobra.Command{
Use: "mcp",
Short: mcpDocShort,
Long: mcpDocLong,
Example: mcpDocExample,
GroupID: "mcp",
RunE: func(cmd *cobra.Command, args []string) error {
- return runMcpServer(config)
+ return runMcpServer(ctx)
},
}
return cmd
}🤖 Prompt for AI Agents
In `@cmd/mcp/mcp.go` around lines 38 - 52, NewCmdMcp currently accepts
*openhue.Config but should accept *openhue.Context for consistency and access to
BuildInfo; change the function signature of NewCmdMcp to take ctx
*openhue.Context, update the internal use to pass ctx (and extract ctx.Config if
runMcpServer still expects Config), and update the RunE closure to call
runMcpServer with the appropriate argument (or refactor runMcpServer to accept
*openhue.Context). Also update any callers (e.g., where NewCmdMcp is invoked in
cmd/root.go) to pass the *openhue.Context instead of *openhue.Config so all
command constructors follow the same pattern.
Co-Authored-By: Warp <agent@warp.dev>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@README.md`:
- Around line 45-46: The README's platform config path list only shows macOS and
Windows; add a third line for Linux consistent with the existing style:
"**Linux**: `~/.config/Claude/claude_desktop_config.json`" placed alongside the
macOS and Windows entries so Linux users see the correct Claude Desktop config
location.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
README.md
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: This is a Cobra-based CLI for controlling Philips Hue smart lighting using the openhue-go library for API communication
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to cmd/**/*.go : Use Cobra framework for CLI command structure, with two command groups: `hue` (light control) and `config` (setup commands)
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: This is a Cobra-based CLI for controlling Philips Hue smart lighting using the openhue-go library for API communication
Applied to files:
README.md
📚 Learning: 2026-01-14T16:44:56.987Z
Learnt from: CR
Repo: openhue/openhue-cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T16:44:56.987Z
Learning: Applies to cmd/**/*.go : Use Cobra framework for CLI command structure, with two command groups: `hue` (light control) and `config` (setup commands)
Applied to files:
README.md
🔇 Additional comments (3)
README.md (3)
21-21: LGTM! Clear feature description.The MCP Server feature addition is concise and fits well with the existing features list.
30-40: LGTM! Well-documented tools table.The available tools are clearly documented with concise descriptions that match the PR objectives.
48-64: LGTM! Clear setup instructions and helpful examples.The JSON configuration example correctly demonstrates the
openhue mcpcommand invocation, and the example prompts effectively showcase the natural language interface capabilities.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@cmd/mcp/tools.go`:
- Around line 189-191: The description for the "color" flag and its usages
contradict color.NewRGBFomHex which requires a leading '#' and 7 chars; update
the code to either (A) change the mcp.Description in the mcp.WithString("color",
mcp.Description(...)) calls to state that a leading '#' is required, or (B)
normalize user input before calling color.NewRGBFomHex (trim whitespace, if the
string is 6 hex chars without '#' prepend '#' then validate) in the functions
that call color.NewRGBFomHex so both "FF5500" and "#FF5500" are accepted; apply
the same change to the other occurrences referenced around the same flag usage.
- Around line 291-293: The color description strings for the
mcp.WithString("color", mcp.Description(...)) entries must match the parser's
requirement for a leading '#' (same fix as setLightHandler): update the
description text to only show the '#'-prefixed example (e.g., "#FF5500") or
explicitly state that the '#' is required, and make the same change for the
other mcp.WithString("color", ...) block referenced (lines 349-356) so the
UI/help matches NewRGBFromHex's expected input.
🧹 Nitpick comments (2)
cmd/mcp/tools.go (1)
220-258: Consider extracting shared option-building logic.The option-building code (on/off, brightness, temperature, color handling) is duplicated between
setLightHandlerandsetRoomHandler. Extracting a helper function would reduce duplication and ensure consistency.♻️ Example helper extraction
func buildSetLightOptions(req mcp.CallToolRequest) (*openhue.SetLightOptions, error) { opts := openhue.NewSetLightOptions() if args := req.GetArguments(); args != nil { if _, exists := args["on"]; exists { if req.GetBool("on", false) { opts.Status = openhue.LightStatusOn } else { opts.Status = openhue.LightStatusOff } } if _, exists := args["brightness"]; exists { brightness := req.GetFloat("brightness", -1) if brightness < 0 || brightness > 100 { return nil, fmt.Errorf("brightness must be between 0 and 100") } opts.Brightness = float32(brightness) } if _, exists := args["temperature"]; exists { tempInt := int(req.GetFloat("temperature", -1)) if tempInt < 153 || tempInt > 500 { return nil, fmt.Errorf("temperature must be between 153 and 500") } opts.Temperature = tempInt } } if colorHex := req.GetString("color", ""); colorHex != "" { rgb, err := color.NewRGBFomHex(colorHex) if err != nil { return nil, fmt.Errorf("invalid color: %w", err) } opts.Color = *rgb.ToXY() } return opts, nil }Also applies to: 318-356
cmd/mcp/mcp.go (1)
62-66: Consider usingctx.BuildInfo.Versionfor the server version.The version is hardcoded as
"1.0.0"at line 64, butctx.BuildInfo.Versionis available and contains the actual CLI version. Using it would keep the MCP server version in sync with releases.♻️ Suggested change
// Create MCP server s := server.NewMCPServer( "OpenHue", - "1.0.0", + ctx.BuildInfo.Version, server.WithToolCapabilities(true), )
| mcp.WithString("color", | ||
| mcp.Description("Color as hex RGB value (e.g., '#FF5500' or 'FF5500')"), | ||
| ), |
There was a problem hiding this comment.
Color hex format mismatch between description and implementation.
The tool description on line 190 states colors can be provided as '#FF5500' or 'FF5500', but color.NewRGBFomHex (per relevant code snippets) requires the # prefix and validates for exactly 7 characters starting with #. Users (or LLMs) providing 'FF5500' will get an error.
Either update the description to clarify that # is required, or normalize the input:
🐛 Option A: Fix description (minimal change)
mcp.WithString("color",
- mcp.Description("Color as hex RGB value (e.g., '#FF5500' or 'FF5500')"),
+ mcp.Description("Color as hex RGB value (e.g., '#FF5500')"),
),♻️ Option B: Normalize input to support both formats
// Handle color
if colorHex := req.GetString("color", ""); colorHex != "" {
+ if len(colorHex) == 6 && colorHex[0] != '#' {
+ colorHex = "#" + colorHex
+ }
rgb, err := color.NewRGBFomHex(colorHex)Also applies to: 251-258
🤖 Prompt for AI Agents
In `@cmd/mcp/tools.go` around lines 189 - 191, The description for the "color"
flag and its usages contradict color.NewRGBFomHex which requires a leading '#'
and 7 chars; update the code to either (A) change the mcp.Description in the
mcp.WithString("color", mcp.Description(...)) calls to state that a leading '#'
is required, or (B) normalize user input before calling color.NewRGBFomHex (trim
whitespace, if the string is 6 hex chars without '#' prepend '#' then validate)
in the functions that call color.NewRGBFomHex so both "FF5500" and "#FF5500" are
accepted; apply the same change to the other occurrences referenced around the
same flag usage.
- Add workflow hints to list tools pointing to their control counterparts - Mark optional parameters explicitly with "Optional:" prefix - Clarify action types in activate_scene with behavioral descriptions - Improve temperature description with more intuitive examples
Summary
Introduce
openhue mcpcommand that exposes a Model Context Protocol (MCP) server, enabling LLMs like Claude to control Philips Hue lights through standardized tools.MCP Tools
list_lightslist_roomslist_scenesset_lightset_roomactivate_sceneUsage
Start the MCP server:
Configure in Claude Desktop (
claude_desktop_config.json):{ "mcpServers": { "openhue": { "command": "openhue", "args": ["mcp"] } } }Changes
cmd/mcp/mcp.go- MCP command and server setupcmd/mcp/tools.go- Tool definitions and handlerscmd/root.go- Register MCP commandgithub.com/mark3labs/mcp-godependencyCo-Authored-By: Warp agent@warp.dev
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.