Building a custom MCP server in Ruby so LLMs can control an Elgato Key Light
Edu Depetris
- Mar 28, 2026- Ruby
- Ai
- Mcp
- Docker
- Llms
- Ai Tools
TL;DR
I built a small MCP server in Ruby to control my Elgato Key Light from an LLM.
Totally unnecessary.
Also very fun.
Let’s Overengineering how to switch a light on and off
I have a beautiful Elgato Key Light that I’d love to control from any chatbot, like ChatGPT, Claude, or any other LLM-powered tool.
Even though the light can be controlled with a very simple HTTP call (if you know the IP address), I wanted to overengineering it a bit and build my own MCP server so LLMs can control it.
The idea
We’ll build a small MCP server in Ruby that exposes tools to:
- Turn the light on/off
- Adjust brightness and temperature
- Read the current state
Stack
- Ruby 4
- Bundler
A bit about MCP
The Model Context Protocol (MCP) supports two main transports:
- **stdio** for local, process-based communication
- **HTTP / SSE** for remote communication
These transports use JSON-RPC to handle messages.
For this example, we’ll keep things simple and use **stdio**.
Step 1: Talk to the light
First, let’s implement two simple HTTP methods to control and read the light.
module ElgatoKeyLight
def self.set_light_state(on: ON, brightness: 75, temperature: MIN_TEMPERATURE)
uri = URI('http://192.168.100.13:9123/elgato/lights')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Put.new(uri.path, 'Content-Type': 'application/json')
body = { "lights": [ {"on":on,"brightness":brightness,"temperature":temperature} ] }.to_json
request.body = body
response = http.request(request)
response.code
end
def self.get_light_state
uri = URI('http://192.168.100.13:9123/elgato/lights')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.path, 'Content-Type': 'application/json')
response = http.request(request)
JSON.parse(response.body)
end
endNow we can talk to the light programmatically.
Step 2: Expose MCP tools
Next, we wrap these methods as MCP tools.
Source: https://github.com/edudepetris/elgato-key-light-mcp/blob/main/stdio_server.rb
class ElgatoKeyLightControlTool < MCP::Tool
class << self
def call(on:, brightness:, temperature:)
result = ElgatoKeyLight.set_light_state(on: on, brightness: brightness, temperature: temperature)
MCP::Tool::Response.new([{
type: "text",
text: "Light state set with response code: #{result}",
}])
rescue => e
MCP::Tool::Response.new([{ type: "text", text: JSON.generate({ error: e.message }) }])
end
end
end
class ElgatoKeyLightStatusTool < MCP::Tool
class << self
def call
result = ElgatoKeyLight.get_light_state
MCP::Tool::Response.new([{
type: "text",
text: JSON.generate(result),
}])
rescue => e
MCP::Tool::Response.new([{ type: "text", text: JSON.generate({ error: e.message }) }])
end
end
endStep 3: Create the MCP server
Let's create a simple server and use STDIO to listen and talk:
server = MCP::Server.new( name: "elgato_key_light_control_server", tools: [ElgatoKeyLightControlTool, ElgatoKeyLightStatusTool], ) transport = MCP::Server::Transports::StdioTransport.new(server) transport.open
Step 4: Run and test
Finally let's run our server and test it out with elgato_key_light_status_tool
Start the server:
$ ruby stdio_server.rb
Send a request:
{"jsonrpc": "2.0","id": 1,"method": "tools/call","params": {"name": "elgato_key_light_status_tool","arguments": {}}}Response:
{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\"numberOfLights\":1,\"lights\":[{\"on\":0,\"brightness\":0,\"temperature\":143}]}"}],"isError":false}}It's alive!
Alternative you can use
You can also use the MCP inspector:
npx @modelcontextprotocol/inspector ruby stdio_server.rb
Plug it into an LLM
Now you can whitelist your MCP server in Claude (or any compatible client) and control your light directly from chat.
Notes
I also created a Dockerfile to test this easily.
However, it won’t work out of the box on macOS due to Docker network limitations when accessing local network devices.
There are workarounds—but I didn’t include them in this repo.
Happy clauding!