@coffeerunhobby/mcp-ha-connect 2.2.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/.env.example +1 -1
  2. package/README.md +24 -2
  3. package/dist/omadaClient/accessControl.d.ts +290 -0
  4. package/dist/omadaClient/accessControl.js +509 -0
  5. package/dist/omadaClient/accessControl.js.map +1 -0
  6. package/dist/omadaClient/client.d.ts +6 -1
  7. package/dist/omadaClient/client.js +12 -1
  8. package/dist/omadaClient/client.js.map +1 -1
  9. package/dist/omadaClient/diagnose.d.ts +68 -0
  10. package/dist/omadaClient/diagnose.js +164 -0
  11. package/dist/omadaClient/diagnose.js.map +1 -0
  12. package/dist/omadaClient/index.d.ts +9 -0
  13. package/dist/omadaClient/index.js +19 -0
  14. package/dist/omadaClient/index.js.map +1 -1
  15. package/dist/omadaClient/macGroups.d.ts +58 -0
  16. package/dist/omadaClient/macGroups.js +110 -0
  17. package/dist/omadaClient/macGroups.js.map +1 -0
  18. package/dist/omadaClient/serialize.d.ts +13 -0
  19. package/dist/omadaClient/serialize.js +29 -0
  20. package/dist/omadaClient/serialize.js.map +1 -0
  21. package/dist/omadaClient/timeRange.d.ts +41 -0
  22. package/dist/omadaClient/timeRange.js +109 -0
  23. package/dist/omadaClient/timeRange.js.map +1 -0
  24. package/dist/tools/infra/actions.d.ts +1 -1
  25. package/dist/tools/infra/actions.js +1 -1
  26. package/dist/tools/omada/accessControl.d.ts +151 -0
  27. package/dist/tools/omada/accessControl.js +158 -0
  28. package/dist/tools/omada/accessControl.js.map +1 -0
  29. package/dist/tools/omada/index.js +9 -0
  30. package/dist/tools/omada/index.js.map +1 -1
  31. package/dist/tools/omada/macGroups.d.ts +19 -0
  32. package/dist/tools/omada/macGroups.js +77 -0
  33. package/dist/tools/omada/macGroups.js.map +1 -0
  34. package/dist/tools/omada/namespace.js +16 -0
  35. package/dist/tools/omada/namespace.js.map +1 -1
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +5 -1
  39. package/.codex/review-rules.md +0 -16
  40. package/.dockerignore +0 -8
  41. package/.gitattributes +0 -30
  42. package/.github/workflows/ci.yml +0 -193
  43. package/AGENTS.md +0 -389
  44. package/Dockerfile +0 -50
  45. package/docker-compose.yml +0 -23
  46. package/docs/CHANGELOG.md +0 -341
  47. package/docs/COVERAGE.md +0 -219
  48. package/docs/QUICK_START.md +0 -240
  49. package/eslint.config.js +0 -33
  50. package/n8n/ai-agent-safety-monitor.json +0 -355
  51. package/openapi.json +0 -1259
  52. package/scripts/check-version-sync.mjs +0 -69
  53. package/scripts/generate-jwt.ts +0 -121
  54. package/src/config.ts +0 -440
  55. package/src/haClient/automations.ts +0 -261
  56. package/src/haClient/calendars.ts +0 -94
  57. package/src/haClient/config.ts +0 -52
  58. package/src/haClient/devices.ts +0 -653
  59. package/src/haClient/entities.ts +0 -128
  60. package/src/haClient/events.ts +0 -689
  61. package/src/haClient/history.ts +0 -73
  62. package/src/haClient/index.ts +0 -336
  63. package/src/haClient/request.ts +0 -142
  64. package/src/haClient/services.ts +0 -45
  65. package/src/haClient/states.ts +0 -60
  66. package/src/haClient/updates.ts +0 -58
  67. package/src/haClient/wsCommand.ts +0 -44
  68. package/src/index.ts +0 -164
  69. package/src/localAI/index.ts +0 -88
  70. package/src/localAI/prompts.ts +0 -50
  71. package/src/localAI/providers/base.ts +0 -120
  72. package/src/localAI/providers/ollama.ts +0 -78
  73. package/src/localAI/providers/openai.ts +0 -86
  74. package/src/localAI/types.ts +0 -67
  75. package/src/omadaClient/auth.ts +0 -178
  76. package/src/omadaClient/client.ts +0 -337
  77. package/src/omadaClient/device.ts +0 -118
  78. package/src/omadaClient/index.ts +0 -310
  79. package/src/omadaClient/network.ts +0 -284
  80. package/src/omadaClient/request.ts +0 -365
  81. package/src/omadaClient/security.ts +0 -51
  82. package/src/omadaClient/site.ts +0 -344
  83. package/src/ownerQuestions/service.ts +0 -536
  84. package/src/permissions/index.ts +0 -143
  85. package/src/resources/index.ts +0 -213
  86. package/src/server/auth.ts +0 -175
  87. package/src/server/chatFace.ts +0 -789
  88. package/src/server/chatSlice.ts +0 -57
  89. package/src/server/common.ts +0 -170
  90. package/src/server/eventSubscription.ts +0 -343
  91. package/src/server/http.ts +0 -626
  92. package/src/server/instructions.ts +0 -97
  93. package/src/server/rateLimiter.ts +0 -227
  94. package/src/server/stdio.ts +0 -41
  95. package/src/server/stream.ts +0 -205
  96. package/src/tools/ai/analyzeSensors.ts +0 -33
  97. package/src/tools/ai/index.ts +0 -20
  98. package/src/tools/common.ts +0 -442
  99. package/src/tools/homeassistant/askOwner.ts +0 -133
  100. package/src/tools/homeassistant/automationControls.ts +0 -66
  101. package/src/tools/homeassistant/calendar.ts +0 -97
  102. package/src/tools/homeassistant/callService.ts +0 -25
  103. package/src/tools/homeassistant/controlClimate.ts +0 -68
  104. package/src/tools/homeassistant/controlCover.ts +0 -48
  105. package/src/tools/homeassistant/controlFan.ts +0 -44
  106. package/src/tools/homeassistant/controlLight.ts +0 -38
  107. package/src/tools/homeassistant/controlMediaPlayer.ts +0 -50
  108. package/src/tools/homeassistant/createAutomation.ts +0 -35
  109. package/src/tools/homeassistant/deleteAutomation.ts +0 -24
  110. package/src/tools/homeassistant/entityAction.ts +0 -35
  111. package/src/tools/homeassistant/entityGuard.ts +0 -28
  112. package/src/tools/homeassistant/getAllSensors.ts +0 -46
  113. package/src/tools/homeassistant/getAutomationTrace.ts +0 -30
  114. package/src/tools/homeassistant/getDomainSummary.ts +0 -29
  115. package/src/tools/homeassistant/getEntitiesByDomain.ts +0 -56
  116. package/src/tools/homeassistant/getHistory.ts +0 -24
  117. package/src/tools/homeassistant/getState.ts +0 -24
  118. package/src/tools/homeassistant/getStates.ts +0 -46
  119. package/src/tools/homeassistant/getVersion.ts +0 -21
  120. package/src/tools/homeassistant/index.ts +0 -121
  121. package/src/tools/homeassistant/listAutomations.ts +0 -35
  122. package/src/tools/homeassistant/listEntities.ts +0 -44
  123. package/src/tools/homeassistant/listPersons.ts +0 -46
  124. package/src/tools/homeassistant/sceneAndScript.ts +0 -53
  125. package/src/tools/homeassistant/searchEntities.ts +0 -58
  126. package/src/tools/homeassistant/sendNotification.ts +0 -179
  127. package/src/tools/homeassistant/systemTools.ts +0 -52
  128. package/src/tools/homeassistant/triggerAutomation.ts +0 -24
  129. package/src/tools/index.ts +0 -78
  130. package/src/tools/infra/actions.ts +0 -95
  131. package/src/tools/infra/index.ts +0 -138
  132. package/src/tools/omada/blockClient.ts +0 -28
  133. package/src/tools/omada/cyclePoePort.ts +0 -32
  134. package/src/tools/omada/deleteClient.ts +0 -27
  135. package/src/tools/omada/disableClientRateLimit.ts +0 -23
  136. package/src/tools/omada/getClient.ts +0 -14
  137. package/src/tools/omada/getDevice.ts +0 -14
  138. package/src/tools/omada/getFirewallSetting.ts +0 -14
  139. package/src/tools/omada/getInternetInfo.ts +0 -14
  140. package/src/tools/omada/getLanNetworkList.ts +0 -14
  141. package/src/tools/omada/getLanProfileList.ts +0 -14
  142. package/src/tools/omada/getPortForwardingStatus.ts +0 -26
  143. package/src/tools/omada/getRateLimitProfiles.ts +0 -14
  144. package/src/tools/omada/getSiteNtpStatus.ts +0 -14
  145. package/src/tools/omada/getSsidDetail.ts +0 -22
  146. package/src/tools/omada/getSsidList.ts +0 -21
  147. package/src/tools/omada/getSwitchStackDetail.ts +0 -14
  148. package/src/tools/omada/getThreatList.ts +0 -42
  149. package/src/tools/omada/getWlanGroupList.ts +0 -14
  150. package/src/tools/omada/graph.ts +0 -187
  151. package/src/tools/omada/index.ts +0 -153
  152. package/src/tools/omada/listBlockedClients.ts +0 -17
  153. package/src/tools/omada/listClients.ts +0 -14
  154. package/src/tools/omada/listClientsActivity.ts +0 -25
  155. package/src/tools/omada/listClientsPastConnections.ts +0 -44
  156. package/src/tools/omada/listDevices.ts +0 -14
  157. package/src/tools/omada/listDevicesStats.ts +0 -41
  158. package/src/tools/omada/listMostActiveClients.ts +0 -14
  159. package/src/tools/omada/listSites.ts +0 -18
  160. package/src/tools/omada/namespace.ts +0 -1164
  161. package/src/tools/omada/searchDevices.ts +0 -20
  162. package/src/tools/omada/setClientRateLimit.ts +0 -26
  163. package/src/tools/omada/setClientRateLimitProfile.ts +0 -25
  164. package/src/tools/omada/setSiteNtpServers.ts +0 -36
  165. package/src/tools/omada/setSsidEnabled.ts +0 -38
  166. package/src/tools/omada/unblockClient.ts +0 -26
  167. package/src/tools/types.ts +0 -11
  168. package/src/tools/utils/pagination-schema.ts +0 -27
  169. package/src/types/ai/index.ts +0 -11
  170. package/src/types/ai/types.ts +0 -67
  171. package/src/types/homeassistant/automations.ts +0 -43
  172. package/src/types/homeassistant/calendars.ts +0 -26
  173. package/src/types/homeassistant/config.ts +0 -14
  174. package/src/types/homeassistant/entities.ts +0 -14
  175. package/src/types/homeassistant/errors.ts +0 -33
  176. package/src/types/homeassistant/haConfig.ts +0 -7
  177. package/src/types/homeassistant/history.ts +0 -13
  178. package/src/types/homeassistant/index.ts +0 -46
  179. package/src/types/homeassistant/request.ts +0 -6
  180. package/src/types/homeassistant/serviceCall.ts +0 -18
  181. package/src/types/homeassistant/states.ts +0 -58
  182. package/src/types/homeassistant/updates.ts +0 -28
  183. package/src/types/index.ts +0 -12
  184. package/src/types/omada/activeClientInfo.ts +0 -17
  185. package/src/types/omada/clientActivity.ts +0 -32
  186. package/src/types/omada/clientBlockStatus.ts +0 -11
  187. package/src/types/omada/clientPastConnection.ts +0 -82
  188. package/src/types/omada/index.ts +0 -14
  189. package/src/types/omada/omadaApiResponse.ts +0 -5
  190. package/src/types/omada/omadaClientInfo.ts +0 -11
  191. package/src/types/omada/omadaDeviceInfo.ts +0 -11
  192. package/src/types/omada/omadaDeviceStats.ts +0 -31
  193. package/src/types/omada/omadaSiteSummary.ts +0 -5
  194. package/src/types/omada/oswStackDetail.ts +0 -11
  195. package/src/types/omada/paginatedResult.ts +0 -6
  196. package/src/types/omada/rateLimitProfile.ts +0 -58
  197. package/src/types/omada/threatInfo.ts +0 -38
  198. package/src/types/omada/tokenResult.ts +0 -6
  199. package/src/types.ts +0 -2
  200. package/src/utils/config-validations.ts +0 -68
  201. package/src/utils/jwt.ts +0 -112
  202. package/src/utils/logger.ts +0 -121
  203. package/src/utils/plainText.ts +0 -48
  204. package/src/utils/redact.ts +0 -59
  205. package/src/utils/sanitizeError.ts +0 -18
  206. package/src/utils/tlsDispatcher.ts +0 -69
  207. package/src/version.ts +0 -5
  208. package/tests/bdd/automation-creation.spec.ts +0 -214
  209. package/tests/bdd/automation-management.spec.ts +0 -267
  210. package/tests/bdd/calendar.spec.ts +0 -190
  211. package/tests/bdd/configuration.spec.ts +0 -283
  212. package/tests/bdd/device-control.spec.ts +0 -344
  213. package/tests/bdd/entity-listing.spec.ts +0 -208
  214. package/tests/bdd/entity-search.spec.ts +0 -183
  215. package/tests/bdd/history-retrieval.spec.ts +0 -195
  216. package/tests/bdd/notifications.spec.ts +0 -238
  217. package/tests/bdd/service-calls.spec.ts +0 -144
  218. package/tests/bdd/state-retrieval.spec.ts +0 -204
  219. package/tests/bench/hotpaths.bench.ts +0 -68
  220. package/tests/config.test.ts +0 -355
  221. package/tests/features/automation-creation.feature +0 -36
  222. package/tests/features/automation-management.feature +0 -54
  223. package/tests/features/calendar.feature +0 -37
  224. package/tests/features/configuration.feature +0 -70
  225. package/tests/features/device-control.feature +0 -100
  226. package/tests/features/entity-listing.feature +0 -51
  227. package/tests/features/entity-search.feature +0 -44
  228. package/tests/features/history-retrieval.feature +0 -38
  229. package/tests/features/notifications.feature +0 -37
  230. package/tests/features/service-calls.feature +0 -35
  231. package/tests/features/state-retrieval.feature +0 -37
  232. package/tests/fixtures/openapi-default-slice.json +0 -290
  233. package/tests/haClient/automationTrace.test.ts +0 -59
  234. package/tests/haClient/automations.test.ts +0 -149
  235. package/tests/haClient/config.test.ts +0 -117
  236. package/tests/haClient/entities.test.ts +0 -188
  237. package/tests/haClient/events.test.ts +0 -455
  238. package/tests/haClient/eventsTls.test.ts +0 -52
  239. package/tests/haClient/history.test.ts +0 -228
  240. package/tests/haClient/index.test.ts +0 -243
  241. package/tests/haClient/request.test.ts +0 -217
  242. package/tests/haClient/services.test.ts +0 -120
  243. package/tests/haClient/states.test.ts +0 -119
  244. package/tests/haClient/updates.test.ts +0 -193
  245. package/tests/index.test.ts +0 -160
  246. package/tests/integration/homeassistant.test.ts +0 -144
  247. package/tests/integration/omada.test.ts +0 -167
  248. package/tests/integration/server.test.ts +0 -151
  249. package/tests/localAI/index.test.ts +0 -177
  250. package/tests/localAI/ollama.test.ts +0 -187
  251. package/tests/localAI/openai.test.ts +0 -257
  252. package/tests/omadaClient/actions.test.ts +0 -161
  253. package/tests/omadaClient/clientBlocks.test.ts +0 -170
  254. package/tests/omadaClient/ntp.test.ts +0 -381
  255. package/tests/omadaClient/site.test.ts +0 -37
  256. package/tests/ownerQuestions/service.test.ts +0 -499
  257. package/tests/ownerQuestions/tools.test.ts +0 -186
  258. package/tests/permissions/index.test.ts +0 -217
  259. package/tests/resources/index.test.ts +0 -288
  260. package/tests/scripts/generateJwt.test.ts +0 -44
  261. package/tests/security/accessControl.test.ts +0 -223
  262. package/tests/security/authentication.test.ts +0 -117
  263. package/tests/security/configuration.test.ts +0 -170
  264. package/tests/security/cryptoTransport.test.ts +0 -172
  265. package/tests/security/dependencies.test.ts +0 -152
  266. package/tests/security/injection.test.ts +0 -117
  267. package/tests/security/insecureDesign.test.ts +0 -121
  268. package/tests/security/logging.test.ts +0 -131
  269. package/tests/security/resourceConsumption.test.ts +0 -142
  270. package/tests/server/auth.test.ts +0 -249
  271. package/tests/server/chatFace.test.ts +0 -320
  272. package/tests/server/common.test.ts +0 -84
  273. package/tests/server/http.test.ts +0 -561
  274. package/tests/server/instructions.test.ts +0 -152
  275. package/tests/server/memoizeJsonSchema.test.ts +0 -71
  276. package/tests/server/stdio.test.ts +0 -99
  277. package/tests/server/stream.test.ts +0 -285
  278. package/tests/server/toolVisibility.http.test.ts +0 -111
  279. package/tests/server/toolVisibility.test.ts +0 -129
  280. package/tests/tools/ai/analyzeSensors.test.ts +0 -227
  281. package/tests/tools/common.test.ts +0 -123
  282. package/tests/tools/homeassistant/handlers.test.ts +0 -872
  283. package/tests/tools/infra/invokeAction.test.ts +0 -280
  284. package/tests/tools/omada/graph.test.ts +0 -385
  285. package/tests/tools/omada/handlers.test.ts +0 -897
  286. package/tests/tools/omada/namespace.test.ts +0 -483
  287. package/tests/tools/registry.test.ts +0 -168
  288. package/tests/utils/config-validations.test.ts +0 -352
  289. package/tests/utils/jwt.test.ts +0 -115
  290. package/tests/utils/logger.test.ts +0 -327
  291. package/tests/utils/redact.test.ts +0 -105
  292. package/tsconfig.json +0 -24
  293. package/vitest.config.ts +0 -38
