# openHAB MCP Server
The openHAB MCP add-on lets AI assistants — Claude Desktop, Claude Code, ChatGPT, and other Model Context Protocol (opens new window) (MCP) clients read and control your openHAB smart home.
Once you've connected a client, you can say things like:
- "What's the status of my home?"
- "Dim the office to 30%."
- "Turn on the porch light every day at sunset."
- "Let me know if the garage door opens while we're talking."
- "Turn off the kitchen light at 10pm tonight."
- "Modernize all my openHAB UI Pages."
The assistant uses your home's semantic model (rooms, equipment, devices) along with fuzzy matching logic to understand which items you mean to monitor or control without having to use the exact item name.
# Installation
Install the Model Context Protocol (MCP) Server add-on in openHAB under Settings → Add-on Store → System Integrations.
The server starts automatically on http://<your-openhab>:8080/mcp.
Every connection must present an openHAB bearer token (see Authentication below).
# Settings
| Setting | Default | Advanced | Description |
|---|---|---|---|
enabled | true | NO | Turn the server on or off without uninstalling. |
enableFullApiAccess | false | NO | Give the assistant access to the full openHAB REST API — including destructive endpoints. Only turn this on if you trust the assistant with that scope. See Full REST API access. |
enableScripting | false | NO | Let the assistant include JavaScript snippets as rule actions/conditions and run scripts ad-hoc via execute_script. Requires the openhab-automation-jsscripting add-on. See Scripting. |
enableLoggingAccess | false | NO | Give the assistant tools to read recent log entries and adjust logger verbosity for diagnostics. Level changes require an ADMIN-scoped token. See Diagnostic logging. |
enableUiDesign | false | NO | Let the assistant design Main UI pages and reusable custom widgets via list_widgets, describe_widget, get_page_skeleton, manage_ui_component, and validate_ui_component. Writes require an ADMIN-scoped token. See Main UI design. |
enableStaticAssets | false | NO | Let the assistant list, read, upload, and delete files in $OPENHAB_CONF/html (the folder served at /static/*). Useful for SVG icons, images, CSS overrides, and other static files the UI references. ADMIN-scoped token required. See Static assets. |
registerCloudWebhook | false | NO | Let remote MCP clients reach this server through openHAB Cloud (myopenhab.org) with no port forwarding. Requires the openHAB Cloud add-on. See openHAB Cloud under Connecting a client. |
exposeUntaggedItems | false | YES | Also include items that aren't assigned to a Location/Equipment/Point. Most people leave this off. |
maxItemsPerPage | 100 | YES | Maximum items returned in one paginated response. |
resourceCoalesceMs | 500 | YES | Rate-limit update notifications for chatty items like power meters. |
# Authentication
Every connection needs an openHAB API token.
Tokens can be obtained in 2 different ways:
- Clients that support oAuth will automatically redirect users to login to openHAB and obtain a user token. This will likely require the openHAB Cloud webhook option to allow outside ingress to the binding.
- Clients can be manually configured with a token and either used locally, directly to openHAB, or with the openHAB Cloud webhook.
# Manual Token Generation
Generate a token in the openHAB UI:
- Click your user menu (bottom-left) → Profile.
- Open the API Tokens section → + Add API Token.
- Give it a name (e.g. "Claude") and a scope (leave blank for full access).
- Copy the token — it looks like
oh.abcDEF123…. You won't see it again.
Paste the token into your MCP client as an Authorization: Bearer oh.… header — the examples below show how for each client.
Tokens are tied to the user that created them, so the assistant's permissions match whatever that user can do through the openHAB UI. If you want to restrict what the assistant can touch, create a dedicated openHAB user with limited permissions and generate the token from that account.
Modern MCP clients (recent Claude, ChatGPT,
mcp-remote) can drive a browser-based OAuth login — no token pasting needed. The server advertises the necessary OAuth metadata automatically; just give the client your server URL and it'll walk you through sign-in the first time.
# Connecting a client
There are three ways to connect an MCP client to your openHAB server. Which one you use depends on whether the client can reach your openHAB instance directly and whether it speaks Streamable HTTP natively.
# Connection methods
# 1. Direct connection (LAN or VPN)
Point the client at http://<your-openhab>:8080/mcp with an Authorization: Bearer oh.… header.
The client must be able to reach your openHAB instance over the network.
Use this when the client is on the same LAN, connected via VPN, or you have a reverse proxy set up.
# 2. openHAB Cloud
Warning: Exposing the MCP service via a webhook will make the openHAB user login UI available to anyone with the unique generated cloud URL. This URL should be considered private and not shared outside of using it with trusted AI providers. To remove the webhook, disable the option from the settings menu. Enabling will generate a new unique URL.
Enable registerCloudWebhook in the add-on settings.
The server registers with your openHAB Cloud service and you get a public HTTPS URL:
https://myopenhab.org/api/hooks/<uuid>
The generated URL will be visible in the configuration menu under Settings → Add-on Settings → Model Context Protocol (MCP) Server.
Use this URL in any MCP client — no port forwarding or reverse proxy needed.
You can either include a static oh.* token in the client config, or let the client handle sign-in automatically via OAuth (Claude Desktop, ChatGPT, and mcp-remote all support this).
First-time OAuth login: you'll sign in twice — once to the openHAB Cloud service, then again to openHAB itself to authorize the MCP client. Subsequent connections reuse stored tokens.
# 3. mcp-remote bridge (stdio)
Some clients (like Claude Desktop) can also connect using a local Node.js bridge that translates between stdio and HTTP. This requires Node.js 18+ (opens new window) installed on the machine running the client. The bridge can point at either a direct LAN URL or a Cloud URL.
# Client setup
# Claude Desktop
Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json).
Completely quit and reopen Claude Desktop after editing — you should see "openhab" in the MCP indicator.
Option A — Local network with token:
{
"mcpServers": {
"openhab": {
"command": "npx",
"args": [
"mcp-remote@latest",
"http://openhab.local:8080/mcp",
"--allow-http",
"--header",
"Authorization: Bearer oh.YOUR_TOKEN"
]
}
}
}
Option B — openHAB Cloud with token:
{
"mcpServers": {
"openhab": {
"command": "npx",
"args": [
"mcp-remote@latest",
"https://myopenhab.org/api/hooks/<uuid>",
"--header",
"Authorization: Bearer oh.YOUR_TOKEN"
]
}
}
}
Option C — openHAB Cloud with OAuth (no token needed):
- In the Claude app, go to Settings → Connectors → Add Connector.
- Enter the Cloud hook URL (e.g.
https://myopenhab.org/api/hooks/<uuid>). - Claude will open a browser to sign in the first time you connect.
No config file editing or Node.js required.
Options A and B require Node.js 18+ (opens new window).
# Claude Code (CLI)
claude mcp add --transport http openhab http://openhab.local:8080/mcp \
--header "Authorization: Bearer oh.YOUR_TOKEN"
Works with both direct LAN URLs and Cloud URLs.
# ChatGPT (chatgpt.com (opens new window), desktop, mobile)
ChatGPT requires a public HTTPS URL, so use the openHAB Cloud method. ChatGPT handles sign-in through a browser OAuth prompt — no token pasting needed. Requires a paid ChatGPT plan (Plus, Pro, Business, Enterprise, Education).
- Turn on
registerCloudWebhookin the add-on settings and copy the hook URL from the openHAB log. - In ChatGPT: Settings → Apps & Connectors → Advanced settings → toggle Developer mode on.
- Settings → Connectors → Create.
- Name:
openHAB. Description: "Smart-home control. Use this to query device state, turn things on/off, check temperatures, and create automations." - Connector URL: the hook URL from step 1 (e.g.
https://myopenhab.org/api/hooks/<uuid>). - Save — ChatGPT will walk you through sign-in the first time, then list the tools.
# Example prompts
# Query & control
- "What's the status of my home right now?"
- "What rooms are in my house?"
- "Turn the kitchen lights off."
- "Dim the office to 30%."
# Automations
- "Turn on the office light at 7pm tonight." — one-shot rule, auto-deletes after it fires.
- "Every day at sunset, turn on the porch light." — recurring schedule.
- "When the garage door opens, turn on the garage light." — event-driven rule that runs even when you're not chatting.
- "Delete the rules you created earlier." — the assistant can list and remove rules it set up.
# Watching for changes
- "Keep an eye on the garage door — tell me if it opens while we're talking."
- "Is the dishwasher done yet?" — start watching once, ask again later for a summary.
Current MCP clients don't surface server-pushed notifications to the LLM yet, so the assistant can only report on changes when you ask — it won't interrupt you proactively. That will improve as clients add support; the server is already ready for it.
# What the assistant can do
# Items
| Tool | What it does |
|---|---|
get_semantic_model | Returns the home layout: Locations → Equipment → Points. The assistant usually calls this first to orient itself. |
search_items | Fuzzy search across names, labels, and synonyms. Tolerates typos and word reordering. |
get_item | Current state and details for one or more items by exact name. |
manage_item | Create, update, or delete an item. action='create' (requires name+type), action='update' (label/tags/groups), action='delete' (also removes its links). |
set_item | Change an item's value. action='command' sends a command to control the device (ON/OFF, dimmer levels, etc.); action='state' sends an update to the item. |
get_home_status | A single snapshot: security (open doors/windows), active lights, climate, energy, device health. |
get_system_info | openHAB version, item/thing/rule counts, installed bindings, and the server's current date/time + timezone. Relative offsets like "+30s" or "+1h" on a datetime trigger are resolved server-side, so this tool isn't needed for simple "in N minutes" scheduling — the assistant calls it when it needs the server's wall-clock for reasoning ("is it morning?", "what day is it?") or for absolute scheduling against an unfamiliar timezone. |
# Things & links
| Tool | What it does |
|---|---|
get_things | Lists Things with online/offline status. |
get_thing_details | Channels, configuration, and linked items for one Thing. |
manage_link | List, create, or delete item-channel links. action='get' (filter by item name and/or channel UID prefix), action='create', action='delete'. |
# Rules
| Tool | What it does |
|---|---|
get_rules | Lists automation rules. tag='MCP' scopes the list to rules the assistant created. |
create_rule | Creates a rule with one or more triggers, optional conditions, and a list of actions. See Rule building blocks below for the supported trigger/condition/action types. |
update_rule | Modify a rule's name, description, tags, conditions, or actions without recreating it. |
manage_rule | Enable, disable, manually trigger, or delete a rule. |
# Rule building blocks
Each create_rule call has a triggers array (or a single trigger), an optional conditions array, and a required actions array. Each entry has a type discriminator plus type-specific fields.
Triggers (any matching trigger fires the rule):
type | Fields |
|---|---|
time_of_day | time: "HH:MM" |
cron | cronExpression: "0 0 8 ? * MON-FRI" |
item_state_change | itemName, optional state, optional previousState |
item_command | itemName, optional command |
datetime | datetime: "2026-04-17T15:00:00" (absolute one-shot, auto-deleted) or a relative offset resolved against the server's clock: "+30s", "+5m", "+2h", "+1d", "+1w"; ISO duration "PT30M", "PT2H"; ISO period "P1D", "P2W" |
Conditions (all must pass for actions to run):
type | Fields |
|---|---|
item_state | itemName, operator (=/!=/</<=/>/>=, default =), state |
time_of_day | startTime, endTime (HH:MM) |
day_of_week | days: ["MONDAY", "TUESDAY", …] |
ephemeris | kind: "weekend"/"weekday"/"holiday"/"not_holiday"/"dayset", optional offset/dayset |
script | script: "…" — needs enableScripting and the JS scripting add-on |
Actions (executed in order when triggered and conditions pass):
type | Fields |
|---|---|
item_command | itemName, command |
item_state_update | itemName, state |
notification | scope: "user" (default, requires userId) / "broadcast" / "log"; message; optional title, icon, tag, referenceId, mediaAttachmentUrl, actionButton1-3. Requires the openHAB Cloud add-on. |
run_rule | ruleUIDs: [...], optional considerConditions (default true) |
rule_enablement | ruleUIDs: [...], enable: true or false |
script | script: "…" — JavaScript via openhab-js; needs enableScripting and the JS scripting add-on |
# Watching for changes
| Tool | What it does |
|---|---|
watch_items | Start or stop tracking items for state changes in this session. action='start' (with itemNames), action='stop' (omit itemNames to stop everything). |
get_events | Return any state changes buffered since the last call and drain the buffer. |
# Improving how the assistant resolves names
Items have a built-in synonyms metadata namespace.
Add a comma-separated list of nicknames (e.g. den, study, office nook on your family room light) and the assistant will match them when you use those words.
Items can also be associated with a Location via the hasLocation metadata, not just Group membership.
# Full REST API access (opt-in)
Note: With this flag on, the assistant can call any openHAB REST endpoint, including destructive ones (delete items, modify Things, change service configs). It uses your bearer token, so it can only do things your user could do from the UI. Only turn this on if you trust the assistant with that scope.
Setting enableFullApiAccess=true exposes three extra tools:
| Tool | What it does |
|---|---|
list_api_endpoints | Enumerate every REST endpoint from openHAB's live OpenAPI spec. Filter by tag (items, rules, …) or method. |
describe_api_endpoint | Return the schema fragment for one endpoint so the assistant knows what parameters to pass. |
call_api | Invoke any endpoint. Supports every HTTP method. |
Useful when the built-in tools don't cover the task, for example:
- "Rename the metadata namespace 'alexa' to 'matter' on all items."
- "Query the last 24 hours of temperature persistence for the hallway sensor."
- "Approve the pending Zigbee discovery result for the bulb I just paired."
# Scripting (opt-in)
Note: Scripts run with the same privileges as any openHAB script — they can access every item, make HTTP requests via
actions.HTTP, execute system commands viaactions.Exec, look up OSGi services, and even create new rules. Only enable this if you trust the assistant (and the human directing it) with that scope.
Setting enableScripting=true does two things:
- Unlocks
scriptas a valid action and conditiontypeincreate_rule/update_rule, with ascriptfield holding the JS source. - Exposes an extra
execute_scripttool.
| Tool | What it does |
|---|---|
execute_script | Runs a JavaScript snippet immediately against the openHAB scripting engine and returns its result. Primary use is to dry-run a script before adding it as a script action in a scheduled rule — syntax and runtime errors come back structured so the assistant can fix them now rather than discover them when the rule fires at 3am. |
Scripts get the full openhab-js environment (items, actions, things, rules, cache, time, …).
Requires the openhab-automation-jsscripting add-on.
Things you can ask once scripting is on:
- "At 7pm every weekday, dim the living room lights to 40% only if someone is home — write that as a script action."
- "My morning routine should check three weather items and adjust the thermostat — set that up as a rule."
- "Test a snippet that calculates the average of these temperature items before adding it to a rule."
# Diagnostic logging (opt-in)
Note: Reading logs is gated by
enableLoggingAccessalone. Changing log levels additionally requires the connected user's bearer token to have administrator scope — openHAB's REST API enforces this, the MCP server just forwards the token.
Setting enableLoggingAccess=true exposes two tools:
| Tool | What it does |
|---|---|
get_logs | Reads recent log entries from openHAB's in-memory buffer (typically the last 500-5000 entries, with stack traces). Filters: loggerFilter (regex), minLevel, sinceMs, sinceSequence, search, limit (default 100, max 1000). |
manage_log_level | action='get' lists current effective log levels via GET /rest/logging/ (optional loggerFilter substring). action='set' changes one logger via PUT /rest/logging/{name} (or DELETE when level="DEFAULT"). Auto-reverts to the previous level after revertAfterSeconds (default 1800 = 30 min). Pass revertAfterSeconds=0 for a persistent change. |
Things you can ask once this is enabled:
- "My Zigbee binding has been flaky — what does the log say?"
- "Why didn't my evening lights rule fire last night?"
- "Turn on DEBUG logging for the Hue binding for a few minutes so I can reproduce something."
- "Has anything errored out in the last hour?"
The assistant will scope log reads to the relevant binding by default. When increasing log verbosity, it auto-reverts after 30 minutes so DEBUG won't be left on overnight by accident.
It's also instructed to ask for confirmation before bumping infrastructure loggers (org.eclipse.jetty.*, org.apache.karaf.*, org.apache.cxf.*, org.ops4j.pax.web.*) since those can flood logs or hurt performance.
# Main UI design (opt-in)
Setting enableUiDesign=true lets the assistant design your Main UI: create pages, edit existing ones, and build reusable custom widgets.
The assistant knows about every page type (layout, home, tabbed, chart, map, floorplan), all the standard cards (toggle, slider, color picker, etc.), how to wire them to your items, and how to compose them — so you can describe what you want in plain English instead of editing YAML.
Things you can ask once this is enabled:
- "Build me a kitchen dashboard with toggles for the lights, a slider for the dimmer, and a temperature reading at the top."
- "Create a chart page showing the last 24 hours of outdoor temperature and humidity."
- "Add a floor-plan page using the image I just uploaded — and place markers for each room's main light."
- "Make a tabbed page with a tab for each floor of the house."
- "Add a Sonos tab to my media page with controls for all four speakers."
- "Design a custom widget I can reuse for each smart blind — show its position and provide up/down/stop buttons."
- "Rename the 'Living Room' page title to 'Family Room'."
- "Delete the test page I asked you to make yesterday."
The five UI tools the assistant has available (you don't need to call these directly — just ask in plain English):
| Tool | What it does |
|---|---|
list_widgets | Browse the catalog of available components, optionally by category (page types, standard cards, layout primitives, list items, map/plan markers). |
describe_widget | Look up the full prop and slot schema for any component, so the assistant writes valid configuration the first time. |
get_page_skeleton | Get a starter scaffold for any of the 6 page types with sensible defaults. |
manage_ui_component | Create / read / update / patch / delete pages and custom widgets through /rest/ui/components/{namespace}. Single-field edits use an efficient patch path instead of re-sending the whole component. |
validate_ui_component | Pre-check a composed page or widget against the schema before saving — catches typos, missing required props, and bad slot names. |
Custom widgets: when the assistant creates a custom widget, it lives in the widget namespace and can be reused across any page as widget:<your-uid>.
The widget can declare its own parameters (item name, color, threshold, etc.) so a single widget definition serves many devices.
Tip — visual verification: if your agent also has a browser-automation server connected (e.g. Claude in Chrome, Playwright MCP), the assistant will offer to screenshot the rendered page so you can both see what was built.
It'll ask you once for the URL you use to reach openHAB (e.g. http://openhab.local:8080) since the internal hostname isn't always reachable from a remote browser.
UI Design Token Usage: UI design uses noticeably more of an agent's context window than simple item control. Pages and especially custom widgets are large JSON structures (tens of KB each), and the assistant often reads, edits, and re-reads them several times in one design session. Expect to start fresh chats more often when working on the UI — particularly for big dashboards or multi-widget projects. The assistant has been tuned to keep responses small (minimal write confirmations, in-place edits instead of full rewrites) but the underlying data is inherently large.
Recommended clients for UI design: because of the above, coding-oriented agents like Claude Code (opens new window), Codex CLI (opens new window), or Cursor (opens new window) work much better here than chat-only clients. They have larger context windows, can spawn sub-agents that each get their own fresh context for focused sub-tasks (e.g. "design this one widget"), and can combine the MCP server with a browser-automation MCP (Claude in Chrome, Playwright MCP) in the same session so the agent can build a page and then screenshot the result to verify it. Chat-only clients like Claude Desktop and ChatGPT still work for small edits, but you'll hit context limits sooner on larger projects.
# Static assets (opt-in)
Setting enableStaticAssets=true lets the assistant manage the files in $OPENHAB_CONF/html, which is the folder served at http://<your-openhab>:8080/static/*.
These are static assets: SVGs, images (PNG/JPG/GIF/WebP/ICO), CSS overrides, and small text/JS files — anything a Main UI component or rule references by URL.
SVGs can be particularly useful for agents to manipulate, the assistant can author and edit the markup directly, and openHAB can bind to elements inside an SVG to make it dynamic, so you can ask for a custom icon or diagram and have it react to item states.
Pairs naturally with enableUiDesign, but mind the split: this flag manages the asset (the image or SVG file), while the page or widget that displays it is a Main UI component handled by the design tools.
Things you can ask once this is enabled (typically combined with UI design):
- "Draw an SVG icon for a garage door and upload it as
icons/garage-door.svg." — the assistant writes the SVG markup and uploads it, ready to reference from a widget. - "Upload the attached photo to
images/living-room.jpg." - "What images do I have under
images/?" — lists the folder so you can reuse one. - "Replace my custom CSS with this update." — reads the current
css/custom.css, applies the change, writes it back. - "Delete the test icon you uploaded yesterday."
| Tool | What it does |
|---|---|
manage_static_asset | List, read, upload, and delete files under $OPENHAB_CONF/html. Uploads are capped at 10 MB per call. |
Only common asset types are allowed (images, SVG, CSS, JS, and a few text formats), and the assistant needs an ADMIN-scoped token to use the tool at all.
# Real-time subscriptions (advanced)
Most clients (including Claude Desktop) don't expose this to the LLM yet. Use
watch_items/get_eventsinstead for now — they work with every client.
MCP clients that honour notifications/resources/updated can subscribe to these URIs and receive push notifications:
| URI template | Fires on |
|---|---|
openhab://item/{itemName} | Item state changes |
openhab://thing/{thingUID} | Thing online/offline transitions |
openhab://rule/{ruleUID} | Rule status transitions |
openhab://home/semantic-model | The full Location/Equipment/Point tree |
High-frequency items (power meters, fast sensors) are coalesced to at most one notification per resourceCoalesceMs window.
# Troubleshooting
Enable debug logging from the openHAB console to see every request and response:
log:set DEBUG org.openhab.io.mcp
For raw wire-level dumps (headers and full body), turn on TRACE:
log:set TRACE org.openhab.io.mcp