Openhab MCP Server

Hi everyone,

I was planning to try to create a MCP Server to interface my OH instance to an LLM mainly to find out what it could do. Fortunately I found this little project here before I actually started implementing something myself:

It took me a while to set it up the way I was intending to use it (switch to http-transport and integrate it in Windsurf IDE/Cascade AI) but I must say I’m really impressed and pleased with what it can do. If you are interested I’m happy to do a write-up of what I needed to do to get this going in the end.

To learn more about this I also created a fork and added the functionality to query persistence data which gives a lot more fun use cases. What I tried/did so far:

  1. I don’t have any descriptions for my OH rules yet: Cascade is able to analyze my rules and update the rules with meaningful descriptions.
  2. Analyzing of persistence data: For example getting the highest/lowest or average temperature of an item within a given time frame works quite nicely.
  3. Get more complex information about the whole system: Which room is currently the warmest? The LLM did a mistake there and did not actually put the highest temperature on the list but you can get some insights that would take otherwise a lot of work to put together.
  4. Analyze naming convention breaches of my items and give me hints where my items seem to be diverging from otherwise similar configurations.
  5. Create simple rules: Switch on the bedroom light when I switch on the hallway light. It did generate Rule DSL script but it was working perfectly. It made sure to find the correct items first with my help (wasn’t able to find it by itself).
  6. Analyse my rules to find the ones with the highest code complexity.

Ultimately there are so many more options with further extensions of the MCP server. For example I think this could be used to do some time consuming work after replacing equipment to link the items to the new thing or to create new things and connected items when you add another device of a type that is already in your setup or to add a new custom metadata field to a lot of different items or rename items (aka delete, recreate and re-link)…

6 Likes

I would greatly appreciate tou writing a comprehensive write-up of your setup.

For now I do not have the resources to run a local LLM but concepts like MCP will change everything in the near future.

So knowing that OpenHAB will be ready for the next quantum leap i so good to know.

1 Like

Actually this might also replace rules if you have an agent running that can decide for himself when there is a good time to check some parameters. I have a rule that calculates whether it is good time to ventilate a room to reduce humidity indoors. You can do the same with an AI agent:

Question:
Would it be a good time to ventilate the bathroom to reduce indoor humidity based on the temperature, humidity and dewpoint values you can obtain from openhab? You can use the carport values as a reference for outside measurements

Answer:
<AI gathers all required information>

Here's what I found:

Outside (Carport):

* Temperature: 12.7°C
* Dewpoint: 4.4°C

Bathroom:

* Temperature: 23.3°C
* Dewpoint: 10°C

Based on these values, here's my analysis:

1. The bathroom temperature is significantly higher than the outside temperature (23.3°C vs 12.7°C)
2. The bathroom dewpoint is higher than the outside dewpoint (10°C vs 4.4°C)
3. The temperature difference between inside and outside is 10.6°C, which is quite significant

Recommendation: Yes, it would be a good time to ventilate the bathroom because:

1. The outside air is significantly cooler than the inside air, which will help cool down the bathroom
2. The outside dewpoint is lower than the inside dewpoint, which means bringing in outside air will actually reduce humidity levels
3. The significant temperature difference (10.6°C) means that ventilation will be effective in both cooling and dehumidifying

The ventilation will work because:

* The cooler outside air will help reduce the temperature in the bathroom
* The lower dewpoint of the outside air means that when it mixes with the warmer inside air, it will actually reduce the relative humidity
* The temperature difference is large enough to create a good air exchange

I recommend ventilating the bathroom for about 15-20 minutes to achieve good air exchange. After that, you might want to close the windows to maintain the temperature inside.

Sorry that this is German but this is a scenario I was hoping for:

  1. AI creates a new Semantic Tag for me
  2. Finds matching groups that should be updated with the new tag
  3. Updates the tag
  4. (Forgets to remove the general Equipment Tags but that’s done with another small hint)
  5. EDIT: Ok, and it switched the item type from Group to String :smiley: But it could also fix that pretty fast. And is was actually a coding issue, not the AI that messed up…

Me: Kannst du bitte ein neues Equipment Tag für Access Points anlegen?

AI Output

Ich werde jetzt ein neues Equipment Tag für Access Points erstellen:

MCP Tool: openhab / create_tag

Ran with these arguments:

{
  "tag": {
    "uid": "Equipment_AccessPoint",
    "name": "AccessPoint",
    "label": "Access Point",
    "description": "WLAN Access Points für die Netzwerkkonnektivität",
    "synonyms": [
      "WLAN",
      "Wireless Access Point",
      "WiFi Access Point"
    ],
    "editable": false
  }
}

Output

{
  "uid": "Equipment_AccessPoint",
  "name": "AccessPoint",
  "label": "Access Point",
  "description": "WLAN Access Points für die Netzwerkkonnektivität",
  "synonyms": [
    "WLAN",
    "Wireless Access Point",
    "WiFi Access Point"
  ],
  "editable": true
}

Perfekt! Das neue Tag wurde erfolgreich erstellt. Lassen Sie uns es jetzt den Access Points zuordnen. Zuerst aktualisieren wir

ap_diningroom

MCP Tool: openhab / update_item

Ran with these arguments:

{
  "item_name": "ap_diningroom",
  "item": {
    "name": "ap_diningroom",
    "tags": [
      "Equipment",
      "Equipment_AccessPoint"
    ]
  }
}

Output

{
  "type": "String",
  "name": "ap_diningroom",
  "state": "NULL",
  "label": "Access Point Erdgeschoss",
  "tags": [
    "Equipment",
    "Equipment_AccessPoint"
  ],
  "groupNames": [
    "loc_diningroom",
    "aps"
  ]
}

Lassen Sie uns jetzt auch die anderen Access Points aktualisieren:

MCP Tool: openhab / update_item

Ran with these arguments:

{
  "item_name": "ap_garden",
  "item": {
    "name": "ap_garden",
    "tags": [
      "Equipment",
      "Equipment_AccessPoint"
    ]
  }
}

Output

{
  "type": "String",
  "name": "ap_garden",
  "state": "NULL",
  "label": "Access Point Garten",
  "tags": [
    "Equipment",
    "Equipment_AccessPoint"
  ],
  "groupNames": [
    "loc_garden",
    "aps"
  ]
}

MCP Tool: openhab / update_item

Ran with these arguments:

{
  "item_name": "ap_office",
  "item": {
    "name": "ap_office",
    "tags": [
      "Equipment",
      "Equipment_AccessPoint"
    ]
  }
}

Output

{
  "type": "String",
  "name": "ap_office",
  "state": "NULL",
  "label": "Access Point Dachgeschoss",
  "tags": [
    "Equipment",
    "Equipment_AccessPoint"
  ],
  "groupNames": [
    "loc_office",
    "aps"
  ]
}

MCP Tool: openhab / update_item

Ran with these arguments:

{
  "item_name": "ap_storageroom",
  "item": {
    "name": "ap_storageroom",
    "tags": [
      "Equipment",
      "Equipment_AccessPoint"
    ]
  }
}

Output

{
  "type": "String",
  "name": "ap_storageroom",
  "state": "NULL",
  "label": "Access Point Keller",
  "tags": [
    "Equipment",
    "Equipment_AccessPoint"
  ],
  "groupNames": [
    "loc_storageroom",
    "aps"
  ]
}

Perfekt! Alle Access Points wurden erfolgreich mit dem neuen Tag “Equipment_AccessPoint” versehen. Die Struktur ist jetzt:

  1. Neues Equipment Tag:
  • UID: Equipment_AccessPoint

  • Label: Access Point

  • Synonyms: WLAN, Wireless Access Point, WiFi Access Point

  1. Aktualisierte Access Points:
    ap_diningroom (Erdgeschoss)
    ap_garden (Garten)
    ap_office (Dachgeschoss)
    ap_storageroom (Keller)

Alle Access Points haben jetzt sowohl das generelle

Equipment

Tag als auch das spezifische

Equipment_AccessPoint

Tag. Möchten Sie noch weitere Anpassungen vornehmen?

OMG - My AI Buddy just optimized a rule with me. After that he asked if we want to test the rule. I agreed and so he created a test plan for every possible state combination that would affect the outcome of the rule.

But the best of all: He can execute the whole test plan by himself without me needing to open or close any windows. He just updates the states accordingly and checks whether other items show the expected changes :smiley:

Thank you very much for the inspiration @DrRSatzteil! After 4h of try & error I was finally able to connect my openhab system with Claude Desktop under Windows. It’s amazing what improvement an LLM is compared to Alexa. Can’t wait until Claude Desktop supports TTS and voice recognition. :smiley:
Things I learned:

  • Docker Desktop needs to have the “MCP Toolkit” extension installed and in this extension it needs to have Claude Desktop enabled as MCP Client
  • the claude_desktop_config.json is wrong in the github page and must be as follows:
{
	"mcpServers": {
		"MCP_DOCKER": {
			"name": "openhab-mcp",
			"command": "docker",
			"args": [
				"run",
				"--rm",
				"-i",
				"-p",
				"8081:8080",
				"-e",
				"OPENHAB_URL=http://yourURLorIP",
				"-e",
				"OPENHAB_API_TOKEN=oh.MCP.yourToken",
				"--name",
				"openhab-mcp",
				"openhab-mcp"
			]
		}
	}
}

Hope this helps others to shorten the time to get it up & running :wink:

My current struggle is, that the amount of items in my openhab system is too big, so that Claude Desktop (free plan) often tells me, that I’ve reached the conversation limit. :sweat_smile:

I also ran into some problems but more related to the communication between my MCP Server and Client. I therefore added some pagination in the list tools which can reduce the amount of data queried in one go.

If you want you can check out my repo here:

One thing you might want to change back is the transport type. Just change the last line in the openhab_mcp_server.py from mcp.run(transport=“streamable-http”) to mcp.run().

For others who also want to run the MCP Server on a different machine you can leave it as it is.

Besides of adding pagination for list_items and list_things I also did some other changes:

  • Added tools to get persistence data for items
  • Added tools to handle tags (create, change, delete)
  • Removed item details when you use the list_items tool. I figured out that I still need to rename the tool get_item tool because the LLM does not realize that it needs to use this tool to get details for an item and will only get a summary from the list_items tool. So I will change that when I find the time
  • Other minor fixes/improvements

I also got the impression that the AI could handle the pagination better when the page index would start from 1, so I will probably change this in the future too.

As I am lazy I haven’t setup a local pipeline to get the new code on my server but used Github for that (you can see that in my commit history). So don’t be surprised to get lots of changes there every now and again or if the state is broken for a while.