package/AGENTS.md DELETED
@@ -1,389 +0,0 @@
1
- # mcp-ha-connect -- Codex Agent
2
-
3
- ## Identity & Scope
4
-
5
- This agent owns the **mcp-ha-connect MCP server** — a Model Context Protocol server for
6
- Home Assistant, TP-Link Omada, and Local AI integration.
7
-
8
- **Repo:** `github.com/coffeerunhobby/mcp-ha-connect`
9
- **npm:** `@coffeerunhobby/mcp-ha-connect`
10
- **Docker:** `ghcr.io/coffeerunhobby/mcp-ha-connect`
11
- **Live endpoint:** Cloudflare domain → NAS Docker port 3000 (domain in NAS `.env`)
12
-
13
- **Working directory:** `C:\workspace\mcp` — this is the canonical copy.
14
- `W:\mcp-homeassistant` is a stale export at v0.8.0 — ignore it.
15
-
16
- ---
17
-
18
- ## Stack
19
-
20
- - **Runtime:** Node.js 20+ (dev machine uses nvm)
21
- - **Language:** TypeScript, compiled to `dist/` via `tsc`
22
- - **MCP SDK:** v2 split packages — `@modelcontextprotocol/server` + `@modelcontextprotocol/node` (Node HTTP transport); `@modelcontextprotocol/client` is dev-only (tests)
23
- - **Transport:** Streamable HTTP (port 3000, path `/mcp`)
24
- - **Auth:** JWT bearer tokens with RBAC permission system
25
- - **Plugins:** Home Assistant · TP-Link Omada SDN · Local AI (Ollama/OpenAI-compat)
26
- - **Tests:** Vitest (1064 unit tests) + integration tests in `tests/integration/`
27
- - **Container:** Alpine Node image, deployed on Synology NAS via Docker Compose
28
-
29
- ---
30
-
31
- ## Infrastructure
32
-
33
- | Resource | Address | Notes |
34
- |---|---|---|
35
- | **MCP server** | `mcpserver.10.0.0.18.nip.io:3000` | NAS Docker container |
36
- | **Home Assistant** | `homeassistant.10.0.0.19.nip.io:8123` | |
37
- | **Omada controller** | `omada.10.0.0.18.nip.io:8043` | HTTPS, self-signed cert |
38
- | **Ollama** | `ollama.10.0.0.17.nip.io:11434` | model `phi4:14b` |
39
- | **n8n** | `n8n.10.0.0.18.nip.io:5678` | also exposed via Cloudflare |
40
- | **NAS Docker path** | `/volume1/docker/mcp-ha-connect/` | compose.yaml + .env |
41
- | **Cloudflare** | proxies public domain → NAS port 3000 | domain stored in `.env` |
42
-
43
- ---
44
-
45
- ## Secrets & Credentials
46
-
47
- Server secrets live in the NAS `.env` file. The one release secret — the GHCR push token
48
- (a GitHub classic PAT with `write:packages`, used for the Docker push in step 5) — lives only
49
- in the maintainer's password manager. Never store it as an environment variable, in a file, in
50
- a commit, in chat, or in this file; fetch it at push time and pipe it to `docker login
51
- --password-stdin`.
52
-
53
- ### npm publishing needs no token
54
-
55
- npm publish is done by CI via **OIDC trusted publishing** (see Release Process step 4) —
56
- there is no npm token to store, rotate, or revoke. Publishing is configured at
57
- npmjs.com → package Settings → **Trusted Publisher** → GitHub Actions (this repo +
58
- `ci.yml`), not via a token.
59
-
60
- ---
61
-
62
- ## Release Process
63
-
64
- ### 1. Pre-flight
65
-
66
- ```powershell
67
- cd C:\workspace\mcp
68
- npm test # all tests must pass
69
- npm run build # must compile clean
70
- npm run lint # ZERO errors AND zero warnings (see Code Quality policy)
71
- ```
72
-
73
- ### 2. Version bump
74
-
75
- Update **both** files to the new version:
76
- - `package.json` → `"version": "X.Y.Z"`
77
- - `src/version.ts` → `export const VERSION = 'X.Y.Z';`
78
-
79
- Update `docs/CHANGELOG.md` with the new version section.
80
-
81
- ### 3. Commit & tag
82
-
83
- ```powershell
84
- git add .
85
- git commit -m "vX.Y.Z - <short description>"
86
- git tag vX.Y.Z
87
- git push origin main
88
- git push origin vX.Y.Z
89
- ```
90
-
91
- Create the GitHub Release at:
92
- `https://github.com/coffeerunhobby/mcp-ha-connect/releases/new?tag=vX.Y.Z`
93
-
94
- ### 4. npm publish — AUTOMATED by CI (do NOT publish by hand)
95
-
96
- **The `v*` tag you pushed in step 3 already publishes npm.** `.github/workflows/ci.yml`
97
- has a `publish-npm` job that fires on `refs/tags/v*` and publishes via **OIDC trusted
98
- publishing** — tokenless, `--provenance`, no `npm login`. It is **idempotent** (skips if
99
- `pkg@version` is already on the registry) and it re-runs `npm ci && npm run build`
100
- because `dist/` is gitignored. The same tag also runs a `release` job that auto-creates
101
- the GitHub Release (idempotent — a manual `gh release create` just no-ops).
102
-
103
- > Agent rule: **do NOT run `npm publish`/`npm login`.** Just push the tag, then verify:
104
- > ```powershell
105
- > gh run list --limit 3 # publish-npm + release jobs green on the v tag
106
- > npm view @coffeerunhobby/mcp-ha-connect version # shows the new version
107
- > ```
108
- > `npm whoami` from the agent returns 401 (no session) — expected, not a failure.
109
-
110
- ### 5. Docker build & push — via WSL (NO Docker Desktop)
111
-
112
- **Docker Desktop is not used on this box.** The working engine is **Docker CE inside
113
- WSL2 Ubuntu-24.04**. CI's `docker-smoke` job only *boot-tests* the image — it does
114
- **not** push to GHCR, so this push is the one genuinely manual release leg.
115
-
116
- ```powershell
117
- # GHCR login — token fetched from the maintainer's password manager at push time and piped
118
- # over stdin (never a command-line arg or env var). WSL keeps the login for the push below.
119
- <token from password manager> | wsl -d Ubuntu-24.04 -- bash -lc "docker login ghcr.io -u coffeerunhobby --password-stdin"
120
-
121
- # Build (repo is /mnt/c/workspace/mcp inside WSL) + push both tags
122
- wsl -d Ubuntu-24.04 -- bash -lc "cd /mnt/c/workspace/mcp && docker build --no-cache -t ghcr.io/coffeerunhobby/mcp-ha-connect:X.Y.Z -t ghcr.io/coffeerunhobby/mcp-ha-connect:latest ."
123
- wsl -d Ubuntu-24.04 -- bash -lc "docker push ghcr.io/coffeerunhobby/mcp-ha-connect:X.Y.Z && docker push ghcr.io/coffeerunhobby/mcp-ha-connect:latest"
124
- ```
125
-
126
- WSL2 is amd64 = the NAS arch, so a plain `docker build` (not `buildx --platform`)
127
- produces the correct image.
128
-
129
- ### 6. Deploy to NAS
130
-
131
- The compose file pulls `ghcr.io/coffeerunhobby/mcp-ha-connect:latest`. On the NAS:
132
-
133
- ```bash
134
- cd /volume1/docker/mcp-ha-connect
135
- docker compose pull mcp-ha-connect && docker compose up -d mcp-ha-connect
136
- docker compose logs --tail=30
137
- ```
138
-
139
- Verify: `curl https://<cloudflare-domain>/openapi.json` → `info.version` is the new
140
- version, and `curl https://<cloudflare-domain>/health` → `{"status":"healthy"}`.
141
-
142
- ---
143
-
144
- ## NAS `.env` — critical settings
145
-
146
- ```env
147
- MCP_HTTP_BIND_ADDR=0.0.0.0
148
- ```
149
-
150
- Must be `0.0.0.0`, not `127.0.0.1`. Docker port mapping requires the server to listen on
151
- all interfaces inside the container — loopback only means Cloudflare gets a 502.
152
-
153
- ```env
154
- MCP_HTTP_ALLOWED_ORIGINS=...,<cloudflare-domain>,https://<cloudflare-domain>
155
- ```
156
-
157
- Must include both the bare hostname AND the `https://` form of the Cloudflare domain.
158
- The MCP SDK checks the `host` header (no protocol) for DNS rebinding protection AND
159
- the `origin` header (with protocol) for CORS.
160
-
161
- ---
162
-
163
- ## Omada SDN — known issues
164
-
165
- **After upgrading the Omada controller version**, the `OMADA_OMADAC_ID` changes.
166
- Error code `-44106` is returned for BOTH wrong credentials AND wrong `OMADA_OMADAC_ID` —
167
- the API does not distinguish between them.
168
-
169
- **To get the new Omada ID:**
170
- Omada web UI → Settings → Open API → click the eye icon → copy **Omada ID**.
171
-
172
- ```bash
173
- sed -i 's/OMADA_OMADAC_ID=.*/OMADA_OMADAC_ID=<new-id>/' /volume1/docker/mcp-ha-connect/.env
174
- docker compose up -d
175
- ```
176
-
177
- If Omada is unreachable or credentials are wrong, the server **gracefully degrades** —
178
- Omada tools are disabled but HA and AI keep working. Set `OMADA_PLUGIN_ENABLED=false`
179
- to disable Omada entirely while troubleshooting.
180
-
181
- **`OMADA_SITE_ID` is validated at startup against the live site list** (the controller is
182
- the source of truth; the env var is only a selector). If `OMADA_SITE_ID` is set but **not**
183
- present on the controller — a site id can drift after a controller migration, exactly like
184
- `OMADA_OMADAC_ID` — the **Omada plugin is disabled** at boot with a `logger.error` that
185
- names the bad value and lists the valid sites (HA + AI stay up). This turns the old failure
186
- mode (a silent default scoping every read to a dead site → later "user does not have
187
- permissions to access this site") into a loud, self-diagnosing startup error. Fix by setting
188
- `OMADA_SITE_ID` to one of the listed ids. Validation lives in `checkConfiguredSite()`
189
- (`src/omadaClient/site.ts`); a *valid* default still auto-resolves, so site-scoped reads need
190
- no explicit `siteId`.
191
-
192
- ---
193
-
194
- ## Code Quality — fix every error and warning
195
-
196
- **Always fix errors AND warnings. Never label something "pre-existing debt" and move on.**
197
-
198
- - `npm run lint`, `npm run build`, and `npm test` must all be **completely clean** —
199
- zero errors, zero warnings — before a change is considered done.
200
- - If you touch a file (or a release surfaces lint output) and find existing errors or
201
- warnings, they are now yours to fix. "It was already broken" is not an acceptable
202
- reason to leave it.
203
- - **Exception — release timing:** do not cut a patch release *solely* to clean up lint.
204
- If quality issues are found right after a release has shipped, fold the fixes into the
205
- **next feature's** commit rather than churning a `vX.Y.(Z+1)`. Outstanding cleanup of
206
- this kind is tracked in `.Codex/NEXT-SESSION.md` so it isn't forgotten.
207
-
208
- > No lint cleanup currently outstanding. (The former `Function`-type warnings in the
209
- > test mock helpers were resolved — `npm run lint --max-warnings=0` is clean as of v1.5.0.)
210
-
211
- ---
212
-
213
- ## Development
214
-
215
- ```powershell
216
- cd C:\workspace\mcp
217
- npm run dev # tsx watch with .env
218
- npm test # vitest (excludes integration)
219
- npm run test:watch # watch mode
220
- npm run lint # eslint
221
- npm run build # tsc compile to dist/
222
- npm run generate:jwt # generate a test JWT token
223
- ```
224
-
225
- ### Integration tests (requires live .env)
226
-
227
- ```powershell
228
- npm run test:integration
229
- ```
230
-
231
- ---
232
-
233
- ## Live testing against the running server
234
-
235
- The dev machine can exercise the deployed MCP server two ways:
236
-
237
- 1. **Connected `homeassistant` MCP in Codex Desktop** — this agent has the live server
238
- wired in as an MCP connection, so HA/Omada tools can be invoked directly (no curl, no
239
- manual token; the configured bearer is used). **Caveat:** the MCP client caches the tool
240
- list at *connection time*. After flipping `MCP_TOOL_REGISTRATION_MODE` (eager ⇄ graph)
241
- and redeploying, that cached list is **stale** — Codex Desktop must reconnect (restart)
242
- before the new tool surface (`omada_browse`/`omada_read`, or the typed getters) shows up.
243
- Until it reconnects, calling a tool the live server no longer registers will fail.
244
-
245
- 2. **Direct `/mcp` curl** — generate a token with
246
- `npm run generate:jwt -- --sub <user> --exp 1h` (signs with the local `.env`
247
- `MCP_AUTH_SECRET`, which currently matches the NAS), then POST JSON-RPC to the server's
248
- `/mcp` with headers `Authorization: Bearer <tok>`, `Content-Type: application/json`,
249
- `Accept: application/json, text/event-stream`. Responses come back as SSE
250
- (`data: {...}` lines). `tools/list` and `tools/call` work **without** an initialize
251
- handshake in stateless mode. The reachable server address lives in
252
- `.Codex/NEXT-SESSION.md` (gitignored — never put the real IP/domain here).
253
-
254
- Permissions are resolved **server-side** from the token's `sub` via `MCP_PERMISSIONS_CONFIG`
255
- (they are NOT carried in the token). Pick a `sub` whose mapped role has the bit you need —
256
- e.g. the ADMIN-gated `/security/threats` node needs a `sub` mapped to an ADMIN role. The NAS
257
- role map differs from the local `.env`, so don't assume `sub=admin` is actually ADMIN there.
258
-
259
- ---
260
-
261
- ## Never assume. Always verify.
262
-
263
- - **Cloudflare 502**: check the container is running (`docker compose ps`) and
264
- `MCP_HTTP_BIND_ADDR=0.0.0.0` is set.
265
- - **MCP client connects but tools fail**: check `MCP_HTTP_ALLOWED_ORIGINS` includes
266
- the Cloudflare domain in both bare and `https://` form.
267
- - **Codex Desktop shows "Server disconnected" but `/health` is 200**: the client's
268
- mcp-remote auth header is malformed. A `"Authorization: Bearer ${MCP_AUTH_TOKEN}"`
269
- placeholder does NOT expand on Windows (`cmd /c` uses `%VAR%`, mcp-remote doesn't
270
- substitute `${VAR}`) → literal text is sent → server 401 → mcp-remote drops into a
271
- failing OAuth flow. Fix: inline the literal JWT in `claude_desktop_config.json`'s
272
- `--header` arg, and add `-y` to `npx` so first run doesn't hang. Verify the server
273
- side first with a direct `curl` POST to `/mcp` carrying the token.
274
- - **npm version not updating after a tag push**: check the `publish-npm` CI job on the
275
- tag (`gh run list`) — publishing is OIDC via CI, never manual (see Release step 4).
276
- - **Omada auth error -44106**: check `OMADA_OMADAC_ID` first, not just credentials —
277
- this error code covers both cases.
278
- - **Before a release**: always run `npm test` and `npm run build` clean.
279
- - **On release, bump the version everywhere**: `package.json`, `src/version.ts`,
280
- `docs/CHANGELOG.md`, AND `openapi.json` (`info.version`) — the last one drifts easily.
281
-
282
- ---
283
-
284
- ## Tool registration modes (`MCP_TOOL_REGISTRATION_MODE`)
285
-
286
- Implemented as a **resource-graph** mode for the Omada plugin (config parses the var;
287
- `config.toolRegistrationMode` threads through `createServer` → `registerAllTools` →
288
- `registerOmadaTools`). Values:
289
-
290
- - **`eager`** (default) — register every typed tool (~26 Omada tools). Today's behavior;
291
- fully backwards-compatible.
292
- - **`graph`** — register only `omada_browse` + `omada_read` + the 5 typed write/action
293
- tools. Reads collapse into a discoverable resource graph (`src/tools/omada/namespace.ts`
294
- manifest + `src/tools/omada/graph.ts`), shrinking the tool-schema budget for low-context
295
- models while still exposing every read endpoint.
296
-
297
- Design invariants (don't regress these):
298
- - **browse returns TYPES, not instances** — `omada_browse(path)` lists permission-filtered
299
- child resource types + metadata (kind, permission, pagination, params). It never
300
- enumerates MACs or calls the controller.
301
- - **read is per-path RBAC, fail-closed** — `omada_read` declares NO static tool permission;
302
- each manifest node declares its own bit (most reads = QUERY; the `/security/*` subtree =
303
- ADMIN). Unknown / container / under-privileged / missing-required-param paths all return a
304
- clean error and never fetch.
305
- - **single-page pagination** — paginated nodes forward one page/pageSize to a single GET via
306
- `OmadaClient.readResource()`; they never walk every page.
307
- - **graph is a complete superset of eager reads** — every eager read getter has a graph node,
308
- including the ADMIN-gated IDS/IPS threat-management log at `/security/threats`. Its mandatory
309
- epoch time-window is enforced via required `params.startTime`/`params.endTime` (epoch seconds),
310
- validated by `omada_read` before any controller call.
311
-
312
- HA and AI plugins are unaffected (still eager). Writes stay typed + individually gated in
313
- both modes — generic write verbs were deliberately rejected (prompt-injection / hallucination
314
- surface).
315
-
316
- ---
317
-
318
- ## Security
319
-
320
- This server controls real devices (HA, Omada) and is exposed publicly via Cloudflare, so
321
- treat it as security-sensitive. A full audit (2026-06-07) with file/line findings is in
322
- `.Codex/NEXT-SESSION.md`; this section is the **durable policy** distilled from it.
323
-
324
- ### Dependencies — update, don't rewrite
325
-
326
- - Runtime deps are intentionally tiny: `@modelcontextprotocol/server`, `@modelcontextprotocol/node`,
327
- `undici`, `ws`, `zod` (^4.2 — SDK v2 requires it). Since v2.0.0 (SDK v2) the whole production tree
328
- is **8 packages** (was 95 with SDK v1, which dragged in Express/cors/cross-spawn/ajv).
329
- **Do not hand-roll replacements** for these (or for transitive libs like `hono`). A homegrown version loses community review and adds more
330
- bugs than it removes. Zod and the MCP SDK earn their keep.
331
- - Transitive advisories now can only come from `hono`/`@hono/node-server` (via
332
- `@modelcontextprotocol/node`) or `@modelcontextprotocol/core`. **Bumping the SDK packages
333
- fixes most of them.** `ws` and `undici` are direct deps to bump on their own.
334
- - Triage with `npm audit --omit=dev` — dev-only vulns (vitest/esbuild/tsx) don't ship.
335
- The question is *reachability in production*, not the raw count.
336
- - Remediate on a branch: `npm install @modelcontextprotocol/server@latest @modelcontextprotocol/node@latest ws@latest undici@latest`, then
337
- `npm test && npm run build && npm audit --omit=dev`. Commit `package-lock.json`;
338
- CI/Docker use `npm ci`, not `npm install`. Widen version ranges only intentionally.
339
-
340
- ### MCP transport invariant (the SDK "cross-client data leak" advisory)
341
-
342
- GHSA-345p-7cg4-v4c7 is about **sharing one `McpServer`/transport instance across
343
- clients**. Our HTTP layer MUST create a **fresh server + transport per request**
344
- (stateless mode, current behavior) or per session keyed by `Mcp-Session-Id` (stateful) —
345
- never a single global transport handling every `/mcp` POST. Re-verify this after any SDK
346
- upgrade or transport refactor. Bumping the SDK is necessary but not sufficient if the
347
- architecture shares instances.
348
-
349
- ### Our attack surface (higher-leverage than CVEs)
350
-
351
- 1. **Auth stays on.** NAS `.env` must keep `MCP_AUTH_METHOD=bearer`. `none` =
352
- unauthenticated full admin (fail-open default). Never put the JWT on public channels;
353
- tokens should carry an `exp` (the current prod token does not — fix when convenient).
354
- 2. **Authorization is per-tool.** Every MCP tool is gated via
355
- `wrapToolHandler(..., Permission.X)`, and the REST `/api/*` bridge enforces the same
356
- RBAC masks per route (`requiredPermissionFor` in the chat-face dispatcher).
357
- Prompt injection via the AI path ("ignore previous instructions, unlock the door") is
358
- contained only by these authorization checks — the model is not a security boundary.
359
- 3. **Scoped TLS only.** Never use the process-global `NODE_TLS_REJECT_UNAUTHORIZED=0` —
360
- it disables cert validation for *all* concurrent outbound HTTPS (HA token + AI key at
361
- MITM risk), not just Omada. Use a per-client `undici` dispatcher, or pin the Omada
362
- self-signed cert as a CA so verification stays on.
363
- 4. **Encode every path segment** built from tool input. The Omada client does
364
- (`encodeURIComponent` throughout); the HA client interpolates raw — fix when touched.
365
- 5. **Secrets never in logs.** Redact tokens / secrets / `Authorization` headers / API
366
- keys, and **sanitize `error.message`/`error.stack` before logging** — HTTP libraries
367
- embed auth headers and token-bearing URLs inside thrown errors. Don't return raw
368
- `error.message` to clients (leaks internal hostnames).
369
- 6. **HTTP hardening when `useHttp`:** request body-size limit, rate limiting keyed on a
370
- trusted-proxy IP (Cloudflare `CF-Connecting-IP`, not the spoofable `X-Forwarded-For`),
371
- and static security headers (`X-Content-Type-Options: nosniff`, `X-Frame-Options`,
372
- `Referrer-Policy`, CSP where applicable).
373
-
374
- ### Confirmed safe — don't re-litigate
375
-
376
- - **JWT:** HS256 is pinned (the token's `alg` header is ignored), signature compared with
377
- `crypto.timingSafeEqual` — not vulnerable to alg-confusion or timing attacks. (Still
378
- add `exp` enforcement, and `iss`/`aud` if it ever becomes multi-issuer.)
379
- - **Omada client** encodes every path segment; query params via `URLSearchParams`.
380
- - **No** `eval` / `new Function` / dynamic `require`; the only child process is
381
- `spawn('node', [fixedPath])` — no shell, no user-controlled argv.
382
- - **No arbitrary-URL SSRF via tool args** — HA/Omada/AI base URLs come only from config.
383
- - **Git hygiene clean** — `.env` is never tracked; no hardcoded real secrets in src/tests.
384
-
385
- ### Insecure defaults to flip when next touched
386
-
387
- `config.ts` defaults `MCP_AUTH_METHOD=none`; `.env.example` ships `none` +
388
- `HA_STRICT_SSL=false`; `docker-compose.yml` hardcodes `MCP_HTTP_ALLOWED_ORIGINS=*`.
389
- None of these reflect the (secure) live deployment — make the secure values the defaults.
package/Dockerfile DELETED
@@ -1,50 +0,0 @@
1
- # syntax=docker/dockerfile:1.7
2
-
3
- FROM node:24-alpine AS deps
4
- WORKDIR /app
5
- COPY package*.json ./
6
- # npm ci installs exactly from the committed package-lock.json (reproducible) and
7
- # fails if package.json and the lockfile have drifted. The `package*.json` COPY
8
- # above pulls in package-lock.json, so the lockfile is present in this stage.
9
- RUN npm ci
10
-
11
- FROM node:24-alpine AS build
12
- WORKDIR /app
13
- COPY --from=deps /app/node_modules ./node_modules
14
- COPY . .
15
- RUN npm run build
16
-
17
- FROM node:24-alpine AS runtime
18
-
19
- # OCI metadata labels
20
- LABEL org.opencontainers.image.title="MCP-HA-Connect"
21
- LABEL org.opencontainers.image.description="MCP server for Home Assistant + TP-Link Omada + Local AI integration"
22
- LABEL org.opencontainers.image.authors="Coffee Run Hobby"
23
- LABEL org.opencontainers.image.url="https://github.com/coffeerunhobby/mcp-ha-connect"
24
- LABEL org.opencontainers.image.source="https://github.com/coffeerunhobby/mcp-ha-connect"
25
- LABEL org.opencontainers.image.documentation="https://github.com/coffeerunhobby/mcp-ha-connect#readme"
26
- LABEL org.opencontainers.image.licenses="MIT"
27
-
28
- WORKDIR /app
29
- ENV NODE_ENV=production
30
- # apk upgrade: pick up Alpine security patches even when the node base tag lags
31
- # (the 2026-07 Trivy scan's 24 libssl/libcrypto CVEs were exactly this rot).
32
- RUN apk upgrade --no-cache && apk add --no-cache curl
33
- COPY package*.json ./
34
- # Reproducible prod-only install from the lockfile (drops devDependencies), then
35
- # strip the package managers from the runtime image: nothing invokes npm/yarn after
36
- # this line, and the npm CLI's vendored deps were the only Trivy findings left on
37
- # v1.5.5 (undici 6.26.0 inside /usr/local/lib/node_modules/npm).
38
- RUN npm ci --omit=dev && \
39
- rm -rf /usr/local/lib/node_modules /usr/local/bin/npm /usr/local/bin/npx \
40
- /usr/local/bin/corepack /opt/yarn* /usr/local/bin/yarn /usr/local/bin/yarnpkg \
41
- /root/.npm
42
- COPY --from=build /app/dist ./dist
43
-
44
- # M10: drop root. The official node image ships a non-root `node` user (uid 1000).
45
- # Built artifacts under /app are world-readable, so no chown is required; the
46
- # server listens on 3000 (>1024) which needs no privileged capability.
47
- USER node
48
- # --experimental-quic was dropped 2026-07-10: nothing in src/ uses QUIC, and the
49
- # flag tied the image to a specific Node major (25, now EOL) for no benefit.
50
- CMD ["node", "dist/index.js"]
@@ -1,23 +0,0 @@
1
- services:
2
- mcp-ha-connect:
3
- build: .
4
- container_name: mcp-ha-connect
5
- restart: unless-stopped
6
- ports:
7
- - "3000:3000"
8
- extra_hosts:
9
- - "host.docker.internal:host-gateway"
10
- env_file:
11
- - .env
12
- environment:
13
- # Bind all interfaces inside the container so Docker's port map works.
14
- - MCP_HTTP_BIND_ADDR=0.0.0.0
15
- # M1: do NOT hard-code a wildcard origin here. Set MCP_HTTP_ALLOWED_ORIGINS
16
- # in .env to the exact public origin(s) — e.g. your Cloudflare domain in
17
- # both bare-host and https:// form — so DNS-rebinding/CORS protection works.
18
- healthcheck:
19
- test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
20
- interval: 30s
21
- timeout: 10s
22
- retries: 3
23
- start_period: 10s