I use my implementation of the server in the Windsurf IDE (https://windsurf.com). Add this to your mcp_config.json to connect to the MCP Server:

{
    "mcpServers": {
      "openhab": {
        "command": "npx",
        "args": [
          "mcp-remote",
          "http://<IP>:8081/mcp",
          "--allow-http"
        ]
      }
    }
}

Make sure to install npx first. The --allow-http flag is only needed if you don’t have a https connection. Port needs to match the port you used in your docker port mapping. Also you need to map the port to 8000 in the container (documentation in the project says 8080). I guess that is the default port for the streamable-http transport. At least that works for me.

1 Like

And one more thing: make sure to set an environment variable FASTMCP_HOST to 0.0.0.0 or else you won’t be able to route your requests to the server in the container. By default the http server only binds to 127.0.0.1. This wasn’t necessary the last time I was playing around with it and it was giving me headaches yesterday…

I also found the time to cleanup my code yesterday and to test all the tools. They should all be working right now.

I can approve that it’s working. Used your repo since the beginning of my experiments. After creating OH4 rules out of some NodeRed flows (where I was too lazy to create it in OH) with the help of my AI buddy, I pushed the limits further. I was able to automatically create Grafana dashboard (with corresponding MCP server), which shows the power consumption of my 3d printer. It looks really great and provides everything what I need. Awsome!! Just with a small prompt:
Create a Grafana dashboard that shows me the power consumption of the item “Socket_3D_Printer_Power_Consumption”
Even giving the name of the item would have not been required, but I wanted to avoid Claude to list items, because this always pushes me in the throtteling :wink:
Amazing…I’m feeling like a child exploring the world.

1 Like

I added the ability to add links between items and channels.

I just successfully created a new thing, new items, channel links and a rule via the LLM by referencing exiting things, items and rules as reference :star_struck:

1 Like

Added some more changes this evening:

  • Create, update or remove item metadata
  • CommandDescriptions (not yet tested)
  • StateDescriptions (not yet tested)
  • Give a meaningful error message to the LLM when it hallucinates its own configurations that are not part of the model

Oh I just realise: updating an item with command and state description and the other additional item field will definitely not work right now. I will provide an update for that soon

EDIT: Should work now. Rule update still seems to fail. I still need to have a closer look at that one.

Hi Thomas, yes, I would be very much interested about the details how you got it going. I am currently not able to get the server running in OH - the docker container always stops after 1 second.

Hi,

I’m sorry for that, I will provide a more structured write up for you and everyone else interested. Right now I’m testing the server in conjunction with AI agents in n8n and discovered some problems in multiple areas of the server. Therefore in currently improving data validation, error handling and the data model itself. So there may be times when there are actual problems with the code however I try to leave the checked in code in a working state when I leave my desk :grimacing:

So since it is working for me please do the following: please run the container without the “remove/rm” flag so that you can inspect the logs after it shut down. I may be able to help you to get it running with these logs :grimacing::+1:

1 Like

So basically all you should need to do to set the server up should be this:

git clone https://github.com/DrRSatzteil/openhab-mcp

cd openhab-mcp

make docker-build

docker run -d -t  -p <PORT>:8000 --restart unless-stopped  -e OPENHAB_URL=<OPENHAB_URL> -e FASTMCP_HOST=0.0.0.0  -e OPENHAB_API_TOKEN=<TOKEN> -e OPENHAB_MCP_TRANSPORT=<TRANSPORT>  --name openhab-mcp   openhab-mcp

The variables that need to be set:

<PORT>: Any port you like. Make sure that your client uses this port
<OPENHAB_URL>: the url where your OH instance runs including protocol and ports: e.g. http://openhab:8080 or https://openhab:8443
<TOKEN>: openHAB API token you can obtain from the OH UI
<TRANSPORT>: stdio (only local connection), sse (legacy http for clients that do not support streamable-http like n8n) or streamable-http for clients that support this new transport

The client url changes a bit for the transports: use ip:port/sse/ for sse and ip:port/mcp/ for streamable-http. I haven’t used stdio transport so please let me know if there is something else to consider there

To update you can use this script in your openhab-mcp folder (insert your docker run statement as last line):


docker stop openhab-mcp
sleep 2
docker rm openhab-mcp

git pull

make docker-build

Thank you for the short guide - now it works!

Cool, please let me know if you spot any bugs. Its quite hard to test this thing as you never know what stupid ideas the AI will come up with…

One thing I noticed: I accidentally deleted some group memberships while I was editing some items. That might be a problem in the code but I did not investigate any further. Also I guess the update item method cannot really handle cases well where you for example want to delete all group memberships. I would need to come up with a better idea for that…

no bug, but maybe an amendment to the above docu - by trial and error (and very! helpful comments from my local AI (I am using qwen3:14b) I found that for the <OPENHAB_URL> in the docker command I need to specify the full URL including protocol and port, e.g. “http://192.168.178.37:8080

True, thanks for pointing that out! You don’t need the port for standard http/https ports but for all others you would need to specify that.

Should the docker container stay running after the run command is issued? I’m trying to connect this into VSCode and from either VSCode or Terminal (OSX) the container runs and exits immediately with no error. Weird.

On your version @DrRSatzteil I do get more output, but it still exits almost immediately. Is that expected and this just isn’t compatible with VSCode or am I doing something incorrectly?

This is what I see in terminal if I run it:

 % docker run --rm -p 8081:8080 \
  -e OPENHAB_URL=http://<my domain name>:8080 \
  -e OPENHAB_API_TOKEN=<my token> \
  --name openhab-mcp \
  openhab-mcp
DEBUG:mcp.server.lowlevel.server:Initializing server 'OpenHAB MCP Server'
DEBUG:mcp.server.lowlevel.server:Registering handler for ListToolsRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for CallToolRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for ListResourcesRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for ReadResourceRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for PromptListRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for GetPromptRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for ListResourceTemplatesRequest
DEBUG:mcp.server.lowlevel.server:Initializing server 'OpenHAB MCP Server'
DEBUG:mcp.server.lowlevel.server:Registering handler for ListToolsRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for CallToolRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for ListResourcesRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for ReadResourceRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for PromptListRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for GetPromptRequest
DEBUG:mcp.server.lowlevel.server:Registering handler for ListResourceTemplatesRequest
DEBUG:asyncio:Using selector: EpollSelector

No that is definitely not expected. The container should keep running. You are missing the FASTMCP_HOST=0.0.0.0 parameter. It is required as the server will otherwise just be listening on 127.0.0.1 (inside the container, not your host machine) and you cannot access it from outside (not even locally). I should set this somewhere in the container directly as it does not even make sense to have this configurable by the user. This was just a quick fix when I had this problem and I did not clean that up…